Files
prestashop-icons/src/MprIcons.php
myprestarocks 99a8f5bf9b Add shape prefix to symbol IDs, fix brand naming, add assets
- Symbol IDs now include shape for uniqueness: {shape}_{brand}_{mode}
  e.g., text-only_paypal_lm, rectangle_visa_la, icon_mastercard_dm
- Fixed brand name inconsistencies:
  - amazon_pay → amazon
  - naverpay → naver_pay
  - p24 → przelewy24
  - direct_debit → bacs (unified BACS Direct Debit name)
- Added font assets (FontAwesome, Material Icons)
- Added MprIconsAssets and MprIconsConfig classes
- Updated preview.html with shape parameter support
- All 752 icons complete (38 payment × 4 shapes × 4 modes + 12 social × 3 shapes × 4 modes)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 10:17:23 +00:00

392 lines
12 KiB
PHP

<?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);
}
/**
* Register icon font CSS assets
* Convenience method that delegates to MprIconsAssets
* Call from hookHeader or setMedia
*
* @param \Module $module The module instance
* @return bool Whether assets were registered
*/
public static function registerAssets(\Module $module): bool
{
return MprIconsAssets::registerAssets($module);
}
/**
* Check if self-hosted icon fonts are enabled
*/
public static function isSelfHostEnabled(): bool
{
return MprIconsConfig::isSelfHostEnabled();
}
}