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:
370
src/MprIcons.php
Normal file
370
src/MprIcons.php
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user