You are here

CssOptimizer.php in Advanced CSS/JS Aggregation 8.2

File

advagg_css_minify/src/Asset/CssOptimizer.php
View source
<?php

namespace Drupal\advagg_css_minify\Asset;

use Drupal\Component\Utility\Unicode;
use Drupal\Core\Asset\AssetOptimizerInterface;
use Drupal\Core\Asset\CssOptimizer as CoreCssOptimizer;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\State\StateInterface;

/**
 * Optimizes a CSS asset.
 */
class CssOptimizer extends CoreCssOptimizer implements AssetOptimizerInterface {

  /**
   * The minify cache.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $cache;

  /**
   * A config object for the advagg css minify configuration.
   *
   * @var \Drupal\Core\Config\Config
   */
  protected $config;

  /**
   * A config object for the advagg configuration.
   *
   * @var \Drupal\Core\Config\Config
   */
  protected $advaggConfig;

  /**
   * The AdvAgg file status state information storage service.
   *
   * @var \Drupal\Core\State\StateInterface
   */
  protected $advaggFiles;

  /**
   * Module handler service.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * Construct the optimizer instance.
   *
   * @param \Drupal\Core\Cache\CacheBackendInterface $minify_cache
   *   The minify cache.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   A config factory for retrieving required config objects.
   * @param \Drupal\Core\State\StateInterface $advagg_files
   *   The AdvAgg file status state information storage service.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   */
  public function __construct(CacheBackendInterface $minify_cache, ConfigFactoryInterface $config_factory, StateInterface $advagg_files, ModuleHandlerInterface $module_handler) {
    $this->cache = $minify_cache;
    $this->config = $config_factory
      ->get('advagg_css_minify.settings');
    $this->advaggConfig = $config_factory
      ->get('advagg.settings');
    $this->advaggFiles = $advagg_files;
    $this->moduleHandler = $module_handler;
  }

  /**
   * Generate the css minification configuration.
   *
   * @return array
   *   Array($options, $description, $minifiers, $functions).
   */
  public function getConfiguration() {
    $description = '';
    $options = [
      0 => t('Disabled'),
      1 => t('Core'),
      2 => t('YUI'),
    ];
    $minifiers = [
      NULL,
      NULL,
      NULL,
    ];
    $functions = [
      NULL,
      NULL,
      NULL,
    ];

    // Allow for other modules to alter this list.
    $options_desc = [
      $options,
      $description,
    ];
    $this->moduleHandler
      ->alter('advagg_css_minify_configuration', $options_desc, $minifiers, $functions);
    list($options, $description) = $options_desc;
    return [
      $options,
      $description,
      $minifiers,
      $functions,
    ];
  }

  /**
   * Loads the stylesheet and resolves all @import commands.
   *
   * Loads a stylesheet and replaces @import commands with the contents of the
   * imported file. Use this instead of file_get_contents when processing
   * stylesheets.
   *
   * The returned contents are compressed removing white space and comments only
   * when CSS aggregation is enabled. This optimization will not apply for
   * color.module enabled themes with CSS aggregation turned off.
   *
   * Note: the only reason this method is public is so color.module can call it;
   * it is not on the AssetOptimizerInterface, so future refactorings can make
   * it protected.
   *
   * @param string $file
   *   Name of the stylesheet to be processed.
   * @param bool $optimize
   *   (optional) Defines if CSS contents should be compressed or not. Not used
   *   in AdvAgg implementation.
   * @param bool $reset_basepath
   *   (optional) Used internally to facilitate recursive resolution of @import
   *   commands.
   *
   * @return string
   *   Contents of the stylesheet, including any resolved @import commands.
   */
  public function loadFile($file, $optimize = NULL, $reset_basepath = TRUE) {

    // These statics are not cache variables, so we don't use drupal_static().
    static $basepath;
    if ($reset_basepath) {
      $basepath = '';
    }

    // Stylesheets are relative one to each other. Start by adding a base path
    // prefix provided by the parent stylesheet (if necessary).
    if ($basepath && !file_uri_scheme($file)) {
      $file = $basepath . '/' . $file;
    }

    // Store the parent base path to restore it later.
    $parent_base_path = $basepath;

    // Set the current base path to process possible child imports.
    $basepath = dirname($file);

    // Load the CSS stylesheet. We suppress errors because themes may specify
    // stylesheets in their .info.yml file that don't exist in the theme's path,
    // but are merely there to disable certain module CSS files.
    $content = '';
    if ($contents = @file_get_contents($file)) {

      // If a BOM is found, convert the file to UTF-8, then use substr() to
      // remove the BOM from the result.
      if ($encoding = Unicode::encodingFromBOM($contents)) {
        $contents = Unicode::substr(Unicode::convertToUtf8($contents, $encoding), 1);
      }
      elseif (preg_match('/^@charset "([^"]+)";/', $contents, $matches)) {
        if ($matches[1] !== 'utf-8' && $matches[1] !== 'UTF-8') {
          $contents = substr($contents, strlen($matches[0]));
          $contents = Unicode::convertToUtf8($contents, $matches[1]);
        }
      }
      $minifier = $this->config
        ->get('minifier');
      if ($file_settings = $this->config
        ->get('file_settings')) {
        $file_settings = array_column($file_settings, 'minifier', 'path');
        if (isset($file_settings[$file])) {
          $minifier = $file_settings[$file];
        }
      }
      $info = $this->advaggFiles
        ->get($file);
      $cid = 'css_minify:' . $minifier . ':' . $info['filename_hash'];
      $cid .= !empty($info['content_hash']) ? ':' . $info['content_hash'] : '';
      $cached_data = $this->cache
        ->get($cid);
      if (!empty($cached_data->data)) {
        $content = $cached_data->data;
      }
      else {
        if (!$minifier || $this->advaggConfig
          ->get('cache_level') < 0) {
          $content = $this
            ->processCss($contents, FALSE);
        }
        elseif ($minifier == 1) {
          $content = $this
            ->processCss($contents, TRUE);
        }
        elseif ($minifier == 2) {
          $content = $this
            ->processCssMin($contents);
        }
        else {
          $content = $this
            ->processCssOther($contents, $minifier);
        }
      }

      // Cache minified data for at least 1 week.
      $this->cache
        ->set($cid, $content, REQUEST_TIME + 86400 * 7, [
        'advagg_css',
        $info['filename_hash'],
      ]);
    }

    // Restore the parent base path as the file and its children are processed.
    $basepath = $parent_base_path;
    return $content;
  }

  /**
   * Processes the contents of a stylesheet through CSSMin for minification.
   *
   * @param string $contents
   *   The contents of the stylesheet.
   *
   * @return string
   *   Minified contents of the stylesheet including the imported stylesheets.
   */
  protected function processCssMin($contents) {
    $contents = $this
      ->clean($contents);
    if (!class_exists('CSSmin')) {
      include drupal_get_path('module', 'advagg_css_minify') . '/yui/CSSMin.inc';
    }
    $cssmin = new \CSSmin(TRUE);

    // Minify the CSS splitting lines after 4k of text.
    $contents = $cssmin
      ->run($contents, 4096);

    // Replaces @import commands with the actual stylesheet content.
    // This happens recursively but omits external files.
    $contents = preg_replace_callback('/@import\\s*(?:url\\(\\s*)?[\'"]?(?![a-z]+:)(?!\\/\\/)([^\'"\\()]+)[\'"]?\\s*\\)?\\s*;/', [
      $this,
      'loadNestedFile',
    ], $contents);
    return $contents;
  }

  /**
   * Processes the contents of a stylesheet for minification.
   *
   * @param string $contents
   *   The contents of the stylesheet.
   * @param string $minifier
   *   The name of the minifier to use.
   *
   * @return string
   *   Minified contents of the stylesheet including the imported stylesheets.
   */
  protected function processCssOther($contents, $minifier) {
    $contents = $this
      ->clean($contents);
    list(, , , $functions) = $this
      ->getConfiguration();
    if (isset($functions[$minifier])) {
      $run = $functions[$minifier];
      if (function_exists($run)) {
        $run($contents);
      }
    }

    // Replaces @import commands with the actual stylesheet content.
    // This happens recursively but omits external files.
    $contents = preg_replace_callback('/@import\\s*(?:url\\(\\s*)?[\'"]?(?![a-z]+:)(?!\\/\\/)([^\'"\\()]+)[\'"]?\\s*\\)?\\s*;/', [
      $this,
      'loadNestedFile',
    ], $contents);
    return $contents;
  }

}

Classes

Namesort descending Description
CssOptimizer Optimizes a CSS asset.