Fix icon detection: check theme.css content for Material Icons

PS 1.7+ classic theme bundles Material Icons inside theme.css via
webpack — no separate material-icons.css file exists. The old
detection only scanned filenames, so it wrongly defaulted to FA.

Now reads the first 50KB of theme.css to check for 'Material Icons'
or 'FontAwesome' font-face declarations. Falls back to version-based
default: Material for PS 1.7+, FA for PS 1.6.

Also fixes themeHasIconFont() to use the same CSS content check,
so self-hosted icons are correctly skipped when the theme provides them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 09:56:49 +00:00
parent 5b3374e2d5
commit b75eb1454f

View File

@@ -379,9 +379,12 @@ class MprIcons
/** /**
* Detect which icon set the current theme uses * Detect which icon set the current theme uses
* *
* Checks theme config and assets for Material Icons indicators. * PS 1.6 themes use Font Awesome.
* Returns 'fontawesome' for classic and most themes, * PS 1.7+ themes (classic, hummingbird, etc.) bundle Material Icons
* 'material' for Hummingbird and Material-based themes. * inside theme.css — no separate CSS file to scan for.
*
* Strategy: read the first ~50KB of theme.css and check for
* 'Material Icons' font-face. Fast, reliable, works across all versions.
*/ */
public static function detectThemeIcons(): string public static function detectThemeIcons(): string
{ {
@@ -390,19 +393,32 @@ class MprIcons
} }
try { try {
// PS 1.7+ — check theme.yml // Check compiled theme CSS for Material Icons font-face
$themeYml = _PS_THEME_DIR_ . 'config/theme.yml'; // PS 1.7+ bundles icons into theme.css via webpack
if (file_exists($themeYml)) { $cssFiles = [
$content = file_get_contents($themeYml); _PS_THEME_DIR_ . 'assets/css/theme.css',
if (stripos($content, 'hummingbird') !== false) { _PS_THEME_DIR_ . 'css/theme.css',
return self::MATERIAL; ];
foreach ($cssFiles as $cssFile) {
if (!file_exists($cssFile)) {
continue;
} }
if (stripos($content, 'material-icons') !== false || stripos($content, 'material_icons') !== false) { // Read first 50KB — font-face is always near the top
return self::MATERIAL; $handle = fopen($cssFile, 'r');
if ($handle) {
$chunk = fread($handle, 51200);
fclose($handle);
if (stripos($chunk, 'Material Icons') !== false) {
return self::MATERIAL;
}
if (stripos($chunk, 'FontAwesome') !== false) {
return self::FA;
}
} }
} }
// Check theme assets for material-icons CSS // Fallback: check for separate icon CSS files
$dirs = [ $dirs = [
_PS_THEME_DIR_ . 'assets/css/', _PS_THEME_DIR_ . 'assets/css/',
_PS_THEME_DIR_ . 'css/', _PS_THEME_DIR_ . 'css/',
@@ -416,20 +432,26 @@ class MprIcons
if (stripos($file, 'material') !== false) { if (stripos($file, 'material') !== false) {
return self::MATERIAL; return self::MATERIAL;
} }
if (stripos($file, 'fontawesome') !== false || stripos($file, 'font-awesome') !== false) {
return self::FA;
}
} }
} }
// PS 1.6 — check for parent theme name // PS 1.6 — check for font-awesome directory
if (defined('_THEME_NAME_')) { $faDir = _PS_THEME_DIR_ . 'css/font-awesome/';
$themeName = _THEME_NAME_; if (is_dir($faDir)) {
if (stripos($themeName, 'hummingbird') !== false) { return self::FA;
return self::MATERIAL;
}
} }
} catch (\Exception $e) { } catch (\Exception $e) {
// Detection failed — use safe default // Detection failed — use safe default
} }
// Safe default: FA for PS 1.6, Material for 1.7+
if (defined('_PS_VERSION_') && version_compare(_PS_VERSION_, '1.7.0.0', '>=')) {
return self::MATERIAL;
}
return self::FA; return self::FA;
} }
@@ -444,8 +466,30 @@ class MprIcons
} }
$iconSet = self::getIconSet(); $iconSet = self::getIconSet();
$needle = ($iconSet === self::MATERIAL) ? 'Material Icons' : 'FontAwesome';
try { try {
// Check compiled theme CSS — icons are typically bundled here
$cssFiles = [
_PS_THEME_DIR_ . 'assets/css/theme.css',
_PS_THEME_DIR_ . 'css/theme.css',
];
foreach ($cssFiles as $cssFile) {
if (!file_exists($cssFile)) {
continue;
}
$handle = fopen($cssFile, 'r');
if ($handle) {
$chunk = fread($handle, 51200);
fclose($handle);
if (stripos($chunk, $needle) !== false) {
return true;
}
}
}
// Check for separate icon CSS files
$dirs = [ $dirs = [
_PS_THEME_DIR_ . 'assets/css/', _PS_THEME_DIR_ . 'assets/css/',
_PS_THEME_DIR_ . 'css/', _PS_THEME_DIR_ . 'css/',
@@ -455,34 +499,25 @@ class MprIcons
if (!is_dir($dir)) { if (!is_dir($dir)) {
continue; continue;
} }
$files = scandir($dir); $files = scandir($dir);
foreach ($files as $file) { foreach ($files as $file) {
if ($file === '.' || $file === '..') { if ($file === '.' || $file === '..') {
continue; continue;
} }
if ($iconSet === self::MATERIAL && stripos($file, 'material') !== false) {
if ($iconSet === self::MATERIAL) { return true;
if (stripos($file, 'material') !== false) { }
return true; if ($iconSet === self::FA && (stripos($file, 'fontawesome') !== false || stripos($file, 'font-awesome') !== false)) {
} return true;
} else {
if (stripos($file, 'fontawesome') !== false || stripos($file, 'font-awesome') !== false) {
return true;
}
} }
} }
} }
// For Font Awesome — most PS 1.6/1.7 themes include it as a dependency // PS 1.6 — check for font-awesome directory
// Check if theme's package.json or theme.yml lists it
if ($iconSet === self::FA) { if ($iconSet === self::FA) {
$themeYml = _PS_THEME_DIR_ . 'config/theme.yml'; $faDir = _PS_THEME_DIR_ . 'css/font-awesome/';
if (file_exists($themeYml)) { if (is_dir($faDir)) {
$content = file_get_contents($themeYml); return true;
if (stripos($content, 'font-awesome') !== false || stripos($content, 'fontawesome') !== false) {
return true;
}
} }
} }
} catch (\Exception $e) { } catch (\Exception $e) {