- 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>
392 lines
12 KiB
PHP
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();
|
|
}
|
|
}
|