[],'label' => []]; protected string $style; protected array $styles = [ 'regular', 'bold', 'duotone', 'fill', 'light', 'thin' ]; protected array $labels = [ 'submenu' => 'Toggle Menu', 'dashboard' => 'Dashboard', 'arrow-fat-down' => 'Downvote', 'arrow-fat-up' => 'Upvote', 'hamburger' => 'Menu', 'check-square-offset'=> 'Approval Requests', 'h1' => 'Heading 1', 'h2' => 'Heading 2', 'h3' => 'Heading 3', 'note-pencil' => 'Notes', 'sun-dim' => 'Light Mode', 'moon' => 'Dark Mode', 'grid' => 'Grid View', 'list' => 'List View', 'clock-clockwise' => 'Upcoming Events', 'clock-counter-clockwise'=>'Past Events', 'repeat' => 'Recurring Events', 'floppy-disk' => 'Save', ]; protected array $map = [ 'time' => 'clock', 'back' => 'arrow-u-up-left', 'logo' => 'logo.svg', 'logo-basic' => 'logo-basic.svg', 'save' => 'floppy-disk', 'restore' => 'arrow-counter-clockwise', 'help' => 'question', 'upload' => 'cloud-arrow-up', 'download' => 'cloud-arrow-down', 'synced' => 'cloud-check', 'syncing' => 'cloud-sync-thin.svg', 'offline' => 'cloud-slash', 'error' => 'cloud-warning', 'cart' => 'shopping-cart', 'delete' => 'trash', 'edit' => 'pencil-simple', 'bio' => 'user', 'login' => 'sign-in', 'logout' => 'sign-out', 'future' => 'clock-clockwise', 'past' => 'clock-counter-clockwise', 'light' => 'sun-dim', 'dark' => 'moon', 'h1' => 'text-h-one', 'h2' => 'text-h-two', 'h3' => 'text-h-three', 'bold' => 'text-b', 'italic' => 'text-italic', 'underline' => 'text-underline', 'strike' => 'text-strikethrough', 'image' => 'image-square', 'reply' => 'arrow-bend-up-left', 'approval' => 'check-square-offset', 'response' => 'chat-teardrop', 'karma' => 'scales', 'show' => 'eye', 'publish' => 'eye', 'hide' => 'eye-closed', 'draft' => 'eye-closed', 'asc' => 'sort-ascending', 'desc' => 'sort-descending', 'all' => 'infinity', 'random' => 'shuffle', 'location' => 'map-pin', 'hours' => 'clock', 'favourite' => 'heart', 'email' => 'envelope', 'text' => 'chat', 'integrations' => 'plugs-connected', 'connected' => 'plugs-connected', 'disconnected' => 'plugs', 'umami' => 'chart-line', 'square-up' => 'square-logo', 'tiktok' => 'tiktok-logo', 'threads' => 'threads-logo', 'twitch' => 'twitch-logo', 'snapchat' => 'snapchat-logo', 'linktree' => 'linktree-logo', 'fediverse' => 'fediverse-logo', 'mastadon' => 'mastadon-logo', 'youtube' => 'youtube-logo', 'twitter' => 'twitter-logo', 'messenger' => 'messenger-logo', 'facebook' => 'facebook-logo', 'instagram' => 'instagram-logo', 'dash' => 'door', 'event' => 'calendar', 'events' => 'calendar', 'settings' => 'gear-six', 'grid' => 'squares-four', 'list' => 'rows', 'pinned' => 'push-pin', 'search' => 'magnifying-glass', 'add' => 'plus-square', 'minus' => 'minus-square', 'support' => 'question', 'grab' => 'dots-six-vertical', 'checkout' => 'receipt', 'home' => 'house', 'elbow-right-down' => 'arrow-elbow-right-down', 'elbow-right-up'=> 'arrow-elbow-right-up', 'elbow-left-down'=> 'arrow-elbow-left-down', 'elbow-left-up' => 'arrow-elbow-left-up', 'menu' => 'list', 'submenu' => 'caret-down', 'close' => 'x', 'close-square' => 'x-square', 'dashboard' => 'door', 'system' => 'gear-six', 'options' => 'gear-six', 'news' => 'newspaper', 'metrics' => 'chart-line', 'prev' => 'caret-left', 'up' => 'caret-circle-up', 'right' => 'caret-double-right', 'left' => 'caret-double-left', 'next' => 'caret-right', 'refresh' => 'arrows-clockwise', 'pending' => 'arrows-clockwise', 'copy' => 'copy-simple', 'align-center' => 'text-align-center', 'align-left' => 'text-align-left', 'align-right' => 'text-align-right', 'alphabetical' => 'alphabetical.svg', 'date' => 'calendar', 'down' => 'caret-double-down', 'tattoo' => 'drop-simple', 'tattoos' => 'drop-simple', 'theme' => 'folder-open', 'arttheme' => 'folder-open', 'style' => 'hash', 'artstyle' => 'hash', 'colour' => 'drop', 'placement' => 'person-arms-spread', 'artmedia' => 'squares-four', 'artist' => 'user', 'client' => 'user', 'artists' => 'users-three', 'partner' => 'currency-circle-dollar', 'shop' => 'storefront', 'piercing' => 'needle', 'piercings' => 'needle', 'artwork' => 'palette', 'artform' => 'shapes', 'city' => 'map-pin', 'type' => 'users-three', 'pstyle' => 'nut', 'project' => 'code', 'map' => 'map-trifold', 'offer' => 'gift', 'referrals' => 'hand-heart' ]; private const ICON_GROUPS = [ 'navigation' => [ 'back' => ['filename' => 'back', 'label' => 'Back', 'size' => 24], 'home' => ['filename' => 'house', 'label' => 'Home'], 'right' => ['filename' => 'arrow-fat-right', 'label' => ''], 'elbow-right-down'=> ['filename' => 'arrow-elbow-right-down', 'label' => ''], 'elbow-right-up'=> ['filename' => 'arrow-elbow-right-up', 'label' => ''], 'elbow-left-down'=> ['filename' => 'arrow-elbow-left-down', 'label' => ''], 'elbow-left-up'=> ['filename' => 'arrow-elbow-left-up', 'label' => ''], 'menu' => ['filename' => 'list', 'label' => 'Toggle Menu', 'size' => 32], 'submenu' => ['filename' => 'caret-down', 'label' => 'Toggle Submenu'], 'close' => ['filename' => 'x', 'label' => 'Close', 'size' => 32], 'close-square' => ['filename' => 'x-square', 'label' => 'Close'], 'dashboard' => ['filename' => 'door', 'label'=> 'Behind the Scenes', 'size' => 24], 'dash' => ['filename' => 'door', 'label'=> 'Behind the Scenes', 'size' => 24], 'settings' => ['filename' => 'gear-six', 'label'=> 'Settings', 'size' => 24], 'system' => ['filename' => 'gear-six', 'label'=> 'Settings', 'size' => 24], 'options' => ['filename' => 'gear-six', 'label'=> 'Options', 'size' => 24], 'news' => ['filename' => 'newspaper', 'label' => 'News', 'size' => 24], 'metrics' => ['filename' => 'chart-line', 'label' => 'Metrics', 'size' => 24], 'prev' => ['filename' => 'caret-left', 'label' => 'Previous', 'size' => 24], 'up' => ['filename' => 'caret-circle-up', 'label' => 'Up', 'size' => 24], 'down' => ['filename' => 'caret-double-down', 'label' => 'Down', 'size' => 24], 'right' => ['filename' => 'caret-double-right', 'label' => 'Right', 'size' => 24], 'next' => ['filename' => 'caret-right', 'label' => 'Next', 'size' => 24], 'refresh' => ['filename' => 'arrows-clockwise', 'label' => 'Refresh', 'size' => 24], 'pending' => ['filename' => 'arrows-clockwise', 'label' => 'Refresh', 'size' => 24], 'clock' => ['filename' => 'clock', 'label' => 'Hours'], 'copy' => ['filename' => 'copy-simple', 'label' => 'Duplicate'], 'list-heart' => ['filename' => 'list-heart', 'label'=> 'Lists'], 'downvoted' => ['filename' => 'arrow-fat-down-fill.svg', 'label'=> 'Downvote'], 'upvoted' => ['filename' => 'arrow-fat-up-fill.svg', 'label'=> 'Upvote'], 'downvote' => ['filename' => 'arrow-fat-down', 'label'=> 'Downvote'], 'upvote' => ['filename' => 'arrow-fat-up', 'label'=> 'Upvote'], 'karma' => ['filename' => 'scales', 'label'=> 'Karma'], 'response' => ['filename' => 'chat-teardrop', 'label' => 'Responses'], 'reply' => ['filename' => 'arrow-bend-up-left', 'label' => 'Reply'], 'approval' => ['filename' => 'check-square-offset', 'label' => 'Approval Requests'], 'check' => ['filename' => 'check-circle', 'label' => 'done'], 'hamburger' => ['filename' => 'hamburger', 'label' => 'menu'] ], 'formatting' => [ 'paragraph' => ['filename' => 'paragraph', 'label' => 'Paragraph', 'size' => 24], 'h1' => ['filename' => 'text-h-one', 'label' => 'Heading 1', 'size' => 24], 'h2' => ['filename' => 'text-h-two', 'label' => 'Heading 2', 'size' => 24], 'h3' => ['filename' => 'text-h-three', 'label' => 'Heading 3', 'size' => 24], 'bold' => ['filename' => 'text-b-bold.svg', 'label' => 'Bold', 'size' => 24], 'italic' => ['filename' => 'text-italic', 'label' => 'Italic', 'size' => 24], 'underline' => ['filename' => 'text-underline', 'label' => 'Underline', 'size' => 24], 'strike' => ['filename' => 'text-strikethrough', 'label' => 'Strikethrough', 'size' => 24], 'list-bullets' => ['filename' => 'list-bullets', 'label' => 'Bulleted List', 'size' => 24], 'list-numbers' => ['filename' => 'list-numbers', 'label' => 'Numbered List', 'size' => 24], 'image' => ['filename' => 'image-square', 'label' => 'Image', 'size' => 24], 'align-left' => ['filename' => 'text-align-left', 'label' => 'Align Left', 'size' => 24], 'align-right' => ['filename' => 'text-align-right', 'label' => 'Align Right', 'size' => 24], 'align-center' => ['filename' => 'text-align-center', 'label' => 'Align Center', 'size' => 24], 'link' =>['filename' => 'link', 'label' => 'Link', 'size' => 24], 'note' => ['filename' => 'note-pencil', 'label' => 'Notes', 'size' => 24], 'password' => ['filename' => 'password', 'label' => 'Password'] ], 'theme' => [ 'light' => ['filename' => 'sun-dim', 'label' => 'Light Mode'], 'dark' => ['filename' => 'moon', 'label' => 'Dark Mode'], 'grid' => ['filename' => 'squares-four', 'label' => 'Grid View'], 'list' => ['filename' => 'rows', 'label' => 'List View'], ], 'content' => [ 'offer' => ['filename'=>'gift', 'label'=>'Offer'], 'logo' => ['filename' => 'logo.svg', 'label' => 'North\'eh', 'size' => 20], 'logo-basic' => ['filename' => 'logo-basic.svg', 'label' => 'North\'eh', 'size' => 20], 'theme' => ['filename' => 'folder-open', 'label' => 'Theme', 'size' => 20], 'arttheme' => ['filename' => 'folder-open', 'label' => 'Art Theme', 'size' => 20], 'style' => ['filename' => 'hash', 'label' => 'Style', 'size' => 20], 'artstyle' => ['filename' => 'hash', 'label' => 'Art Style', 'size' => 20], 'colour' => ['filename' => 'drop', 'label' => 'Colour', 'size' => 20], 'placement' => ['filename' => 'person-arms-spread', 'label' => 'Placement', 'size' => 20], 'artmedia' => ['filename' => 'squares-four', 'label' => 'Media', 'size' => 20], 'artist' => ['filename' => 'user', 'label'=> 'Artist', 'size' => 24], 'client' => ['filename' => 'user', 'label'=> 'Artist', 'size' => 24], 'artists' => ['filename' => 'users-three', 'label' => 'Artists'], 'partner' => ['filename' => 'currency-circle-dollar', 'label' => 'Partner'], 'shop' => ['filename' => 'storefront', 'label' => 'Shop', 'size' => 20], 'tattoo' => ['filename' => 'drop-simple', 'label' => 'Tattoo', 'size' => 20], 'tattoos' => ['filename' => 'drop-simple', 'label' => 'Your Tattoos', 'size' => 20], 'event' => ['filename' => 'calendar', 'label' => 'Event', 'size' => 20], 'events' => ['filename' => 'calendar', 'label' => 'Your Events', 'size' => 20], 'piercing' => ['filename' => 'needle', 'label' => 'Piercings', 'size' => 20], 'piercings' => ['filename' => 'needle', 'label' => 'Your Piercings', 'size' => 20], 'artwork' => ['filename' => 'palette', 'label' => 'Artwork', 'size' => 20], 'artform' => ['filename' => 'shapes', 'label' => 'Art Form', 'size' => 20], 'city' => ['filename' => 'map-pin', 'label' => 'City', 'size' => 20], 'type' => ['filename' => 'users-three', 'label' => 'Artist Type', 'size' => 20], 'pstyle' => ['filename' => 'nut', 'label' => 'Body Modifications', 'size' => 20], 'past' => ['filename' => 'clock-counter-clockwise', 'label' => 'Past Events', 'size' => 20], 'future' => ['filename' => 'clock-clockwise', 'label' => 'Upcoming Events', 'size' => 20], 'repeat' => ['filename' => 'repeat', 'label' => 'Recurring Events', 'size' => 20], 'project' => ['filename' => 'code', 'label' => 'Project'], 'gauge' => ['filename' => 'gauge', 'label'=> 'Dashboard'], 'map' => ['filename' => 'map-trifold', 'label' => 'Map'], ], 'users' => [ 'new-user' => ['filename' => 'user-circle-plus', 'label' => 'New Artist'], 'user' => ['filename' => 'user', 'label' => 'Artist', 'size' => 20], 'bio' => ['filename' => 'user', 'label' => 'Your Bio', 'size' => 20], 'login' => ['filename' => 'sign-in', 'label' => 'Login'], 'logout' => ['filename' => 'sign-out', 'label' => 'Logout'] ], 'actions' => [ 'save' => ['filename' => 'floppy-disk', 'label' => 'Save'], 'restore'=> ['filename' => 'arrow-counter-clockwise', 'label' => 'Restore', 'size' => 32], 'edit' => ['filename' => 'pencil-simple', 'label' => 'Edit', 'size' => 24], 'delete'=> ['filename' => 'trash', 'label' => 'Delete', 'size' => 32], 'search'=> ['filename' => 'magnifying-glass', 'label' => 'Search', 'size' => 32], 'add' => ['filename' => 'plus-square', 'label' => 'Add New', 'size' => 32], 'minus' => ['filename' => 'minus-square', 'label' => 'Collapse', 'size' => 32], 'help' => ['filename' => 'question', 'label' => 'Toggle Quick Help'], 'support' => ['filename' => 'question', 'label' => 'Toggle Quick Help'], 'grab' => ['filename' => 'dots-six-vertical', 'label'=> 'Grab'], 'share' => ['filename' => 'share', 'label' => 'Share', 'size'=> 24], 'cart' => ['filename' => 'shopping-cart', 'label' => 'Your Cart', 'size'=> 24], 'checkout' => ['filename' => 'receipt', 'label' => 'Checkout', 'size'=> 24], 'upload' => ['filename' => 'cloud-arrow-up', 'label' => 'Upload to Server', 'size'=> 24], 'download' => ['filename' => 'cloud-arrow-down', 'label' => 'Downloading', 'size'=> 24], 'synced' => ['filename' => 'cloud-check', 'label' => 'Synced', 'size'=> 24], 'syncing' => ['filename' => 'cloud-sync-thin.svg', 'label' => 'Synced', 'size'=> 24], 'cloud' => ['filename' => 'cloud', 'label' => 'Synced', 'size'=> 24], // 'pending' => ['filename' => 'cloud', 'label' => 'Synced', 'size'=> 24], 'offline' => ['filename' => 'cloud-slash', 'label' => 'Offline', 'size'=> 24], 'error' => ['filename' => 'cloud-warning', 'label' => 'Upload Failed', 'size'=> 24], ], 'status' => [ 'show' => ['filename' => 'eye', 'label' => 'Show', 'size' => 20], 'publish' => ['filename' => 'eye', 'label' => 'Public', 'size' => 20], 'hide' => ['filename' => 'eye-closed', 'label' => 'Hide', 'size' => 20], 'draft' => ['filename' => 'eye-closed', 'label' => 'Hidden', 'size' => 20], 'pinned' => ['filename' => 'push-pin-simple', 'label' => 'Pin', 'size' => 32], 'bell' => ['filename' => 'bell', 'label' => 'Notification', 'size' => 32], 'bell-ringing' => ['filename' => 'bell-ringing', 'label' => 'Notification', 'size' => 32] ], 'sorting' => [ 'table' => ['filename' => 'table', 'label' => 'Table View', 'size' => 20], 'columns' => ['filename' => 'columns', 'label' => 'Show Columns', 'size' => 20], 'alphabetical' => ['filename' => 'alphabetical', 'label' => 'Alphabetical', 'size' => 20], 'calendar' => ['filename' => 'calendar-heart', 'label' => 'Date', 'size' => 20], 'asc' => ['filename' => 'sort-ascending', 'label' => 'Sort Ascending', 'size' => 20], 'ASC' => ['filename' => 'sort-ascending', 'label' => 'Sort Ascending', 'size' => 20], 'desc' => ['filename' => 'sort-descending', 'label' => 'Sort Descending', 'size' => 20], 'DESC' => ['filename' => 'sort-descending', 'label' => 'Sort Descending', 'size' => 20], 'filter' => ['filename' => 'faders', 'label' => 'Filter'], 'all' => ['filename' => 'infinity', 'label' => 'All', 'size' => 32], 'random' => ['filename' => 'shuffle', 'label' => 'Random', 'size' => 20] ], 'business' => [ 'location' => ['filename' => 'map-pin', 'label' => 'Location'], 'hours' => ['filename' => 'clock', 'label' => 'Hours'], 'star' => ['filename' => 'star', 'label' => 'Empty Star'], 'star-fill' => ['filename' => 'star-fill.svg', 'label' => 'Star'], 'star-half' => ['filename' => 'star-half-fill.svg', 'label' => 'Half Star'], ], 'social' => [ 'heart' => ['filename' => 'heart', 'label' => 'Favourite', 'size' => 24], 'favourite' => ['filename' => 'heart', 'label' => 'Favourite', 'size' => 24], 'favourites'=> ['filename' => 'heart', 'label' => 'Your Favourites', 'size' => 24], 'heart-fill'=> ['filename' => 'heart-fill.svg', 'label' => 'Favourited', 'size' => 24], 'share' => ['filename' => 'share', 'label' => 'Share', 'size' => 32], 'email' => ['filename' => 'envelope','label'=> 'Email'], 'text' => ['filename' => 'chat','label'=> 'Text'], 'phone' => ['filename' => 'phone','label'=> 'Call'], ], 'external' => [ 'integrations' => ['filename' => 'plugs-connected', 'label' => 'Integrations'], 'connected' => ['filename' => 'plugs-connected', 'label' => 'Connected'], 'disconnected' => ['filename' => 'plugs', 'label' => 'Disconnected'], 'instagram' => ['filename' => 'instagram-logo', 'label' => 'Instagram'], 'facebook' => ['filename' => 'facebook-logo', 'label' => 'Facebook'], 'messenger' => ['filename' => 'messenger-logo', 'label' => 'Facebook Messenger'], 'twitter' => ['filename' => 'twitter-logo', 'label' => 'Twitter'], 'x' => ['filename' => 'x-logo', 'label' => 'X'], 'youtube' => ['filename' => 'youtube-logo', 'label' => 'YouTube'], 'mastadon' => ['filename' => 'mastadon-logo', 'label' => 'Mastadon'], 'fediverse' => ['filename' => 'fediverse-logo', 'label' => 'Fediverse'], 'linktree' => ['filename' => 'linktree-logo', 'label' => 'LinkTree'], 'snapchat' => ['filename' => 'snapchat-logo', 'label' => 'Snapchat'], 'twitch' => ['filename' => 'twitch-logo', 'label' => 'Twitch'], 'threads' => ['filename' => 'threads-logo', 'label' => 'Threads'], 'tiktok' => ['filename' => 'tiktok-logo', 'label' => 'TikTok'], 'square-up' => ['filename' => 'square-logo', 'label' => 'Square'], 'umami' => ['filename' => 'chart-line', 'label' => 'Umami'], ] ]; private string $defaultStyle = '-thin'; private int $defaultSize = 16; protected string $name; protected array $used; protected int $size = 20; protected CacheManager $cache; public function __construct() { $this->cache = CacheManager::for('icons', WEEK_IN_SECONDS); // $this->cache->invalidateGroup('icons'); $this->style = JVB_SITE['icons']??'regular'; $this->used = get_option(BASE.'used_icons', [ $this->style => [ 'heart', 'hours', 'random', 'alphabetical', 'calendar', 'asc', 'desc', 'all', 'paragraph', 'h1', 'h2', 'h3', 'bold', 'italic', 'underline', 'strike', 'list-bullets', 'list-numbers', 'image', 'align-left', 'align-right', 'align-center', 'link', 'note', 'password', 'light', 'left', 'dark', 'grid', 'list', 'save', 'restore', 'edit', 'delete', 'search', 'add', 'minus', 'help', 'support', 'grab', 'share', 'cart', 'checkout', 'upload', 'download', 'synced', 'syncing', 'cloud', 'offline', 'error', 'show', 'publish', 'hide', 'draft', 'pinned', 'bell', 'bell-ringing', 'back', 'home', 'right', 'elbow-right-down', 'elbow-right-up', 'elbow-left-down', 'elbow-left-up', 'menu', 'submenu', 'close', 'close-square', 'dashboard', 'dash', 'settings', 'system', 'options', 'news', 'metrics', 'prev', 'up', 'down', 'right', 'copy', 'next', 'refresh', 'pending', 'clock', 'copy', 'list-heart', 'karma', 'response', 'reply', 'approval', 'check', 'hamburger', 'location', 'hours', 'star', 'star-half', 'exclamation-mark' ], 'fill' => [ 'heart', 'arrow-fat-down', 'arrow-fat-up', 'star' ] ]); } protected function updateUsed():void { $icons = []; foreach ($this->used as $style => $items) { $temp = array_unique($items); sort($temp); $icons[$style] = $temp; } update_option(BASE . 'used_icons', $icons); } protected function checkMap(string $name): string { $result = apply_filters('jvbIconMap', (array_key_exists($name, $this->map)) ? $this->map[$name] : $name, $name); return $result; } protected function checkName(string $name) { $name = $this->checkMap($name); $path = (str_contains($name, '.svg')) ? '/assets/icons/' : '/assets/phosphor-icons/regular/'; $filename = (str_contains($name, '.svg')) ? $name : $name.'.svg'; return file_exists(JVB_DIR . $path . $filename); } public function getIcon(string $name, array $options = []):?string { if (!$this->checkName($name)) { error_log('[Icons]Icon not found for: '.print_r($name, true)); return ''; } $style = (array_key_exists('style', $options) && in_array($options['style'], $this->styles)) ? $options['style'] : 'regular'; $update = false; if (!array_key_exists($style, $this->used)) { $this->used[$style] = []; $update = true; } if (!in_array($name, $this->used[$style])) { $this->used[$style][] = $name; $update = true; } if ($update) { $this->updateUsed(); } $name = $this->checkMap($name); $this->name = str_replace('.svg', '', $name); // Merge options with defaults and icon config $options = array_merge([ 'title' => $options['label']??$this->getIconLabel($name), 'size' => $options['size'] ?? $this->size, 'style' => $style, 'class' => '', 'wrap' => true, 'color' => 'currentColor' ], $options); return $this->cache->remember( array_merge($options, ['name' => $name]), function () use ($name, $options) { return $this->buildIcon($name, $options); } ); } public function getIconsByGroup(string $group):array { if (!isset(self::ICON_GROUPS[$group])) { return []; } $icons = []; foreach (self::ICON_GROUPS[$group] as $name => $config) { $icons[$name] = $this->getIcon($name); } return array_filter($icons); } public function getAllIcons():array { $icons = []; foreach (self::ICON_GROUPS as $group => $groupIcons) { foreach ($groupIcons as $name => $config) { $icons[$name] = $this->getIcon($name); } } return array_filter($icons); } public function getGroups():array { return array_keys(self::ICON_GROUPS); } private function findIconConfig(string $name):?array { foreach (self::ICON_GROUPS as $groupIcons) { if (isset($groupIcons[$name])) { return $groupIcons[$name]; } } return null; } private function buildIcon(string $name, array $options):?string { $filepath = $this->buildFilePath($name, $options['style']); if (!file_exists($filepath)) { error_log("Icon file not found: $filepath"); return null; } $svg = file_get_contents($filepath); if ($svg === false) { return null; } return $this->formatSvg($svg, $options); } private function buildFilePath(string $filename, string $style = ''):string { $svg = false; if (str_contains($filename, '.svg')) { $svg = true; $nameExtra = ''; $path = '/assets/icons/'; } else { $nameExtra = ($style === 'regular') ? '' : '-'.$style; $path = '/assets/phosphor-icons/'; } $filename = (str_contains($filename, '.svg')) ? $filename : $filename . $nameExtra . '.svg'; $style = ($style === '') ? '' : $style .'/'; $style = ($svg) ? '' : $style; return JVB_DIR . $path . $style . $filename; } private function formatSvg(string $svg, array $options): string { // Clean up SVG $svg = preg_replace("/([\n\t]+)/", ' ', $svg); $svg = preg_replace('/>\s*<', $svg); // Add size attributes - FIXED: assign results back to $svg if ($options['size'] !== 32) { $svg = str_replace('width="32"', 'width="' . (int)$options['size'] . '"', $svg); $svg = str_replace('height="32"', 'height="' . (int)$options['size'] . '"', $svg); } // Add color if provided - FIXED: assign results back to $svg if ($options['color'] !== 'currentColor') { $svg = str_replace('currentColor', $options['color'], $svg); } // Add title if provided if (!empty($options['title'])) { $svg = str_replace('name . ' ' . $options['class']); if (array_key_exists('wrap', $options) && $options['wrap'] === false) { return $svg; } return sprintf( '%s', esc_attr($classes), $svg ); } /**************************** * Converts to CSS icon ***************************/ public function getCSSIcon(string $name, array $options = []): ?string { if (!$this->checkName($name)) { return ''; } $name = $this->checkMap($name); $style = (array_key_exists('style', $options) && in_array($options['style'], $this->styles)) ? $options['style'] : 'regular'; $this->name = str_replace('.svg', '', $name); // Merge options with defaults and icon config $options = array_merge([ 'title' => $options['label']??$this->getIconLabel($name), 'size' => $options['size'] ?? $this->size, 'style' => $style, 'class' => '', 'wrap' => true, 'color' => 'currentColor' ], $options); $svg = $this->cache->remember( array_merge($options, ['name' => $name, 'css' => true]), function() use ($name, $options) { return $this->buildRawSvg($name, $options); } ); // Convert to base64 data URI $svg = ($svg) ? 'data:image/svg+xml;base64,' . base64_encode($svg) : null; return $svg; } private function buildRawSvg(string $filename, array $options): ?string { $filepath = $this->buildFilePath($filename, $options['style']); if (!file_exists($filepath)) { error_log("Icon file not found: $filepath"); return null; } $svg = file_get_contents($filepath); if ($svg === false) { return null; } return $this->formatRawSvg($svg, $options); } private function formatRawSvg(string $svg, array $options): string { // Clean up SVG $svg = preg_replace("/([\n\t]+)/", ' ', $svg); $svg = preg_replace('/>\s*<', $svg); // Add size attributes - FIXED: assign results back to $svg if ($options['size'] > 0) { $svg = str_replace('width="32"', 'width="' . (int)$options['size'] . '"', $svg); $svg = str_replace('height="32"', 'height="' . (int)$options['size'] . '"', $svg); } // Add color if provided and not currentColor - FIXED: assign results back to $svg if ($options['color'] !== 'currentColor') { $svg = str_replace('currentColor', $options['color'], $svg); } return $svg; } public function localizeIcons():array { if (empty($this->used)) { return []; } $used = []; foreach ($this->used as $style => $icons) { foreach ($icons as $icon) { $used[$icon] = $this->getIcon($icon, ['style' => $style]); } } return $used; } public function getIconLabel(string $name): string { $name = str_replace('.svg', '', $name); $label = (array_key_exists($name, $this->labels)) ? $this->labels[$name] : ''; $result = apply_filters('jvbIconLabel', $label, $name); return $result; } }