From 46d681c6b825d21b3f698d793c4e630c687d90ad Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Thu, 21 May 2026 21:41:53 +0000
Subject: [PATCH] =Major CustomBlocks.php overhaul, expanding block support and customization from the editor. theme.json should now be updated on new themes to set brand colours, etc. Also note: major change to .col vs .row alignment: simplifying it to .top .bottom vs the confusion of the differences for .col/.row .start and .a-start
---
inc/managers/IconsManager.php | 758 ++++++++++++++++++++++++++++++++++++++++++++++-----------
1 files changed, 607 insertions(+), 151 deletions(-)
diff --git a/inc/managers/IconsManager.php b/inc/managers/IconsManager.php
index d3710d0..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,78 +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()
+
+ /**
+ * Constructor now takes source parameter
+ */
+ private function __construct(string $source)
{
- $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';
+ $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();
+
+ // 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();
+ }
- $this->usedIcons = get_option(BASE.'used_icons', []);
- $this->includeIcons();
- // Register hooks only once
- $this->registerHooks();
+
+ /**
+ * Register all custom icons (runs once)
+ */
+ protected function registerCustomIcons(): void
+ {
+ $icons = array_merge(apply_filters('jvbRegisterCustomIcons', []), ['syncing' => JVB_DIR . '/assets/icons/cloud-sync-thin.svg',
+ 'alphabetical' => JVB_DIR . '/assets/icons/alphabetical.svg']);
+
+ // 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 includeIcons():array
+ protected function processCustomIconsArray(array $icons): array
{
- $icons = get_option(BASE.'includeIcons');
-// $icons = false;
- if (!$icons) {
- $icons = $this->addIncludeIcons();
- }
- $include = apply_filters('jvbIncludeIcons', []);
- $add = array_filter($include, function($addIt) use ($icons) {
- return !in_array($addIt, $icons);
- });
- error_log('Adding icons: '.print_r($add, true));
- if (!empty($add)) {
- $this->addIncludeIcons($add);
+ $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 $icons;
+ return $out;
}
- protected function addIncludeIcons(array $add = []) {
- $icons = get_option(BASE.'includeIcons');
- if (!$icons) {
- $icons = [
+ /**
+ * 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
+ {
+ $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',
@@ -91,9 +159,11 @@
'share-fat',
'trash',
'star',
+ 'alphabetical',
['name' => 'star-half', 'style' => 'fill'],
['name' => 'star', 'style' => 'fill'],
- //FORMATTING
+ ],
+ 'forms' => [
'copy',
'paragraph',
'text-h-one',
@@ -119,47 +189,177 @@
'file-doc',
'file-txt',
'file-xls',
- ];
- $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);
+ ],
+// 'dash' => [
+//
+// ]
+ ];
- update_option(BASE.'includeIcons', $icons);
- update_option(BASE.'icons_needs_update', true);
+
+ // Add icons from content/taxonomy/user configs (like old behavior)
+ $configIcons = $this->getIconsFromConfigs();
+ if (!empty($configIcons)) {
+ $defaults['icons'] = array_merge($defaults['icons'], $configIcons);
}
- $add = apply_filters('jvbIncludeIcons', $add);
- $add = array_filter($add, function($addIt) use ($icons) {
- return !in_array($addIt, $icons);
- });
- if (!empty($add)) {
- $icons = array_merge($add, $icons);
- update_option(BASE.'usedIcons', $icons);
- update_option(BASE.'icons_needs_update', true);
+
+ // 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]);
}
- return $icons;
+ if (!empty($icons)) {
+ $this->include($icons);
+ }
}
- protected function addMap():void
+ /**
+ * Get icons from Registrar instances
+ *
+ */
+ protected function getIconsFromConfigs(): array
+ {
+ $icons = [];
+ $registered = Registrar::getRegistered();
+
+ foreach ($registered as $type) {
+ $registrar = Registrar::getInstance($type);
+ $icons[] = $registrar->getIcon();
+ }
+
+
+ return array_unique(array_filter($icons));
+ }
+
+ /**
+ * Public method to include icons in this source
+ */
+ public function include(array $icons): self
+ {
+ $processed = $this->processIconArray($icons);
+ $changed = false;
+
+ foreach ($processed as $style => $names) {
+ if (!isset($this->icons[$style])) {
+ $this->icons[$style] = [];
+ }
+
+ foreach ($names as $name) {
+ // Skip if already in this source
+ if (in_array($name, $this->icons[$style])) {
+ continue;
+ }
+
+ // 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;
+ }
+
+ /**
+ * 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);
@@ -169,35 +369,128 @@
}
/**
- * Register WordPress hooks
+ * Register global hooks (only once)
*/
- protected function registerHooks(): void
+ protected function registerGlobalHooks(): void
{
- add_action('init', [$this, 'checkCSS']);
- 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_DIR.'/assets/css/icons.css';
+ $css_dir = JVB_CHILD_DIR.'/assets/css/';
- if (file_put_contents($css_path, $css) !== false) {
- CacheManager::updateTimestamp('icons');
- } else {
- error_log('[IconsManager]Could not write css.');
+ if (!file_exists($css_dir)) {
+ wp_mkdir_p($css_dir);
+ }
+
+ // Load all icons from database option
+ $allIcons = get_option(BASE.'usedIcons', []);
+
+ // If no specific sources provided, regenerate all
+ if (empty($sourcesToUpdate)) {
+ $sourcesToUpdate = array_fill_keys(array_keys($allIcons), true);
+ }
+
+ // 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");
+ }
}
}
@@ -226,57 +519,66 @@
* - '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
{
- if (!array_key_exists('style', $options)) {
- $options['style'] = $this->style;
+ if (empty($name)) {
+ //No icon requested
+ return '';
}
- $name = (array_key_exists($name, $this->map)) ? $this->map[$name] : $name;
+ $style = $options['style'] ?? $this->style;
+ $name = $this->map[$name] ?? $name;
// Validate icon exists
- if (!$this->iconExists($name, $options['style'])) {
+ if (!$this->iconExists($name, $style)) {
error_log('[IconsManager] Icon not found: ' . $name);
return '';
}
- $style = $options['style'] ?? $this->style;
+ // Track usage - only if not already tracked
+ if (!isset($this->icons[$style])) {
+ $this->icons[$style] = [];
+ }
- // Track icon usage
- $this->trackIconUsage($name, $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();
+ }
+ }
- // Build classes
- $classes = ['icon', 'icon-' . $name];
- if (!empty($options['class'])) {
+ // Build icon HTML (same as before)
+ $styleClass = ($style !== $this->style) ? '-'.substr($style, 0, 2) : '';
+ $classes = ['icon', 'icon-' . $name.$styleClass];
+
+ 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
- {
- if (!array_key_exists($style, $this->usedIcons)) {
- $this->usedIcons[$style] = [];
+ 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;
- update_option(BASE.'used_icons', $this->usedIcons);
- // Flag for regeneration on next init
- update_option(BASE.'icons_needs_update', true);
-
- // Clear cache
- $this->cache->delete('icon_styles_css');
+ if (isset($options['size'])) {
+ $attrs['style'] = sprintf('--icon-size: %dpx;', absint($options['size']));
}
+
+ $attr_string = '';
+ foreach ($attrs as $key => $value) {
+ $attr_string .= sprintf(' %s="%s"', $key, $value);
+ }
+
+ return sprintf('<i%s></i>', $attr_string);
}
/**
@@ -323,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;
@@ -342,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_URL.'assets/css/icons.css',
+ wp_register_style(
+ $handle,
+ JVB_CHILD_URL . "assets/css/{$this->source}.css",
[],
$timestamp
);
@@ -369,18 +666,28 @@
protected function generateIconCSS(): string
{
$css = '';
- 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);
}
@@ -395,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 '';
+ });
}
/**
@@ -427,8 +743,148 @@
*/
public function clearIconCache(): void
{
- delete_option(BASE . 'icon_usage_list'); // Clear DB option
- $this->cache->delete('icon_styles_css');
- CacheManager::updateTimestamp('icons');
+ delete_option(BASE . 'icon_usage_list'); // Legacy
+ delete_option(BASE.'usedIcons');
+ delete_option(BASE.'iconMap');
+
+ // Clear cache for all sources
+ foreach (self::$instances as $source => $instance) {
+ $instance->cache->forget('icon_styles_css');
+ }
+ }
+
+ protected function archiveCurrentVersion(string $css): void
+ {
+ $history = $this->getVersionHistory();
+
+ $icon_count = 0;
+ foreach ($this->icons as $style => $names) {
+ $icon_count += count($names);
+ }
+
+ $newEntry = [
+ 'css' => $css,
+ 'iconList' => $this->icons,
+ 'timestamp' => time(),
+ 'icon_count' => $icon_count,
+ 'size' => strlen($css),
+ 'size_formatted' => size_format(strlen($css), 2)
+ ];
+
+ array_unshift($history, $newEntry);
+
+ if (count($history) > self::MAX_VERSIONS) {
+ $history = array_slice($history, 0, self::MAX_VERSIONS);
+ }
+
+ update_option(BASE.'icon_css_history_' . $this->source, $history);
+ }
+
+ public function getVersionHistory(): array
+ {
+ return get_option(BASE.'icon_css_history_' . $this->source, []);
+ }
+
+ public function restoreVersion(int $timestamp): bool
+ {
+ $history = $this->getVersionHistory();
+
+ foreach ($history as $entry) {
+ if ($entry['timestamp'] === $timestamp) {
+ $css_path = JVB_CHILD_DIR . '/assets/css/' . $this->source . '.css';
+
+ // Archive current before restoring
+ $current_css = file_get_contents($css_path);
+ if ($current_css !== false) {
+ $this->archiveCurrentVersion($current_css);
+ }
+
+ // Restore the version
+ if (file_put_contents($css_path, $entry['css']) !== false) {
+ $this->icons = $entry['iconList'];
+ $this->saveIcons();
+ Cache::touch('icons_' . $this->source);
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ error_log("[IconsManager] Version {$timestamp} not found in history for source {$this->source}");
+ return false;
+ }
+
+ public function forceRefresh(): void
+ {
+ $this->clearIconCache();
+ $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
+ {
+ if (empty($timestamps)) {
+ return false;
+ }
+
+ $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)) {
+ foreach ($entry['iconList'] as $style => $icons) {
+ if (!isset($merged_icons[$style])) {
+ $merged_icons[$style] = [];
+ }
+ // Merge and keep unique
+ $merged_icons[$style] = array_unique(
+ array_merge($merged_icons[$style], $icons)
+ );
+ }
+ }
+ }
+
+ if (empty($merged_icons)) {
+ error_log('[IconsManager] No icons found in selected versions');
+ return false;
+ }
+
+ // Archive current version
+ $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->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