3 namespace Drupal\advagg\Asset;
5 use Drupal\Core\Asset\AssetCollectionOptimizerInterface;
6 use Drupal\Core\Asset\AssetResolver as CoreAssetResolver;
7 use Drupal\Core\Asset\AssetResolverInterface;
8 use Drupal\Core\Asset\AttachedAssetsInterface;
9 use Drupal\Core\Asset\LibraryDiscoveryInterface;
10 use Drupal\Core\Asset\LibraryDependencyResolverInterface;
11 use Drupal\Component\Utility\Crypt;
12 use Drupal\Component\Utility\NestedArray;
13 use Drupal\Core\Cache\CacheBackendInterface;
14 use Drupal\Core\Extension\ModuleHandlerInterface;
15 use Drupal\Core\Language\LanguageManagerInterface;
16 use Drupal\Core\Theme\ThemeManagerInterface;
17 use Drupal\Core\State\StateInterface;
20 * The default asset resolver.
22 class AssetResolver extends CoreAssetResolver implements AssetResolverInterface {
25 * The CSS collection optimizer.
27 * @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface
29 protected $cssCollectionOptimizer;
32 * The JS collection optimizer.
34 * @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface
36 protected $jsCollectionOptimizer;
39 * The AdvAgg file status state information storage service.
41 * @var \Drupal\Core\State\StateInterface
43 protected $advaggFiles;
46 * Constructs a new AssetResolver instance.
48 * @param \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery
49 * The library discovery service.
50 * @param \Drupal\Core\Asset\LibraryDependencyResolverInterface $library_dependency_resolver
51 * The library dependency resolver.
52 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
54 * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
56 * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
57 * The language manager.
58 * @param \Drupal\Core\Cache\CacheBackendInterface $cache
60 * @param \Drupal\Core\Asset\AssetCollectionOptimizerInterface $css_collection_optimizer
61 * The CSS collection optimizer.
62 * @param \Drupal\Core\Asset\AssetCollectionOptimizerInterface $js_collection_optimizer
63 * The JS collection optimizer.
64 * @param \Drupal\Core\State\StateInterface $advagg_files
65 * The AdvAgg file status state information storage service.
67 public function __construct(LibraryDiscoveryInterface $library_discovery, LibraryDependencyResolverInterface $library_dependency_resolver, ModuleHandlerInterface $module_handler, ThemeManagerInterface $theme_manager, LanguageManagerInterface $language_manager, CacheBackendInterface $cache, AssetCollectionOptimizerInterface $css_collection_optimizer, AssetCollectionOptimizerInterface $js_collection_optimizer, StateInterface $advagg_files) {
68 $this->libraryDiscovery = $library_discovery;
69 $this->libraryDependencyResolver = $library_dependency_resolver;
70 $this->moduleHandler = $module_handler;
71 $this->themeManager = $theme_manager;
72 $this->languageManager = $language_manager;
73 $this->cache = $cache;
74 $this->cssCollectionOptimizer = $css_collection_optimizer;
75 $this->jsCollectionOptimizer = $js_collection_optimizer;
76 $this->advaggFiles = $advagg_files;
82 public function getCssAssets(AttachedAssetsInterface $assets, $optimize) {
83 $theme_info = $this->themeManager->getActiveTheme();
84 // Add the theme name to the cache key since themes may implement
86 $libraries_to_load = $this->getLibrariesToLoad($assets);
87 $cid = 'css:' . $theme_info->getName() . ':' . Crypt::hashBase64(serialize($libraries_to_load)) . (int) $optimize;
88 if ($cached = $this->cache->get($cid)) {
95 'group' => CSS_AGGREGATE_DEFAULT,
102 foreach ($libraries_to_load as $library) {
103 list($extension, $name) = explode('/', $library, 2);
104 $definition = $this->libraryDiscovery->getLibraryByName($extension, $name);
105 if (isset($definition['css'])) {
106 foreach ($definition['css'] as $options) {
107 $options += $default_options;
108 $options['browsers'] += [
113 // Files with a query string cannot be preprocessed.
114 if ($options['type'] === 'file' && $options['preprocess'] && strpos($options['data'], '?') !== FALSE) {
115 $options['preprocess'] = FALSE;
118 // Always add a tiny value to the weight, to conserve the insertion
120 $options['weight'] += count($css) / 1000;
122 // CSS files are being keyed by the full path.
123 $css[$options['data']] = $options;
128 // Allow modules and themes to alter the CSS assets.
129 $this->moduleHandler->alter('css', $css, $assets);
130 $this->themeManager->alter('css', $css, $assets);
132 // After alter get file information (in case alter changes things).
133 $this->advaggFiles->getMultiple(array_column($css, 'data'));
135 // Sort CSS items, so that they appear in the correct order.
136 uasort($css, 'static::sort');
138 // Allow themes to remove CSS files by CSS files full path and file name.
139 // @todo Remove in Drupal 9.0.x.
140 if ($stylesheet_remove = $theme_info->getStyleSheetsRemove()) {
141 foreach ($css as $key => $options) {
142 if (isset($stylesheet_remove[$key])) {
149 $css = $this->cssCollectionOptimizer->optimize($css);
151 $this->cache->set($cid, $css, CacheBackendInterface::CACHE_PERMANENT, ['library_info']);
159 public function getJsAssets(AttachedAssetsInterface $assets, $optimize) {
160 $theme_info = $this->themeManager->getActiveTheme();
161 // Add the theme name to the cache key since themes may implement
162 // hook_js_alter(). Additionally add the current language to support
163 // translation of JavaScript files.
164 $libraries_to_load = $this->getLibrariesToLoad($assets);
165 $cid = 'js:' . $theme_info->getName() . ':' . $this->languageManager->getCurrentLanguage()->getId() . ':' . Crypt::hashBase64(serialize($libraries_to_load)) . (int) (count($assets->getSettings()) > 0) . (int) $optimize;
167 if ($cached = $this->cache->get($cid)) {
168 list($js_assets_header, $js_assets_footer, $settings, $settings_in_header) = $cached->data;
174 'group' => JS_DEFAULT,
177 'preprocess' => TRUE,
183 // Collect all libraries that contain JS assets and are in the header.
184 $header_js_libraries = [];
185 foreach ($libraries_to_load as $library) {
186 list($extension, $name) = explode('/', $library, 2);
187 $definition = $this->libraryDiscovery->getLibraryByName($extension, $name);
188 if (isset($definition['js']) && !empty($definition['header'])) {
189 $header_js_libraries[] = $library;
192 // The current list of header JS libraries are only those libraries that
193 // are in the header, but their dependencies must also be loaded for them
194 // to function correctly, so update the list with those.
195 $header_js_libraries = $this->libraryDependencyResolver->getLibrariesWithDependencies($header_js_libraries);
197 foreach ($libraries_to_load as $library) {
198 list($extension, $name) = explode('/', $library, 2);
199 $definition = $this->libraryDiscovery->getLibraryByName($extension, $name);
200 if (isset($definition['js'])) {
201 foreach ($definition['js'] as $options) {
202 $options += $default_options;
204 // 'scope' is a calculated option, based on which libraries are
205 // marked to be loaded from the header (see above).
206 $options['scope'] = in_array($library, $header_js_libraries) ? 'header' : 'footer';
208 // Preprocess can only be set if caching is enabled and no
209 // attributes are set.
210 $options['preprocess'] = $options['cache'] && empty($options['attributes']) ? $options['preprocess'] : FALSE;
212 // Always add a tiny value to the weight, to conserve the insertion
214 $options['weight'] += count($javascript) / 1000;
216 // Local and external files must keep their name as the associative
217 // key so the same JavaScript file is not added twice.
218 $javascript[$options['data']] = $options;
223 // Allow modules and themes to alter the JavaScript assets.
224 $this->moduleHandler->alter('js', $javascript, $assets);
225 $this->themeManager->alter('js', $javascript, $assets);
227 // After alter get file information (in case alter changes things).
228 $this->advaggFiles->getMultiple(array_column($javascript, 'data'));
230 // Sort JavaScript assets, so that they appear in the correct order.
231 uasort($javascript, 'static::sort');
233 // Prepare the return value: filter JavaScript assets per scope.
234 $js_assets_header = [];
235 $js_assets_footer = [];
236 foreach ($javascript as $key => $item) {
237 if ($item['scope'] == 'header') {
238 $js_assets_header[$key] = $item;
240 elseif ($item['scope'] == 'footer') {
241 $js_assets_footer[$key] = $item;
246 $js_assets_header = $this->jsCollectionOptimizer->optimize($js_assets_header);
247 $js_assets_footer = $this->jsCollectionOptimizer->optimize($js_assets_footer);
250 // If the core/drupalSettings library is being loaded or is already
251 // loaded, get the JavaScript settings assets, and convert them into a
252 // single "regular" JavaScript asset.
253 $libraries_to_load = $this->getLibrariesToLoad($assets);
254 $settings_required = in_array('core/drupalSettings', $libraries_to_load) || in_array('core/drupalSettings', $this->libraryDependencyResolver->getLibrariesWithDependencies($assets->getAlreadyLoadedLibraries()));
255 $settings_have_changed = count($libraries_to_load) > 0 || count($assets->getSettings()) > 0;
257 // Initialize settings to FALSE since they are not needed by default. This
258 // distinguishes between an empty array which must still allow
259 // hook_js_settings_alter() to be run.
261 if ($settings_required && $settings_have_changed) {
262 $settings = $this->getJsSettingsAssets($assets);
263 // Allow modules to add cached JavaScript settings.
264 foreach ($this->moduleHandler->getImplementations('js_settings_build') as $module) {
265 $function = $module . '_js_settings_build';
266 $function($settings, $assets);
269 $settings_in_header = in_array('core/drupalSettings', $header_js_libraries);
270 $this->cache->set($cid, [
275 ], CacheBackendInterface::CACHE_PERMANENT, ['library_info']);
278 if ($settings !== FALSE) {
279 // Attached settings override both library definitions and
280 // hook_js_settings_build().
281 $settings = NestedArray::mergeDeepArray([$settings, $assets->getSettings()], TRUE);
282 // Allow modules and themes to alter the JavaScript settings.
283 $this->moduleHandler->alter('js_settings', $settings, $assets);
284 $this->themeManager->alter('js_settings', $settings, $assets);
285 // Update the $assets object accordingly, so that it reflects the final
287 $assets->setSettings($settings);
288 $settings_as_inline_javascript = [
290 'group' => JS_SETTING,
295 $settings_js_asset = ['drupalSettings' => $settings_as_inline_javascript];
296 // Prepend to the list of JS assets, to render it first. Preferably in
297 // the footer, but in the header if necessary.
298 if ($settings_in_header) {
299 $js_assets_header = $settings_js_asset + $js_assets_header;
302 $js_assets_footer = $settings_js_asset + $js_assets_footer;