3 namespace Drupal\advagg_css_minify\Asset;
5 use Drupal\Component\Utility\Unicode;
6 use Drupal\Core\Asset\AssetOptimizerInterface;
7 use Drupal\Core\Asset\CssOptimizer as CoreCssOptimizer;
8 use Drupal\Core\Cache\CacheBackendInterface;
9 use Drupal\Core\Config\ConfigFactoryInterface;
10 use Drupal\Core\Extension\ModuleHandlerInterface;
11 use Drupal\Core\State\StateInterface;
14 * Optimizes a CSS asset.
16 class CssOptimizer extends CoreCssOptimizer implements AssetOptimizerInterface {
21 * @var \Drupal\Core\Cache\CacheBackendInterface
26 * A config object for the advagg css minify configuration.
28 * @var \Drupal\Core\Config\Config
33 * A config object for the advagg configuration.
35 * @var \Drupal\Core\Config\Config
37 protected $advaggConfig;
40 * The AdvAgg file status state information storage service.
42 * @var \Drupal\Core\State\StateInterface
44 protected $advaggFiles;
47 * Module handler service.
49 * @var \Drupal\Core\Extension\ModuleHandlerInterface
51 protected $moduleHandler;
54 * Construct the optimizer instance.
56 * @param \Drupal\Core\Cache\CacheBackendInterface $minify_cache
58 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
59 * A config factory for retrieving required config objects.
60 * @param \Drupal\Core\State\StateInterface $advagg_files
61 * The AdvAgg file status state information storage service.
62 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
65 public function __construct(CacheBackendInterface $minify_cache, ConfigFactoryInterface $config_factory, StateInterface $advagg_files, ModuleHandlerInterface $module_handler) {
66 $this->cache = $minify_cache;
67 $this->config = $config_factory->get('advagg_css_minify.settings');
68 $this->advaggConfig = $config_factory->get('advagg.settings');
69 $this->advaggFiles = $advagg_files;
70 $this->moduleHandler = $module_handler;
74 * Generate the css minification configuration.
77 * Array($options, $description, $minifiers, $functions).
79 public function getConfiguration() {
87 $minifiers = [NULL, NULL, NULL];
88 $functions = [NULL, NULL, NULL];
90 // Allow for other modules to alter this list.
91 $options_desc = [$options, $description];
92 $this->moduleHandler->alter('advagg_css_minify_configuration', $options_desc, $minifiers, $functions);
93 list($options, $description) = $options_desc;
95 return [$options, $description, $minifiers, $functions];
99 * Loads the stylesheet and resolves all @import commands.
101 * Loads a stylesheet and replaces @import commands with the contents of the
102 * imported file. Use this instead of file_get_contents when processing
105 * The returned contents are compressed removing white space and comments only
106 * when CSS aggregation is enabled. This optimization will not apply for
107 * color.module enabled themes with CSS aggregation turned off.
109 * Note: the only reason this method is public is so color.module can call it;
110 * it is not on the AssetOptimizerInterface, so future refactorings can make
113 * @param string $file
114 * Name of the stylesheet to be processed.
115 * @param bool $optimize
116 * (optional) Defines if CSS contents should be compressed or not. Not used
117 * in AdvAgg implementation.
118 * @param bool $reset_basepath
119 * (optional) Used internally to facilitate recursive resolution of @import
123 * Contents of the stylesheet, including any resolved @import commands.
125 public function loadFile($file, $optimize = NULL, $reset_basepath = TRUE) {
126 // These statics are not cache variables, so we don't use drupal_static().
128 if ($reset_basepath) {
132 // Stylesheets are relative one to each other. Start by adding a base path
133 // prefix provided by the parent stylesheet (if necessary).
134 if ($basepath && !file_uri_scheme($file)) {
135 $file = $basepath . '/' . $file;
137 // Store the parent base path to restore it later.
138 $parent_base_path = $basepath;
139 // Set the current base path to process possible child imports.
140 $basepath = dirname($file);
142 // Load the CSS stylesheet. We suppress errors because themes may specify
143 // stylesheets in their .info.yml file that don't exist in the theme's path,
144 // but are merely there to disable certain module CSS files.
146 if ($contents = @file_get_contents($file)) {
147 // If a BOM is found, convert the file to UTF-8, then use substr() to
148 // remove the BOM from the result.
149 if ($encoding = (Unicode::encodingFromBOM($contents))) {
150 $contents = Unicode::substr(Unicode::convertToUtf8($contents, $encoding), 1);
152 // If no BOM, check for fallback encoding. Per CSS spec the regex is very
154 elseif (preg_match('/^@charset "([^"]+)";/', $contents, $matches)) {
155 if ($matches[1] !== 'utf-8' && $matches[1] !== 'UTF-8') {
156 $contents = substr($contents, strlen($matches[0]));
157 $contents = Unicode::convertToUtf8($contents, $matches[1]);
161 $minifier = $this->config->get('minifier');
162 if ($file_settings = $this->config->get('file_settings')) {
163 $file_settings = array_column($file_settings, 'minifier', 'path');
164 if (isset($file_settings[$file])) {
165 $minifier = $file_settings[$file];
169 $info = $this->advaggFiles->get($file);
170 $cid = 'css_minify:' . $minifier . ':' . $info['filename_hash'];
171 $cid .= !empty($info['content_hash']) ? ':' . $info['content_hash'] : '';
172 $cached_data = $this->cache->get($cid);
173 if (!empty($cached_data->data)) {
174 $content = $cached_data->data;
177 if (!$minifier || $this->advaggConfig->get('cache_level') < 0) {
178 $content = $this->processCss($contents, FALSE);
180 elseif ($minifier == 1) {
181 $content = $this->processCss($contents, TRUE);
183 elseif ($minifier == 2) {
184 $content = $this->processCssMin($contents);
187 $content = $this->processCssOther($contents, $minifier);
190 // Cache minified data for at least 1 week.
191 $this->cache->set($cid, $content, REQUEST_TIME + (86400 * 7), ['advagg_css', $info['filename_hash']]);
194 // Restore the parent base path as the file and its children are processed.
195 $basepath = $parent_base_path;
200 * Processes the contents of a stylesheet through CSSMin for minification.
202 * @param string $contents
203 * The contents of the stylesheet.
206 * Minified contents of the stylesheet including the imported stylesheets.
208 protected function processCssMin($contents) {
209 $contents = $this->clean($contents);
210 if (!class_exists('CSSmin')) {
211 include drupal_get_path('module', 'advagg_css_minify') . '/yui/CSSMin.inc';
213 $cssmin = new \CSSmin(TRUE);
215 // Minify the CSS splitting lines after 4k of text.
216 $contents = $cssmin->run($contents, 4096);
218 // Replaces @import commands with the actual stylesheet content.
219 // This happens recursively but omits external files.
220 $contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)(?!\/\/)([^\'"\()]+)[\'"]?\s*\)?\s*;/', [$this, 'loadNestedFile'], $contents);
226 * Processes the contents of a stylesheet for minification.
228 * @param string $contents
229 * The contents of the stylesheet.
230 * @param string $minifier
231 * The name of the minifier to use.
234 * Minified contents of the stylesheet including the imported stylesheets.
236 protected function processCssOther($contents, $minifier) {
237 $contents = $this->clean($contents);
238 list(, , , $functions) = $this->getConfiguration();
239 if (isset($functions[$minifier])) {
240 $run = $functions[$minifier];
241 if (function_exists($run)) {
245 // Replaces @import commands with the actual stylesheet content.
246 // This happens recursively but omits external files.
247 $contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)(?!\/\/)([^\'"\()]+)[\'"]?\s*\)?\s*;/', [$this, 'loadNestedFile'], $contents);