Commit da8ea3bf authored by alexpott's avatar alexpott

Issue #2346937 by dawehner, larowlan, Wim Leers, claudiu.cristea, msonnabaum:...

Issue #2346937 by dawehner, larowlan, Wim Leers, claudiu.cristea, msonnabaum: Implement a Renderer service; reduces drupal_render / _theme service container calls
parent 040dc5ff
......@@ -667,7 +667,7 @@ services:
- { name: event_subscriber }
main_content_renderer.html:
class: Drupal\Core\Render\MainContent\HtmlRenderer
arguments: ['@title_resolver', '@plugin.manager.display_variant', '@event_dispatcher', '@module_handler']
arguments: ['@title_resolver', '@plugin.manager.display_variant', '@event_dispatcher', '@module_handler', '@renderer']
tags:
- { name: render.main_content_renderer, format: html }
main_content_renderer.ajax:
......@@ -700,6 +700,7 @@ services:
arguments: ['@content_negotiation', '@title_resolver']
bare_html_page_renderer:
class: Drupal\Core\Render\BareHtmlPageRenderer
arguments: ['@renderer']
private_key:
class: Drupal\Core\PrivateKey
arguments: ['@state']
......@@ -994,7 +995,7 @@ services:
class: Zend\Feed\Writer\Extension\WellFormedWeb\Renderer\Entry
theme.manager:
class: Drupal\Core\Theme\ThemeManager
arguments: ['@theme.registry', '@theme.negotiator', '@theme.initialization', '@request_stack']
arguments: ['@app.root', '@theme.registry', '@theme.negotiator', '@theme.initialization', '@request_stack', '@module_handler']
theme.initialization:
class: Drupal\Core\Theme\ThemeInitialization
arguments: ['@app.root', '@theme_handler', '@state']
......@@ -1097,3 +1098,6 @@ services:
arguments: ['@module_handler']
tags:
- { name: mime_type_guesser }
renderer:
class: Drupal\Core\Render\Renderer
arguments: ['@controller_resolver', '@theme.manager', '@plugin.manager.element_info']
......@@ -22,7 +22,6 @@
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;
......@@ -2365,509 +2364,25 @@ function drupal_pre_render_links($element) {
/**
* Renders final HTML given a structured array tree.
*
* Calls drupal_render() in such a way that #post_render_cache callbacks are
* applied.
*
* Should therefore only be used in occasions where the final rendering is
* happening, just before sending a Response:
* - system internals that are responsible for rendering the final HTML
* - render arrays for non-HTML responses, such as feeds
*
* @param array $elements
* The structured array describing the data to be rendered.
*
* @return string
* The rendered HTML.
* @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0. Use the
* 'renderer' service instead.
*
* @see drupal_render()
* @see \Drupal\Core\Render\RendererInterface::renderRoot()
*/
function drupal_render_root(&$elements) {
return drupal_render($elements, TRUE);
return \Drupal::service('renderer')->renderRoot($elements);
}
/**
* Renders HTML given a structured array tree.
*
* Renderable arrays have two kinds of key/value pairs: properties and children.
* Properties have keys starting with '#' and their values influence how the
* array will be rendered. Children are all elements whose keys do not start
* with a '#'. Their values should be renderable arrays themselves, which will
* be rendered during the rendering of the parent array. The markup provided by
* 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
* the following steps:
* - 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
* associative array with one or several of the following keys:
* - 'keys': An array of one or more keys that identify the element. If
* 'keys' is set, the cache ID is created automatically from these keys.
* Cache keys may either be static (just strings) or tokens (placeholders
* that are converted to static keys by the @cache_contexts service,
* depending on the request). See drupal_render_cid_create().
* - 'cid': Specify the cache ID directly. Either 'keys' or 'cid' is
* required. If 'cid' is set, 'keys' is ignored. Use only if you have
* special requirements.
* - '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(),
* are merged into the array. #defaults_loaded is set by functions that
* process render arrays and call element_info() before passing the array to
* drupal_render(), such as \Drupal::formBuilder()->doBuildForm() in the
* Form API.
* - If this element has an array of #pre_render functions defined, they are
* 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
* database query, set $elements['#sorted'] = TRUE to avoid sorting them a
* second time.
* - The main render phase to produce #children for this element takes place:
* - If this element has #theme defined and #theme is an implemented theme
* hook/suggestion then _theme() is called and must render both the element
* and its children. If #render_children is set, _theme() will not be
* called. #render_children is usually only set internally by _theme() so
* that we can avoid the situation where drupal_render() called from
* within a theme preprocess function creates an infinite loop.
* - If this element does not have a defined #theme, or the defined #theme
* hook is not implemented, or #render_children is set, then
* drupal_render() is called recursively on each of the child elements of
* this element, and the result of each is concatenated onto #children.
* This is skipped if #children is not empty at this point.
* - Once #children has been rendered for this element, if #theme is not
* implemented and #markup is set for this element, #markup will be
* prepended to #children.
* - If this element has #states defined then JavaScript state information is
* added to this element's #attached attribute by drupal_process_states().
* - If this element has #attached defined then any required libraries,
* JavaScript, CSS, or other custom data are added to the current page by
* drupal_process_attached().
* - If this element has an array of #theme_wrappers defined and
* #render_children is not set, #children is then re-rendered by passing the
* element in its current state to _theme() successively for each item in
* #theme_wrappers. Since #theme and #theme_wrappers hooks often define
* variables with the same names it is possible to explicitly override each
* attribute passed to each #theme_wrappers hook by setting the hook name as
* the key and an array of overrides as the value in #theme_wrappers array.
* For example, if we have a render element as follows:
* @code
* array(
* '#theme' => 'image',
* '#attributes' => array('class' => array('foo')),
* '#theme_wrappers' => array('container'),
* );
* @endcode
* and we need to pass the class 'bar' as an attribute for 'container', we
* can rewrite our element thus:
* @code
* array(
* '#theme' => 'image',
* '#attributes' => array('class' => array('foo')),
* '#theme_wrappers' => array(
* 'container' => array(
* '#attributes' => array('class' => array('bar')),
* ),
* ),
* );
* @endcode
* - If this element has an array of #post_render functions defined, they are
* called sequentially to modify the rendered #children. Unlike #pre_render
* functions, #post_render functions are passed both the rendered #children
* 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 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
* output.
*
* @param array $elements
* The structured array describing the data to be rendered.
* @param bool $is_root_call
* (Internal use only.) Whether this is a recursive call or not. See
* drupal_render_root().
*
* @return string
* The rendered HTML.
* @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0. Use the
* 'renderer' service instead.
*
* @throws \LogicException
* If a root call to drupal_render() does not result in an empty stack, this
* indicates an erroneous drupal_render() root call (a root call within a root
* call, which makes no sense). Therefore, a logic exception is thrown.
* @throws \Exception
* If a #pre_render callback throws an exception, it is caught to reset the
* stack used for bubbling rendering metadata, and then the exception is re-
* thrown.
*
* @see element_info()
* @see _theme()
* @see drupal_process_states()
* @see drupal_process_attached()
* @see drupal_render_root()
*/
function drupal_render(&$elements, $is_root_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'] = Cache::mergeTags($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. Reset the stack for the next root call.
if ($stack->count() === 1) {
$stack = NULL;
return;
}
// Merge the current and the parent stack frame.
$current = $stack->pop();
$parent = $stack->pop();
$current->tags = Cache::mergeTags($current->tags, $parent->tags);
$current->attached = drupal_merge_attached($current->attached, $parent->attached);
$current->postRenderCache = NestedArray::mergeDeep($current->postRenderCache, $parent->postRenderCache);
$stack->push($current);
};
/** @var \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver */
$controller_resolver = \Drupal::service('controller_resolver');
if (!isset($elements['#access']) && isset($elements['#access_callback'])) {
if (is_string($elements['#access_callback']) && strpos($elements['#access_callback'], '::') === FALSE) {
$elements['#access_callback'] = $controller_resolver->getControllerFromDefinition($elements['#access_callback']);
}
$elements['#access'] = call_user_func($elements['#access_callback'], $elements);
}
// Early-return nothing if user does not have access.
if (empty($elements) || (isset($elements['#access']) && !$elements['#access'])) {
return '';
}
// Do not print elements twice.
if (!empty($elements['#printed'])) {
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'])) {
$cached_element = drupal_render_cache_get($elements);
if ($cached_element !== FALSE) {
$elements = $cached_element;
// Only when we're not in a root (non-recursive) drupal_render() call,
// #post_render_cache callbacks must be executed, to prevent breaking the
// render cache in case of nested elements with #cache set.
if ($is_root_call) {
_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'];
}
}
// If the default values for this element have not been loaded yet, populate
// them.
if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {
$elements += element_info($elements['#type']);
}
// Make any final changes to the element before it is rendered. This means
// that the $element or the children can be altered or corrected before the
// element is rendered into the final text.
if (isset($elements['#pre_render'])) {
foreach ($elements['#pre_render'] as $callable) {
if (is_string($callable) && strpos($callable, '::') === FALSE) {
$callable = $controller_resolver->getControllerFromDefinition($callable);
}
// Since #pre_render callbacks may be used for generating a render array's
// content, and we might be rendering the main content for the page, it is
// possible that a #pre_render callback throws an exception that will
// cause a different page to be rendered (e.g. throwing
// \Symfony\Component\HttpKernel\Exception\NotFoundHttpException will
// cause the 404 page to be rendered). That page might also use
// drupal_render(), but if exceptions aren't caught here, the stack will
// be left in an inconsistent state.
// Hence, catch all exceptions and reset the stack and re-throw them.
try {
$elements = call_user_func($callable, $elements);
}
catch (\Exception $e) {
// Reset stack and re-throw exception.
$stack = NULL;
throw $e;
}
}
}
// 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 '';
}
// Add any JavaScript state information associated with the element.
if (!empty($elements['#states'])) {
drupal_process_states($elements);
}
// Get the children of the element, sorted by weight.
$children = Element::children($elements, TRUE);
// Initialize this element's #children, unless a #pre_render callback already
// preset #children.
if (!isset($elements['#children'])) {
$elements['#children'] = '';
}
// @todo Simplify after https://drupal.org/node/2273925
if (isset($elements['#markup'])) {
$elements['#markup'] = SafeMarkup::set($elements['#markup']);
}
// Assume that if #theme is set it represents an implemented hook.
$theme_is_implemented = isset($elements['#theme']);
// Check the elements for insecure HTML and pass through sanitization.
if (isset($elements)) {
$markup_keys = array(
'#description',
'#field_prefix',
'#field_suffix',
);
foreach ($markup_keys as $key) {
if (!empty($elements[$key]) && is_scalar($elements[$key])) {
$elements[$key] = SafeMarkup::checkAdminXss($elements[$key]);
}
}
}
// Call the element's #theme function if it is set. Then any children of the
// element have to be rendered there. If the internal #render_children
// property is set, do not call the #theme function to prevent infinite
// recursion.
if ($theme_is_implemented && !isset($elements['#render_children'])) {
$elements['#children'] = \Drupal::theme()->render($elements['#theme'], $elements);
// If _theme() returns FALSE this means that the hook in #theme was not
// found in the registry and so we need to update our flag accordingly. This
// is common for theme suggestions.
$theme_is_implemented = ($elements['#children'] !== FALSE);
}
// If #theme is not implemented or #render_children is set and the element has
// an empty #children attribute, render the children now. This is the same
// process as drupal_render_children() but is inlined for speed.
if ((!$theme_is_implemented || isset($elements['#render_children'])) && empty($elements['#children'])) {
foreach ($children as $key) {
$elements['#children'] .= drupal_render($elements[$key]);
}
$elements['#children'] = SafeMarkup::set($elements['#children']);
}
// If #theme is not implemented and the element has raw #markup as a
// fallback, prepend the content in #markup to #children. In this case
// #children will contain whatever is provided by #pre_render prepended to
// what is rendered recursively above. If #theme is implemented then it is
// the responsibility of that theme implementation to render #markup if
// required. Eventually #theme_wrappers will expect both #markup and
// #children to be a single string as #children.
if (!$theme_is_implemented && isset($elements['#markup'])) {
$elements['#children'] = SafeMarkup::set($elements['#markup'] . $elements['#children']);
}
// Let the theme functions in #theme_wrappers add markup around the rendered
// children.
// #states and #attached have to be processed before #theme_wrappers, because
// the #type 'page' render array from drupal_prepare_page() would render the
// $page and wrap it into the html.html.twig template without the attached
// assets otherwise.
// If the internal #render_children property is set, do not call the
// #theme_wrappers function(s) to prevent infinite recursion.
if (isset($elements['#theme_wrappers']) && !isset($elements['#render_children'])) {
foreach ($elements['#theme_wrappers'] as $key => $value) {
// If the value of a #theme_wrappers item is an array then the theme hook
// is found in the key of the item and the value contains attribute
// overrides. Attribute overrides replace key/value pairs in $elements for
// only this _theme() call. This allows #theme hooks and #theme_wrappers
// hooks to share variable names without conflict or ambiguity.
$wrapper_elements = $elements;
if (is_string($key)) {
$wrapper_hook = $key;
foreach ($value as $attribute => $override) {
$wrapper_elements[$attribute] = $override;
}
}
else {
$wrapper_hook = $value;
}
$elements['#children'] = \Drupal::theme()->render($wrapper_hook, $wrapper_elements);
}
}
// Filter the outputted content and make any last changes before the
// content is sent to the browser. The changes are made on $content
// which allows the outputted text to be filtered.
if (isset($elements['#post_render'])) {
foreach ($elements['#post_render'] as $callable) {
if (is_string($callable) && strpos($callable, '::') === FALSE) {
$callable = $controller_resolver->getControllerFromDefinition($callable);
}
$elements['#children'] = call_user_func($callable, $elements['#children'], $elements);
}
}
// We store the resulting output in $elements['#markup'], to be consistent
// with how render cached output gets stored. This ensures that
// #post_render_cache callbacks get the same data to work with, no matter if
// #cache is disabled, #cache is enabled, there is a cache hit or miss.
$prefix = isset($elements['#prefix']) ? SafeMarkup::checkAdminXss($elements['#prefix']) : '';
$suffix = isset($elements['#suffix']) ? SafeMarkup::checkAdminXss($elements['#suffix']) : '';
$elements['#markup'] = $prefix . $elements['#children'] . $suffix;
// 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'])) {
drupal_render_cache_set($elements['#markup'], $elements);
}
// Only when we're in a root (non-recursive) drupal_render() call,
// #post_render_cache callbacks must be executed, to prevent breaking the
// render cache in case of nested elements with #cache set.
//
// By running them here, we ensure that:
// - they run when #cache is disabled,
// - they run when #cache is enabled and there is a cache miss.
// Only the case of a cache hit when #cache is enabled, is not handled here,
// that is handled earlier in drupal_render().
if ($is_root_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'] = Cache::mergeTags($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);
if ($stack->count() !== 1) {
throw new \LogicException('A stray drupal_render() invocation with $is_root_call = TRUE is causing bubbling of attached assets to break.');
}
}
// Rendering is finished, all necessary info collected!
$bubble_stack();
$elements['#printed'] = TRUE;
$elements['#markup'] = SafeMarkup::set($elements['#markup']);
return $elements['#markup'];
* @see \Drupal\Core\Render\RendererInterface::render()
*/
function drupal_render(&$elements, $is_recursive_call = FALSE) {
return \Drupal::service('renderer')->render($elements, $is_recursive_call);
}
/**
......@@ -3105,43 +2620,6 @@ function drupal_render_cache_generate_placeholder($callback, array &$context) {
return '<drupal-render-cache-placeholder callback="' . $callback . '" token="' . $context['token'] . '"></drupal-render-cache-placeholder>';
}
/**
* Processes #post_render_cache callbacks.
*
* #post_render_cache callbacks may modify:
* - #markup: to replace placeholders
* - #attached: to add libraries or JavaScript settings
*
* Note that in either of these cases, #post_render_cache callbacks are
* implicitly idempotent: a placeholder that has been replaced can't be replaced
* again, and duplicate attachments are ignored.
*
* @param array &$elements
* The structured array describing the data being rendered.
*
* @see drupal_render()
* @see drupal_render_collect_post_render_cache
*/
function _drupal_render_process_post_render_cache(array &$elements) {
if (isset($elements['#post_render_cache'])) {
/** @var \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver */
$controller_resolver = \Drupal::service('controller_resolver');
// Call all #post_render_cache callbacks, passing the provided context.
foreach (array_keys($elements['#post_render_cache']) as $callback) {
if (strpos($callback, '::') === FALSE) {
$callable = $controller_resolver->getControllerFromDefinition($callback);
}
else {
$callable = $callback;
}
foreach ($elements['#post_render_cache'][$callback] as $context) {
$elements = call_user_func_array($callable, array($elements, $context));
}
}
}
}
/**
* Creates the cache ID for a renderable element.
*
......
......@@ -156,307 +156,6 @@ function list_themes($refresh = FALSE) {
return $theme_handler->listInfo();
}
/**
* Generates themed output (internal use only).
*
* _theme() is an internal function. Do not call this function directly as it
* will prevent the following items from working correctly:
* - Render caching.
* - JavaScript and CSS asset attachment.
* - Pre / post render hooks.
* - Defaults provided by hook_element_info(), including attached assets.
* Instead, build a render array with a #theme key, and either return the
* array (where possible) or call drupal_render() to convert it to HTML.
*
* All requests for themed output must go through this function, which is
* invoked as part of the @link theme_render drupal_render() process @endlink.
* The appropriate theme function is indicated by the #theme property
* of a renderable array. _theme() examines the request and routes it to the
* appropriate @link themeable theme function or template @endlink, by checking
* the theme registry.
*
* @param $hook
* The name of the theme hook to call. If the name contains a
* double-underscore ('__') and there isn't an implementation for the full
* name, the part before the '__' is checked. This allows a fallback to a
* more generic implementation. For example, if _theme('links__node', ...) is
* called, but there is no implementation of that theme hook, then the
* 'links' implementation is used. This process is iterative, so if
* _theme('links__contextual__node', ...) is called, _theme() checks for the
* following implementations, and uses the first one that exists:
* - links__contextual__node
* - links__contextual
* - links
* This allows themes to create specific theme implementations for named
* objects and contexts of otherwise generic theme hooks. The $hook parameter
* may also be an array, in which case the first theme hook that has an
* implementation is used. This allows for the code that calls _theme() to
* explicitly specify the fallback order in a situation where using the '__'
* convention is not desired or is insufficient.
* @param $variables
* An associative array of variables to merge with defaults from the theme
* registry, pass to preprocess functions for modification, and finally, pass
* to the function or template implementing the theme hook. Alternatively,
* this can be a renderable array, in which case, its properties are mapped to
* variables expected by the theme hook implementations.
*
* @return string|false
* An HTML string representing the themed output or FALSE if the passed $hook
* is not implemented.
*
* @see drupal_render()
* @see themeable
* @see hook_theme()
* @see template_preprocess()
*/
function _theme($hook, $variables = array()) {
static $default_attributes;
$module_handler = \Drupal::moduleHandler();
$active_theme = \Drupal::theme()->getActiveTheme();
// If called before all modules are loaded, we do not necessarily have a full
// theme registry to work with, and therefore cannot process the theme
// request properly. See also \Drupal\Core\Theme\Registry::get().
if (!$module_handler->isLoaded() && !defined('MAINTENANCE_MODE')) {
throw new Exception(t('_theme() may not be called until all modules are loaded.'));
}
/** @var \Drupal\Core\Utility\ThemeRegistry $theme_registry */
$theme_registry = \Drupal::service('theme.registry')->getRuntime();
// If an array of hook candidates were passed, use the first one that has an
// implementation.
if (is_array($hook)) {
foreach ($hook as $candidate) {
if ($theme_registry->has($candidate)) {
break;
}
}
$hook = $candidate;
}
// Save the original theme hook, so it can be supplied to theme variable
// preprocess callbacks.
$original_hook = $hook;
// If there's no implementation, check for more generic fallbacks. If there's
// still no implementation, log an error and return an empty string.
if (!$theme_registry->has($hook)) {
// Iteratively strip everything after the last '__' delimiter, until an
// implementation is found.
while ($pos = strrpos($hook, '__')) {
$hook = substr($hook, 0, $pos);
if ($theme_registry->has($hook)) {
break;
}
}
if (!$theme_registry->has($hook)) {
// Only log a message when not trying theme suggestions ($hook being an
// array).
if (!isset($candidate)) {
\Drupal::logger('theme')->warning('Theme hook %hook not found.', array('%hook' => $hook));
}
// There is no theme implementation for the hook passed. Return FALSE so
// the function calling _theme() can differentiate between a hook that
// exists and renders an empty string and a hook that is not implemented.
return FALSE;
}
}
$info = $theme_registry->get($hook);
// If a renderable array is passed as $variables, then set $variables to
// the arguments expected by the theme function.
if (isset($variables['#theme']) || isset($variables['#theme_wrappers'])) {
$element = $variables;
$variables = array();
if (isset($info['variables'])) {
foreach (array_keys($info['variables']) as $name) {
if (isset($element["#$name"]) || array_key_exists("#$name", $element)) {
$variables[$name] = $element["#$name"];
}
}
}