Jake Vanderwerf
2026-08-27 d7c8752b4d8f688a13645130d4c3237efd5ad1bf
inc/managers/IconsManager.php
@@ -1,7 +1,8 @@
<?php
namespace JVBase\managers;
use JVBase\utility\Features;
use JVBase\registrar\Registrar;
use JVBase\base\Site;
if (!defined('ABSPATH')) {
   exit;
@@ -18,13 +19,22 @@
   // Instance-specific properties
   protected string $source;
   protected array $icons = []; // Icons for THIS source [style => [names]]
   protected CacheManager $cache;
   protected Cache $cache;
   protected string $style = 'regular';
   protected array $styles = ['regular', 'bold', 'duotone', 'fill', 'light', 'thin'];
   protected array $customIcons = []; // Custom icons for THIS source
   protected array $map = [];
   protected const MAX_VERSIONS = 5;
   protected string $timestampKey;
   protected string $iconKey;
   protected string $iconHistoryKey;
   protected static string $allIcons = 'icons_all_used';
   protected static string $updateIcons = 'icons_needs_update';
   protected static string $iconMap = 'icons_map';
   /**
    * Factory method - get or create instance for a source
    */
@@ -33,6 +43,9 @@
      if (!isset(self::$instances[$source])) {
         self::$instances[$source] = new self($source);
      }
      self::$instances[$source]->setTimestampKey(BASE.'icons_'.$source.'_timestamp');
      self::$instances[$source]->setIconKey(BASE.'icons_'.$source);
      self::$instances[$source]->setIconHistoryKey(BASE.'icons_history_'.$source);
      return self::$instances[$source];
   }
@@ -42,11 +55,8 @@
   private function __construct(string $source)
   {
      $this->source = $source;
      $this->cache = CacheManager::for('icons_' . $source, WEEK_IN_SECONDS);
      $this->style = (array_key_exists('icons', JVB_SITE) && in_array(JVB_SITE['icons'], $this->styles))
         ? JVB_SITE['icons']
         : 'regular';
      $this->cache = Cache::for('icons_' . $source);
      $this->style = Site::icon();
      $this->addMap();
@@ -74,6 +84,19 @@
      $this->registerInstanceHooks();
   }
   protected function setIconKey(string $key):void
   {
      $this->iconKey = $key;
   }
   protected function setTimestampKey(string $key):void
   {
      $this->timestampKey = $key;
   }
   protected function setIconHistoryKey(string $key):void
   {
      $this->iconHistoryKey = $key;
   }
   /**
@@ -126,7 +149,7 @@
    */
   protected function loadStoredIcons(): void
   {
      $allIcons = get_option(BASE.'usedIcons', []);
      $allIcons = get_option(BASE.self::$allIcons, []);
      $storedIcons = $allIcons[$this->source] ?? [];
      // Merge stored icons with any existing icons (like custom icons)
@@ -146,6 +169,7 @@
            'apple-logo',
            'check-circle',
            'close-circle',
            'faders-horizontal',
            'cloud-slash',
            'exclamation-mark',
            'cloud-arrow-down',
@@ -196,15 +220,11 @@
//       ]
      ];
      // Add icons from content/taxonomy/user configs (like old behavior)
      $configIcons = $this->getIconsFromConfigs();
      if (!empty($configIcons)) {
         foreach ($configIcons as $source => $icons) {
            if (!isset($defaults[$source])) {
               $defaults[$source] = [];
            }
            $defaults[$source] = array_merge($defaults[$source], $icons);
         }
         $defaults['icons'] = array_merge($defaults['icons'], $configIcons);
      }
      // Allow filtering per source (extensibility)
@@ -215,35 +235,27 @@
      if (isset($allIcons[$this->source])) {
         $icons = array_merge($icons, $allIcons[$this->source]);
      }
      if (!empty($icons)) {
         $this->include($icons);
      }
   }
   /**
    * Get icons from JVB_CONTENT, JVB_TAXONOMY, JVB_USER configs
    * Get icons from Registrar instances
    *
    */
   protected function getIconsFromConfigs(): array
   {
      $icons = [];
      $check = [JVB_CONTENT, JVB_TAXONOMY, JVB_USER];
      $registered = Registrar::getRegistered();
      foreach ($check as $constant) {
         foreach ($constant as $key => $value) {
            if (isset($value['icon'])) {
               // Determine source based on context (you could add 'icon_source' to configs)
               $source = $value['icon_source'] ?? 'icons';
               if (!isset($icons[$source])) {
                  $icons[$source] = [];
               }
               $icons[$source][] = $value['icon'];
            }
         }
      foreach ($registered as $type) {
         $registrar = Registrar::getInstance($type);
         $icons[] = $registrar->getIcon();
      }
      return $icons;
      return array_unique(array_filter($icons));
   }
   /**
@@ -317,20 +329,19 @@
    */
   protected function saveIcons(): void
   {
      $allIcons = [];
      foreach (self::$instances as $source => $instance) {
         $allIcons[$source] = $instance->icons;
      }
      $allIcons = array_map(function ($instance) {
         return $instance->icons;
      }, self::$instances);
      update_option(BASE.'usedIcons', $allIcons);
      update_option(BASE.self::$allIcons, $allIcons);
      // Track WHICH source needs updating
      $needsUpdate = get_option(BASE.'icons_needs_update', []);
      $needsUpdate = get_option(BASE.self::$updateIcons, []);
      if (!is_array($needsUpdate)) {
         $needsUpdate = [];
      }
      $needsUpdate[$this->source] = true;
      update_option(BASE.'icons_needs_update', $needsUpdate);
      update_option(BASE.self::$updateIcons, $needsUpdate);
   }
   /**
@@ -358,24 +369,24 @@
   protected function addMap(): void
   {
      $map = get_option(BASE.'iconMap');
      $map = get_option(BASE.self::$iconMap);
      if (!$map) {
         $map = [
            'seo' => 'robot'
         ];
         if (Features::forSite()->has('referrals')) {
         if (Site::has('referrals')) {
            $map['referrals'] = 'hand-heart';
         }
         if (Features::forSite()->has('dashboard')) {
         if (Site::has('dashboard')) {
            $map['dash'] = 'door';
         }
         if (Features::forSite()->has('magicLink')) {
         if (Site::has('magic_link')) {
            $map['magicLink'] = 'magic-wand';
         }
         if (Features::hasAnyIntegration()) {
         if (Site::hasAnyIntegration()) {
            $map['integrations'] = 'plugs-connected';
         }
         update_option(BASE.'iconMap', $map);
         update_option(BASE.self::$iconMap, $map);
      }
      $this->map = apply_filters('jvbMapIcons', $map);
@@ -386,7 +397,8 @@
    */
   protected function registerGlobalHooks(): void
   {
      add_action('init', [$this, 'checkCSS']);
      add_action('wp_loaded', [self::class, 'checkCSS']);
      add_action('shutdown', [self::class, 'checkCSS']);
   }
   /**
@@ -408,20 +420,26 @@
   public function enqueueIconStyles():void
   {
      wp_enqueue_style('jvb-icons-'.$this->source);
//    if (file_exists(JVB_CHILD_URL . "assets/css/{$this->source}.css")){
         wp_enqueue_style('jvb-icons-'.$this->source);
//    }
   }
   public function checkCSS(): void
   public static function checkCSS(): void
   {
      $needsUpdate = get_option(BASE.'icons_needs_update', []);
      $needsUpdate = get_option(BASE.self::$updateIcons, []);
      $needsUpdate = array_filter($needsUpdate, function ($item) {
         return $item === true;
      });
      if (!empty($needsUpdate)) {
         error_log('Regenerating CSS for sources: ' . implode(', ', array_keys($needsUpdate)));
         delete_option(BASE.'icons_needs_update');
         delete_option(BASE.self::$updateIcons);
         self::regenerateAllCSS($needsUpdate);
      }
   }
   protected static function regenerateAllCSS(array $sourcesToUpdate = []): void
   public static function regenerateAllCSS(array $sourcesToUpdate = []): void
   {
      error_log('[IconsManager]:regenerateCSS');
      $css_dir = JVB_CHILD_DIR.'/assets/css/';
@@ -430,28 +448,41 @@
         wp_mkdir_p($css_dir);
      }
      // Load all icons from database option
      $allIcons = get_option(BASE.self::$allIcons, []);
      // If no specific sources provided, regenerate all
      if (empty($sourcesToUpdate)) {
         $sourcesToUpdate = array_fill_keys(array_keys(self::$instances), true);
         $sourcesToUpdate = array_fill_keys(array_keys($allIcons), true);
      }
      // Generate CSS only for sources that need it
      foreach (self::$instances as $source => $instance) {
         if (!isset($sourcesToUpdate[$source])) {
      // Generate CSS for each source that needs it
      foreach ($sourcesToUpdate as $source => $needsUpdate) {
         if (!$needsUpdate || !isset($allIcons[$source])) {
            continue;
         }
         // Get or create instance for this source
         $instance = self::for($source);
         // Temporarily set icons from database
         $originalIcons = $instance->icons;
         $instance->icons = $allIcons[$source];
         $css = $instance->generateIconCSS();
         $css_path = $css_dir . $source . '.css';
         $instance->archiveCurrentVersion($css);
         if (file_put_contents($css_path, $css) !== false) {
            CacheManager::updateTimestamp('icons_' . $source);
            Cache::for('icons_'.$source)->flush();
            error_log("[IconsManager] Updated {$source}.css");
         } else {
            error_log("[IconsManager] Could not write {$source}.css");
         }
         // Restore original icons
         $instance->icons = $originalIcons;
      }
   }
@@ -482,7 +513,7 @@
         $instance->archiveCurrentVersion($css);
         if (file_put_contents($css_path, $css) !== false) {
            CacheManager::updateTimestamp('icons_' . $source);
            Cache::for('icons_'.$source)->flush();
            error_log("[IconsManager] Updated {$source}.css");
         } else {
            error_log("[IconsManager] Could not write {$source}.css");
@@ -517,6 +548,10 @@
    */
   public function get(string $name, array $options = []): string
   {
      if (empty($name)) {
         //No icon requested
         return '';
      }
      $style = $options['style'] ?? $this->style;
      $name = $this->map[$name] ?? $name;
@@ -527,7 +562,7 @@
      }
      // Track usage - only if not already tracked
      if (!isset($this->icons[$style])) {
      if (!array_key_exists($style, $this->icons)) {
         $this->icons[$style] = [];
      }
@@ -617,7 +652,7 @@
   /**
    * Get raw SVG content for CSS mask-image
    */
   protected function getRawSvg(string $name, ?string $style = null): ?string
   public function getRawSvg(string $name, ?string $style = null): ?string
   {
      if (!$style) {
         $style = $this->style;
@@ -636,14 +671,12 @@
      // Clean up SVG for CSS usage
      $svg = preg_replace("/([\n\t]+)/", ' ', $svg);
      $svg = preg_replace('/>\s*</', '><', $svg);
      $svg = trim($svg);
      return $svg;
      return trim($svg);
   }
   public function registerStyle(): void
   {
      $timestamp = CacheManager::getTimestamp('icons_' . $this->source);
      $timestamp = get_option($this->timestampKey);
      $handle = 'jvb-icons-' . $this->source;
      wp_register_style(
@@ -666,13 +699,22 @@
         foreach ($names as $icon) {
            $svg = $this->getEncodedSVG($icon, $style);
            if ($svg !== '') {
               if ($icon === 'caret-down') {
                  $css .= 'details summary::after,';
               } elseif ($icon === 'faders-horizontal') {
                  $css .= 'details.all-filters summary::after,';
               } elseif ($icon === 'link') {
                  $css .= 'input[type=url],';
               } elseif ($icon === apply_filters('jvbSeparatorLogo', 'logo')) {
                  $css .= 'hr.logo::before,';
               }
               $css .= ".icon-{$icon}{$styleClass}{";
               $css .= "--icon:url('data:image/svg+xml;base64,{$svg}');";
               $css .= "}";
            }
         }
      }
      update_option($this->timestampKey, time());
      return $this->minifyCss($css);
   }
@@ -693,6 +735,14 @@
      if (!$style) {
         $style = $this->style;
      }
      $icon = $this->map[$icon] ?? $icon;
      // Validate icon exists
      if (!$this->iconExists($icon, $style)) {
         error_log('[IconsManager] Icon not found: ' . $icon);
         return '';
      }
      $svg = $this->getEncodedSVG($icon, $style);
      if ($svg !== '') {
         return "data:image/svg+xml;base64,{$svg}";
@@ -720,14 +770,12 @@
    */
   public function clearIconCache(): void
   {
      delete_option(BASE . 'icon_usage_list'); // Legacy
      delete_option(BASE.'usedIcons');
      delete_option(BASE.'iconMap');
      delete_option(self::$allIcons);
      delete_option(self::$iconMap);
      // Clear cache for all sources
      foreach (self::$instances as $source => $instance) {
         $instance->cache->delete('icon_styles_css');
         CacheManager::updateTimestamp('icons_' . $source);
         $instance->cache->forget('icon_styles_css');
      }
   }
@@ -755,12 +803,12 @@
         $history = array_slice($history, 0, self::MAX_VERSIONS);
      }
      update_option(BASE.'icon_css_history_' . $this->source, $history);
      update_option($this->iconHistoryKey, $history);
   }
   public function getVersionHistory(): array
   {
      return get_option(BASE.'icon_css_history_' . $this->source, []);
      return get_option($this->iconHistoryKey, []);
   }
   public function restoreVersion(int $timestamp): bool
@@ -781,7 +829,7 @@
            if (file_put_contents($css_path, $entry['css']) !== false) {
               $this->icons = $entry['iconList'];
               $this->saveIcons();
               CacheManager::updateTimestamp('icons_' . $this->source);
               Cache::for('icons_'.$this->source)->flush();
               return true;
            }
@@ -796,13 +844,14 @@
   public function forceRefresh(): void
   {
      $this->clearIconCache();
      $needsUpdate = get_option(BASE.'icons_needs_update', []);
      $needsUpdate = get_option(BASE.self::$updateIcons, []);
      if (!is_array($needsUpdate)) {
         $needsUpdate = [];
      }
      $needsUpdate[$this->source] = true;
      update_option(BASE.'icons_needs_update', $needsUpdate);
      CacheManager::updateTimestamp('icons_' . $this->source);
      update_option(BASE.self::$updateIcons, $needsUpdate);
      Cache::for('icons_'.$this->source)->flush();
   }
   public function mergeVersions(array $timestamps): bool
@@ -811,7 +860,7 @@
         return false;
      }
      $history = get_option(BASE.'icon_css_history_' . $this->source, []);
      $history = get_option($this->iconHistoryKey, []);
      $merged_icons = [];
      // Collect icons from selected versions