Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 6 |
CRAP | |
48.39% |
60 / 124 |
| AssetResolver | |
0.00% |
0 / 1 |
|
0.00% |
0 / 6 |
310.18 | |
48.39% |
60 / 124 |
| __construct | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 7 |
|||
| getLibrariesToLoad | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
| getCssAssets | |
0.00% |
0 / 1 |
16.39 | |
68.75% |
22 / 32 |
|||
| getJsSettingsAssets | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 7 |
|||
| getJsAssets | |
0.00% |
0 / 1 |
58.96 | |
57.58% |
38 / 66 |
|||
| sort | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 9 |
|||
| <?php | |
| /** | |
| * @file | |
| * Contains \Drupal\Core\Asset\AssetResolver. | |
| */ | |
| 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 $language_manager | |
| */ | |
| 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; | |
| } | |
| } | |
| } |