Chris@0: state = $state; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function render(array $css_assets) { Chris@0: $elements = []; Chris@0: Chris@0: // A dummy query-string is added to filenames, to gain control over Chris@0: // browser-caching. The string changes on every update or full cache Chris@0: // flush, forcing browsers to load a new copy of the files, as the Chris@0: // URL changed. Chris@0: $query_string = $this->state->get('system.css_js_query_string') ?: '0'; Chris@0: Chris@0: // Defaults for LINK and STYLE elements. Chris@0: $link_element_defaults = [ Chris@0: '#type' => 'html_tag', Chris@0: '#tag' => 'link', Chris@0: '#attributes' => [ Chris@0: 'rel' => 'stylesheet', Chris@0: ], Chris@0: ]; Chris@0: $style_element_defaults = [ Chris@0: '#type' => 'html_tag', Chris@0: '#tag' => 'style', Chris@0: ]; Chris@0: Chris@0: // For filthy IE hack. Chris@0: $current_ie_group_keys = NULL; Chris@0: $get_ie_group_key = function ($css_asset) { Chris@0: return [$css_asset['type'], $css_asset['preprocess'], $css_asset['group'], $css_asset['media'], $css_asset['browsers']]; Chris@0: }; Chris@0: Chris@0: // Loop through all CSS assets, by key, to allow for the special IE Chris@0: // workaround. Chris@0: $css_assets_keys = array_keys($css_assets); Chris@0: for ($i = 0; $i < count($css_assets_keys); $i++) { Chris@0: $css_asset = $css_assets[$css_assets_keys[$i]]; Chris@0: switch ($css_asset['type']) { Chris@0: // For file items, there are three possibilities. Chris@0: // - There are up to 31 CSS assets on the page (some of which may be Chris@0: // aggregated). In this case, output a LINK tag for file CSS assets. Chris@0: // - There are more than 31 CSS assets on the page, yet we must stay Chris@0: // below IE<10's limit of 31 total CSS inclusion tags, we handle this Chris@0: // in two ways: Chris@0: // - file CSS assets that are not eligible for aggregation (their Chris@0: // 'preprocess' flag has been set to FALSE): in this case, output a Chris@0: // LINK tag. Chris@0: // - file CSS assets that can be aggregated (and possibly have been): Chris@0: // in this case, figure out which subsequent file CSS assets share Chris@0: // the same key properties ('group', 'media' and 'browsers') and Chris@0: // output this group into as few STYLE tags as possible (a STYLE Chris@0: // tag may contain only 31 @import statements). Chris@0: case 'file': Chris@0: // The dummy query string needs to be added to the URL to control Chris@0: // browser-caching. Chris@0: $query_string_separator = (strpos($css_asset['data'], '?') !== FALSE) ? '&' : '?'; Chris@0: Chris@0: // As long as the current page will not run into IE's limit for CSS Chris@0: // assets: output a LINK tag for a file CSS asset. Chris@0: if (count($css_assets) <= 31) { Chris@0: $element = $link_element_defaults; Chris@0: $element['#attributes']['href'] = file_url_transform_relative(file_create_url($css_asset['data'])) . $query_string_separator . $query_string; Chris@0: $element['#attributes']['media'] = $css_asset['media']; Chris@0: $element['#browsers'] = $css_asset['browsers']; Chris@0: $elements[] = $element; Chris@0: } Chris@0: // The current page will run into IE's limits for CSS assets: work Chris@0: // around these limits by performing a light form of grouping. Chris@0: // Once Drupal only needs to support IE10 and later, we can drop this. Chris@0: else { Chris@0: // The file CSS asset is ineligible for aggregation: output it in a Chris@0: // LINK tag. Chris@0: if (!$css_asset['preprocess']) { Chris@0: $element = $link_element_defaults; Chris@0: $element['#attributes']['href'] = file_url_transform_relative(file_create_url($css_asset['data'])) . $query_string_separator . $query_string; Chris@0: $element['#attributes']['media'] = $css_asset['media']; Chris@0: $element['#browsers'] = $css_asset['browsers']; Chris@0: $elements[] = $element; Chris@0: } Chris@0: // The file CSS asset can be aggregated, but hasn't been: combine Chris@0: // multiple items into as few STYLE tags as possible. Chris@0: else { Chris@0: $import = []; Chris@0: // Start with the current CSS asset, iterate over subsequent CSS Chris@0: // assets and find which ones have the same 'type', 'group', Chris@0: // 'preprocess', 'media' and 'browsers' properties. Chris@0: $j = $i; Chris@0: $next_css_asset = $css_asset; Chris@0: $current_ie_group_key = $get_ie_group_key($css_asset); Chris@0: do { Chris@0: // The dummy query string needs to be added to the URL to Chris@0: // control browser-caching. IE7 does not support a media type on Chris@0: // the @import statement, so we instead specify the media for Chris@0: // the group on the STYLE tag. Chris@0: $import[] = '@import url("' . Html::escape(file_url_transform_relative(file_create_url($next_css_asset['data'])) . '?' . $query_string) . '");'; Chris@0: // Move the outer for loop skip the next item, since we Chris@0: // processed it here. Chris@0: $i = $j; Chris@0: // Retrieve next CSS asset, unless there is none: then break. Chris@0: if ($j + 1 < count($css_assets_keys)) { Chris@0: $j++; Chris@0: $next_css_asset = $css_assets[$css_assets_keys[$j]]; Chris@0: } Chris@0: else { Chris@0: break; Chris@0: } Chris@0: } while ($get_ie_group_key($next_css_asset) == $current_ie_group_key); Chris@0: Chris@0: // In addition to IE's limit of 31 total CSS inclusion tags, it Chris@0: // also has a limit of 31 @import statements per STYLE tag. Chris@0: while (!empty($import)) { Chris@0: $import_batch = array_slice($import, 0, 31); Chris@0: $import = array_slice($import, 31); Chris@0: $element = $style_element_defaults; Chris@0: // This simplifies the JavaScript regex, allowing each line Chris@0: // (separated by \n) to be treated as a completely different Chris@0: // string. This means that we can use ^ and $ on one line at a Chris@0: // time, and not worry about style tags since they'll never Chris@0: // match the regex. Chris@0: $element['#value'] = "\n" . implode("\n", $import_batch) . "\n"; Chris@0: $element['#attributes']['media'] = $css_asset['media']; Chris@0: $element['#browsers'] = $css_asset['browsers']; Chris@0: $elements[] = $element; Chris@0: } Chris@0: } Chris@0: } Chris@0: break; Chris@0: Chris@0: // Output a LINK tag for an external CSS asset. The asset's 'data' Chris@0: // property contains the full URL. Chris@0: case 'external': Chris@0: $element = $link_element_defaults; Chris@0: $element['#attributes']['href'] = $css_asset['data']; Chris@0: $element['#attributes']['media'] = $css_asset['media']; Chris@0: $element['#browsers'] = $css_asset['browsers']; Chris@0: $elements[] = $element; Chris@0: break; Chris@0: Chris@0: default: Chris@0: throw new \Exception('Invalid CSS asset type.'); Chris@0: } Chris@0: } Chris@0: Chris@0: return $elements; Chris@0: } Chris@0: Chris@0: }