Mercurial > hg > cmmr2012-drupal-site
comparison core/lib/Drupal/Core/Asset/AssetResolver.php @ 0:c75dbcec494b
Initial commit from drush-created site
author | Chris Cannam |
---|---|
date | Thu, 05 Jul 2018 14:24:15 +0000 |
parents | |
children | 12f9dff5fda9 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c75dbcec494b |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\Core\Asset; | |
4 | |
5 use Drupal\Component\Utility\Crypt; | |
6 use Drupal\Component\Utility\NestedArray; | |
7 use Drupal\Core\Cache\CacheBackendInterface; | |
8 use Drupal\Core\Extension\ModuleHandlerInterface; | |
9 use Drupal\Core\Language\LanguageManagerInterface; | |
10 use Drupal\Core\Theme\ThemeManagerInterface; | |
11 | |
12 /** | |
13 * The default asset resolver. | |
14 */ | |
15 class AssetResolver implements AssetResolverInterface { | |
16 | |
17 /** | |
18 * The library discovery service. | |
19 * | |
20 * @var \Drupal\Core\Asset\LibraryDiscoveryInterface | |
21 */ | |
22 protected $libraryDiscovery; | |
23 | |
24 /** | |
25 * The library dependency resolver. | |
26 * | |
27 * @var \Drupal\Core\Asset\LibraryDependencyResolverInterface | |
28 */ | |
29 protected $libraryDependencyResolver; | |
30 | |
31 /** | |
32 * The module handler. | |
33 * | |
34 * @var \Drupal\Core\Extension\ModuleHandlerInterface | |
35 */ | |
36 protected $moduleHandler; | |
37 | |
38 /** | |
39 * The theme manager. | |
40 * | |
41 * @var \Drupal\Core\Theme\ThemeManagerInterface | |
42 */ | |
43 protected $themeManager; | |
44 | |
45 /** | |
46 * The language manager. | |
47 * | |
48 * @var \Drupal\Core\Language\LanguageManagerInterface | |
49 */ | |
50 protected $languageManager; | |
51 | |
52 /** | |
53 * The cache backend. | |
54 * | |
55 * @var \Drupal\Core\Cache\CacheBackendInterface | |
56 */ | |
57 protected $cache; | |
58 | |
59 /** | |
60 * Constructs a new AssetResolver instance. | |
61 * | |
62 * @param \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery | |
63 * The library discovery service. | |
64 * @param \Drupal\Core\Asset\LibraryDependencyResolverInterface $library_dependency_resolver | |
65 * The library dependency resolver. | |
66 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler | |
67 * The module handler. | |
68 * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager | |
69 * The theme manager. | |
70 * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager | |
71 * The language manager. | |
72 * @param \Drupal\Core\Cache\CacheBackendInterface $cache | |
73 * The cache backend. | |
74 */ | |
75 public function __construct(LibraryDiscoveryInterface $library_discovery, LibraryDependencyResolverInterface $library_dependency_resolver, ModuleHandlerInterface $module_handler, ThemeManagerInterface $theme_manager, LanguageManagerInterface $language_manager, CacheBackendInterface $cache) { | |
76 $this->libraryDiscovery = $library_discovery; | |
77 $this->libraryDependencyResolver = $library_dependency_resolver; | |
78 $this->moduleHandler = $module_handler; | |
79 $this->themeManager = $theme_manager; | |
80 $this->languageManager = $language_manager; | |
81 $this->cache = $cache; | |
82 } | |
83 | |
84 /** | |
85 * Returns the libraries that need to be loaded. | |
86 * | |
87 * For example, with core/a depending on core/c and core/b on core/d: | |
88 * @code | |
89 * $assets = new AttachedAssets(); | |
90 * $assets->setLibraries(['core/a', 'core/b', 'core/c']); | |
91 * $assets->setAlreadyLoadedLibraries(['core/c']); | |
92 * $resolver->getLibrariesToLoad($assets) === ['core/a', 'core/b', 'core/d'] | |
93 * @endcode | |
94 * | |
95 * @param \Drupal\Core\Asset\AttachedAssetsInterface $assets | |
96 * The assets attached to the current response. | |
97 * | |
98 * @return string[] | |
99 * A list of libraries and their dependencies, in the order they should be | |
100 * loaded, excluding any libraries that have already been loaded. | |
101 */ | |
102 protected function getLibrariesToLoad(AttachedAssetsInterface $assets) { | |
103 return array_diff( | |
104 $this->libraryDependencyResolver->getLibrariesWithDependencies($assets->getLibraries()), | |
105 $this->libraryDependencyResolver->getLibrariesWithDependencies($assets->getAlreadyLoadedLibraries()) | |
106 ); | |
107 } | |
108 | |
109 /** | |
110 * {@inheritdoc} | |
111 */ | |
112 public function getCssAssets(AttachedAssetsInterface $assets, $optimize) { | |
113 $theme_info = $this->themeManager->getActiveTheme(); | |
114 // Add the theme name to the cache key since themes may implement | |
115 // hook_library_info_alter(). | |
116 $libraries_to_load = $this->getLibrariesToLoad($assets); | |
117 $cid = 'css:' . $theme_info->getName() . ':' . Crypt::hashBase64(serialize($libraries_to_load)) . (int) $optimize; | |
118 if ($cached = $this->cache->get($cid)) { | |
119 return $cached->data; | |
120 } | |
121 | |
122 $css = []; | |
123 $default_options = [ | |
124 'type' => 'file', | |
125 'group' => CSS_AGGREGATE_DEFAULT, | |
126 'weight' => 0, | |
127 'media' => 'all', | |
128 'preprocess' => TRUE, | |
129 'browsers' => [], | |
130 ]; | |
131 | |
132 foreach ($libraries_to_load as $library) { | |
133 list($extension, $name) = explode('/', $library, 2); | |
134 $definition = $this->libraryDiscovery->getLibraryByName($extension, $name); | |
135 if (isset($definition['css'])) { | |
136 foreach ($definition['css'] as $options) { | |
137 $options += $default_options; | |
138 $options['browsers'] += [ | |
139 'IE' => TRUE, | |
140 '!IE' => TRUE, | |
141 ]; | |
142 | |
143 // Files with a query string cannot be preprocessed. | |
144 if ($options['type'] === 'file' && $options['preprocess'] && strpos($options['data'], '?') !== FALSE) { | |
145 $options['preprocess'] = FALSE; | |
146 } | |
147 | |
148 // Always add a tiny value to the weight, to conserve the insertion | |
149 // order. | |
150 $options['weight'] += count($css) / 1000; | |
151 | |
152 // CSS files are being keyed by the full path. | |
153 $css[$options['data']] = $options; | |
154 } | |
155 } | |
156 } | |
157 | |
158 // Allow modules and themes to alter the CSS assets. | |
159 $this->moduleHandler->alter('css', $css, $assets); | |
160 $this->themeManager->alter('css', $css, $assets); | |
161 | |
162 // Sort CSS items, so that they appear in the correct order. | |
163 uasort($css, 'static::sort'); | |
164 | |
165 // Allow themes to remove CSS files by CSS files full path and file name. | |
166 // @todo Remove in Drupal 9.0.x. | |
167 if ($stylesheet_remove = $theme_info->getStyleSheetsRemove()) { | |
168 foreach ($css as $key => $options) { | |
169 if (isset($stylesheet_remove[$key])) { | |
170 unset($css[$key]); | |
171 } | |
172 } | |
173 } | |
174 | |
175 if ($optimize) { | |
176 $css = \Drupal::service('asset.css.collection_optimizer')->optimize($css); | |
177 } | |
178 $this->cache->set($cid, $css, CacheBackendInterface::CACHE_PERMANENT, ['library_info']); | |
179 | |
180 return $css; | |
181 } | |
182 | |
183 /** | |
184 * Returns the JavaScript settings assets for this response's libraries. | |
185 * | |
186 * Gathers all drupalSettings from all libraries in the attached assets | |
187 * collection and merges them. | |
188 * | |
189 * @param \Drupal\Core\Asset\AttachedAssetsInterface $assets | |
190 * The assets attached to the current response. | |
191 * @return array | |
192 * A (possibly optimized) collection of JavaScript assets. | |
193 */ | |
194 protected function getJsSettingsAssets(AttachedAssetsInterface $assets) { | |
195 $settings = []; | |
196 | |
197 foreach ($this->getLibrariesToLoad($assets) as $library) { | |
198 list($extension, $name) = explode('/', $library, 2); | |
199 $definition = $this->libraryDiscovery->getLibraryByName($extension, $name); | |
200 if (isset($definition['drupalSettings'])) { | |
201 $settings = NestedArray::mergeDeepArray([$settings, $definition['drupalSettings']], TRUE); | |
202 } | |
203 } | |
204 | |
205 return $settings; | |
206 } | |
207 | |
208 /** | |
209 * {@inheritdoc} | |
210 */ | |
211 public function getJsAssets(AttachedAssetsInterface $assets, $optimize) { | |
212 $theme_info = $this->themeManager->getActiveTheme(); | |
213 // Add the theme name to the cache key since themes may implement | |
214 // hook_library_info_alter(). Additionally add the current language to | |
215 // support translation of JavaScript files via hook_js_alter(). | |
216 $libraries_to_load = $this->getLibrariesToLoad($assets); | |
217 $cid = 'js:' . $theme_info->getName() . ':' . $this->languageManager->getCurrentLanguage()->getId() . ':' . Crypt::hashBase64(serialize($libraries_to_load)) . (int) (count($assets->getSettings()) > 0) . (int) $optimize; | |
218 | |
219 if ($cached = $this->cache->get($cid)) { | |
220 list($js_assets_header, $js_assets_footer, $settings, $settings_in_header) = $cached->data; | |
221 } | |
222 else { | |
223 $javascript = []; | |
224 $default_options = [ | |
225 'type' => 'file', | |
226 'group' => JS_DEFAULT, | |
227 'weight' => 0, | |
228 'cache' => TRUE, | |
229 'preprocess' => TRUE, | |
230 'attributes' => [], | |
231 'version' => NULL, | |
232 'browsers' => [], | |
233 ]; | |
234 | |
235 // Collect all libraries that contain JS assets and are in the header. | |
236 $header_js_libraries = []; | |
237 foreach ($libraries_to_load as $library) { | |
238 list($extension, $name) = explode('/', $library, 2); | |
239 $definition = $this->libraryDiscovery->getLibraryByName($extension, $name); | |
240 if (isset($definition['js']) && !empty($definition['header'])) { | |
241 $header_js_libraries[] = $library; | |
242 } | |
243 } | |
244 // The current list of header JS libraries are only those libraries that | |
245 // are in the header, but their dependencies must also be loaded for them | |
246 // to function correctly, so update the list with those. | |
247 $header_js_libraries = $this->libraryDependencyResolver->getLibrariesWithDependencies($header_js_libraries); | |
248 | |
249 foreach ($libraries_to_load as $library) { | |
250 list($extension, $name) = explode('/', $library, 2); | |
251 $definition = $this->libraryDiscovery->getLibraryByName($extension, $name); | |
252 if (isset($definition['js'])) { | |
253 foreach ($definition['js'] as $options) { | |
254 $options += $default_options; | |
255 | |
256 // 'scope' is a calculated option, based on which libraries are | |
257 // marked to be loaded from the header (see above). | |
258 $options['scope'] = in_array($library, $header_js_libraries) ? 'header' : 'footer'; | |
259 | |
260 // Preprocess can only be set if caching is enabled and no | |
261 // attributes are set. | |
262 $options['preprocess'] = $options['cache'] && empty($options['attributes']) ? $options['preprocess'] : FALSE; | |
263 | |
264 // Always add a tiny value to the weight, to conserve the insertion | |
265 // order. | |
266 $options['weight'] += count($javascript) / 1000; | |
267 | |
268 // Local and external files must keep their name as the associative | |
269 // key so the same JavaScript file is not added twice. | |
270 $javascript[$options['data']] = $options; | |
271 } | |
272 } | |
273 } | |
274 | |
275 // Allow modules and themes to alter the JavaScript assets. | |
276 $this->moduleHandler->alter('js', $javascript, $assets); | |
277 $this->themeManager->alter('js', $javascript, $assets); | |
278 | |
279 // Sort JavaScript assets, so that they appear in the correct order. | |
280 uasort($javascript, 'static::sort'); | |
281 | |
282 // Prepare the return value: filter JavaScript assets per scope. | |
283 $js_assets_header = []; | |
284 $js_assets_footer = []; | |
285 foreach ($javascript as $key => $item) { | |
286 if ($item['scope'] == 'header') { | |
287 $js_assets_header[$key] = $item; | |
288 } | |
289 elseif ($item['scope'] == 'footer') { | |
290 $js_assets_footer[$key] = $item; | |
291 } | |
292 } | |
293 | |
294 if ($optimize) { | |
295 $collection_optimizer = \Drupal::service('asset.js.collection_optimizer'); | |
296 $js_assets_header = $collection_optimizer->optimize($js_assets_header); | |
297 $js_assets_footer = $collection_optimizer->optimize($js_assets_footer); | |
298 } | |
299 | |
300 // If the core/drupalSettings library is being loaded or is already | |
301 // loaded, get the JavaScript settings assets, and convert them into a | |
302 // single "regular" JavaScript asset. | |
303 $libraries_to_load = $this->getLibrariesToLoad($assets); | |
304 $settings_required = in_array('core/drupalSettings', $libraries_to_load) || in_array('core/drupalSettings', $this->libraryDependencyResolver->getLibrariesWithDependencies($assets->getAlreadyLoadedLibraries())); | |
305 $settings_have_changed = count($libraries_to_load) > 0 || count($assets->getSettings()) > 0; | |
306 | |
307 // Initialize settings to FALSE since they are not needed by default. This | |
308 // distinguishes between an empty array which must still allow | |
309 // hook_js_settings_alter() to be run. | |
310 $settings = FALSE; | |
311 if ($settings_required && $settings_have_changed) { | |
312 $settings = $this->getJsSettingsAssets($assets); | |
313 // Allow modules to add cached JavaScript settings. | |
314 foreach ($this->moduleHandler->getImplementations('js_settings_build') as $module) { | |
315 $function = $module . '_' . 'js_settings_build'; | |
316 $function($settings, $assets); | |
317 } | |
318 } | |
319 $settings_in_header = in_array('core/drupalSettings', $header_js_libraries); | |
320 $this->cache->set($cid, [$js_assets_header, $js_assets_footer, $settings, $settings_in_header], CacheBackendInterface::CACHE_PERMANENT, ['library_info']); | |
321 } | |
322 | |
323 if ($settings !== FALSE) { | |
324 // Attached settings override both library definitions and | |
325 // hook_js_settings_build(). | |
326 $settings = NestedArray::mergeDeepArray([$settings, $assets->getSettings()], TRUE); | |
327 // Allow modules and themes to alter the JavaScript settings. | |
328 $this->moduleHandler->alter('js_settings', $settings, $assets); | |
329 $this->themeManager->alter('js_settings', $settings, $assets); | |
330 // Update the $assets object accordingly, so that it reflects the final | |
331 // settings. | |
332 $assets->setSettings($settings); | |
333 $settings_as_inline_javascript = [ | |
334 'type' => 'setting', | |
335 'group' => JS_SETTING, | |
336 'weight' => 0, | |
337 'browsers' => [], | |
338 'data' => $settings, | |
339 ]; | |
340 $settings_js_asset = ['drupalSettings' => $settings_as_inline_javascript]; | |
341 // Prepend to the list of JS assets, to render it first. Preferably in | |
342 // the footer, but in the header if necessary. | |
343 if ($settings_in_header) { | |
344 $js_assets_header = $settings_js_asset + $js_assets_header; | |
345 } | |
346 else { | |
347 $js_assets_footer = $settings_js_asset + $js_assets_footer; | |
348 } | |
349 } | |
350 return [ | |
351 $js_assets_header, | |
352 $js_assets_footer, | |
353 ]; | |
354 } | |
355 | |
356 /** | |
357 * Sorts CSS and JavaScript resources. | |
358 * | |
359 * This sort order helps optimize front-end performance while providing | |
360 * modules and themes with the necessary control for ordering the CSS and | |
361 * JavaScript appearing on a page. | |
362 * | |
363 * @param $a | |
364 * First item for comparison. The compared items should be associative | |
365 * arrays of member items. | |
366 * @param $b | |
367 * Second item for comparison. | |
368 * | |
369 * @return int | |
370 */ | |
371 public static function sort($a, $b) { | |
372 // First order by group, so that all items in the CSS_AGGREGATE_DEFAULT | |
373 // group appear before items in the CSS_AGGREGATE_THEME group. Modules may | |
374 // create additional groups by defining their own constants. | |
375 if ($a['group'] < $b['group']) { | |
376 return -1; | |
377 } | |
378 elseif ($a['group'] > $b['group']) { | |
379 return 1; | |
380 } | |
381 // Finally, order by weight. | |
382 elseif ($a['weight'] < $b['weight']) { | |
383 return -1; | |
384 } | |
385 elseif ($a['weight'] > $b['weight']) { | |
386 return 1; | |
387 } | |
388 else { | |
389 return 0; | |
390 } | |
391 } | |
392 | |
393 } |