a28bee467c9b568527496bf2903ee6230b75aa67
[yaffs-website] / web / modules / contrib / advagg / src / Asset / AssetResolver.php
1 <?php
2
3 namespace Drupal\advagg\Asset;
4
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;
18
19 /**
20  * The default asset resolver.
21  */
22 class AssetResolver extends CoreAssetResolver implements AssetResolverInterface {
23
24   /**
25    * The CSS collection optimizer.
26    *
27    * @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface
28    */
29   protected $cssCollectionOptimizer;
30
31   /**
32    * The JS collection optimizer.
33    *
34    * @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface
35    */
36   protected $jsCollectionOptimizer;
37
38   /**
39    * The AdvAgg file status state information storage service.
40    *
41    * @var \Drupal\Core\State\StateInterface
42    */
43   protected $advaggFiles;
44
45   /**
46    * Constructs a new AssetResolver instance.
47    *
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
53    *   The module handler.
54    * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
55    *   The theme manager.
56    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
57    *   The language manager.
58    * @param \Drupal\Core\Cache\CacheBackendInterface $cache
59    *   The cache backend.
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.
66    */
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;
77   }
78
79   /**
80    * {@inheritdoc}
81    */
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
85     // hook_css_alter().
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)) {
89       return $cached->data;
90     }
91
92     $css = [];
93     $default_options = [
94       'type' => 'file',
95       'group' => CSS_AGGREGATE_DEFAULT,
96       'weight' => 0,
97       'media' => 'all',
98       'preprocess' => TRUE,
99       'browsers' => [],
100     ];
101
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'] += [
109             'IE' => TRUE,
110             '!IE' => TRUE,
111           ];
112
113           // Files with a query string cannot be preprocessed.
114           if ($options['type'] === 'file' && $options['preprocess'] && strpos($options['data'], '?') !== FALSE) {
115             $options['preprocess'] = FALSE;
116           }
117
118           // Always add a tiny value to the weight, to conserve the insertion
119           // order.
120           $options['weight'] += count($css) / 1000;
121
122           // CSS files are being keyed by the full path.
123           $css[$options['data']] = $options;
124         }
125       }
126     }
127
128     // Allow modules and themes to alter the CSS assets.
129     $this->moduleHandler->alter('css', $css, $assets);
130     $this->themeManager->alter('css', $css, $assets);
131
132     // After alter get file information (in case alter changes things).
133     $this->advaggFiles->getMultiple(array_column($css, 'data'));
134
135     // Sort CSS items, so that they appear in the correct order.
136     uasort($css, 'static::sort');
137
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])) {
143           unset($css[$key]);
144         }
145       }
146     }
147
148     if ($optimize) {
149       $css = $this->cssCollectionOptimizer->optimize($css);
150     }
151     $this->cache->set($cid, $css, CacheBackendInterface::CACHE_PERMANENT, ['library_info']);
152
153     return $css;
154   }
155
156   /**
157    * {@inheritdoc}
158    */
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;
166
167     if ($cached = $this->cache->get($cid)) {
168       list($js_assets_header, $js_assets_footer, $settings, $settings_in_header) = $cached->data;
169     }
170     else {
171       $javascript = [];
172       $default_options = [
173         'type' => 'file',
174         'group' => JS_DEFAULT,
175         'weight' => 0,
176         'cache' => TRUE,
177         'preprocess' => TRUE,
178         'attributes' => [],
179         'version' => NULL,
180         'browsers' => [],
181       ];
182
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;
190         }
191       }
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);
196
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;
203
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';
207
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;
211
212             // Always add a tiny value to the weight, to conserve the insertion
213             // order.
214             $options['weight'] += count($javascript) / 1000;
215
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;
219           }
220         }
221       }
222
223       // Allow modules and themes to alter the JavaScript assets.
224       $this->moduleHandler->alter('js', $javascript, $assets);
225       $this->themeManager->alter('js', $javascript, $assets);
226
227       // After alter get file information (in case alter changes things).
228       $this->advaggFiles->getMultiple(array_column($javascript, 'data'));
229
230       // Sort JavaScript assets, so that they appear in the correct order.
231       uasort($javascript, 'static::sort');
232
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;
239         }
240         elseif ($item['scope'] == 'footer') {
241           $js_assets_footer[$key] = $item;
242         }
243       }
244
245       if ($optimize) {
246         $js_assets_header = $this->jsCollectionOptimizer->optimize($js_assets_header);
247         $js_assets_footer = $this->jsCollectionOptimizer->optimize($js_assets_footer);
248       }
249
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;
256
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.
260       $settings = FALSE;
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);
267         }
268       }
269       $settings_in_header = in_array('core/drupalSettings', $header_js_libraries);
270       $this->cache->set($cid, [
271         $js_assets_header,
272         $js_assets_footer,
273         $settings,
274         $settings_in_header,
275       ], CacheBackendInterface::CACHE_PERMANENT, ['library_info']);
276     }
277
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
286       // settings.
287       $assets->setSettings($settings);
288       $settings_as_inline_javascript = [
289         'type' => 'setting',
290         'group' => JS_SETTING,
291         'weight' => 0,
292         'browsers' => [],
293         'data' => $settings,
294       ];
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;
300       }
301       else {
302         $js_assets_footer = $settings_js_asset + $js_assets_footer;
303       }
304     }
305     return [
306       $js_assets_header,
307       $js_assets_footer,
308     ];
309   }
310
311 }