Commit 8c684918 authored by webchick's avatar webchick

Issue #2273277 by Wim Leers, effulgentsia, Fabianx: Fixed Figure out a...

Issue #2273277 by Wim Leers, effulgentsia, Fabianx: Fixed Figure out a solution for the problematic interaction between the render system and the theme system when using #pre_render.
parent dcc880bc
......@@ -22,6 +22,7 @@
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\RenderStackFrame;
use Drupal\Core\Site\Settings;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
......@@ -1169,20 +1170,6 @@ function _drupal_add_css($data = NULL, $options = NULL) {
function drupal_get_css($css = NULL, $skip_alter = FALSE, $theme_add_css = TRUE) {
$theme_info = \Drupal::theme()->getActiveTheme();
// @todo There is probably a better place to add the CSS from themes,
// see https://www.drupal.org/node/2322617.
if ($theme_add_css) {
foreach ($theme_info->getStyleSheets() as $media => $stylesheets) {
foreach ($stylesheets as $stylesheet) {
_drupal_add_css($stylesheet, array(
'group' => CSS_AGGREGATE_THEME,
'every_page' => TRUE,
'media' => $media
));
}
}
}
if (!isset($css)) {
$css = _drupal_add_css();
}
......@@ -1685,16 +1672,6 @@ function drupal_js_defaults($data = NULL) {
* @see drupal_js_defaults()
*/
function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALSE, $is_ajax = FALSE, $theme_add_js = TRUE) {
// @todo There is probably a better place to add the JS from themes,
// see https://www.drupal.org/node/2322617.
$active_theme = \Drupal::theme()->getActiveTheme();
if ($theme_add_js) {
// Add libraries used by this theme.
foreach ($active_theme->getLibraries() as $library) {
_drupal_add_library($library);
}
}
if (!isset($javascript)) {
$javascript = _drupal_add_js();
}
......@@ -1722,7 +1699,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
// Don't add settings if there is no other JavaScript on the page, unless
// this is an AJAX request.
if (!empty($items['settings']) || $is_ajax) {
$theme_key = $active_theme->getName();
$theme_key = \Drupal::theme()->getActiveTheme()->getName();
// Provide the page with information about the theme that's used, so that
// a later AJAX request can be rendered using the same theme.
// @see \Drupal\Core\Theme\AjaxBasePageNegotiator
......@@ -1847,6 +1824,41 @@ function drupal_merge_attached(array $a, array $b) {
return NestedArray::mergeDeep($a, $b);
}
/**
* Merges sets of cache tags.
*
* The cache tags array is returned in a format that is valid for
* \Drupal\Core\Cache\CacheBackendInterface::set().
*
* When caching elements, it is necessary to collect all cache tags into a
* single array, from both the element itself and all child elements. This
* allows items to be invalidated based on all tags attached to the content
* they're constituted from.
*
* @param array $tags
* The first set of cache tags.
* @param array $other
* The other set of cache tags.
*
* @return array
* The merged set of cache tags.
*/
function drupal_merge_cache_tags(array $tags, array $other) {
foreach ($other as $namespace => $values) {
if (is_array($values)) {
foreach ($values as $value) {
$tags[$namespace][$value] = $value;
}
}
else {
if (!isset($tags[$namespace])) {
$tags[$namespace] = $values;
}
}
}
return $tags;
}
/**
* Adds attachments to a render() structure.
*
......@@ -2593,6 +2605,14 @@ function drupal_render_page($page) {
* the children is typically inserted into the markup generated by the parent
* array.
*
* An important aspect of rendering is the bubbling of rendering metadata: cache
* tags, attached assets and #post_render_cache metadata all need to be bubbled
* up. That information is needed once the rendering to a HTML string is
* completed: the resulting HTML for the page must know by which cache tags it
* should be invalidated, which (CSS and JavaScript) assets must be loaded, and
* which #post_render_cache callbacks should be executed. A stack data structure
* is used to perform this bubbling.
*
* The process of rendering an element is recursive unless the element defines
* an implemented theme hook in #theme. During each call to drupal_render(), the
* outermost renderable array (also known as an "element") is processed using
......@@ -2600,6 +2620,8 @@ function drupal_render_page($page) {
* - If this element has already been printed (#printed = TRUE) or the user
* does not have access to it (#access = FALSE), then an empty string is
* returned.
* - If no stack data structure has been created yet, it is done now. Next,
* an empty \Drupal\Core\Render\RenderStackFrame is pushed onto the stack.
* - If this element has #cache defined then the cached markup for this
* element will be returned if it exists in drupal_render()'s cache. To use
* drupal_render() caching, set the element's #cache property to an
......@@ -2615,6 +2637,12 @@ function drupal_render_page($page) {
* - 'expire': Set to one of the cache lifetime constants.
* - 'bin': Specify a cache bin to cache the element in. Default is
* 'default'.
* When there is a render cache hit, there is no rendering work left to be
* done, so the stack must be updated. The empty (and topmost) frame that
* was just pushed onto the stack is updated with all bubbleable rendering
* metadata from the element retrieved from render cache. Then, this stack
* frame is bubbled: the two topmost frames are popped from the stack, they
* are merged, and the result is pushed back onto the stack.
* - If this element has #type defined and the default attributes for this
* element have not already been merged in (#defaults_loaded = TRUE) then
* the defaults for this type of element, defined in hook_element_info(),
......@@ -2625,6 +2653,12 @@ function drupal_render_page($page) {
* called sequentially to modify the element before rendering. After all the
* #pre_render functions have been called, #printed is checked a second time
* in case a #pre_render function flags the element as printed.
* If #printed is set, we return early and hence no rendering work is left
* to be done, similarly to a render cache hit. Once again, the empty (and
* topmost) frame that was just pushed onto the stack is updated with all
* bubbleable rendering metadata from the element whose #printed = TRUE.
* Then, this stack frame is bubbled: the two topmost frames are popped from
* the stack, they are merged, and the result is pushed back onto the stack.
* - The child elements of this element are sorted by weight using uasort() in
* \Drupal\Core\Render\Element::children(). Since this is expensive, when
* passing already sorted elements to drupal_render(), for example from a
......@@ -2684,20 +2718,50 @@ function drupal_render_page($page) {
* attribute as a string and the element itself.
* - If this element has #prefix and/or #suffix defined, they are concatenated
* to #children.
* - The rendering of this element is now complete. The next step will be
* render caching. So this is the perfect time to update the the stack. At
* this point, children of this element (if any), have been rendered also,
* and if there were any, their bubbleable rendering metadata will have been
* bubbled up into the stack frame for the element that is currently being
* rendered. The render cache item for this element must contain the
* bubbleable rendering metadata for this element and all of its children.
* However, right now, the topmost stack frame (the one for this element)
* currently only contains the metadata for the children. Therefore, the
* topmost stack frame is updated with this element's metadata, and then the
* element's metadata is replaced with the metadata in the topmost stack
* frame. This element now contains all bubbleable rendering metadata for
* this element and all its children, so it's now ready for render caching.
* - If this element has #cache defined, the rendered output of this element
* is saved to drupal_render()'s internal cache. This includes the changes
* made by #post_render.
* - If this element (or any of its children) has an array of
* #post_render_cache functions defined, they are called sequentially to
* replace placeholders in the final #markup and extend #attached.
* Placeholders must contain a unique token, to guarantee that e.g. samples
* of placeholders are not replaced also. For this, a special element named
* 'render_cache_placeholder' is provided.
* - If this element has an array of #post_render_cache functions defined, or
* any of its children has (which we would know thanks to the stack having
* been updated just before the render caching step), they are called
* sequentially to replace placeholders in the final #markup and extend
* #attached. Placeholders must contain a unique token, to guarantee that
* e.g. samples of placeholders are not replaced also.
* But, since #post_render_cache callbacks add attach additional assets, the
* correct bubbling of those must once again be taken into account. This
* final stage of rendering should be considered as if it were the parent of
* the current element, because it takes that as its input, and then alters
* its #markup. Hence, just before calling the #post_render_cache callbacks,
* a new empty frame is pushed onto the stack, where all assets #attached
* during the execution of those callbacks will end up in. Then, after the
* execution of those callbacks, we merge that back into the element.
* Note that these callbacks run always: when hitting the render cache, when
* missing, or when render caching is not used at all. This is done to allow
* any Drupal module to customize other render arrays without breaking the
* render cache if it is enabled, and to not require it to use other logic
* when render caching is disabled.
* - Just before finishing the rendering of this element, this element's stack
* frame (the topmost one) is bubbled: the two topmost frames are popped
* from the stack, they are merged and the result is pushed back onto the
* stack.
* So if this element e.g. was a child element, then a new frame was pushed
* onto the stack element at the beginning of rendering this element, it was
* updated when the rendering was completed, and now we merge it with the
* frame for the parent, so that the parent now has the bubbleable rendering
* metadata for its child.
* - #printed is set to TRUE for this element to ensure that it is only
* rendered once.
* - The final value of #children for this element is returned as the rendered
......@@ -2717,6 +2781,35 @@ function drupal_render_page($page) {
* @see drupal_process_attached()
*/
function drupal_render(&$elements, $is_recursive_call = FALSE) {
static $stack;
$update_stack = function(&$element) use (&$stack) {
// The latest frame represents the bubbleable data for the subtree.
$frame = $stack->top();
// Update the frame, but also update the current element, to ensure it
// contains up-to-date information in case it gets render cached.
$frame->tags = $element['#cache']['tags'] = drupal_merge_cache_tags($element['#cache']['tags'], $frame->tags);
$frame->attached = $element['#attached'] = drupal_merge_attached($element['#attached'], $frame->attached);
$frame->postRenderCache = $element['#post_render_cache'] = NestedArray::mergeDeep($element['#post_render_cache'], $frame->postRenderCache);
};
$bubble_stack = function() use (&$stack) {
// If there's only one frame on the stack, then this is the root call, and
// we can't bubble up further.
if ($stack->count() === 1) {
$stack->pop();
return;
}
// Merge the current and the parent stack frame.
$current = $stack->pop();
$parent = $stack->pop();
$current->tags = drupal_merge_cache_tags($current->tags, $parent->tags);
$current->attached = drupal_merge_attached($current->attached, $parent->attached);
$current->postRenderCache = NestedArray::mergeDeep($current->postRenderCache, $parent->postRenderCache);
$stack->push($current);
};
// Early-return nothing if user does not have access.
if (empty($elements) || (isset($elements['#access']) && !$elements['#access'])) {
return '';
......@@ -2727,6 +2820,11 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
return '';
}
if (!isset($stack)) {
$stack = new \SplStack();
}
$stack->push(new RenderStackFrame());
// Try to fetch the prerendered element from cache, run any #post_render_cache
// callbacks and return the final markup.
if (isset($elements['#cache'])) {
......@@ -2740,6 +2838,11 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
_drupal_render_process_post_render_cache($elements);
}
$elements['#markup'] = SafeMarkup::set($elements['#markup']);
// The render cache item contains all the bubbleable rendering metadata for
// the subtree.
$update_stack($elements);
// Render cache hit, so rendering is finished, all necessary info collected!
$bubble_stack();
return $elements['#markup'];
}
}
......@@ -2767,8 +2870,18 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
}
}
// Defaults for bubbleable rendering metadata.
$elements['#cache']['tags'] = isset($elements['#cache']['tags']) ? $elements['#cache']['tags'] : array();
$elements['#attached'] = isset($elements['#attached']) ? $elements['#attached'] : array();
$elements['#post_render_cache'] = isset($elements['#post_render_cache']) ? $elements['#post_render_cache'] : array();
// Allow #pre_render to abort rendering.
if (!empty($elements['#printed'])) {
// The #printed element contains all the bubbleable rendering metadata for
// the subtree.
$update_stack($elements);
// #printed, so rendering is finished, all necessary info collected!
$bubble_stack();
return '';
}
......@@ -2777,12 +2890,6 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
drupal_process_states($elements);
}
// Add additional libraries, CSS, JavaScript and other custom
// attached data associated with this element.
if (!empty($elements['#attached'])) {
drupal_process_attached($elements);
}
// Get the children of the element, sorted by weight.
$children = Element::children($elements, TRUE);
......@@ -2872,9 +2979,6 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
if (is_string($callable) && strpos($callable, '::') === FALSE) {
$callable = $controller_resolver->getControllerFromDefinition($callable);
}
else {
$callable = $callable;
}
$elements['#children'] = call_user_func($callable, $elements['#children'], $elements);
}
}
......@@ -2887,20 +2991,8 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
$suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
$elements['#markup'] = $prefix . $elements['#children'] . $suffix;
// Collect all #post_render_cache callbacks associated with this element when:
// - about to store this element in the render cache, or when;
// - about to apply #post_render_cache callbacks.
if (!$is_recursive_call || isset($elements['#cache'])) {
$post_render_cache = drupal_render_collect_post_render_cache($elements);
if ($post_render_cache) {
$elements['#post_render_cache'] = $post_render_cache;
}
}
// Collect all cache tags. This allows the caller of drupal_render() to also
// access the complete list of cache tags.
if (!$is_recursive_call || isset($elements['#cache'])) {
$elements['#cache']['tags'] = drupal_render_collect_cache_tags($elements);
}
// We've rendered this element (and its subtree!), now update the stack.
$update_stack($elements);
// Cache the processed element if #cache is set.
if (isset($elements['#cache'])) {
......@@ -2917,9 +3009,23 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
// Only the case of a cache hit when #cache is enabled, is not handled here,
// that is handled earlier in drupal_render().
if (!$is_recursive_call) {
// We've already called $update_stack() earlier, which updated both the
// element and current stack frame. However,
// _drupal_render_process_post_render_cache() can both change the element
// further and create and render new child elements, so provide a fresh
// stack frame to collect those additions, merge them back to the element,
// and then update the current frame to match the modified element state.
$stack->push(new RenderStackFrame());
_drupal_render_process_post_render_cache($elements);
$post_render_additions = $stack->pop();
$elements['#cache']['tags'] = drupal_merge_cache_tags($elements['#cache']['tags'], $post_render_additions->tags);
$elements['#attached'] = drupal_merge_attached($elements['#attached'], $post_render_additions->attached);
$elements['#post_render_cache'] = NestedArray::mergeDeep($elements['#post_render_cache'], $post_render_additions->postRenderCache);
}
// Rendering is finished, all necessary info collected!
$bubble_stack();
$elements['#printed'] = TRUE;
$elements['#markup'] = SafeMarkup::set($elements['#markup']);
return $elements['#markup'];
......@@ -3064,11 +3170,6 @@ function drupal_render_cache_get(array $elements) {
if (!empty($cid) && $cache = \Drupal::cache($bin)->get($cid)) {
$cached_element = $cache->data;
// Add additional libraries, JavaScript, CSS and other data attached
// to this element.
if (isset($cached_element['#attached'])) {
drupal_process_attached($cached_element);
}
// Return the cached element.
return $cached_element;
}
......@@ -3103,10 +3204,7 @@ function drupal_render_cache_set(&$markup, array $elements) {
$data['#markup'] = $markup;
// Persist attached data associated with this element.
$attached = drupal_render_collect_attached($elements, TRUE);
if ($attached) {
$data['#attached'] = $attached;
}
$data['#attached'] = $elements['#attached'];
// Persist #post_render_cache callbacks associated with this element.
if (isset($elements['#post_render_cache'])) {
......@@ -3204,159 +3302,7 @@ function _drupal_render_process_post_render_cache(array &$elements) {
$elements = call_user_func_array($callable, array($elements, $context));
}
}
// Make sure that any attachments added in #post_render_cache callbacks are
// also executed.
if (isset($elements['#attached'])) {
drupal_process_attached($elements);
}
}
}
/**
* Collects #post_render_cache for an element and its children into a single
* array.
*
* When caching elements, it is necessary to collect all #post_render_cache
* callbacks into a single array, from both the element itself and all child
* elements. This allows drupal_render() to execute all of them when the element
* is retrieved from the render cache.
*
* Note: the theme system may render child elements directly (e.g. rendering a
* node causes its template to be rendered, which causes the node links to be
* drupal_render()ed). On top of that, the theme system transforms render arrays
* into HTML strings. These two facts combined imply that it is impossible for
* #post_render_cache callbacks to bubble up to the root of the render array.
* Therefore, drupal_render_collect_post_render_cache() must be called *before*
* #theme callbacks, so that it has a chance to examine the full render array.
* In short: in order to examine the full render array for #post_render_cache
* callbacks, it must use post-order tree traversal, whereas drupal_render()
* itself uses pre-order tree traversal.
*
* @param array &$elements
* The element to collect #post_render_cache callbacks for.
* @param array $callbacks
* Internal use only. The #post_render_callbacks array so far.
* @param bool $is_root_element
* Internal use only. Whether the element being processed is the root or not.
*
* @return
* The #post_render_cache array for this element and its descendants.
*
* @see drupal_render()
* @see _drupal_render_process_post_render_cache()
*/
function drupal_render_collect_post_render_cache(array &$elements, array $callbacks = array(), $is_root_element = TRUE) {
// Try to fetch the prerendered element from cache, to determine
// #post_render_cache callbacks for this element and all its children. If we
// don't do this, then the #post_render_cache tokens will be re-generated, but
// they would no longer match the tokens in the render cached markup, causing
// the render cache placeholder markup to be sent to the end user!
$retrieved_from_cache = FALSE;
if (!$is_root_element && isset($elements['#cache'])) {
$cached_element = drupal_render_cache_get($elements);
if ($cached_element !== FALSE && isset($cached_element['#post_render_cache'])) {
$elements['#post_render_cache'] = $cached_element['#post_render_cache'];
$retrieved_from_cache = TRUE;
}
}
// Collect all #post_render_cache callbacks for this element.
if (isset($elements['#post_render_cache'])) {
$callbacks = NestedArray::mergeDeep($callbacks, $elements['#post_render_cache']);
}
// Collect the #post_render_cache callbacks for all child elements, unless
// we've already collected them above by retrieving this element (and its
// children) from the render cache.
if (!$retrieved_from_cache && $children = Element::children($elements)) {
foreach ($children as $child) {
$callbacks = drupal_render_collect_post_render_cache($elements[$child], $callbacks, FALSE);
}
}
return $callbacks;
}
/**
* Collects #attached for an element and its children into a single array.
*
* When caching elements, it is necessary to collect all libraries, JavaScript
* and CSS into a single array, from both the element itself and all child
* elements. This allows drupal_render() to add these back to the page when the
* element is returned from cache.
*
* @param $elements
* The element to collect #attached from.
* @param $return
* Whether to return the attached elements and reset the internal static.
*
* @return
* The #attached array for this element and its descendants.
*/
function drupal_render_collect_attached($elements, $return = FALSE) {
$attached = &drupal_static(__FUNCTION__, array());
// Collect all #attached for this element.
if (isset($elements['#attached'])) {
$attached = drupal_merge_attached($attached, $elements['#attached']);
}
if ($children = Element::children($elements)) {
foreach ($children as $child) {
drupal_render_collect_attached($elements[$child]);
}
}
// If this was the first call to the function, return all attached elements
// and reset the static cache.
if ($return) {
$return = $attached;
$attached = array();
return $return;
}
}
/**
* Collects cache tags for an element and its children into a single array.
*
* The cache tags array is returned in a format that is valid for
* \Drupal\Core\Cache\CacheBackendInterface::set().
*
* When caching elements, it is necessary to collect all cache tags into a
* single array, from both the element itself and all child elements. This
* allows items to be invalidated based on all tags attached to the content
* they're constituted from.
*
* @param array $element
* The element to collect cache tags from.
* @param array $tags
* (optional) An array of already collected cache tags (i.e. from a parent
* element). Defaults to an empty array.
*
* @return array
* The cache tags array for this element and its descendants.
*/
function drupal_render_collect_cache_tags($element, $tags = array()) {
if (isset($element['#cache']['tags'])) {
foreach ($element['#cache']['tags'] as $namespace => $values) {
if (is_array($values)) {
foreach ($values as $value) {
$tags[$namespace][$value] = $value;
}
}
else {
if (!isset($tags[$namespace])) {
$tags[$namespace] = $values;
}
}
}
}
if ($children = Element::children($element)) {
foreach ($children as $child) {
$tags = drupal_render_collect_cache_tags($element[$child], $tags);
}
}
return $tags;
}
/**
......
......@@ -11,6 +11,7 @@
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\String;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\StorageException;
......@@ -1750,6 +1751,14 @@ function template_preprocess_html(&$variables) {
$page->addMetaElement($metatag);
}
}
// Add favicon.
if (theme_get_setting('features.favicon')) {
$url = UrlHelper::stripDangerousProtocols(theme_get_setting('favicon.url'));
$link = new LinkElement($url, 'shortcut icon', ['type' => theme_get_setting('favicon.mimetype')]);
$page->addLinkElement($link);
}
$variables['page_top'][] = array('#markup' => $page->getBodyTop());
$variables['page_bottom'][] = array('#markup' => $page->getBodyBottom());
}
......
......@@ -78,7 +78,9 @@ public function render($content) {
* @todo: Remove as part of https://drupal.org/node/2182149
*/
protected function drupalRender(&$elements, $is_recursive_call = FALSE) {
return drupal_render($elements, $is_recursive_call);
$output = drupal_render($elements, $is_recursive_call);
drupal_process_attached($elements);
return $output;
}
/**
......
......@@ -150,7 +150,7 @@ public function render() {
*/
protected function drupalAttachLibrary($name) {
$attached['#attached']['library'][] = $name;
drupal_render($attached);
drupal_process_attached($attached);
}
}
......@@ -93,6 +93,7 @@ public function dialog(Request $request, $_content, $modal = FALSE) {
}
$content = drupal_render($page_content);
drupal_process_attached($page_content);
$title = isset($page_content['#title']) ? $page_content['#title'] : $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT));
$response = new AjaxResponse();
// Fetch any modal options passed in from data-dialog-options.
......
......@@ -73,6 +73,9 @@ protected function createHtmlFragment($page_content, Request $request) {
}
$content = $this->drupalRender($page_content);
if (!empty($page_content)) {
drupal_process_attached($page_content);
}
$cache = !empty($page_content['#cache']['tags']) ? array('tags' => $page_content['#cache']['tags']) : array();
$fragment = new HtmlFragment($content, $cache);
......@@ -85,7 +88,7 @@ protected function createHtmlFragment($page_content, Request $request) {
}
// Add feed links from the page content.
$attached = drupal_render_collect_attached($page_content, TRUE);
$attached = isset($page_content['#attached']) ? $page_content['#attached'] : array();
if (!empty($attached['drupal_add_feed'])) {
foreach ($attached['drupal_add_feed'] as $feed) {
$feed_link = new FeedLinkElement($feed[1], $this->urlGenerator->generateFromPath($feed[0]));
......
......@@ -59,6 +59,14 @@ public function render(HtmlFragmentInterface $fragment, $status_code = 200) {
$page->setContent(drupal_render($page_array));
$page->setStatusCode($status_code);
drupal_process_attached($page_array);
if (isset($page_array['page_top'])) {
drupal_process_attached($page_array['page_top']);
}
if (isset($page_array['page_bottom'])) {
drupal_process_attached($page_array['page_bottom']);
}
if ($fragment instanceof CacheableInterface) {
// Collect cache tags for all the content in all the regions on the page.
$tags = $page_array['#cache']['tags'];
......@@ -102,6 +110,21 @@ public function preparePage(HtmlPage $page, &$page_array) {
$page->addLinkElement($link);
}
// Add libraries and CSS used by this theme.
$active_theme = \Drupal::theme()->getActiveTheme();
foreach ($active_theme->getLibraries() as $library) {
$page_array['#attached']['library'][] = $library;
}
foreach ($active_theme->getStyleSheets() as $media => $stylesheets) {
foreach ($stylesheets as $stylesheet) {
$page_array['#attached']['css'][$stylesheet] = array(
'group' => CSS_AGGREGATE_THEME,