commit 19db248bb4a0a0f42ab8a29bf83b0e227ea3e033 Author: myprestarocks Date: Mon Jan 26 14:49:04 2026 +0100 Initial commit - prestashop-icons package Semantic icon helper for PrestaShop modules. - Maps icon names to Font Awesome or Material Icons - Auto-detects admin context (Material) vs front (FA default) - Reads icon preference from ps_mpr_config table - Theme detection for Material Icons - PHP class + JavaScript helper Co-Authored-By: Claude Opus 4.5 diff --git a/README.md b/README.md new file mode 100644 index 0000000..119fb72 --- /dev/null +++ b/README.md @@ -0,0 +1,117 @@ +# PrestaShop Icons + +Semantic icon helper for PrestaShop modules. + +```php +MprIcons::get('account'); // Just works +``` + +## How It Works + +- **Admin**: Uses Material Icons (auto-detected) +- **Front**: Reads from `ps_mpr_config` table, defaults to Font Awesome + +## Setup (Module Install) + +Call `init()` from your module's install method: + +```php +public function install() +{ + MprIcons::init(); // Creates config table, detects theme icons + return parent::install(); +} +``` + +This will: +1. Create `ps_mpr_config` table if it doesn't exist +2. Detect theme icon set (checks theme.yml, assets for Material) +3. Save the detected preference + +## PHP + +```php +use MyPrestaRocks\Icons\MprIcons; + +// Just call get() - it figures out the rest +echo MprIcons::get('account'); +echo MprIcons::get('cart'); +echo MprIcons::get('success', 'text-green'); // With extra class + +// Get raw class/name (no HTML) +$class = MprIcons::raw('edit'); // 'fa fa-pencil' or 'edit' +``` + +## JavaScript + +Include the script and set icon set from PHP: + +```php +// In your module +$this->context->controller->addJS($this->getLocalPath() . 'vendor/myprestarocks/prestashop-icons/js/mpr-icons.js'); +Media::addJsDef(['mprIconSet' => MprIcons::getIconSet()]); +``` + +```javascript +MprIcons.get('account'); // HTML string +MprIcons.create('warning'); // DOM element +MprIcons.raw('edit'); // Class/name only +``` + +## Configuration + +Front office icon set is stored in `ps_mpr_config`: +- module: `mpr_global` +- key: `front_icon_set` +- value: `fontawesome` or `material` + +```php +// Save preference +MprIcons::saveIconSet('material'); +``` + +## Available Icons + +| Name | Font Awesome | Material | +|------|--------------|----------| +| `account` | `fa fa-user` | `person` | +| `cart` | `fa fa-shopping-cart` | `shopping_cart` | +| `orders` | `fa fa-list` | `receipt_long` | +| `success` | `fa fa-check` | `check` | +| `error` | `fa fa-times` | `close` | +| `warning` | `fa fa-exclamation-triangle` | `warning` | +| `info` | `fa fa-info-circle` | `info` | +| `edit` | `fa fa-pencil` | `edit` | +| `delete` | `fa fa-trash` | `delete` | +| `search` | `fa fa-search` | `search` | +| `settings` | `fa fa-cog` | `settings` | +| `email` | `fa fa-envelope` | `email` | +| `phone` | `fa fa-phone` | `phone` | +| `lock` | `fa fa-lock` | `lock` | +| `logout` | `fa fa-sign-out` | `logout` | +| `home` | `fa fa-home` | `home` | +| `address` | `fa fa-map-marker` | `location_on` | +| `truck` | `fa fa-truck` | `local_shipping` | +| `credit-card` | `fa fa-credit-card` | `credit_card` | +| `calendar` | `fa fa-calendar` | `calendar_today` | +| `heart` | `fa fa-heart` | `favorite` | +| `star` | `fa fa-star` | `star` | +| `eye` | `fa fa-eye` | `visibility` | +| `eye-off` | `fa fa-eye-slash` | `visibility_off` | +| `close` | `fa fa-times` | `close` | +| `check` | `fa fa-check` | `check` | +| `plus` | `fa fa-plus` | `add` | +| `minus` | `fa fa-minus` | `remove` | +| `chevron-*` | `fa fa-chevron-*` | `chevron_*` | +| `arrow-*` | `fa fa-arrow-*` | `arrow_*` | +| `download` | `fa fa-download` | `download` | +| `upload` | `fa fa-upload` | `upload` | +| `refresh` | `fa fa-refresh` | `refresh` | +| `spinner` | `fa fa-spinner fa-spin` | `hourglass_empty` | +| `loading` | `fa fa-circle-o-notch fa-spin` | `autorenew` | + +Full list: `MprIcons::list()` + +## License + +MIT diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..57af3b5 --- /dev/null +++ b/composer.json @@ -0,0 +1,14 @@ +{ + "name": "myprestarocks/prestashop-icons", + "description": "Semantic icon helper for PrestaShop modules - maps icon names to Font Awesome or Material Icons", + "type": "library", + "license": "MIT", + "autoload": { + "psr-4": { + "MyPrestaRocks\\Icons\\": "src/" + } + }, + "require": { + "php": ">=7.1" + } +} diff --git a/js/mpr-icons.js b/js/mpr-icons.js new file mode 100644 index 0000000..5e80eb3 --- /dev/null +++ b/js/mpr-icons.js @@ -0,0 +1,153 @@ +/** + * MprIcons - Semantic Icon Helper for PrestaShop (JavaScript) + * + * Simple API: MprIcons.get('account') - that's it. + * + * Set window.mprIconSet from PHP to configure (defaults to 'fontawesome'). + * + * @package myprestarocks/prestashop-icons + */ + +var MprIcons = (function() { + 'use strict'; + + var FA = 'fontawesome'; + var MATERIAL = 'material'; + + var map = { + // User + 'account': ['fa fa-user', 'person'], + 'user': ['fa fa-user', 'person'], + 'logout': ['fa fa-sign-out', 'logout'], + 'login': ['fa fa-sign-in', 'login'], + + // E-commerce + 'cart': ['fa fa-shopping-cart', 'shopping_cart'], + 'orders': ['fa fa-list', 'receipt_long'], + 'truck': ['fa fa-truck', 'local_shipping'], + 'credit-card': ['fa fa-credit-card', 'credit_card'], + 'gift': ['fa fa-gift', 'card_giftcard'], + 'tag': ['fa fa-tag', 'local_offer'], + 'percent': ['fa fa-percent', 'percent'], + + // Status + 'success': ['fa fa-check', 'check'], + 'error': ['fa fa-times', 'close'], + 'warning': ['fa fa-exclamation-triangle', 'warning'], + 'info': ['fa fa-info-circle', 'info'], + + // Actions + 'edit': ['fa fa-pencil', 'edit'], + 'delete': ['fa fa-trash', 'delete'], + 'search': ['fa fa-search', 'search'], + 'settings': ['fa fa-cog', 'settings'], + 'download': ['fa fa-download', 'download'], + 'upload': ['fa fa-upload', 'upload'], + 'refresh': ['fa fa-refresh', 'refresh'], + 'save': ['fa fa-save', 'save'], + 'print': ['fa fa-print', 'print'], + 'copy': ['fa fa-copy', 'content_copy'], + + // UI + 'close': ['fa fa-times', 'close'], + 'check': ['fa fa-check', 'check'], + 'plus': ['fa fa-plus', 'add'], + 'minus': ['fa fa-minus', 'remove'], + 'menu': ['fa fa-bars', 'menu'], + 'more': ['fa fa-ellipsis-v', 'more_vert'], + + // Arrows + 'arrow-left': ['fa fa-arrow-left', 'arrow_back'], + 'arrow-right': ['fa fa-arrow-right', 'arrow_forward'], + 'arrow-up': ['fa fa-arrow-up', 'arrow_upward'], + 'arrow-down': ['fa fa-arrow-down', 'arrow_downward'], + 'chevron-left': ['fa fa-chevron-left', 'chevron_left'], + 'chevron-right': ['fa fa-chevron-right', 'chevron_right'], + 'chevron-up': ['fa fa-chevron-up', 'expand_less'], + 'chevron-down': ['fa fa-chevron-down', 'expand_more'], + + // Communication + 'email': ['fa fa-envelope', 'email'], + 'phone': ['fa fa-phone', 'phone'], + 'comment': ['fa fa-comment', 'comment'], + 'chat': ['fa fa-comments', 'chat'], + + // Security + 'lock': ['fa fa-lock', 'lock'], + 'unlock': ['fa fa-unlock', 'lock_open'], + 'shield': ['fa fa-shield', 'shield'], + 'key': ['fa fa-key', 'vpn_key'], + + // Misc + 'home': ['fa fa-home', 'home'], + 'address': ['fa fa-map-marker', 'location_on'], + 'calendar': ['fa fa-calendar', 'calendar_today'], + 'clock': ['fa fa-clock-o', 'schedule'], + 'heart': ['fa fa-heart', 'favorite'], + 'star': ['fa fa-star', 'star'], + 'eye': ['fa fa-eye', 'visibility'], + 'eye-off': ['fa fa-eye-slash', 'visibility_off'], + 'file': ['fa fa-file', 'description'], + 'folder': ['fa fa-folder', 'folder'], + 'image': ['fa fa-image', 'image'], + 'link': ['fa fa-link', 'link'], + 'external': ['fa fa-external-link', 'open_in_new'], + 'help': ['fa fa-question-circle', 'help'], + + // Loading + 'spinner': ['fa fa-spinner fa-spin', 'hourglass_empty'], + 'loading': ['fa fa-circle-o-notch fa-spin', 'autorenew'] + }; + + function getIconSet() { + return window.mprIconSet || FA; + } + + function get(name, extraClass) { + if (!map[name]) return ''; + + var set = getIconSet(); + var extra = extraClass ? ' ' + extraClass : ''; + + if (set === MATERIAL) { + return '' + map[name][1] + ''; + } + return ''; + } + + function raw(name) { + if (!map[name]) return ''; + var set = getIconSet(); + return set === MATERIAL ? map[name][1] : map[name][0]; + } + + function create(name, extraClass) { + var html = get(name, extraClass); + if (!html) return null; + var temp = document.createElement('div'); + temp.innerHTML = html; + return temp.firstChild; + } + + function exists(name) { + return !!map[name]; + } + + function list() { + return Object.keys(map); + } + + return { + get: get, + raw: raw, + create: create, + exists: exists, + list: list, + FA: FA, + MATERIAL: MATERIAL + }; +})(); + +if (typeof module !== 'undefined' && module.exports) { + module.exports = MprIcons; +} diff --git a/src/MprIcons.php b/src/MprIcons.php new file mode 100644 index 0000000..fe891b9 --- /dev/null +++ b/src/MprIcons.php @@ -0,0 +1,370 @@ + [FA class, Material name] + */ + private static $map = [ + // User + 'account' => ['fa fa-user', 'person'], + 'user' => ['fa fa-user', 'person'], + 'logout' => ['fa fa-sign-out', 'logout'], + 'login' => ['fa fa-sign-in', 'login'], + + // E-commerce + 'cart' => ['fa fa-shopping-cart', 'shopping_cart'], + 'orders' => ['fa fa-list', 'receipt_long'], + 'truck' => ['fa fa-truck', 'local_shipping'], + 'credit-card' => ['fa fa-credit-card', 'credit_card'], + 'gift' => ['fa fa-gift', 'card_giftcard'], + 'tag' => ['fa fa-tag', 'local_offer'], + 'percent' => ['fa fa-percent', 'percent'], + + // Status + 'success' => ['fa fa-check', 'check'], + 'error' => ['fa fa-times', 'close'], + 'warning' => ['fa fa-exclamation-triangle', 'warning'], + 'info' => ['fa fa-info-circle', 'info'], + + // Actions + 'edit' => ['fa fa-pencil', 'edit'], + 'delete' => ['fa fa-trash', 'delete'], + 'search' => ['fa fa-search', 'search'], + 'settings' => ['fa fa-cog', 'settings'], + 'download' => ['fa fa-download', 'download'], + 'upload' => ['fa fa-upload', 'upload'], + 'refresh' => ['fa fa-refresh', 'refresh'], + 'save' => ['fa fa-save', 'save'], + 'print' => ['fa fa-print', 'print'], + 'copy' => ['fa fa-copy', 'content_copy'], + + // UI + 'close' => ['fa fa-times', 'close'], + 'check' => ['fa fa-check', 'check'], + 'plus' => ['fa fa-plus', 'add'], + 'minus' => ['fa fa-minus', 'remove'], + 'menu' => ['fa fa-bars', 'menu'], + 'more' => ['fa fa-ellipsis-v', 'more_vert'], + + // Arrows + 'arrow-left' => ['fa fa-arrow-left', 'arrow_back'], + 'arrow-right' => ['fa fa-arrow-right', 'arrow_forward'], + 'arrow-up' => ['fa fa-arrow-up', 'arrow_upward'], + 'arrow-down' => ['fa fa-arrow-down', 'arrow_downward'], + 'chevron-left' => ['fa fa-chevron-left', 'chevron_left'], + 'chevron-right' => ['fa fa-chevron-right', 'chevron_right'], + 'chevron-up' => ['fa fa-chevron-up', 'expand_less'], + 'chevron-down' => ['fa fa-chevron-down', 'expand_more'], + + // Communication + 'email' => ['fa fa-envelope', 'email'], + 'phone' => ['fa fa-phone', 'phone'], + 'comment' => ['fa fa-comment', 'comment'], + 'chat' => ['fa fa-comments', 'chat'], + + // Security + 'lock' => ['fa fa-lock', 'lock'], + 'unlock' => ['fa fa-unlock', 'lock_open'], + 'shield' => ['fa fa-shield', 'shield'], + 'key' => ['fa fa-key', 'vpn_key'], + + // Misc + 'home' => ['fa fa-home', 'home'], + 'address' => ['fa fa-map-marker', 'location_on'], + 'calendar' => ['fa fa-calendar', 'calendar_today'], + 'clock' => ['fa fa-clock-o', 'schedule'], + 'heart' => ['fa fa-heart', 'favorite'], + 'star' => ['fa fa-star', 'star'], + 'eye' => ['fa fa-eye', 'visibility'], + 'eye-off' => ['fa fa-eye-slash', 'visibility_off'], + 'file' => ['fa fa-file', 'description'], + 'folder' => ['fa fa-folder', 'folder'], + 'image' => ['fa fa-image', 'image'], + 'link' => ['fa fa-link', 'link'], + 'external' => ['fa fa-external-link', 'open_in_new'], + 'help' => ['fa fa-question-circle', 'help'], + + // Loading + 'spinner' => ['fa fa-spinner fa-spin', 'hourglass_empty'], + 'loading' => ['fa fa-circle-o-notch fa-spin', 'autorenew'], + ]; + + /** + * Get icon HTML - main method + * + * @param string $name Semantic icon name + * @param string $class Extra CSS classes (optional) + * @return string HTML + */ + public static function get(string $name, string $class = ''): string + { + if (!isset(self::$map[$name])) { + return ''; + } + + $set = self::getIconSet(); + [$fa, $material] = self::$map[$name]; + $extra = $class ? ' ' . $class : ''; + + if ($set === self::MATERIAL) { + return '' . $material . ''; + } + + return ''; + } + + /** + * Get raw class/name without HTML wrapper + */ + public static function raw(string $name): string + { + if (!isset(self::$map[$name])) { + return ''; + } + + $set = self::getIconSet(); + [$fa, $material] = self::$map[$name]; + + return $set === self::MATERIAL ? $material : $fa; + } + + /** + * Detect and cache the icon set to use + */ + public static function getIconSet(): string + { + if (self::$iconSet !== null) { + return self::$iconSet; + } + + // Admin context = Material Icons + if (defined('_PS_ADMIN_DIR_')) { + self::$iconSet = self::MATERIAL; + return self::$iconSet; + } + + // Front office - read from config table + self::$iconSet = self::readConfigTable() ?: self::FA; + return self::$iconSet; + } + + /** + * Read icon set preference from ps_mpr_config table + */ + private static function readConfigTable(): ?string + { + if (!defined('_DB_PREFIX_')) { + return null; + } + + try { + $table = _DB_PREFIX_ . self::CONFIG_TABLE; + $sql = "SELECT `value` FROM `{$table}` + WHERE `module` = '" . pSQL(self::CONFIG_MODULE) . "' + AND `key` = '" . pSQL(self::CONFIG_KEY) . "'"; + + $result = \Db::getInstance()->getValue($sql); + + if ($result && in_array($result, [self::FA, self::MATERIAL])) { + return $result; + } + } catch (\Exception $e) { + // Table doesn't exist or query failed - use default + } + + return null; + } + + /** + * Save icon set preference to ps_mpr_config table + */ + public static function saveIconSet(string $set): bool + { + if (!in_array($set, [self::FA, self::MATERIAL])) { + return false; + } + + if (!defined('_DB_PREFIX_')) { + return false; + } + + try { + $table = _DB_PREFIX_ . self::CONFIG_TABLE; + $now = date('Y-m-d H:i:s'); + + $sql = "INSERT INTO `{$table}` (`module`, `key`, `value`, `date_add`, `date_upd`) + VALUES ('" . pSQL(self::CONFIG_MODULE) . "', '" . pSQL(self::CONFIG_KEY) . "', '" . pSQL($set) . "', '{$now}', '{$now}') + ON DUPLICATE KEY UPDATE `value` = '" . pSQL($set) . "', `date_upd` = '{$now}'"; + + $result = \Db::getInstance()->execute($sql); + + if ($result) { + self::$iconSet = null; // Clear cache + } + + return $result; + } catch (\Exception $e) { + return false; + } + } + + /** + * Force a specific icon set (for testing or overriding) + */ + public static function setIconSet(string $set): void + { + if (in_array($set, [self::FA, self::MATERIAL])) { + self::$iconSet = $set; + } + } + + /** + * Clear cached icon set + */ + public static function clearCache(): void + { + self::$iconSet = null; + } + + /** + * Initialize icon system - call from module install + * Creates config table if needed, sets default icon set + */ + public static function init(): bool + { + if (!self::ensureConfigTable()) { + return false; + } + + // If no config exists, detect and save default + $current = self::readConfigTable(); + if ($current === null) { + $detected = self::detectThemeIcons(); + self::saveIconSet($detected); + } + + return true; + } + + /** + * Ensure ps_mpr_config table exists + */ + public static function ensureConfigTable(): bool + { + if (!defined('_DB_PREFIX_')) { + return false; + } + + try { + $table = _DB_PREFIX_ . self::CONFIG_TABLE; + + // Check if table exists + $exists = \Db::getInstance()->getValue( + "SELECT COUNT(*) FROM information_schema.tables + WHERE table_schema = DATABASE() + AND table_name = '{$table}'" + ); + + if ((int)$exists > 0) { + return true; + } + + // Create table + $sql = "CREATE TABLE IF NOT EXISTS `{$table}` ( + `id_config` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `module` VARCHAR(64) NOT NULL, + `key` VARCHAR(128) NOT NULL, + `value` TEXT DEFAULT NULL, + `date_add` DATETIME NOT NULL, + `date_upd` DATETIME NOT NULL, + PRIMARY KEY (`id_config`), + UNIQUE KEY `module_key` (`module`, `key`), + KEY `module` (`module`) + ) ENGINE=" . _MYSQL_ENGINE_ . " DEFAULT CHARSET=utf8mb4;"; + + return \Db::getInstance()->execute($sql); + } catch (\Exception $e) { + return false; + } + } + + /** + * Detect which icon set the current theme likely uses + * Returns 'fontawesome' for most PrestaShop themes + */ + public static function detectThemeIcons(): string + { + // Most PrestaShop themes (including classic) use Font Awesome + // Only return Material if we can positively detect it + + if (!defined('_PS_THEME_DIR_')) { + return self::FA; + } + + try { + // Check theme.yml for icon hints + $themeYml = _PS_THEME_DIR_ . 'config/theme.yml'; + if (file_exists($themeYml)) { + $content = file_get_contents($themeYml); + if (strpos($content, 'material') !== false || strpos($content, 'Material') !== false) { + return self::MATERIAL; + } + } + + // Check if theme has material-icons in assets + $assetsDir = _PS_THEME_DIR_ . 'assets/css/'; + if (is_dir($assetsDir)) { + $files = scandir($assetsDir); + foreach ($files as $file) { + if (strpos($file, 'material') !== false) { + return self::MATERIAL; + } + } + } + } catch (\Exception $e) { + // Ignore detection errors + } + + // Default to Font Awesome (safe for 95% of themes) + return self::FA; + } + + /** + * Check if icon exists + */ + public static function exists(string $name): bool + { + return isset(self::$map[$name]); + } + + /** + * Get all available icon names + */ + public static function list(): array + { + return array_keys(self::$map); + } +}