PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.9
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.9
2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.5.23 All 138 releases
metasync / code-minification / class-css-minifier.php

class-css-minifier.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.9, at code-minification/class-css-minifier.php

164 lines 5.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Metasync_CSS_Minifier
4 * Regex-based CSS minification engine.
5 * Hooks into style_loader_tag to rewrite enqueued stylesheets to cached minified versions.
6 *
7 * @package Search Atlas SEO
8 * @copyright Copyright (C) 2021-2025, Search Atlas Group - support@searchatlas.com
9 * @since 2.5.23
10 */
11
12 if (!defined('ABSPATH')) {
13 exit;
14 }
15
16 class Metasync_CSS_Minifier {
17
18 /** @var array */
19 private $settings;
20
21 /** @var array Handles to exclude from minification. */
22 private $excluded_handles = [];
23
24 public function __construct(array $settings) {
25 $this->settings = $settings;
26 $this->excluded_handles = array_filter(
27 array_map('trim', explode(',', $settings['css_exclude_handles'] ?? ''))
28 );
29
30 add_filter('style_loader_tag', [$this, 'maybe_minify_style'], 999, 4);
31 }
32
33 /**
34 * Filter: attempt to minify the stylesheet and rewrite the tag to use the cached version.
35 *
36 * @param string $tag The full <link> tag HTML.
37 * @param string $handle The style's registered handle.
38 * @param string $href The stylesheet's source URL.
39 * @param string $media The stylesheet's media attribute.
40 * @return string Modified or original tag.
41 */
42 public function maybe_minify_style(string $tag, string $handle, string $href, string $media): string {
43 // Skip admin, login, excluded handles
44 if (is_admin() || $this->is_excluded($handle) || $this->is_login_page()) {
45 return $tag;
46 }
47
48 // Only process local files
49 $local_path = $this->url_to_local_path($href);
50 if (!$local_path || !file_exists($local_path)) {
51 return $tag;
52 }
53
54 // Skip already-minified files
55 if (preg_match('/\.min\.css$/i', $local_path)) {
56 return $tag;
57 }
58
59 // Check cache
60 $cached = Metasync_Minification_Cache::get_cached_file($handle, 'css', $local_path);
61 if ($cached) {
62 return str_replace($href, $cached['url'], $tag);
63 }
64
65 // Read and minify
66 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
67 $content = file_get_contents($local_path);
68 if ($content === false || strlen($content) === 0) {
69 return $tag;
70 }
71
72 $minified = self::minify($content);
73
74 // Store in cache
75 $cached = Metasync_Minification_Cache::store_cached_file($handle, 'css', $local_path, $minified);
76 if ($cached) {
77 return str_replace($href, $cached['url'], $tag);
78 }
79
80 return $tag;
81 }
82
83 /**
84 * Minify a CSS string using regex-based transformations.
85 *
86 * @param string $css Raw CSS content.
87 * @return string Minified CSS.
88 */
89 public static function minify(string $css): string {
90 // 1. Remove CSS comments
91 $css = preg_replace('/\/\*[\s\S]*?\*\//', '', $css);
92
93 // 2. Collapse whitespace
94 $css = preg_replace('/\s+/', ' ', $css);
95
96 // 3. Remove spaces around { } : ; ,
97 $css = preg_replace('/\s*\{\s*/', '{', $css);
98 $css = preg_replace('/\s*\}\s*/', '}', $css);
99 $css = preg_replace('/\s*:\s*/', ':', $css);
100 $css = preg_replace('/\s*;\s*/', ';', $css);
101 $css = preg_replace('/\s*,\s*/', ',', $css);
102
103 // 4. Remove trailing semicolons before }
104 $css = str_replace(';}', '}', $css);
105
106 // 5. Remove units after zero (0px -> 0), but not 0% (used in keyframes/gradients)
107 $css = preg_replace('/(?<=[\s:,])0(?:px|em|rem|ex|ch|vw|vh|vmin|vmax|cm|mm|in|pt|pc)\b/', '0', $css);
108
109 return trim($css);
110 }
111
112 /**
113 * Check if a handle is excluded.
114 */
115 private function is_excluded(string $handle): bool {
116 return in_array($handle, $this->excluded_handles, true);
117 }
118
119 /**
120 * Convert a URL to a local filesystem path.
121 *
122 * @param string $url The stylesheet URL.
123 * @return string|false Local path or false if not local.
124 */
125 private function url_to_local_path(string $url) {
126 // Remove query string
127 $url = strtok($url, '?');
128
129 // Handle protocol-relative URLs
130 if (strpos($url, '//') === 0) {
131 $url = 'https:' . $url;
132 }
133
134 $site_url = site_url();
135 $content_url = content_url();
136 $abspath = ABSPATH;
137
138 // Check if the URL is local
139 if (strpos($url, $site_url) === 0) {
140 $relative = str_replace($site_url, '', $url);
141 return $abspath . ltrim($relative, '/');
142 }
143
144 if (strpos($url, $content_url) === 0) {
145 $relative = str_replace($content_url, '', $url);
146 return WP_CONTENT_DIR . '/' . ltrim($relative, '/');
147 }
148
149 // Check for relative URLs
150 if (strpos($url, '/wp-content/') === 0 || strpos($url, '/wp-includes/') === 0) {
151 return $abspath . ltrim($url, '/');
152 }
153
154 return false;
155 }
156
157 /**
158 * Check if current request is the login page.
159 */
160 private function is_login_page(): bool {
161 return in_array($GLOBALS['pagenow'] ?? '', ['wp-login.php', 'wp-register.php'], true);
162 }
163 }
164