PageRenderTime 27ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://gitlab.com/geeta7/drupal
PHP | 223 lines | 90 code | 17 blank | 116 comment | 11 complexity | b3cb627ec0db3354115c40bf1e3fb4c5 MD5 | raw file
  1. <?php
  2. /**
  3. * @file
  4. * Contains \Drupal\Core\Asset\CssCollectionRenderer.
  5. */
  6. namespace Drupal\Core\Asset;
  7. use Drupal\Component\Utility\Html;
  8. use Drupal\Core\State\StateInterface;
  9. /**
  10. * Renders CSS assets.
  11. *
  12. * For production websites, LINK tags are preferable to STYLE tags with @import
  13. * statements, because:
  14. * - They are the standard tag intended for linking to a resource.
  15. * - On Firefox 2 and perhaps other browsers, CSS files included with @import
  16. * statements don't get saved when saving the complete web page for offline
  17. * use: https://www.drupal.org/node/145218.
  18. * - On IE, if only LINK tags and no @import statements are used, all the CSS
  19. * files are downloaded in parallel, resulting in faster page load, but if
  20. * @import statements are used and span across multiple STYLE tags, all the
  21. * ones from one STYLE tag must be downloaded before downloading begins for
  22. * the next STYLE tag. Furthermore, IE7 does not support media declaration on
  23. * the @import statement, so multiple STYLE tags must be used when different
  24. * files are for different media types. Non-IE browsers always download in
  25. * parallel, so this is an IE-specific performance quirk:
  26. * http://www.stevesouders.com/blog/2009/04/09/dont-use-import/.
  27. *
  28. * However, IE has an annoying limit of 31 total CSS inclusion tags
  29. * (https://www.drupal.org/node/228818) and LINK tags are limited to one file
  30. * per tag, whereas STYLE tags can contain multiple @import statements allowing
  31. * multiple files to be loaded per tag. When CSS aggregation is disabled, a
  32. * Drupal site can easily have more than 31 CSS files that need to be loaded, so
  33. * using LINK tags exclusively would result in a site that would display
  34. * incorrectly in IE. Depending on different needs, different strategies can be
  35. * employed to decide when to use LINK tags and when to use STYLE tags.
  36. *
  37. * The strategy employed by this class is to use LINK tags for all aggregate
  38. * files and for all files that cannot be aggregated (e.g., if 'preprocess' is
  39. * set to FALSE or the type is 'external'), and to use STYLE tags for groups
  40. * of files that could be aggregated together but aren't (e.g., if the site-wide
  41. * aggregation setting is disabled). This results in all LINK tags when
  42. * aggregation is enabled, a guarantee that as many or only slightly more tags
  43. * are used with aggregation disabled than enabled (so that if the limit were to
  44. * be crossed with aggregation enabled, the site developer would also notice the
  45. * problem while aggregation is disabled), and an easy way for a developer to
  46. * view HTML source while aggregation is disabled and know what files will be
  47. * aggregated together when aggregation becomes enabled.
  48. *
  49. * This class evaluates the aggregation enabled/disabled condition on a group
  50. * by group basis by testing whether an aggregate file has been made for the
  51. * group rather than by testing the site-wide aggregation setting. This allows
  52. * this class to work correctly even if modules have implemented custom
  53. * logic for grouping and aggregating files.
  54. */
  55. class CssCollectionRenderer implements AssetCollectionRendererInterface {
  56. /**
  57. * The state key/value store.
  58. *
  59. * @var \Drupal\Core\State\StateInterface
  60. */
  61. protected $state;
  62. /**
  63. * Constructs a CssCollectionRenderer.
  64. *
  65. * @param \Drupal\Core\State\StateInterface
  66. * The state key/value store.
  67. */
  68. public function __construct(StateInterface $state) {
  69. $this->state = $state;
  70. }
  71. /**
  72. * {@inheritdoc}
  73. */
  74. public function render(array $css_assets) {
  75. $elements = array();
  76. // A dummy query-string is added to filenames, to gain control over
  77. // browser-caching. The string changes on every update or full cache
  78. // flush, forcing browsers to load a new copy of the files, as the
  79. // URL changed.
  80. $query_string = $this->state->get('system.css_js_query_string') ?: '0';
  81. // Defaults for LINK and STYLE elements.
  82. $link_element_defaults = array(
  83. '#type' => 'html_tag',
  84. '#tag' => 'link',
  85. '#attributes' => array(
  86. 'rel' => 'stylesheet',
  87. ),
  88. );
  89. $style_element_defaults = array(
  90. '#type' => 'html_tag',
  91. '#tag' => 'style',
  92. );
  93. // For filthy IE hack.
  94. $current_ie_group_keys = NULL;
  95. $get_ie_group_key = function ($css_asset) {
  96. return array($css_asset['type'], $css_asset['preprocess'], $css_asset['group'], $css_asset['media'], $css_asset['browsers']);
  97. };
  98. // Loop through all CSS assets, by key, to allow for the special IE
  99. // workaround.
  100. $css_assets_keys = array_keys($css_assets);
  101. for ($i = 0; $i < count($css_assets_keys); $i++) {
  102. $css_asset = $css_assets[$css_assets_keys[$i]];
  103. switch ($css_asset['type']) {
  104. // For file items, there are three possibilities.
  105. // - There are up to 31 CSS assets on the page (some of which may be
  106. // aggregated). In this case, output a LINK tag for file CSS assets.
  107. // - There are more than 31 CSS assets on the page, yet we must stay
  108. // below IE<10's limit of 31 total CSS inclusion tags, we handle this
  109. // in two ways:
  110. // - file CSS assets that are not eligible for aggregation (their
  111. // 'preprocess' flag has been set to FALSE): in this case, output a
  112. // LINK tag.
  113. // - file CSS assets that can be aggregated (and possibly have been):
  114. // in this case, figure out which subsequent file CSS assets share
  115. // the same key properties ('group', 'media' and 'browsers') and
  116. // output this group into as few STYLE tags as possible (a STYLE
  117. // tag may contain only 31 @import statements).
  118. case 'file':
  119. // The dummy query string needs to be added to the URL to control
  120. // browser-caching.
  121. $query_string_separator = (strpos($css_asset['data'], '?') !== FALSE) ? '&' : '?';
  122. // As long as the current page will not run into IE's limit for CSS
  123. // assets: output a LINK tag for a file CSS asset.
  124. if (count($css_assets) <= 31) {
  125. $element = $link_element_defaults;
  126. $element['#attributes']['href'] = file_url_transform_relative(file_create_url($css_asset['data'])) . $query_string_separator . $query_string;
  127. $element['#attributes']['media'] = $css_asset['media'];
  128. $element['#browsers'] = $css_asset['browsers'];
  129. $elements[] = $element;
  130. }
  131. // The current page will run into IE's limits for CSS assets: work
  132. // around these limits by performing a light form of grouping.
  133. // Once Drupal only needs to support IE10 and later, we can drop this.
  134. else {
  135. // The file CSS asset is ineligible for aggregation: output it in a
  136. // LINK tag.
  137. if (!$css_asset['preprocess']) {
  138. $element = $link_element_defaults;
  139. $element['#attributes']['href'] = file_url_transform_relative(file_create_url($css_asset['data'])) . $query_string_separator . $query_string;
  140. $element['#attributes']['media'] = $css_asset['media'];
  141. $element['#browsers'] = $css_asset['browsers'];
  142. $elements[] = $element;
  143. }
  144. // The file CSS asset can be aggregated, but hasn't been: combine
  145. // multiple items into as few STYLE tags as possible.
  146. else {
  147. $import = array();
  148. // Start with the current CSS asset, iterate over subsequent CSS
  149. // assets and find which ones have the same 'type', 'group',
  150. // 'preprocess', 'media' and 'browsers' properties.
  151. $j = $i;
  152. $next_css_asset = $css_asset;
  153. $current_ie_group_key = $get_ie_group_key($css_asset);
  154. do {
  155. // The dummy query string needs to be added to the URL to
  156. // control browser-caching. IE7 does not support a media type on
  157. // the @import statement, so we instead specify the media for
  158. // the group on the STYLE tag.
  159. $import[] = '@import url("' . Html::escape(file_url_transform_relative(file_create_url($next_css_asset['data'])) . '?' . $query_string) . '");';
  160. // Move the outer for loop skip the next item, since we
  161. // processed it here.
  162. $i = $j;
  163. // Retrieve next CSS asset, unless there is none: then break.
  164. if ($j + 1 < count($css_assets_keys)) {
  165. $j++;
  166. $next_css_asset = $css_assets[$css_assets_keys[$j]];
  167. }
  168. else {
  169. break;
  170. }
  171. } while ($get_ie_group_key($next_css_asset) == $current_ie_group_key);
  172. // In addition to IE's limit of 31 total CSS inclusion tags, it
  173. // also has a limit of 31 @import statements per STYLE tag.
  174. while (!empty($import)) {
  175. $import_batch = array_slice($import, 0, 31);
  176. $import = array_slice($import, 31);
  177. $element = $style_element_defaults;
  178. // This simplifies the JavaScript regex, allowing each line
  179. // (separated by \n) to be treated as a completely different
  180. // string. This means that we can use ^ and $ on one line at a
  181. // time, and not worry about style tags since they'll never
  182. // match the regex.
  183. $element['#value'] = "\n" . implode("\n", $import_batch) . "\n";
  184. $element['#attributes']['media'] = $css_asset['media'];
  185. $element['#browsers'] = $css_asset['browsers'];
  186. $elements[] = $element;
  187. }
  188. }
  189. }
  190. break;
  191. // Output a LINK tag for an external CSS asset. The asset's 'data'
  192. // property contains the full URL.
  193. case 'external':
  194. $element = $link_element_defaults;
  195. $element['#attributes']['href'] = $css_asset['data'];
  196. $element['#attributes']['media'] = $css_asset['media'];
  197. $element['#browsers'] = $css_asset['browsers'];
  198. $elements[] = $element;
  199. break;
  200. default:
  201. throw new \Exception('Invalid CSS asset type.');
  202. }
  203. }
  204. return $elements;
  205. }
  206. }