Jake Vanderwerf
2026-08-25 3d25ad7de4fdbb3eba2cde2a62422e6be5d79a0a
inc/integrations/services/Umami.php
@@ -1,8 +1,10 @@
<?php
namespace JVBase\integrations\services;
use JVBase\integrations\Admin;
use JVBase\integrations\Instance;
use JVBase\Instance;
use JVBase\integrations\Integrations;
use JVBase\managers\Roles;
use JVBase\registrar\Registrar;
if (!defined('ABSPATH')) {
   exit;
@@ -11,8 +13,212 @@
class Umami extends Integrations
{
   use Admin, Instance;
   protected function __construct()
   {
      $this->service_name = 'umami';
      $this->title = 'umami';
      $this->icon = 'chart-line';
      $this->defineFields();
      $this->defineInstructions();
      parent::__construct();
      $this->defineTables();
   }
   protected function defineFields(): void
   {
      $id = $this->addField('website_id', 'Website ID', 'text', 'manage_options');
      $id->setHint('Found in your Umami dashboard under Settings->Websites');
      $id->setRequired();
      $api_url = $this->addField('api_url', 'API URL', 'text', 'manage_options');
      $api_url->setHint('Your Umami server URL. Leave blank if using umami.is cloud service.');
      $api_token = $this->addField('api_token', 'API Token (Optional)', 'text', 'manage_options');
      $api_token->setSubType('password');
      $api_token->setHint('Required only if you want to retrieve analytics data for dashboards.');
   }
   protected function defineInstructions():void
   {
      $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'
      ];
   }
   protected function defineTables():void
   {
      //TODO: Once we have an actual umami setup, we can store the fetched data here for reference
      //Ideally, we have the type (term, user, post), WP ID, created_date, and count
   }
   protected function initialize(): void
   {
      // TODO: Implement initialize() method.
      // Add tracking script to frontend
      add_action('wp_head', [$this, 'renderTrackingScript'], 5);
      // Schedule data collection if API is configured
      if ($this->getApiToken()) {
         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');
         }
      }
   }
   public function renderTrackingScript():void
   {
      if (JVB_TESTING || !$this->isSetUp() || is_admin()) {
         return;
      }
      $script_url = $this->getTrackingScriptUrl();
      if (!$this->getWebsiteID() || empty($script_url)) {
         return;
      }
      echo sprintf(
         '<link rel="preconnect" href="https://%s"/>' . "\n",
         esc_attr(parse_url($script_url, PHP_URL_HOST))
      );
      $attributes = [
         'defer',
         sprintf(
            'src="%s"',
            esc_url($script_url)
         ),
         sprintf(
            'data-website-id="%s"',
            esc_attr($this->getWebsiteID())
         )
      ];
      $tags = $this->getCurrentTag();
      if (!empty($tags)) {
         $attributes[] = $tags;
      }
      echo sprintf(
         '<script %s></script>',
         implode(' ', $attributes)
      );
      $this->outputCustomEvent();
   }
      protected function getCurrentTag():string
      {
         $contentTax = array_map('jvbCheckBase', Registrar::withFeature('is_content', 'term'));
         if (!empty($contentTax) && is_tax($contentTax)) {
            $tax = get_queried_object();
            $reg = Registrar::getInstance($tax->taxonomy);
            return 'data-tag="'.$reg->getUmamiEvent().'"';
         }
         $profiles = array_map('jvbCheckBase', Registrar::withFeature('profile_link'));
         if (!empty($profiles) && is_singular($profiles)) {
            $usr = get_queried_object();
            $reg = Registrar::getInstance($usr->post_type);
            return 'data-tag="'.$reg->getUmamiEvent().'"';
         }
         return '';
      }
      protected function outputCustomEvent():void
      {
         $args = [];
         $event = false;
         $contentTax = array_map('jvbCheckBase', Registrar::withFeature('is_content', 'term'));
         if (!empty($contentTax) && is_tax($contentTax)) {
            $tax = get_queried_object();
            $reg = Registrar::getInstance($tax->taxonomy);
            $event = $reg->getUmamiEvent();
            $args = [
               'id'  => $tax->term_id,
            ];
            if ($reg->isOwnable()) {
               $owners = jvbGetTermOwners($tax->term_id);
               if (!empty($owners)) {
                  $args['owners'] = $owners;
               }
            }
         }
         $profiles = array_map('jvbCheckBase', Registrar::withFeature('profile_link'));
         if (!empty($profiles) && is_singular($profiles)) {
            $usr = get_queried_object();
            $reg = Registrar::getInstance($usr->post_type);
            $event = $reg->getUmamiEvent();
            global $post;
            $args['id'] = get_the_ID();
            $args['owner_id'] = $post->post_author;
         }
         if ($event && !empty($args)) {
            echo sprintf(
               '<script defer>umami.track(event_name: %s, data: %s);</script>',
               $event,
               json_encode($args)
            );
         }
      }
   /*********************************************************
    * UTILITY
    *********************************************************/
   public function getWebsiteID(): string|false
   {
      return $this->loadCredentials()['website_id']??false;
   }
   protected function getApiUrl():string|false
   {
      $api = $this->loadCredentials()['api_url'];
      return empty($api) ? false : $api;
   }
   protected function getApiToken():string|false
   {
      $api = $this->loadCredentials()['api_token'];
      return empty($api) ? false : $api;
   }
   protected function getTrackingScriptUrl():string
   {
      if ($this->getApiUrl()) {
         return rtrim($this->getApiUrl(), '/').'/script.js';
      }
      // Default to Umami cloud
      return 'https://cloud.umami.is/script.js';
   }
   public function buildEvent(string $event, array $args = []):string
   {
      $base = 'data-umami-event';
      $out = [
         $base => esc_attr($event),
      ];
      foreach ($args as $k => $v) {
         $out[$base.$k] = esc_attr(jvbNoBase($v));
      }
      return $this->attributesToString($out);
   }
      protected function attributesToString(array $attributes):string
      {
         $strings = [];
         foreach ($attributes as $key => $value) {
            $strings[] = sprintf(
               '%s="%s"',
               $key,
               $value
            );
         }
         return implode(' ', $strings);
      }
}