From d7e7d248cbe41cd7a9ef9c2fb022b6c4831f99a3 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 31 May 2026 15:22:56 +0000
Subject: [PATCH] =jakevan complete

---
 inc/managers/IconsManager.php |  731 +++++++++++++++++++++++++++++++++++++-------------------
 1 files changed, 482 insertions(+), 249 deletions(-)

diff --git a/inc/managers/IconsManager.php b/inc/managers/IconsManager.php
index 8278763..f54120b 100644
--- a/inc/managers/IconsManager.php
+++ b/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;
@@ -9,79 +10,145 @@
 
 class IconsManager
 {
-	protected static ?IconsManager $instance = null;
-	protected CacheManager $cache;
+	// Static array holding all source instances
+	protected static array $instances = [];
+
+	// Static storage for all custom icons across sources
+	protected static array $customIconsRegistry = [];
+
+	// Instance-specific properties
+	protected string $source;
+	protected array $icons = []; // Icons for THIS source [style => [names]]
+	protected Cache $cache;
 	protected string $style = 'regular';
 	protected array $styles = ['regular', 'bold', 'duotone', 'fill', 'light', 'thin'];
-	// Custom icons registered via filter
-	protected array $customIcons = [];
-	protected array $usedIcons = [];
+	protected array $customIcons = []; // Custom icons for THIS source
 	protected array $map = [];
 	protected const MAX_VERSIONS = 5;
 
 	/**
-	 * Get singleton instance
+	 * Factory method - get or create instance for a source
 	 */
-	public static function getInstance(): IconsManager
+	public static function for(string $source = 'icons'): IconsManager
 	{
-		if (self::$instance === null) {
-			self::$instance = new self();
+		if (!isset(self::$instances[$source])) {
+			self::$instances[$source] = new self($source);
 		}
-		return self::$instance;
+		return self::$instances[$source];
 	}
-	private function __construct()
-	{
-		$this->cache = CacheManager::for('icons', WEEK_IN_SECONDS);
 
-		$this->style = (array_key_exists('icons', JVB_SITE) && in_array(JVB_SITE['icons'], $this->styles))
-			? JVB_SITE['icons']
-			: 'regular';
+	/**
+	 * Constructor now takes source parameter
+	 */
+	private function __construct(string $source)
+	{
+		$this->source = $source;
+		$this->cache = Cache::for('icons_' . $source, WEEK_IN_SECONDS);
+		$this->style = Site::icon();
 
 		$this->addMap();
 
-		// Allow custom icon registration
-		$this->customIcons = apply_filters('jvbRegisterCustomIcons', [
-			'syncing'		=> JVB_DIR .'/assets/icons/cloud-sync-thin.svg',
-			'alphabetical'	=> JVB_DIR.'/assets/icons/alphabetical.svg'
-		]);
+		// Register custom icons only once for all sources
+		if ($source === 'icons') {
+			$this->registerCustomIcons();
+		}
 
+		// Load custom icons for THIS source
+		$this->loadCustomIconsForSource();
 
-		$this->usedIcons = get_option(BASE.'usedIcons', []);
-		$this->includeIcons();
-		// Track custom icons for CSS generation
-		$this->trackCustomIcons();
-		// Register hooks only once
-		$this->registerHooks();
+		// Load stored icons for this source
+		$this->loadStoredIcons();
+
+		if (empty($this->icons)) {
+			$this->includeIcons();
+		}
+
+		// Register global hooks only once (first instance)
+		if (count(self::$instances) === 1) {
+			$this->registerGlobalHooks();
+		}
+
+		// Register instance's hooks (every instance)
+		$this->registerInstanceHooks();
 	}
 
+
+
 	/**
-	 * Ensure custom icons are tracked for CSS generation
+	 * Register all custom icons (runs once)
 	 */
-	protected function trackCustomIcons(): void
+	protected function registerCustomIcons(): void
 	{
-		if (empty($this->customIcons)) {
-			return;
-		}
+		$icons = array_merge(apply_filters('jvbRegisterCustomIcons', []), ['syncing' => JVB_DIR . '/assets/icons/cloud-sync-thin.svg',
+			'alphabetical' => JVB_DIR . '/assets/icons/alphabetical.svg']);
 
-		foreach ($this->customIcons as $name => $path) {
-			$this->trackIconUsage($name, $this->style);
-		}
+		// Process and store in static property so all instances can access
+		self::$customIconsRegistry = $this->processCustomIconsArray($icons);
 	}
 
 	/**
-	 * Include icons via filter (for JS usage, etc.)
+	 * Process custom icons array into source-grouped format
 	 */
+	protected function processCustomIconsArray(array $icons): array
+	{
+		$out = [];
+		foreach ($icons as $name => $source) {
+			if (!file_exists($source)) {
+				error_log('[IconsManager] No file exists for custom Icon: '.$name);
+				continue;
+			}
+			$out[$name] = $source;
+		}
+
+		return $out;
+	}
+
+	/**
+	 * Load custom icons for this instance's source
+	 */
+	protected function loadCustomIconsForSource(): void
+	{
+		$this->customIcons = self::$customIconsRegistry;
+//		foreach ($this->customIcons as $name => $path) {
+//			if (!isset($this->icons[$this->style])) {
+//				$this->icons[$this->style] = [];
+//			}
+//			if (!in_array($name, $this->icons[$this->style])) {
+//				$this->icons[$this->style][] = $name;
+//			}
+//		}
+	}
+
+	/**
+	 * Load previously stored icons for this source
+	 */
+	protected function loadStoredIcons(): void
+	{
+		$allIcons = get_option(BASE.'usedIcons', []);
+		$storedIcons = $allIcons[$this->source] ?? [];
+
+		// Merge stored icons with any existing icons (like custom icons)
+		foreach ($storedIcons as $style => $names) {
+			if (!isset($this->icons[$style])) {
+				$this->icons[$style] = [];
+			}
+			$this->icons[$style] = array_unique(array_merge($this->icons[$style], $names));
+		}
+	}
+
 	protected function includeIcons():void
 	{
-		$icons = get_option(BASE.'includeIcons');
-
-		if (!$icons) {
-			$icons = [
+		$defaults = [
+			'icons' => [
+				'google-logo',
+				'apple-logo',
 				'check-circle',
 				'close-circle',
+				'faders-horizontal',
 				'cloud-slash',
 				'exclamation-mark',
 				'cloud-arrow-down',
+				'caret-down',
 				'cloud-arrow-up',
 				'cloud-check',
 				'cloud-slash',
@@ -92,9 +159,11 @@
 				'share-fat',
 				'trash',
 				'star',
+				'alphabetical',
 				['name' => 'star-half', 'style' => 'fill'],
 				['name' => 'star', 'style' => 'fill'],
-				//FORMATTING
+			],
+			'forms' => [
 				'copy',
 				'paragraph',
 				'text-h-one',
@@ -120,105 +189,177 @@
 				'file-doc',
 				'file-txt',
 				'file-xls',
-			];
+			],
+//			'dash' => [
+//
+//			]
+		];
 
-			$check = [JVB_CONTENT, JVB_TAXONOMY, JVB_USER];
-			foreach ($check as $constant) {
-				foreach ($constant as $key => $value) {
-					if (array_key_exists('icon', $value) && !in_array($value['icon'], $icons)) {
-						$icons[] = $value['icon'];
-					}
-				}
-			}
-			$icons = apply_filters('jvbIncludeIcons', $icons);
-			$icons = $this->maybePrefixIcons($icons);
-			update_option(BASE.'includeIcons', $icons);
+
+		// Add icons from content/taxonomy/user configs (like old behavior)
+		$configIcons = $this->getIconsFromConfigs();
+		if (!empty($configIcons)) {
+			$defaults['icons'] = array_merge($defaults['icons'], $configIcons);
 		}
 
-		// Ensure icons are in the correct format (handle legacy data)
-		if (!$this->isIconsArrayPrefixed($icons)) {
-			$icons = $this->maybePrefixIcons($icons);
-			update_option(BASE.'includeIcons', $icons);
+		// Allow filtering per source (extensibility)
+		$icons = apply_filters("jvbIncludeIcons_{$this->source}", $defaults[$this->source] ?? []);
+
+		// Also allow filtering all sources at once
+		$allIcons = apply_filters('jvbIncludeIcons', $defaults);
+		if (isset($allIcons[$this->source])) {
+			$icons = array_merge($icons, $allIcons[$this->source]);
 		}
-
-		$additional = apply_filters('jvbIncludeIcons', []);
-		if (!empty($additional)) {
-			$additional = $this->maybePrefixIcons($additional);
-			$merged = $this->mergeUsedIcons($icons, $additional);
-
-			if ($icons != $merged) {
-				update_option(BASE.'includeIcons', $merged);
-				$icons = $merged;
-			}
-		}
-
-		foreach ($icons as $style => $theIcons) {
-			foreach($theIcons as $icon) {
-				$this->trackIconUsage($icon, $style);
-			}
+		if (!empty($icons)) {
+			$this->include($icons);
 		}
 	}
 
 	/**
-	 * Check if icons array is in the prefixed format [style => [icons]]
+	 * Get icons from Registrar instances
+	 *
 	 */
-	protected function isIconsArrayPrefixed(array $icons): bool
+	protected function getIconsFromConfigs(): array
 	{
-		if (empty($icons)) {
-			return true;
+		$icons = [];
+		$registered = Registrar::getRegistered();
+
+		foreach ($registered as $type) {
+			$registrar = Registrar::getInstance($type);
+			$icons[] = $registrar->getIcon();
 		}
 
-		// Check if first key is a valid style name
-		$first_key = array_key_first($icons);
-		if (!in_array($first_key, $this->styles)) {
-			return false;
-		}
 
-		// Check if first value is an array
-		return is_array($icons[$first_key]);
+		return array_unique(array_filter($icons));
 	}
 
-	protected function maybePrefixIcons(array $icons):array
+	/**
+	 * Public method to include icons in this source
+	 */
+	public function include(array $icons): self
 	{
-		$out = [];
-		foreach ($icons as $icon) {
-			if (is_array($icon) && array_key_exists('style', $icon)) {
-				if (!array_key_exists($icon['style'], $out)) {
-					$out[$icon['style']] = [];
-				}
-				if (!in_array($icon['name'], $out[$icon['style']])) {
-					$out[$icon['style']][] = $icon['name'];
-				}
-			} elseif(is_array($icon)) {
-				$icon = $icon['name'];
+		$processed = $this->processIconArray($icons);
+		$changed = false;
+
+		foreach ($processed as $style => $names) {
+			if (!isset($this->icons[$style])) {
+				$this->icons[$style] = [];
 			}
-			if (!is_array($icon)) {
-				if (!array_key_exists($this->style, $out)) {
-					$out[$this->style] = [];
+
+			foreach ($names as $name) {
+				// Skip if already in this source
+				if (in_array($name, $this->icons[$style])) {
+					continue;
 				}
-				if (!in_array($icon, $out[$this->style])){
-					$out[$this->style][] = $icon;
+
+				// Skip if already in main 'icons' source
+				if ($this->iconExistsInMainSource($name, $style)) {
+					error_log("[IconsManager] Skipping '{$name}' in '{$this->source}' - already in 'icons' source");
+					continue;
 				}
+
+				$this->icons[$style][] = $name;
+				$changed = true;
 			}
 		}
+
+		// Only save if something actually changed
+		if ($changed) {
+			$this->saveIcons();
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Process icon array into [style => [names]] format
+	 */
+	protected function processIconArray(array $icons): array
+	{
+		$out = [];
+
+		foreach ($icons as $icon) {
+			if (is_array($icon) && isset($icon['style'])) {
+				$style = $icon['style'];
+				$name = $icon['name'];
+			} else {
+				$style = $this->style;
+				$name = is_array($icon) ? $icon['name'] : $icon;
+			}
+
+			if (!isset($out[$style])) {
+				$out[$style] = [];
+			}
+
+			if (!in_array($name, $out[$style])) {
+				$out[$style][] = $name;
+			}
+		}
+
 		return $out;
 	}
 
-	protected function addMap():void
+	/**
+	 * Save all icons across all instances
+	 */
+	protected function saveIcons(): void
+	{
+		$allIcons = [];
+		foreach (self::$instances as $source => $instance) {
+			$allIcons[$source] = $instance->icons;
+		}
+
+		update_option(BASE.'usedIcons', $allIcons);
+
+		// Track WHICH source needs updating
+		$needsUpdate = get_option(BASE.'icons_needs_update', []);
+		if (!is_array($needsUpdate)) {
+			$needsUpdate = [];
+		}
+		$needsUpdate[$this->source] = true;
+		update_option(BASE.'icons_needs_update', $needsUpdate);
+	}
+
+	/**
+	 * Check if icon exists in other sources
+	 */
+	protected function checkDuplicateAcrossInstances(string $name, string $style): void
+	{
+		$foundIn = [];
+
+		foreach (self::$instances as $source => $instance) {
+			if (isset($instance->icons[$style]) && in_array($name, $instance->icons[$style])) {
+				$foundIn[] = $source;
+			}
+		}
+
+		if (count($foundIn) > 1) {
+			error_log(sprintf(
+				'[IconsManager] Warning: Icon "%s" (%s) is registered in multiple sources: %s. Consider consolidating to avoid duplicate CSS output.',
+				$name,
+				$style,
+				implode(', ', $foundIn)
+			));
+		}
+	}
+
+	protected function addMap(): void
 	{
 		$map = get_option(BASE.'iconMap');
 		if (!$map) {
-			$map = [];
-			if (Features::forSite()->has('referrals')){
+			$map = [
+				'seo'	=> 'robot'
+			];
+			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('magicLink')) {
 				$map['magicLink'] = 'magic-wand';
 			}
-			if (Features::hasAnyIntegration()) {
+			if (Site::hasAnyIntegration()) {
 				$map['integrations'] = 'plugs-connected';
 			}
 			update_option(BASE.'iconMap', $map);
@@ -228,44 +369,128 @@
 	}
 
 	/**
-	 * Register WordPress hooks
+	 * Register global hooks (only once)
 	 */
-	protected function registerHooks(): void
+	protected function registerGlobalHooks(): void
 	{
-		add_action('init', [$this, 'includeIcons'], 1);
-		add_action('init', [$this, 'checkCSS'], 10);
-		add_action('wp_enqueue_scripts', [$this, 'enqueueIconStyles']);
+		add_action('wp_loaded', [self::class, 'checkCSS']);
+		add_action('shutdown', [self::class, 'checkCSS']);
+	}
+
+	/**
+	 * Register instance-specific hooks (every instance)
+	 */
+	protected function registerInstanceHooks(): void
+	{
+		// Register this source's stylesheet
+		add_action('init', [$this, 'registerStyle'], 11);
+
+		// Auto-enqueue base icons on front-end
+		if ($this->source === 'icons') {
+			add_action('wp_enqueue_scripts', [$this, 'enqueueIconStyles']);
+		}
+
+		// Auto-enqueue all in admin
 		add_action('admin_enqueue_scripts', [$this, 'enqueueIconStyles']);
 	}
 
-	public function checkCSS():void
+	public function enqueueIconStyles():void
 	{
-//		update_option(BASE.'icons_needs_update', true);
-		if (get_option(BASE.'icons_needs_update', false)) {
-			error_log('Regenerating CSS');
+//		if (file_exists(JVB_CHILD_URL . "assets/css/{$this->source}.css")){
+			wp_enqueue_style('jvb-icons-'.$this->source);
+//		}
+
+	}
+
+	public static function checkCSS(): void
+	{
+		$needsUpdate = get_option(BASE.'icons_needs_update', []);
+		if (!empty($needsUpdate)) {
+			error_log('Regenerating CSS for sources: ' . implode(', ', array_keys($needsUpdate)));
 			delete_option(BASE.'icons_needs_update');
-			$this->regenerateCSS();
+			self::regenerateAllCSS($needsUpdate);
 		}
 	}
 
-	protected function regenerateCSS(): void
+	public static function regenerateAllCSS(array $sourcesToUpdate = []): void
 	{
 		error_log('[IconsManager]:regenerateCSS');
-		$css = $this->generateIconCSS();
-		$css_path = JVB_CHILD_DIR.'/assets/css/';
-		if (!file_exists($css_path)) {
-			wp_mkdir_p($css_path);
+		$css_dir = JVB_CHILD_DIR.'/assets/css/';
+
+		if (!file_exists($css_dir)) {
+			wp_mkdir_p($css_dir);
 		}
-		$css_path .= '/icons.css';
 
+		// Load all icons from database option
+		$allIcons = get_option(BASE.'usedIcons', []);
 
-		// Archive current version before overwriting
-		$this->archiveCurrentVersion($css);
+		// If no specific sources provided, regenerate all
+		if (empty($sourcesToUpdate)) {
+			$sourcesToUpdate = array_fill_keys(array_keys($allIcons), true);
+		}
 
-		if (file_put_contents($css_path, $css) !== false) {
-			CacheManager::updateTimestamp('icons');
-		} else {
-			error_log('[IconsManager]Could not write css.');
+		// 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) {
+				Cache::touch('icons_' . $source);
+				error_log("[IconsManager] Updated {$source}.css");
+			} else {
+				error_log("[IconsManager] Could not write {$source}.css");
+			}
+
+			// Restore original icons
+			$instance->icons = $originalIcons;
+		}
+	}
+
+	protected function regenerateCSS(array $sourcesToUpdate = []): void
+	{
+		error_log('[IconsManager]:regenerateCSS');
+		$css_dir = JVB_CHILD_DIR.'/assets/css/';
+
+		if (!file_exists($css_dir)) {
+			wp_mkdir_p($css_dir);
+		}
+
+		// If no specific sources provided, regenerate all
+		if (empty($sourcesToUpdate)) {
+			$sourcesToUpdate = array_fill_keys(array_keys(self::$instances), true);
+		}
+
+		// Generate CSS only for sources that need it
+		foreach (self::$instances as $source => $instance) {
+			if (!isset($sourcesToUpdate[$source])) {
+				continue; // Skip this source
+			}
+
+			$css = $instance->generateIconCSS();
+			$css_path = $css_dir . $source . '.css';
+
+			// Archive current version before overwriting
+			$instance->archiveCurrentVersion($css);
+
+			if (file_put_contents($css_path, $css) !== false) {
+				Cache::touch('icons_' . $source);
+				error_log("[IconsManager] Updated {$source}.css");
+			} else {
+				error_log("[IconsManager] Could not write {$source}.css");
+			}
 		}
 	}
 
@@ -294,10 +519,14 @@
 	 *   - 'size' => 24 (for custom sizing via inline style)
 	 * @return string HTML icon element
 	 */
-	public function getIcon(string $name, array $options = []): string
+	public function get(string $name, array $options = []): string
 	{
-		$style = array_key_exists('style', $options) ? $options['style'] :$this->style;
-		$name = (array_key_exists($name, $this->map)) ? $this->map[$name] : $name;
+		if (empty($name)) {
+			//No icon requested
+			return '';
+		}
+		$style = $options['style'] ?? $this->style;
+		$name = $this->map[$name] ?? $name;
 
 		// Validate icon exists
 		if (!$this->iconExists($name, $style)) {
@@ -305,56 +534,51 @@
 			return '';
 		}
 
+		// Track usage - only if not already tracked
+		if (!isset($this->icons[$style])) {
+			$this->icons[$style] = [];
+		}
 
+		if (!in_array($name, $this->icons[$style])) {
+			// Check if it's already in main source (for non-main sources)
+			if ($this->iconExistsInMainSource($name, $style)) {
+				// Don't add to this source, but still render the icon
+				// The CSS from icons.css will handle it
+			} else {
+				// Add to this source
+				$this->icons[$style][] = $name;
+				$this->checkDuplicateAcrossInstances($name, $style);
+				$this->saveIcons();
+			}
+		}
 
-		// Track icon usage
-		$this->trackIconUsage($name, $style);
-
-		$styleClass = ($style !== $this->style) ? '-'.substr($style, 0,2) : '';
-		// Build classes
+		// Build icon HTML (same as before)
+		$styleClass = ($style !== $this->style) ? '-'.substr($style, 0, 2) : '';
 		$classes = ['icon', 'icon-' . $name.$styleClass];
-		if (!empty($options['class'])) {
+
+		if (isset($options['class'])) {
 			$classes[] = $options['class'];
 		}
 
+		$attrs = ['class' => implode(' ', $classes)];
 
-		$attrs = ['class="' . esc_attr(implode(' ', $classes)) . '"'];
-		$attrs[] = 'aria-hidden="true"';
-
-
-
-		return '<i ' . implode(' ', $attrs) . '></i>';
-	}
-
-	/**
-	 * Track icon usage for CSS generation
-	 */
-	protected function trackIconUsage(string $name, string $style): void
-	{
-		$needsUpdate = false;
-
-		if (!array_key_exists($style, $this->usedIcons)) {
-			$this->usedIcons[$style] = [];
-			$needsUpdate = true;
+		if (isset($options['label'])) {
+			$attrs['aria-label'] = esc_attr($options['label']);
+			$attrs['role'] = 'img';
+		} elseif (isset($options['decorative']) && $options['decorative']) {
+			$attrs['aria-hidden'] = 'true';
 		}
 
-		if (!in_array($name, $this->usedIcons[$style])) {
-			$this->usedIcons[$style][] = $name;
-			$needsUpdate = true;
+		if (isset($options['size'])) {
+			$attrs['style'] = sprintf('--icon-size: %dpx;', absint($options['size']));
 		}
 
-		if ($needsUpdate) {
-			// Merge with existing option to never lose icons
-			$existing = get_option(BASE.'usedIcons', []);
-			$merged = $this->mergeUsedIcons($existing, $this->usedIcons);
-			update_option(BASE.'usedIcons', $merged);
-
-			// Flag for regeneration on next init
-			update_option(BASE.'icons_needs_update', true);
-
-			// Clear cache
-			$this->cache->delete('icon_styles_css');
+		$attr_string = '';
+		foreach ($attrs as $key => $value) {
+			$attr_string .= sprintf(' %s="%s"', $key, $value);
 		}
+
+		return sprintf('<i%s></i>', $attr_string);
 	}
 
 	/**
@@ -401,7 +625,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;
@@ -420,22 +644,17 @@
 		// 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);
 	}
 
-
-	/**
-	 * Enqueue icon styles via REST endpoint
-	 */
-	public function enqueueIconStyles(): void
+	public function registerStyle(): void
 	{
-		$timestamp = CacheManager::getTimestamp('icons');
+		$timestamp = Cache::lastModified('icons_' . $this->source);
+		$handle = 'jvb-icons-' . $this->source;
 
-		wp_enqueue_style(
-			'jvb-icons',
-			JVB_CHILD_URL.'assets/css/icons.css',
+		wp_register_style(
+			$handle,
+			JVB_CHILD_URL . "assets/css/{$this->source}.css",
 			[],
 			$timestamp
 		);
@@ -447,48 +666,29 @@
 	protected function generateIconCSS(): string
 	{
 		$css = '';
-		$this->mergeUsedIcons();
 
-		foreach ($this->usedIcons as $style => $icons) {
-			$styleClass = ($style !== $this->style) ? '-'.substr($style, 0,2) : '';
-			foreach ($icons as $icon) {
+		foreach ($this->icons as $style => $names) {
+			$styleClass = ($style !== $this->style) ? '-'.substr($style, 0, 2) : '';
+			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 .= "}";
 				}
 			}
 		}
-		return $this->minifyCss($css);
-	}
 
-	protected function mergeUsedIcons(array|bool $oldIcons = true, array|bool $newIcons = true):array
-	{
-		$set = false;
-		if ($oldIcons === true) {
-			$oldIcons = $this->usedIcons;
-			$set = true;
-		}
-		if ($newIcons === true) {
-			$history = $this->getVersionHistory();
-			$newIcons = (count($history) > 0) ? $history[0]['iconList'] : [];
-		}
-		foreach ($newIcons as $style => $icons) {
-			if (!isset($oldIcons[$style])) {
-				//Style  doesn't exist in previous set, add the whole thing
-				$oldIcons[$style] = $icons;
-			} else {
-				$oldIcons[$style] = array_unique(
-					array_merge($oldIcons[$style], $icons)
-				);
-			}
-		}
-		if ($set) {
-			$this->usedIcons = $oldIcons;
-			update_option(BASE.'usedIcons', $oldIcons);
-		}
-		return $oldIcons;
+		return $this->minifyCss($css);
 	}
 
 	protected function minifyCSS(string $css): string
@@ -502,31 +702,40 @@
 
 		return trim($css);
 	}
-	public function getCSSIcon(string $icon, ?string $style=null):string
+
+	public function getCSSIcon(string $icon, ?string $style = null): string
 	{
 		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}";
 		}
 		return '';
 	}
-	public function getEncodedSVG(string $icon, ?string $style = null):string
+
+	public function getEncodedSVG(string $icon, ?string $style = null): string
 	{
 		if (!$style) {
 			$style = $this->style;
 		}
 		return $this->cache->remember($style.$icon,
-		function () use ($icon, $style) {
-			$svg = $this->getRawSvg($icon, $style);
-			if ($svg) {
-				return base64_encode($svg);
-			}
-			return '';
-		});
-
+			function () use ($icon, $style) {
+				$svg = $this->getRawSvg($icon, $style);
+				if ($svg) {
+					return base64_encode($svg);
+				}
+				return '';
+			});
 	}
 
 	/**
@@ -534,12 +743,14 @@
 	 */
 	public function clearIconCache(): void
 	{
-		delete_option(BASE . 'icon_usage_list'); // Clear DB option
+		delete_option(BASE . 'icon_usage_list'); // Legacy
 		delete_option(BASE.'usedIcons');
-		delete_option(BASE.'includeIcons');
 		delete_option(BASE.'iconMap');
-		$this->cache->delete('icon_styles_css');
-		CacheManager::updateTimestamp('icons');
+
+		// Clear cache for all sources
+		foreach (self::$instances as $source => $instance) {
+			$instance->cache->forget('icon_styles_css');
+		}
 	}
 
 	protected function archiveCurrentVersion(string $css): void
@@ -547,13 +758,13 @@
 		$history = $this->getVersionHistory();
 
 		$icon_count = 0;
-		foreach ($this->usedIcons as $style => $icons) {
-			$icon_count += count($icons);
+		foreach ($this->icons as $style => $names) {
+			$icon_count += count($names);
 		}
 
 		$newEntry = [
 			'css' => $css,
-			'iconList' => $this->usedIcons,
+			'iconList' => $this->icons,
 			'timestamp' => time(),
 			'icon_count' => $icon_count,
 			'size' => strlen($css),
@@ -566,12 +777,12 @@
 			$history = array_slice($history, 0, self::MAX_VERSIONS);
 		}
 
-		update_option(BASE.'icon_css_history', $history);
+		update_option(BASE.'icon_css_history_' . $this->source, $history);
 	}
 
 	public function getVersionHistory(): array
 	{
-		return get_option(BASE.'icon_css_history', []);
+		return get_option(BASE.'icon_css_history_' . $this->source, []);
 	}
 
 	public function restoreVersion(int $timestamp): bool
@@ -580,7 +791,7 @@
 
 		foreach ($history as $entry) {
 			if ($entry['timestamp'] === $timestamp) {
-				$css_path = JVB_DIR . '/assets/css/icons.css';
+				$css_path = JVB_CHILD_DIR . '/assets/css/' . $this->source . '.css';
 
 				// Archive current before restoring
 				$current_css = file_get_contents($css_path);
@@ -590,9 +801,9 @@
 
 				// Restore the version
 				if (file_put_contents($css_path, $entry['css']) !== false) {
-					$this->usedIcons = $entry['iconList'];
-					update_option(BASE.'usedIcons', $this->usedIcons);
-					CacheManager::updateTimestamp('icons');
+					$this->icons = $entry['iconList'];
+					$this->saveIcons();
+					Cache::touch('icons_' . $this->source);
 					return true;
 				}
 
@@ -600,15 +811,20 @@
 			}
 		}
 
-		error_log("[IconsManager] Version {$timestamp} not found in history");
+		error_log("[IconsManager] Version {$timestamp} not found in history for source {$this->source}");
 		return false;
 	}
 
 	public function forceRefresh(): void
 	{
 		$this->clearIconCache();
-		update_option(BASE.'icons_needs_update', true);
-		CacheManager::updateTimestamp('icons');
+		$needsUpdate = get_option(BASE.'icons_needs_update', []);
+		if (!is_array($needsUpdate)) {
+			$needsUpdate = [];
+		}
+		$needsUpdate[$this->source] = true;
+		update_option(BASE.'icons_needs_update', $needsUpdate);
+		Cache::touch('icons_' . $this->source);
 	}
 
 	public function mergeVersions(array $timestamps): bool
@@ -617,8 +833,9 @@
 			return false;
 		}
 
-		$history = get_option(BASE.'icon_css_history', []);
+		$history = get_option(BASE.'icon_css_history_' . $this->source, []);
 		$merged_icons = [];
+
 		// Collect icons from selected versions
 		foreach ($history as $entry) {
 			if (in_array($entry['timestamp'], $timestamps)) {
@@ -640,18 +857,34 @@
 		}
 
 		// Archive current version
-		$current_css = file_get_contents(JVB_DIR . '/assets/css/icons.css');
+		$current_css = file_get_contents(JVB_CHILD_DIR . '/assets/css/' . $this->source . '.css');
 		if ($current_css !== false) {
 			$this->archiveCurrentVersion($current_css);
 		}
 
 		// Update used icons and regenerate
-		$this->usedIcons = $merged_icons;
-		update_option(BASE.'usedIcons', $this->usedIcons);
-
-		// Force regeneration
-		$this->regenerateCSS();
+		$this->icons = $merged_icons;
+		$this->saveIcons();
 
 		return true;
 	}
+
+	/**
+	 * Check if icon already exists in the main 'icons' source
+	 */
+	protected function iconExistsInMainSource(string $name, string $style): bool
+	{
+		// If this IS the main source, no need to check
+		if ($this->source === 'icons') {
+			return false;
+		}
+
+		// Check if main icons source exists
+		if (!isset(self::$instances['icons'])) {
+			return false;
+		}
+
+		$mainIcons = self::$instances['icons']->icons;
+		return isset($mainIcons[$style]) && in_array($name, $mainIcons[$style]);
+	}
 }

--
Gitblit v1.10.0