PageRenderTime 55ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/core/lib/Drupal/Core/Asset/AssetResolver.php

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