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