From d7d587255deb93104298e952c81f86574d4cc042 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Fri, 8 Aug 2014 11:52:12 -0500
Subject: [PATCH] Issue #2307419 by Wim Leers:
 AssetCollectionOptimizerInterface should allow listing and deleting all
 aggregates (optimized collection assets).

---
 core/core.services.yml                        |  2 +-
 core/includes/common.inc                      | 31 ++++++-----------
 .../AssetCollectionOptimizerInterface.php     | 13 ++++++++
 .../Core/Asset/CssCollectionOptimizer.php     | 27 +++++++++++++--
 .../Core/Asset/JsCollectionOptimizer.php      | 26 +++++++++++++--
 .../UpdateServiceProvider.php                 |  3 +-
 .../Drupal/Core/Extension/ThemeHandler.php    | 24 ++++++++------
 core/modules/locale/locale.module             |  8 ++++-
 .../system/src/Form/PerformanceForm.php       | 33 ++++++++++++++++---
 .../Tests/Core/Extension/ThemeHandlerTest.php | 12 ++++++-
 10 files changed, 135 insertions(+), 44 deletions(-)

diff --git a/core/core.services.yml b/core/core.services.yml
index 6ff5dd54fa3b..8527b2773abd 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -254,7 +254,7 @@ services:
     arguments: ['%container.modules%', '@cache.bootstrap']
   theme_handler:
     class: Drupal\Core\Extension\ThemeHandler
-    arguments: ['@config.factory', '@module_handler', '@state', '@info_parser', '@config.installer', '@router.builder']
+    arguments: ['@config.factory', '@module_handler', '@state', '@info_parser', '@asset.css.collection_optimizer', '@config.installer', '@router.builder']
   entity.manager:
     class: Drupal\Core\Entity\EntityManager
     arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@language_manager', '@string_translation', '@class_resolver', '@typed_data_manager']
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 8b241ab37006..6c6c4a3a88af 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -1328,24 +1328,12 @@ function drupal_pre_render_styles($elements) {
 
 /**
  * Deletes old cached CSS files.
- */
-function drupal_clear_css_cache() {
-  \Drupal::state()->delete('drupal_css_cache_files');
-  file_scan_directory('public://css', '/.*/', array('callback' => 'drupal_delete_file_if_stale'));
-}
-
-/**
- * Deletes files modified more than a set time ago.
  *
- * Callback for file_scan_directory() within:
- * - drupal_clear_css_cache()
- * - drupal_clear_js_cache()
+ * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
+ *   Use \Drupal\Core\Asset\AssetCollectionOptimizerInterface::deleteAll().
  */
-function drupal_delete_file_if_stale($uri) {
-  // Default stale file threshold is 30 days.
-  if (REQUEST_TIME - filemtime($uri) > \Drupal::config('system.performance')->get('stale_file_threshold')) {
-    file_unmanaged_delete($uri);
-  }
+function drupal_clear_css_cache() {
+  \Drupal::service('asset.css.collection_optimizer')->deleteAll();
 }
 
 /**
@@ -2463,11 +2451,12 @@ function drupal_attach_tabledrag(&$element, array $options) {
 
 /**
  * Deletes old cached JavaScript files and variables.
+ *
+ * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
+ *   Use \Drupal\Core\Asset\AssetCollectionOptimizerInterface::deleteAll().
  */
 function drupal_clear_js_cache() {
-  \Drupal::state()->delete('system.javascript_parsed');
-  \Drupal::state()->delete('system.js_cache_files');
-  file_scan_directory('public://js', '/.*/', array('callback' => 'drupal_delete_file_if_stale'));
+  \Drupal::service('asset.js.collection_optimizer')->deleteAll();
 }
 
 /**
@@ -3956,8 +3945,8 @@ function drupal_flush_all_caches() {
   }
 
   // Flush asset file caches.
-  drupal_clear_css_cache();
-  drupal_clear_js_cache();
+  \Drupal::service('asset.css.collection_optimizer')->deleteAll();
+  \Drupal::service('asset.js.collection_optimizer')->deleteAll();
   _drupal_flush_css_js();
 
   // Reset all static caches.
diff --git a/core/lib/Drupal/Core/Asset/AssetCollectionOptimizerInterface.php b/core/lib/Drupal/Core/Asset/AssetCollectionOptimizerInterface.php
index f7102d244bbd..4963c82879a6 100644
--- a/core/lib/Drupal/Core/Asset/AssetCollectionOptimizerInterface.php
+++ b/core/lib/Drupal/Core/Asset/AssetCollectionOptimizerInterface.php
@@ -22,4 +22,17 @@ interface AssetCollectionOptimizerInterface {
    */
   public function optimize(array $assets);
 
+  /**
+   * Returns all optimized asset collections assets.
+   *
+   * @return string[]
+   *   URIs for all optimized asset collection assets.
+   */
+  public function getAll();
+
+  /**
+   * Deletes all optimized asset collections assets.
+   */
+  public function deleteAll();
+
 }
diff --git a/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php b/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php
index 3da9d9104239..b02bb46b9bf1 100644
--- a/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php
+++ b/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php
@@ -71,8 +71,8 @@ public function __construct(AssetCollectionGrouperInterface $grouper, AssetOptim
    * variable is emptied to force a rebuild of the cache. Second, the cache file
    * is generated if it is missing on disk. Old cache files are not deleted
    * immediately when the lookup variable is emptied, but are deleted after a
-   * set period by drupal_delete_file_if_stale(). This ensures that files
-   * referenced by a cached page will still be available.
+   * configurable period (@code system.performance.stale_file_threshold @endcode)
+   * to ensure that files referenced by a cached page will still be available.
    */
   public function optimize(array $css_assets) {
     // Group the assets.
@@ -175,4 +175,27 @@ protected function generateHash(array $css_group) {
     }
     return hash('sha256', serialize($css_data));
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAll() {
+    return $this->state->get('drupal_css_cache_files');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteAll() {
+    $this->state->delete('drupal_css_cache_files');
+
+    $delete_stale = function($uri) {
+      // Default stale file threshold is 30 days.
+      if (REQUEST_TIME - filemtime($uri) > \Drupal::config('system.performance')->get('stale_file_threshold')) {
+        file_unmanaged_delete($uri);
+      }
+    };
+    file_scan_directory('public://css', '/.*/', array('callback' => $delete_stale));
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php b/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php
index 4126e57e4b01..23564797760f 100644
--- a/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php
+++ b/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php
@@ -72,8 +72,8 @@ public function __construct(AssetCollectionGrouperInterface $grouper, AssetOptim
    * variable is emptied to force a rebuild of the cache. Second, the cache file
    * is generated if it is missing on disk. Old cache files are not deleted
    * immediately when the lookup variable is emptied, but are deleted after a
-   * set period by drupal_delete_file_if_stale(). This ensures that files
-   * referenced by a cached page will still be available.
+   * configurable period (@code system.performance.stale_file_threshold @endcode)
+   * to ensure that files referenced by a cached page will still be available.
    */
   public function optimize(array $js_assets) {
     // Group the assets.
@@ -163,4 +163,26 @@ protected function generateHash(array $js_group) {
     }
     return hash('sha256', serialize($js_data));
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAll() {
+    return $this->state->get('system.js_cache_files');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteAll() {
+    $this->state->delete('system.js_cache_files');
+    $delete_stale = function($uri) {
+      // Default stale file threshold is 30 days.
+      if (REQUEST_TIME - filemtime($uri) > \Drupal::config('system.performance')->get('stale_file_threshold')) {
+        file_unmanaged_delete($uri);
+      }
+    };
+    file_scan_directory('public://js', '/.*/', array('callback' => $delete_stale));
+  }
+
 }
diff --git a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php b/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php
index 32ad9b768252..ec30db020ef9 100644
--- a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php
+++ b/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php
@@ -42,7 +42,8 @@ public function register(ContainerBuilder $container) {
         ->addArgument(new Reference('config.factory'))
         ->addArgument(new Reference('module_handler'))
         ->addArgument(new Reference('state'))
-        ->addArgument(new Reference('info_parser'));
+        ->addArgument(new Reference('info_parser'))
+        ->addArgument(new Reference('asset.css.collection_optimizer'));
     }
   }
 
diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php
index dbe2c78dca90..e1b8cbfdcbf2 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandler.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Extension;
 
 use Drupal\Component\Utility\String;
+use Drupal\Core\Asset\AssetCollectionOptimizerInterface;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Config\ConfigInstallerInterface;
@@ -92,6 +93,13 @@ class ThemeHandler implements ThemeHandlerInterface {
    */
   protected $extensionDiscovery;
 
+  /**
+   * The CSS asset collection optimizer service.
+   *
+   * @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface
+   */
+  protected $cssCollectionOptimizer;
+
   /**
    * Constructs a new ThemeHandler.
    *
@@ -103,6 +111,8 @@ class ThemeHandler implements ThemeHandlerInterface {
    *   The state store.
    * @param \Drupal\Core\Extension\InfoParserInterface $info_parser
    *   The info parser to parse the theme.info.yml files.
+   * @param \Drupal\Core\Asset\AssetCollectionOptimizerInterface $css_collection_optimizer
+   *   The CSS asset collection optimizer service.
    * @param \Drupal\Core\Config\ConfigInstallerInterface $config_installer
    *   (optional) The config installer to install configuration. This optional
    *   to allow the theme handler to work before Drupal is installed and has a
@@ -112,11 +122,12 @@ class ThemeHandler implements ThemeHandlerInterface {
    * @param \Drupal\Core\Extension\ExtensionDiscovery $extension_discovery
    *   (optional) A extension discovery instance (for unit tests).
    */
-  public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, StateInterface $state, InfoParserInterface $info_parser, ConfigInstallerInterface $config_installer = NULL, RouteBuilder $route_builder = NULL, ExtensionDiscovery $extension_discovery = NULL) {
+  public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, StateInterface $state, InfoParserInterface $info_parser, AssetCollectionOptimizerInterface $css_collection_optimizer = NULL, ConfigInstallerInterface $config_installer = NULL, RouteBuilder $route_builder = NULL, ExtensionDiscovery $extension_discovery = NULL) {
     $this->configFactory = $config_factory;
     $this->moduleHandler = $module_handler;
     $this->state = $state;
     $this->infoParser = $info_parser;
+    $this->cssCollectionOptimizer = $css_collection_optimizer;
     $this->configInstaller = $config_installer;
     $this->routeBuilder = $route_builder;
     $this->extensionDiscovery = $extension_discovery;
@@ -260,7 +271,7 @@ public function enable(array $theme_list, $enable_dependencies = TRUE) {
       watchdog('system', '%theme theme enabled.', array('%theme' => $key), WATCHDOG_INFO);
     }
 
-    $this->clearCssCache();
+    $this->cssCollectionOptimizer->deleteAll();
     $this->resetSystem();
 
     // Invoke hook_themes_enabled() after the themes have been enabled.
@@ -297,7 +308,7 @@ public function disable(array $theme_list) {
       }
     }
 
-    $this->clearCssCache();
+    $this->cssCollectionOptimizer->deleteAll();
 
     $extension_config = $this->configFactory->get('core.extension');
     $current_theme_data = $this->state->get('system.theme.data', array());
@@ -636,13 +647,6 @@ protected function systemListReset() {
     system_list_reset();
   }
 
-  /**
-   * Wraps drupal_clear_css_cache().
-   */
-  protected function clearCssCache() {
-    drupal_clear_css_cache();
-  }
-
   /**
    * Wraps drupal_theme_rebuild().
    */
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index b44d1a1d0a37..c8803709879e 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -541,6 +541,13 @@ function locale_system_remove($components) {
   }
 }
 
+/**
+ * Implements hook_cache_flush().
+ */
+function locale_cache_flush() {
+  \Drupal::state()->delete('system.javascript_parsed');
+}
+
 /**
  * Implements hook_js_alter().
  */
@@ -1397,4 +1404,3 @@ function locale_translation_language_table($form_element) {
   }
   return $form_element;
 }
-
diff --git a/core/modules/system/src/Form/PerformanceForm.php b/core/modules/system/src/Form/PerformanceForm.php
index 85d241540816..98b0b8288eec 100644
--- a/core/modules/system/src/Form/PerformanceForm.php
+++ b/core/modules/system/src/Form/PerformanceForm.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Form;
 
+use Drupal\Core\Asset\AssetCollectionOptimizerInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Cache\CacheBackendInterface;
@@ -20,7 +21,7 @@
 class PerformanceForm extends ConfigFormBase {
 
   /**
-   * The render cache object.
+   * The render cache bin.
    *
    * @var \Drupal\Core\Cache\CacheBackendInterface
    */
@@ -33,6 +34,20 @@ class PerformanceForm extends ConfigFormBase {
    */
   protected $dateFormatter;
 
+  /**
+   * The CSS asset collection optimizer service.
+   *
+   * @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface
+   */
+  protected $cssCollectionOptimizer;
+
+  /**
+   * The JavaScript asset collection optimizer service.
+   *
+   * @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface
+   */
+  protected $jsCollectionOptimizer;
+
   /**
    * Constructs a PerformanceForm object.
    *
@@ -41,12 +56,18 @@ class PerformanceForm extends ConfigFormBase {
    * @param \Drupal\Core\Cache\CacheBackendInterface $render_cache
    * @param \Drupal\Core\Datetime\DateFormatter $date_formater
    *   The date formatter service.
+   * @param \Drupal\Core\Asset\AssetCollectionOptimizerInterface $css_collection_optimizer
+   *   The CSS asset collection optimizer service.
+   * @param \Drupal\Core\Asset\AssetCollectionOptimizerInterface $js_collection_optimizer
+   *   The JavaScript asset collection optimizer service.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, CacheBackendInterface $render_cache, DateFormatter $date_formater) {
+  public function __construct(ConfigFactoryInterface $config_factory, CacheBackendInterface $render_cache, DateFormatter $date_formater, AssetCollectionOptimizerInterface $css_collection_optimizer, AssetCollectionOptimizerInterface $js_collection_optimizer) {
     parent::__construct($config_factory);
 
     $this->renderCache = $render_cache;
     $this->dateFormatter = $date_formater;
+    $this->cssCollectionOptimizer = $css_collection_optimizer;
+    $this->jsCollectionOptimizer = $js_collection_optimizer;
   }
 
   /**
@@ -56,7 +77,9 @@ public static function create(ContainerInterface $container) {
     return new static(
       $container->get('config.factory'),
       $container->get('cache.render'),
-      $container->get('date.formatter')
+      $container->get('date.formatter'),
+      $container->get('asset.css.collection_optimizer'),
+      $container->get('asset.js.collection_optimizer')
     );
   }
 
@@ -157,8 +180,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    drupal_clear_css_cache();
-    drupal_clear_js_cache();
+    $this->cssCollectionOptimizer->deleteAll();
+    $this->jsCollectionOptimizer->deleteAll();
     // This form allows page compression settings to be changed, which can
     // invalidate cached pages in the render cache, so it needs to be cleared on
     // form submit.
diff --git a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
index 675d04ede3cf..5daac3f9c915 100644
--- a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
@@ -70,6 +70,13 @@ class ThemeHandlerTest extends UnitTestCase {
    */
   protected $extensionDiscovery;
 
+  /**
+   * The CSS asset collection optimizer service.
+   *
+   * @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cssCollectionOptimizer;
+
   /**
    * The tested theme handler.
    *
@@ -100,7 +107,10 @@ protected function setUp() {
     $this->extensionDiscovery = $this->getMockBuilder('Drupal\Core\Extension\ExtensionDiscovery')
       ->disableOriginalConstructor()
       ->getMock();
-    $this->themeHandler = new TestThemeHandler($this->configFactory, $this->moduleHandler, $this->state, $this->infoParser, $this->configInstaller, $this->routeBuilder, $this->extensionDiscovery);
+    $this->cssCollectionOptimizer = $this->getMockBuilder('\Drupal\Core\Asset\CssCollectionOptimizer') //\Drupal\Core\Asset\AssetCollectionOptimizerInterface');
+      ->disableOriginalConstructor()
+      ->getMock();
+    $this->themeHandler = new TestThemeHandler($this->configFactory, $this->moduleHandler, $this->state, $this->infoParser, $this->cssCollectionOptimizer, $this->configInstaller, $this->routeBuilder, $this->extensionDiscovery);
 
     $cache_backend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
     $this->getContainerWithCacheBins($cache_backend);
-- 
GitLab