diff core/lib/Drupal/Core/Asset/AssetResolver.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children af1871eacc83
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/lib/Drupal/Core/Asset/AssetResolver.php	Wed Nov 29 16:09:58 2017 +0000
@@ -0,0 +1,393 @@
+<?php
+
+namespace Drupal\Core\Asset;
+
+use Drupal\Component\Utility\Crypt;
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Theme\ThemeManagerInterface;
+
+/**
+ * The default asset resolver.
+ */
+class AssetResolver implements AssetResolverInterface {
+
+  /**
+   * The library discovery service.
+   *
+   * @var \Drupal\Core\Asset\LibraryDiscoveryInterface
+   */
+  protected $libraryDiscovery;
+
+  /**
+   * The library dependency resolver.
+   *
+   * @var \Drupal\Core\Asset\LibraryDependencyResolverInterface
+   */
+  protected $libraryDependencyResolver;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The theme manager.
+   *
+   * @var \Drupal\Core\Theme\ThemeManagerInterface
+   */
+  protected $themeManager;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
+   * The cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cache;
+
+  /**
+   * Constructs a new AssetResolver instance.
+   *
+   * @param \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery
+   *   The library discovery service.
+   * @param \Drupal\Core\Asset\LibraryDependencyResolverInterface $library_dependency_resolver
+   *   The library dependency resolver.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
+   *   The theme manager.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
+   *   The cache backend.
+   */
+  public function __construct(LibraryDiscoveryInterface $library_discovery, LibraryDependencyResolverInterface $library_dependency_resolver, ModuleHandlerInterface $module_handler, ThemeManagerInterface $theme_manager, LanguageManagerInterface $language_manager, CacheBackendInterface $cache) {
+    $this->libraryDiscovery = $library_discovery;
+    $this->libraryDependencyResolver = $library_dependency_resolver;
+    $this->moduleHandler = $module_handler;
+    $this->themeManager = $theme_manager;
+    $this->languageManager = $language_manager;
+    $this->cache = $cache;
+  }
+
+  /**
+   * Returns the libraries that need to be loaded.
+   *
+   * For example, with core/a depending on core/c and core/b on core/d:
+   * @code
+   * $assets = new AttachedAssets();
+   * $assets->setLibraries(['core/a', 'core/b', 'core/c']);
+   * $assets->setAlreadyLoadedLibraries(['core/c']);
+   * $resolver->getLibrariesToLoad($assets) === ['core/a', 'core/b', 'core/d']
+   * @endcode
+   *
+   * @param \Drupal\Core\Asset\AttachedAssetsInterface $assets
+   *   The assets attached to the current response.
+   *
+   * @return string[]
+   *   A list of libraries and their dependencies, in the order they should be
+   *   loaded, excluding any libraries that have already been loaded.
+   */
+  protected function getLibrariesToLoad(AttachedAssetsInterface $assets) {
+    return array_diff(
+      $this->libraryDependencyResolver->getLibrariesWithDependencies($assets->getLibraries()),
+      $this->libraryDependencyResolver->getLibrariesWithDependencies($assets->getAlreadyLoadedLibraries())
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCssAssets(AttachedAssetsInterface $assets, $optimize) {
+    $theme_info = $this->themeManager->getActiveTheme();
+    // Add the theme name to the cache key since themes may implement
+    // hook_library_info_alter().
+    $libraries_to_load = $this->getLibrariesToLoad($assets);
+    $cid = 'css:' . $theme_info->getName() . ':' . Crypt::hashBase64(serialize($libraries_to_load)) . (int) $optimize;
+    if ($cached = $this->cache->get($cid)) {
+      return $cached->data;
+    }
+
+    $css = [];
+    $default_options = [
+      'type' => 'file',
+      'group' => CSS_AGGREGATE_DEFAULT,
+      'weight' => 0,
+      'media' => 'all',
+      'preprocess' => TRUE,
+      'browsers' => [],
+    ];
+
+    foreach ($libraries_to_load as $library) {
+      list($extension, $name) = explode('/', $library, 2);
+      $definition = $this->libraryDiscovery->getLibraryByName($extension, $name);
+      if (isset($definition['css'])) {
+        foreach ($definition['css'] as $options) {
+          $options += $default_options;
+          $options['browsers'] += [
+            'IE' => TRUE,
+            '!IE' => TRUE,
+          ];
+
+          // Files with a query string cannot be preprocessed.
+          if ($options['type'] === 'file' && $options['preprocess'] && strpos($options['data'], '?') !== FALSE) {
+            $options['preprocess'] = FALSE;
+          }
+
+          // Always add a tiny value to the weight, to conserve the insertion
+          // order.
+          $options['weight'] += count($css) / 1000;
+
+          // CSS files are being keyed by the full path.
+          $css[$options['data']] = $options;
+        }
+      }
+    }
+
+    // Allow modules and themes to alter the CSS assets.
+    $this->moduleHandler->alter('css', $css, $assets);
+    $this->themeManager->alter('css', $css, $assets);
+
+    // Sort CSS items, so that they appear in the correct order.
+    uasort($css, 'static::sort');
+
+    // Allow themes to remove CSS files by CSS files full path and file name.
+    // @todo Remove in Drupal 9.0.x.
+    if ($stylesheet_remove = $theme_info->getStyleSheetsRemove()) {
+      foreach ($css as $key => $options) {
+        if (isset($stylesheet_remove[$key])) {
+          unset($css[$key]);
+        }
+      }
+    }
+
+    if ($optimize) {
+      $css = \Drupal::service('asset.css.collection_optimizer')->optimize($css);
+    }
+    $this->cache->set($cid, $css, CacheBackendInterface::CACHE_PERMANENT, ['library_info']);
+
+    return $css;
+  }
+
+  /**
+   * Returns the JavaScript settings assets for this response's libraries.
+   *
+   * Gathers all drupalSettings from all libraries in the attached assets
+   * collection and merges them.
+   *
+   * @param \Drupal\Core\Asset\AttachedAssetsInterface $assets
+   *   The assets attached to the current response.
+   * @return array
+   *   A (possibly optimized) collection of JavaScript assets.
+   */
+  protected function getJsSettingsAssets(AttachedAssetsInterface $assets) {
+    $settings = [];
+
+    foreach ($this->getLibrariesToLoad($assets) as $library) {
+      list($extension, $name) = explode('/', $library, 2);
+      $definition = $this->libraryDiscovery->getLibraryByName($extension, $name);
+      if (isset($definition['drupalSettings'])) {
+        $settings = NestedArray::mergeDeepArray([$settings, $definition['drupalSettings']], TRUE);
+      }
+    }
+
+    return $settings;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getJsAssets(AttachedAssetsInterface $assets, $optimize) {
+    $theme_info = $this->themeManager->getActiveTheme();
+    // Add the theme name to the cache key since themes may implement
+    // hook_library_info_alter(). Additionally add the current language to
+    // support translation of JavaScript files via hook_js_alter().
+    $libraries_to_load = $this->getLibrariesToLoad($assets);
+    $cid = 'js:' . $theme_info->getName() . ':' . $this->languageManager->getCurrentLanguage()->getId() . ':' . Crypt::hashBase64(serialize($libraries_to_load)) . (int) (count($assets->getSettings()) > 0) . (int) $optimize;
+
+    if ($cached = $this->cache->get($cid)) {
+      list($js_assets_header, $js_assets_footer, $settings, $settings_in_header) = $cached->data;
+    }
+    else {
+      $javascript = [];
+      $default_options = [
+        'type' => 'file',
+        'group' => JS_DEFAULT,
+        'weight' => 0,
+        'cache' => TRUE,
+        'preprocess' => TRUE,
+        'attributes' => [],
+        'version' => NULL,
+        'browsers' => [],
+      ];
+
+      // Collect all libraries that contain JS assets and are in the header.
+      $header_js_libraries = [];
+      foreach ($libraries_to_load as $library) {
+        list($extension, $name) = explode('/', $library, 2);
+        $definition = $this->libraryDiscovery->getLibraryByName($extension, $name);
+        if (isset($definition['js']) && !empty($definition['header'])) {
+          $header_js_libraries[] = $library;
+        }
+      }
+      // The current list of header JS libraries are only those libraries that
+      // are in the header, but their dependencies must also be loaded for them
+      // to function correctly, so update the list with those.
+      $header_js_libraries = $this->libraryDependencyResolver->getLibrariesWithDependencies($header_js_libraries);
+
+      foreach ($libraries_to_load as $library) {
+        list($extension, $name) = explode('/', $library, 2);
+        $definition = $this->libraryDiscovery->getLibraryByName($extension, $name);
+        if (isset($definition['js'])) {
+          foreach ($definition['js'] as $options) {
+            $options += $default_options;
+
+            // 'scope' is a calculated option, based on which libraries are
+            // marked to be loaded from the header (see above).
+            $options['scope'] = in_array($library, $header_js_libraries) ? 'header' : 'footer';
+
+            // Preprocess can only be set if caching is enabled and no
+            // attributes are set.
+            $options['preprocess'] = $options['cache'] && empty($options['attributes']) ? $options['preprocess'] : FALSE;
+
+            // Always add a tiny value to the weight, to conserve the insertion
+            // order.
+            $options['weight'] += count($javascript) / 1000;
+
+            // Local and external files must keep their name as the associative
+            // key so the same JavaScript file is not added twice.
+            $javascript[$options['data']] = $options;
+          }
+        }
+      }
+
+      // Allow modules and themes to alter the JavaScript assets.
+      $this->moduleHandler->alter('js', $javascript, $assets);
+      $this->themeManager->alter('js', $javascript, $assets);
+
+      // Sort JavaScript assets, so that they appear in the correct order.
+      uasort($javascript, 'static::sort');
+
+      // Prepare the return value: filter JavaScript assets per scope.
+      $js_assets_header = [];
+      $js_assets_footer = [];
+      foreach ($javascript as $key => $item) {
+        if ($item['scope'] == 'header') {
+          $js_assets_header[$key] = $item;
+        }
+        elseif ($item['scope'] == 'footer') {
+          $js_assets_footer[$key] = $item;
+        }
+      }
+
+      if ($optimize) {
+        $collection_optimizer = \Drupal::service('asset.js.collection_optimizer');
+        $js_assets_header = $collection_optimizer->optimize($js_assets_header);
+        $js_assets_footer = $collection_optimizer->optimize($js_assets_footer);
+      }
+
+      // If the core/drupalSettings library is being loaded or is already
+      // loaded, get the JavaScript settings assets, and convert them into a
+      // single "regular" JavaScript asset.
+      $libraries_to_load = $this->getLibrariesToLoad($assets);
+      $settings_required = in_array('core/drupalSettings', $libraries_to_load) || in_array('core/drupalSettings', $this->libraryDependencyResolver->getLibrariesWithDependencies($assets->getAlreadyLoadedLibraries()));
+      $settings_have_changed = count($libraries_to_load) > 0 || count($assets->getSettings()) > 0;
+
+      // Initialize settings to FALSE since they are not needed by default. This
+      // distinguishes between an empty array which must still allow
+      // hook_js_settings_alter() to be run.
+      $settings = FALSE;
+      if ($settings_required && $settings_have_changed) {
+        $settings = $this->getJsSettingsAssets($assets);
+        // Allow modules to add cached JavaScript settings.
+        foreach ($this->moduleHandler->getImplementations('js_settings_build') as $module) {
+          $function = $module . '_' . 'js_settings_build';
+          $function($settings, $assets);
+        }
+      }
+      $settings_in_header = in_array('core/drupalSettings', $header_js_libraries);
+      $this->cache->set($cid, [$js_assets_header, $js_assets_footer, $settings, $settings_in_header], CacheBackendInterface::CACHE_PERMANENT, ['library_info']);
+    }
+
+    if ($settings !== FALSE) {
+      // Attached settings override both library definitions and
+      // hook_js_settings_build().
+      $settings = NestedArray::mergeDeepArray([$settings, $assets->getSettings()], TRUE);
+      // Allow modules and themes to alter the JavaScript settings.
+      $this->moduleHandler->alter('js_settings', $settings, $assets);
+      $this->themeManager->alter('js_settings', $settings, $assets);
+      // Update the $assets object accordingly, so that it reflects the final
+      // settings.
+      $assets->setSettings($settings);
+      $settings_as_inline_javascript = [
+        'type' => 'setting',
+        'group' => JS_SETTING,
+        'weight' => 0,
+        'browsers' => [],
+        'data' => $settings,
+      ];
+      $settings_js_asset = ['drupalSettings' => $settings_as_inline_javascript];
+      // Prepend to the list of JS assets, to render it first. Preferably in
+      // the footer, but in the header if necessary.
+      if ($settings_in_header) {
+        $js_assets_header = $settings_js_asset + $js_assets_header;
+      }
+      else {
+        $js_assets_footer = $settings_js_asset + $js_assets_footer;
+      }
+    }
+    return [
+      $js_assets_header,
+      $js_assets_footer,
+    ];
+  }
+
+  /**
+   * Sorts CSS and JavaScript resources.
+   *
+   * This sort order helps optimize front-end performance while providing
+   * modules and themes with the necessary control for ordering the CSS and
+   * JavaScript appearing on a page.
+   *
+   * @param $a
+   *   First item for comparison. The compared items should be associative
+   *   arrays of member items.
+   * @param $b
+   *   Second item for comparison.
+   *
+   * @return int
+   */
+  public static function sort($a, $b) {
+    // First order by group, so that all items in the CSS_AGGREGATE_DEFAULT
+    // group appear before items in the CSS_AGGREGATE_THEME group. Modules may
+    // create additional groups by defining their own constants.
+    if ($a['group'] < $b['group']) {
+      return -1;
+    }
+    elseif ($a['group'] > $b['group']) {
+      return 1;
+    }
+    // Finally, order by weight.
+    elseif ($a['weight'] < $b['weight']) {
+      return -1;
+    }
+    elseif ($a['weight'] > $b['weight']) {
+      return 1;
+    }
+    else {
+      return 0;
+    }
+  }
+
+}