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
*
* Checks theme config and assets for Material Icons indicators.
* Returns 'fontawesome' for classic and most themes,
* 'material' for Hummingbird and Material-based themes.
* PS 1.6 themes use Font Awesome.
* PS 1.7+ themes (classic, hummingbird, etc.) bundle Material Icons
* 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
{
@@ -390,19 +393,32 @@ class MprIcons
}
try {
// PS 1.7+ — check theme.yml
$themeYml = _PS_THEME_DIR_ . 'config/theme.yml';
if (file_exists($themeYml)) {
$content = file_get_contents($themeYml);
if (stripos($content, 'hummingbird') !== false) {
// Check compiled theme CSS for Material Icons font-face
// PS 1.7+ bundles icons into theme.css via webpack
$cssFiles = [
_PS_THEME_DIR_ . 'assets/css/theme.css',
_PS_THEME_DIR_ . 'css/theme.css',
];
foreach ($cssFiles as $cssFile) {
if (!file_exists($cssFile)) {
continue;
}
// Read first 50KB — font-face is always near the top
$handle = fopen($cssFile, 'r');
if ($handle) {
$chunk = fread($handle, 51200);
fclose($handle);
if (stripos($chunk, 'Material Icons') !== false) {
return self::MATERIAL;
}
if (stripos($content, 'material-icons') !== false || stripos($content, '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 = [
_PS_THEME_DIR_ . 'assets/css/',
_PS_THEME_DIR_ . 'css/',
@@ -416,20 +432,26 @@ class MprIcons
if (stripos($file, 'material') !== false) {
return self::MATERIAL;
}
if (stripos($file, 'fontawesome') !== false || stripos($file, 'font-awesome') !== false) {
return self::FA;
}
}
}
// PS 1.6 — check for parent theme name
if (defined('_THEME_NAME_')) {
$themeName = _THEME_NAME_;
if (stripos($themeName, 'hummingbird') !== false) {
return self::MATERIAL;
}
// PS 1.6 — check for font-awesome directory
$faDir = _PS_THEME_DIR_ . 'css/font-awesome/';
if (is_dir($faDir)) {
return self::FA;
}
} catch (\Exception $e) {
// 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;
}
@@ -444,8 +466,30 @@ class MprIcons
}
$iconSet = self::getIconSet();
$needle = ($iconSet === self::MATERIAL) ? 'Material Icons' : 'FontAwesome';
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 = [
_PS_THEME_DIR_ . 'assets/css/',
_PS_THEME_DIR_ . 'css/',
@@ -455,36 +499,27 @@ class MprIcons
if (!is_dir($dir)) {
continue;
}
$files = scandir($dir);
foreach ($files as $file) {
if ($file === '.' || $file === '..') {
continue;
}
if ($iconSet === self::MATERIAL) {
if (stripos($file, 'material') !== false) {
if ($iconSet === self::MATERIAL && stripos($file, 'material') !== false) {
return true;
}
} else {
if (stripos($file, 'fontawesome') !== false || stripos($file, 'font-awesome') !== false) {
if ($iconSet === self::FA && (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
// Check if theme's package.json or theme.yml lists it
// PS 1.6 — check for font-awesome directory
if ($iconSet === self::FA) {
$themeYml = _PS_THEME_DIR_ . 'config/theme.yml';
if (file_exists($themeYml)) {
$content = file_get_contents($themeYml);
if (stripos($content, 'font-awesome') !== false || stripos($content, 'fontawesome') !== false) {
$faDir = _PS_THEME_DIR_ . 'css/font-awesome/';
if (is_dir($faDir)) {
return true;
}
}
}
} catch (\Exception $e) {
// Detection failed
}