From a8ff85042ffbfe94b678fd818c02deb613bfd116 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Fri, 07 Aug 2026 20:42:54 +0000
Subject: [PATCH] =Continuing on with Integrations setup. Working on PostMark now

---
 inc/integrations/services/Umami.php |  793 --------------------------------------------------------
 1 files changed, 4 insertions(+), 789 deletions(-)

diff --git a/inc/integrations/services/Umami.php b/inc/integrations/services/Umami.php
index 807ec3b..ebc22df 100644
--- a/inc/integrations/services/Umami.php
+++ b/inc/integrations/services/Umami.php
@@ -1,5 +1,7 @@
 <?php
 namespace JVBase\integrations\services;
+use JVBase\integrations\Admin;
+use JVBase\integrations\Instance;
 use JVBase\integrations\Integrations;
 
 if (!defined('ABSPATH')) {
@@ -8,796 +10,9 @@
 
 class Umami extends Integrations
 {
-	private string $website_id;
-	private string $api_url;
-	private string $api_token;
-
-	// Tracking configuration
-	private array $valid_events = [
-		'view_feed',
-		'view_taxonomy',
-		'view_profile',
-		'view_shop',
-		'view_content',
-		'toggle_favourite',
-		'click_profile',
-		'click_content',
-		'click_taxonomy',
-		'click_shop',
-		'user_session'
-	];
-
-	// Database tables
-	private string $events_table;
-	private string $metrics_table;
-
-	protected static array $instances = [];
-	public static function getInstance(?int $userID = null):self
-	{
-		$key = is_null($userID) ? 'base' : $userID;
-		if (!array_key_exists($key, self::$instances)) {
-			self::$instances[$key] = new static($userID);
-		}
-		return self::$instances[$key];
-	}
-
-	protected function __construct(?int $userID = null)
-	{
-		$this->service_name = 'umami';
-		$this->title = 'Umami.js';
-		$this->icon = 'chart-line';
-		$this->apiBase = '';  // Set dynamically based on credentials
-		$this->apiEndpoints = [
-			'api/websites',
-			'api/websites/{websiteId}/stats',
-			'api/websites/{websiteId}/pageviews',
-			'api/websites/{websiteId}/events'
-		];
-
-		$this->fields = [
-			'website_id' => [
-				'label'		=> 'Website ID',
-				'type'		=> 'text',
-				'placeholder'=> 'Enter Umami Website ID',
-				'hint'		=> 'Found in your Umami dashboard under Settings → Websites.',
-				'required'	=> true
-			],
-		];
-
-		$this->advanced = [
-			'api_url' => [
-				'label'		=> 'API URL',
-				'type'		=> 'text',
-				'placeholder'=> 'https://analytics.yourdomain.com',
-				'hint'		=> 'Your Umami server URL. Leave blank if using umami.is cloud service.'
-			],
-			'api_token' => [
-				'label'		=> 'API Token (Optional)',
-				'type'		=> 'text',
-				'subtype'	=> 'password',
-				'placeholder'=> 'Enter API Token for analytics data',
-				'hint'		=> 'Required only if you want to retrieve analytics data for dashboards.'
-			]
-		];
-
-		$this->instructions = [
-			'Login to your <a href="https://cloud.umami.is/settings/websites" target="_blank">umami.is</a> account, select your website, and copy the website ID'
-		];
-
-		parent::__construct();
-
-		$this->actions = array_merge(
-			$this->actions,
-			[
-				'refresh_data'	=> 	'handleRefreshData'
-			]
-		);
-
-		global $wpdb;
-		$this->events_table = $wpdb->prefix . BASE . 'umami_events';
-		$this->metrics_table = $wpdb->prefix . BASE . 'performance_metrics';
-	}
-
-	/**
-	 * Initialize service-specific properties
-	 */
+	use Admin, Instance;
 	protected function initialize(): void
 	{
-		$this->website_id = $this->credentials['website_id'] ?? '';
-		$this->api_url = $this->credentials['api_url'] ?? '';
-		$this->api_token = $this->credentials['api_token'] ?? '';
-		$this->ttl = intval($this->credentials['cache_duration'] ?? 60) * 60;
-
-		// Set dynamic API base if provided
-		if (!empty($this->api_url)) {
-			$this->apiBase = rtrim($this->api_url, '/');
-		}
-	}
-
-	/**
-	 * Register additional WordPress hooks
-	 */
-	protected function registerAdditionalHooks(): void
-	{
-		// Add tracking script to frontend
-		add_action('wp_head', [$this, 'renderTrackingScript'], 5);
-
-		// Add user session tracking
-		add_action('wp_footer', [$this, 'trackUserSession'], 100);
-
-		// Schedule data collection if API is configured
-		if (!empty($this->api_token)) {
-			add_action('jvb_daily_umami_collection', [$this, 'collectDailyData']);
-
-			if (!wp_next_scheduled('jvb_daily_umami_collection')) {
-				wp_schedule_event(time(), 'daily', 'jvb_daily_umami_collection');
-			}
-		}
-	}
-
-	/***************************************************************************
-	 * TRACKING SCRIPT METHODS
-	 ***************************************************************************/
-
-	/**
-	 * Render tracking script in head
-	 */
-	public function renderTrackingScript(): void
-	{
-		// Skip on local environments
-		if (JVB_TESTING) {
-			return;
-		}
-		if (!$this->isSetUp() || is_admin()) {
-			return;
-		}
-
-
-		$script_url = $this->getTrackingScriptUrl();
-		$website_id = $this->getWebsiteId();
-
-		if (!$website_id || !$script_url) {
-			return;
-		}
-
-		// Preconnect for performance
-		$domain = parse_url($script_url, PHP_URL_HOST);
-		echo '<link rel="preconnect" href="https://' . esc_attr($domain) . '"/>' . "\n";
-
-		// Add tracking script
-		$attributes = [
-			'defer',
-			'src="' . esc_url($script_url) . '"',
-			'data-website-id="' . esc_attr($website_id) . '"'
-		];
-
-		// Add optional configuration
-		if (!empty($this->credentials['respect_dnt']) && $this->credentials['respect_dnt']) {
-			$attributes[] = 'data-do-not-track="true"';
-		}
-
-		if (!empty($this->credentials['domains'])) {
-			$attributes[] = 'data-domains="' . esc_attr($this->credentials['domains']) . '"';
-		}
-
-		echo '<script ' . implode(' ', $attributes) . '></script>' . "\n";
-	}
-
-	/**
-	 * Track user session information
-	 */
-	public function trackUserSession(): void
-	{
-		if (!$this->isSetUp() || is_admin()) {
-			return;
-		}
-
-		$attributes = $this->buildTrackingAttributes('user_session', 'pageview', [
-			'logged_in' => is_user_logged_in() ? 'true' : 'false',
-			'user_type' => $this->getCurrentUserType()
-		]);
-
-		if (empty($attributes)) {
-			return;
-		}
-
-		echo '<div ' . $this->attributesToString($attributes) . ' style="display:none"></div>' . "\n";
-
-		// Add page-specific tracking
-		$this->trackPageView();
-	}
-
-	/**
-	 * Track specific page views
-	 */
-	private function trackPageView(): void
-	{
-		$tracker_args = [];
-		$event = '';
-		$type = '';
-
-		if (is_singular([BASE . 'artist', BASE . 'partner'])) {
-			// Profile view
-			global $post;
-			$event = 'view_profile';
-			$type = get_post_type();
-			$tracker_args = [
-				'id' => get_the_ID(),
-				'from' => $this->getTrafficSource(),
-				'owner_id' => $post->post_author
-			];
-
-			// Check for specific content in query
-			foreach (['tattoo', 'piercing', 'artwork'] as $content_type) {
-				if (isset($_GET[$content_type])) {
-					$tracker_args['item'] = sanitize_text_field($_GET[$content_type]);
-					break;
-				}
-			}
-
-		} elseif (is_tax(BASE . 'shop')) {
-			// Shop view
-			$event = 'view_shop';
-			$type = BASE . 'shop';
-			$tracker_args = [
-				'id' => get_queried_object_id(),
-				'from' => $this->getTrafficSource()
-			];
-
-		} elseif (is_tax()) {
-			// Taxonomy view
-			$obj = get_queried_object();
-			$event = 'view_taxonomy';
-			$type = $obj->taxonomy;
-			$tracker_args = [
-				'id' => $obj->term_id,
-				'from' => $this->getTrafficSource()
-			];
-		}
-
-		if ($event && $type) {
-			$attributes = $this->buildTrackingAttributes($event, $type, $tracker_args);
-			echo '<div ' . $this->attributesToString($attributes) . ' style="display:none"></div>' . "\n";
-		}
-	}
-
-	/***************************************************************************
-	 * TRACKING ATTRIBUTE BUILDER METHODS
-	 ***************************************************************************/
-
-	/**
-	 * Build tracking attributes for events
-	 */
-	public function buildTrackingAttributes(string $event, string $type, array $args = []): array
-	{
-		if (!in_array($event, $this->valid_events)) {
-			return [];
-		}
-
-		// Normalize type
-		$normalized_type = str_replace(BASE, '', $type);
-
-		// Build base attributes
-		$attributes = [
-			'data-umami-event' => esc_attr($event),
-			'data-umami-event-type' => esc_attr($normalized_type)
-		];
-
-		// Add ID if provided
-		if (!empty($args['id'])) {
-			$attributes['data-umami-event-id'] = esc_attr($args['id']);
-		}
-
-		// Add source information
-		if (!empty($args['source_id'])) {
-			$attributes['data-umami-event-source'] = esc_attr($args['source_id']);
-			if (!empty($args['source_type'])) {
-				$attributes['data-umami-event-source-type'] = esc_attr(
-					str_replace(BASE, '', $args['source_type'])
-				);
-			}
-		}
-
-		// Add owner information
-		if (!empty($args['owner_id'])) {
-			$attributes['data-umami-event-owner'] = esc_attr($args['owner_id']);
-			if (!empty($args['owner_type'])) {
-				$attributes['data-umami-event-owner-type'] = esc_attr(
-					str_replace(BASE, '', $args['owner_type'])
-				);
-			}
-		}
-
-		// Add traffic source
-		if (!empty($args['from'])) {
-			$attributes['data-umami-event-from'] = esc_attr($args['from']);
-		}
-
-		// Add item reference
-		if (!empty($args['item'])) {
-			$attributes['data-umami-event-item'] = esc_attr($args['item']);
-		}
-
-		// Add metadata
-		foreach ($args as $key => $value) {
-			if (in_array($key, ['id', 'source_id', 'source_type', 'owner_id', 'owner_type', 'from', 'item'])) {
-				continue;
-			}
-			$clean_key = 'data-umami-event-' . str_replace('_', '-', sanitize_key($key));
-			$attributes[$clean_key] = esc_attr($value);
-		}
-
-		return $attributes;
-	}
-
-	/**
-	 * Convert attributes array to HTML string
-	 */
-	public function attributesToString(array $attributes): string
-	{
-		$attr_strings = [];
-		foreach ($attributes as $key => $value) {
-			$attr_strings[] = $key . '="' . $value . '"';
-		}
-		return implode(' ', $attr_strings);
-	}
-
-	/**
-	 * Public tracking methods for use in templates
-	 */
-	public function trackContentClick(int $id, string $type, array $args = []): string
-	{
-		$args['id'] = $id;
-
-		// Auto-detect owner for content types
-		if (empty($args['owner_id']) && in_array($type, [BASE . 'tattoo', BASE . 'artwork', BASE . 'piercing'])) {
-			$post = get_post($id);
-			if ($post && !empty($post->post_author)) {
-				$args['owner_id'] = $post->post_author;
-				$args['owner_type'] = 'user';
-			}
-		}
-
-		return $this->attributesToString(
-			$this->buildTrackingAttributes('click_content', $type, $args)
-		);
-	}
-
-	public function trackProfileClick(int $id, string $type, array $args = []): string
-	{
-		$args['id'] = $id;
-		return $this->attributesToString(
-			$this->buildTrackingAttributes('click_profile', $type, $args)
-		);
-	}
-
-	public function trackTaxonomyClick(int $id, string $taxonomy, array $args = []): string
-	{
-		$args['id'] = $id;
-		return $this->attributesToString(
-			$this->buildTrackingAttributes('click_taxonomy', $taxonomy, $args)
-		);
-	}
-
-	public function trackContentTaxonomyClick(int $id, string $taxonomy, array $args = []): string
-	{
-		$args['id'] = $id;
-		return $this->attributesToString(
-			$this->buildTrackingAttributes('click_' . $taxonomy, $taxonomy, $args)
-		);
-	}
-
-	public function trackFavouriteToggle(int $id, string $type, bool $is_favourite, array $args = []): string
-	{
-		$args['id'] = $id;
-		$args['action'] = $is_favourite ? 'add' : 'remove';
-		return $this->attributesToString(
-			$this->buildTrackingAttributes('toggle_favourite', $type, $args)
-		);
-	}
-
-	public function trackFeedView(int $id, string $type, array $args = []): array
-	{
-		$args['id'] = $id;
-		return $this->buildTrackingAttributes('view_feed', $type, $args);
-	}
-
-	/***************************************************************************
-	 * API DATA COLLECTION METHODS
-	 ***************************************************************************/
-
-	/**
-	 * Get analytics data from Umami API
-	 */
-	public function getAnalytics(string $start_date, string $end_date, bool $use_cache = true): ?array
-	{
-		if (empty($this->api_url) || empty($this->api_token)) {
-			return null;
-		}
-
-		// Check cache first
-		if ($use_cache && $this->cache) {
-			$cache_key = md5("analytics_{$start_date}_{$end_date}");
-			$cached = $this->cache->get($cache_key);
-			if ($cached !== false) {
-				return $cached;
-			}
-		}
-
-		try {
-			$url = rtrim($this->api_url, '/') . "/api/websites/{$this->website_id}/stats";
-			$params = [
-				'startAt' => strtotime($start_date) * 1000,
-				'endAt' => strtotime($end_date) * 1000
-			];
-
-			$response = wp_remote_get($url . '?' . http_build_query($params), [
-				'headers' => [
-					'Authorization' => 'Bearer ' . $this->api_token,
-					'Accept' => 'application/json'
-				],
-				'timeout' => 30
-			]);
-
-			if (is_wp_error($response)) {
-				$this->logError('API request failed', ['error' => $response->get_error_message()]);
-				return null;
-			}
-
-			$body = wp_remote_retrieve_body($response);
-			$data = json_decode($body, true);
-
-			if (json_last_error() !== JSON_ERROR_NONE) {
-				$this->logError('Invalid JSON response from API');
-				return null;
-			}
-
-			// Cache the result
-			if ($use_cache && $this->cache) {
-				$this->cache->set($cache_key, $data, $this->ttl);
-			}
-
-			return $data;
-
-		} catch (Exception $e) {
-			$this->logError('Exception during API call', ['error' => $e->getMessage()]);
-			return null;
-		}
-	}
-
-	/**
-	 * Get page views for a specific URL
-	 */
-	public function getPageViews(string $url, string $start_date, string $end_date): ?array
-	{
-		if (empty($this->api_url) || empty($this->api_token)) {
-			return null;
-		}
-
-		$api_endpoint = rtrim($this->api_url, '/') . "/api/websites/{$this->website_id}/pageviews";
-		$params = [
-			'startAt' => strtotime($start_date) * 1000,
-			'endAt' => strtotime($end_date) * 1000,
-			'url' => $url,
-			'unit' => 'day'
-		];
-
-		$response = wp_remote_get($api_endpoint . '?' . http_build_query($params), [
-			'headers' => [
-				'Authorization' => 'Bearer ' . $this->api_token,
-				'Accept' => 'application/json'
-			],
-			'timeout' => 30
-		]);
-
-		if (is_wp_error($response)) {
-			return null;
-		}
-
-		return json_decode(wp_remote_retrieve_body($response), true);
-	}
-
-	/**
-	 * Get custom events data
-	 */
-	public function getEvents(string $event_name = '', string $start_date = '', string $end_date = ''): ?array
-	{
-		if (empty($this->api_url) || empty($this->api_token)) {
-			return null;
-		}
-
-		$api_endpoint = rtrim($this->api_url, '/') . "/api/websites/{$this->website_id}/events";
-
-		$params = [];
-		if ($start_date && $end_date) {
-			$params['startAt'] = strtotime($start_date) * 1000;
-			$params['endAt'] = strtotime($end_date) * 1000;
-		}
-		if ($event_name) {
-			$params['event'] = $event_name;
-		}
-
-		$url = $api_endpoint;
-		if (!empty($params)) {
-			$url .= '?' . http_build_query($params);
-		}
-
-		$response = wp_remote_get($url, [
-			'headers' => [
-				'Authorization' => 'Bearer ' . $this->api_token,
-				'Accept' => 'application/json'
-			],
-			'timeout' => 30
-		]);
-
-		if (is_wp_error($response)) {
-			return null;
-		}
-
-		return json_decode(wp_remote_retrieve_body($response), true);
-	}
-
-	/**
-	 * Collect daily analytics data and store in database
-	 */
-	public function collectDailyData(): void
-	{
-		if (!$this->isSetUp() || empty($this->api_token)) {
-			return;
-		}
-
-		$yesterday = date('Y-m-d', strtotime('-1 day'));
-		$data = $this->getAnalytics($yesterday, $yesterday, false);
-
-		if (!$data) {
-			$this->logError('Failed to collect daily data', ['date' => $yesterday]);
-			return;
-		}
-
-		global $wpdb;
-
-		// Store basic metrics
-		$wpdb->insert($this->metrics_table, [
-			'date' => $yesterday,
-			'pageviews' => $data['pageviews']['value'] ?? 0,
-			'visitors' => $data['visitors']['value'] ?? 0,
-			'visits' => $data['visits']['value'] ?? 0,
-			'bounces' => $data['bounces']['value'] ?? 0,
-			'total_time' => $data['totaltime']['value'] ?? 0,
-			'created_at' => current_time('mysql')
-		]);
-
-		// Collect and store events
-		$events = $this->getEvents('', $yesterday, $yesterday);
-		if ($events) {
-			foreach ($events as $event) {
-				$wpdb->insert($this->events_table, [
-					'date' => $yesterday,
-					'event_name' => $event['x'] ?? '',
-					'event_count' => $event['y'] ?? 0,
-					'created_at' => current_time('mysql')
-				]);
-			}
-		}
-
-		$this->logDebug('Successfully collected daily data', ['date' => $yesterday]);
-	}
-
-	/***************************************************************************
-	 * HELPER METHODS
-	 ***************************************************************************/
-
-	/**
-	 * Get current user type for tracking
-	 */
-	private function getCurrentUserType(): string
-	{
-		if (!is_user_logged_in()) {
-			return 'guest';
-		}
-
-		$user = wp_get_current_user();
-		$roles = $user->roles;
-
-		if (empty($roles)) {
-			return 'user';
-		}
-
-		$role = array_values($roles)[0];
-		return str_replace(BASE, '', $role);
-	}
-
-	/**
-	 * Determine traffic source
-	 */
-	private function getTrafficSource(): string
-	{
-		if (isset($_GET['utm_source'])) {
-			return sanitize_text_field($_GET['utm_source']);
-		}
-
-		if (isset($_SERVER['HTTP_REFERER'])) {
-			$referer = $_SERVER['HTTP_REFERER'];
-			$site_url = get_site_url();
-
-			if (strpos($referer, $site_url) === false) {
-				$domain = parse_url($referer, PHP_URL_HOST);
-				return $domain ?: 'external';
-			}
-
-			return 'internal';
-		}
-
-		return 'direct';
-	}
-
-	/**
-	 * Get website ID
-	 */
-	public function getWebsiteId(): string
-	{
-		$this->ensureInitialized();
-		return $this->website_id;
-	}
-
-	/**
-	 * Get tracking script URL
-	 */
-	public function getTrackingScriptUrl(): string
-	{
-		if (!empty($this->api_url)) {
-			return rtrim($this->api_url, '/') . '/script.js';
-		}
-
-		// Default to Umami cloud
-		return 'https://cloud.umami.is/script.js';
-	}
-
-	/***************************************************************************
-	 * REQUIRED ABSTRACT METHOD IMPLEMENTATIONS
-	 ***************************************************************************/
-
-	/**
-	 * Get request headers for API calls
-	 */
-	protected function getRequestHeaders(): array
-	{
-		$headers = [
-			'Accept' => 'application/json',
-			'Content-Type' => 'application/json'
-		];
-
-		if (!empty($this->api_token)) {
-			$headers['Authorization'] = 'Bearer ' . $this->api_token;
-		}
-
-		return $headers;
-	}
-
-	protected function handleRefreshData():array
-	{
-		// Collect today's data
-		$today = date('Y-m-d');
-		$data = $this->getAnalytics($today, $today, false);
-
-		if ($data) {
-			// Clear cache for today
-			$cache_key = md5("analytics_{$today}_{$today}");
-			$this->cache->forget($cache_key);
-
-			return [
-				'success' => true,
-				'message' => 'Data refreshed successfully',
-				'data' => $data
-			];
-		}
-
-		return [
-			'success'	=> false,
-			'message'	=> 'Something went wrong...'
-		];
-	}
-
-	/**
-	 * Process queued operations
-	 */
-	public function processOperation(array|\WP_Error $result, object $operation, array $data): array|\WP_Error
-	{
-		$prefix = strtolower($this->service_name) . '_';
-
-		switch ($operation->type) {
-			case $prefix . 'collect_analytics':
-				$this->collectDailyData();
-				return ['success' => true, 'message' => 'Analytics collected'];
-
-			default:
-				return $result;
-		}
-	}
-
-	/**
-	 * Test API connection
-	 */
-	protected function performConnectionTest(): bool
-	{
-		// Basic tracking only needs website ID
-		if (empty($this->website_id)) {
-			return false;
-		}
-
-		// If API credentials provided, test them
-		if (!empty($this->api_url) && !empty($this->api_token)) {
-			try {
-				$response = wp_remote_get(rtrim($this->api_url, '/') . '/api/websites', [
-					'headers' => [
-						'Authorization' => 'Bearer ' . $this->api_token,
-						'Accept' => 'application/json'
-					],
-					'timeout' => 10
-				]);
-
-				if (is_wp_error($response)) {
-					return false;
-				}
-
-				return wp_remote_retrieve_response_code($response) === 200;
-
-			} catch (Exception $e) {
-				$this->logError('Connection test failed', ['error' => $e->getMessage()]);
-				return false;
-			}
-		}
-
-		// Website ID exists, basic tracking will work
-		return true;
-	}
-
-	/**
-	 * Get service description
-	 */
-	public function getServiceDescription(): string
-	{
-		return "Privacy-focused analytics to understand your website traffic.";
-	}
-
-	/**
-	 * Create database tables for metrics storage
-	 */
-	public static function createTables(): void
-	{
-		global $wpdb;
-		$charset_collate = $wpdb->get_charset_collate();
-
-		// Events table
-		$events_table = $wpdb->prefix . BASE . 'umami_events';
-		$events_sql = "CREATE TABLE IF NOT EXISTS $events_table (
-            id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
-            date date NOT NULL,
-            event_name varchar(255) NOT NULL,
-            event_count int(11) NOT NULL DEFAULT 0,
-            event_data longtext,
-            created_at datetime NOT NULL,
-            PRIMARY KEY (id),
-            KEY date_event (date, event_name)
-        ) $charset_collate;";
-
-		// Metrics table
-		$metrics_table = $wpdb->prefix . BASE . 'performance_metrics';
-		$metrics_sql = "CREATE TABLE IF NOT EXISTS $metrics_table (
-            id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
-            date date NOT NULL,
-            pageviews int(11) NOT NULL DEFAULT 0,
-            visitors int(11) NOT NULL DEFAULT 0,
-            visits int(11) NOT NULL DEFAULT 0,
-            bounces int(11) NOT NULL DEFAULT 0,
-            total_time int(11) NOT NULL DEFAULT 0,
-            created_at datetime NOT NULL,
-            PRIMARY KEY (id),
-            UNIQUE KEY date_unique (date)
-        ) $charset_collate;";
-
-		require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
-		dbDelta($events_sql);
-		dbDelta($metrics_sql);
+		// TODO: Implement initialize() method.
 	}
 }

--
Gitblit v1.10.0