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 <noreply@anthropic.com>
This commit is contained in:
2026-01-26 14:49:04 +01:00
commit 19db248bb4
4 changed files with 654 additions and 0 deletions

117
README.md Normal file
View File

@@ -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

14
composer.json Normal file
View File

@@ -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"
}
}

153
js/mpr-icons.js Normal file
View File

@@ -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 '<i class="material-icons' + extra + '">' + map[name][1] + '</i>';
}
return '<i class="' + map[name][0] + extra + '"></i>';
}
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;
}

370
src/MprIcons.php Normal file
View File

@@ -0,0 +1,370 @@
<?php
/**
* MprIcons - Semantic Icon Helper for PrestaShop
*
* Simple API: MprIcons::get('account') - that's it.
*
* - Admin: auto-detects and uses Material Icons
* - Front: reads icon set from ps_mpr_config table
*
* @package myprestarocks/prestashop-icons
*/
namespace MyPrestaRocks\Icons;
class MprIcons
{
const FA = 'fontawesome';
const MATERIAL = 'material';
const CONFIG_TABLE = 'mpr_config';
const CONFIG_MODULE = 'mpr_global';
const CONFIG_KEY = 'front_icon_set';
private static $iconSet = null;
/**
* Semantic name => [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 '<i class="material-icons' . $extra . '">' . $material . '</i>';
}
return '<i class="' . $fa . $extra . '"></i>';
}
/**
* 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);
}
}