Issue #2346937 by dawehner, larowlan, Wim Leers, claudiu.cristea, msonnabaum: Implement a Renderer service; reduces drupal_render / _theme service container calls
parent
040dc5ff27
commit
da8ea3bfaa
|
@ -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\Tags;
|
|||
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.
|
||||
* @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0. Use the
|
||||
* 'renderer' service instead.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @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.
|
||||
* @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0. Use the
|
||||
* 'renderer' service instead.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @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()
|
||||
* @see \Drupal\Core\Render\RendererInterface::render()
|
||||
*/
|
||||
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'];
|
||||
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"];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$variables[$info['render element']] = $element;
|
||||
// Give a hint to render engines to prevent infinite recursion.
|
||||
$variables[$info['render element']]['#render_children'] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Merge in argument defaults.
|
||||
if (!empty($info['variables'])) {
|
||||
$variables += $info['variables'];
|
||||
}
|
||||
elseif (!empty($info['render element'])) {
|
||||
$variables += array($info['render element'] => array());
|
||||
}
|
||||
// Supply original caller info.
|
||||
$variables += array(
|
||||
'theme_hook_original' => $original_hook,
|
||||
);
|
||||
|
||||
// Set base hook for later use. For example if '#theme' => 'node__article'
|
||||
// is called, we run hook_theme_suggestions_node_alter() rather than
|
||||
// hook_theme_suggestions_node__article_alter(), and also pass in the base
|
||||
// hook as the last parameter to the suggestions alter hooks.
|
||||
if (isset($info['base hook'])) {
|
||||
$base_theme_hook = $info['base hook'];
|
||||
}
|
||||
else {
|
||||
$base_theme_hook = $hook;
|
||||
}
|
||||
|
||||
// Invoke hook_theme_suggestions_HOOK().
|
||||
$suggestions = $module_handler->invokeAll('theme_suggestions_' . $base_theme_hook, array($variables));
|
||||
// If _theme() was invoked with a direct theme suggestion like
|
||||
// '#theme' => 'node__article', add it to the suggestions array before
|
||||
// invoking suggestion alter hooks.
|
||||
if (isset($info['base hook'])) {
|
||||
$suggestions[] = $hook;
|
||||
}
|
||||
|
||||
// Invoke hook_theme_suggestions_alter() and
|
||||
// hook_theme_suggestions_HOOK_alter().
|
||||
$hooks = array(
|
||||
'theme_suggestions',
|
||||
'theme_suggestions_' . $base_theme_hook,
|
||||
);
|
||||
$module_handler->alter($hooks, $suggestions, $variables, $base_theme_hook);
|
||||
\Drupal::theme()->alter($hooks, $suggestions, $variables, $base_theme_hook);
|
||||
|
||||
// Check if each suggestion exists in the theme registry, and if so,
|
||||
// use it instead of the hook that _theme() was called with. For example, a
|
||||
// function may call _theme('node', ...), but a module can add
|
||||
// 'node__article' as a suggestion via hook_theme_suggestions_HOOK_alter(),
|
||||
// enabling a theme to have an alternate template file for article nodes.
|
||||
foreach (array_reverse($suggestions) as $suggestion) {
|
||||
if ($theme_registry->has($suggestion)) {
|
||||
$info = $theme_registry->get($suggestion);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Include a file if the theme function or variable preprocessor is held
|
||||
// elsewhere.
|
||||
if (!empty($info['includes'])) {
|
||||
foreach ($info['includes'] as $include_file) {
|
||||
include_once \Drupal::root() . '/' . $include_file;
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke the variable preprocessors, if any.
|
||||
if (isset($info['base hook'])) {
|
||||
$base_hook = $info['base hook'];
|
||||
$base_hook_info = $theme_registry->get($base_hook);
|
||||
// Include files required by the base hook, since its variable preprocessors
|
||||
// might reside there.
|
||||
if (!empty($base_hook_info['includes'])) {
|
||||
foreach ($base_hook_info['includes'] as $include_file) {
|
||||
include_once \Drupal::root() . '/' . $include_file;
|
||||
}
|
||||
}
|
||||
// Replace the preprocess functions with those from the base hook.
|
||||
if (isset($base_hook_info['preprocess functions'])) {
|
||||
// Set a variable for the 'theme_hook_suggestion'. This is used to
|
||||
// maintain backwards compatibility with template engines.
|
||||
$theme_hook_suggestion = $hook;
|
||||
$info['preprocess functions'] = $base_hook_info['preprocess functions'];
|
||||
}
|
||||
}
|
||||
if (isset($info['preprocess functions'])) {
|
||||
foreach ($info['preprocess functions'] as $preprocessor_function) {
|
||||
if (function_exists($preprocessor_function)) {
|
||||
$preprocessor_function($variables, $hook, $info);
|
||||
}
|
||||
}
|
||||
// Allow theme preprocess functions to set $variables['#attached'] and use
|
||||
// it like the #attached property on render arrays. In Drupal 8, this is the
|
||||
// (only) officially supported method of attaching assets from preprocess
|
||||
// functions. Assets attached here should be associated with the template
|
||||
// that we're preprocessing variables for.
|
||||
if (isset($variables['#attached'])) {
|
||||
$preprocess_attached = ['#attached' => $variables['#attached']];
|
||||
drupal_render($preprocess_attached);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the output using either a function or a template.
|
||||
$output = '';
|
||||
if (isset($info['function'])) {
|
||||
if (function_exists($info['function'])) {
|
||||
$output = SafeMarkup::set($info['function']($variables));
|
||||
}
|
||||
}
|
||||
else {
|
||||
$render_function = 'twig_render_template';
|
||||
$extension = '.html.twig';
|
||||
|
||||
// The theme engine may use a different extension and a different renderer.
|
||||
$theme_engine = $active_theme->getEngine();
|
||||
if (isset($theme_engine)) {
|
||||
if ($info['type'] != 'module') {
|
||||
if (function_exists($theme_engine . '_render_template')) {
|
||||
$render_function = $theme_engine . '_render_template';
|
||||
}
|
||||
$extension_function = $theme_engine . '_extension';
|
||||
if (function_exists($extension_function)) {
|
||||
$extension = $extension_function();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In some cases, a template implementation may not have had
|
||||
// template_preprocess() run (for example, if the default implementation is
|
||||
// a function, but a template overrides that default implementation). In
|
||||
// these cases, a template should still be able to expect to have access to
|
||||
// the variables provided by template_preprocess(), so we add them here if
|
||||
// they don't already exist. We don't want the overhead of running
|
||||
// template_preprocess() twice, so we use the 'directory' variable to
|
||||
// determine if it has already run, which while not completely intuitive,
|
||||
// is reasonably safe, and allows us to save on the overhead of adding some
|
||||
// new variable to track that.
|
||||
if (!isset($variables['directory'])) {
|
||||
$default_template_variables = array();
|
||||
template_preprocess($default_template_variables, $hook, $info);
|
||||
$variables += $default_template_variables;
|
||||
}
|
||||
if (!isset($default_attributes)) {
|
||||
$default_attributes = new Attribute();
|
||||
}
|
||||
foreach (array('attributes', 'title_attributes', 'content_attributes') as $key) {
|
||||
if (isset($variables[$key]) && !($variables[$key] instanceof Attribute)) {
|
||||
if ($variables[$key]) {
|
||||
$variables[$key] = new Attribute($variables[$key]);
|
||||
}
|
||||
else {
|
||||
// Create empty attributes.
|
||||
$variables[$key] = clone $default_attributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render the output using the template file.
|
||||
$template_file = $info['template'] . $extension;
|
||||
if (isset($info['path'])) {
|
||||
$template_file = $info['path'] . '/' . $template_file;
|
||||
}
|
||||
// Add the theme suggestions to the variables array just before rendering
|
||||
// the template for backwards compatibility with template engines.
|
||||
$variables['theme_hook_suggestions'] = $suggestions;
|
||||
// For backwards compatibility, pass 'theme_hook_suggestion' on to the
|
||||
// template engine. This is only set when calling a direct suggestion like
|
||||
// '#theme' => 'menu__shortcut_default' when the template exists in the
|
||||
// current theme.
|
||||
if (isset($theme_hook_suggestion)) {
|
||||
$variables['theme_hook_suggestion'] = $theme_hook_suggestion;
|
||||
}
|
||||
$output = $render_function($template_file, $variables);
|
||||
}
|
||||
|
||||
return (string) $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows themes and/or theme engines to discover overridden theme functions.
|
||||
*
|
||||
|
|
|
@ -11,6 +11,7 @@ use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
|||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
|
@ -25,14 +26,24 @@ class EntityViewController implements ContainerInjectionInterface {
|
|||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* The renderer service.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Creates an EntityViewController object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer service.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $entity_manager) {
|
||||
public function __construct(EntityManagerInterface $entity_manager, RendererInterface $renderer) {
|
||||
$this->entityManager = $entity_manager;
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,7 +51,8 @@ class EntityViewController implements ContainerInjectionInterface {
|
|||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity.manager')
|
||||
$container->get('entity.manager'),
|
||||
$container->get('renderer')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -79,7 +91,7 @@ class EntityViewController implements ContainerInjectionInterface {
|
|||
$build = $this->entityManager->getTranslationFromContext($_entity)
|
||||
->get($label_field)
|
||||
->view($view_mode);
|
||||
$page['#title'] = drupal_render($build);
|
||||
$page['#title'] = $this->renderer->render($build);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,23 @@ namespace Drupal\Core\Render;
|
|||
*/
|
||||
class BareHtmlPageRenderer implements BareHtmlPageRendererInterface {
|
||||
|
||||
/**
|
||||
* The renderer service.
|
||||
*
|
||||
* @var \Drupal\Core\Render\Renderer
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Constructs a new BareHtmlPageRenderer.
|
||||
*
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer service.
|
||||
*/
|
||||
public function __construct(RendererInterface $renderer) {
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -36,12 +53,12 @@ class BareHtmlPageRenderer implements BareHtmlPageRendererInterface {
|
|||
// \Drupal\Core\Render\MainContent\HtmlRenderer::renderResponse() for more
|
||||
// information about this; the exact same pattern is used there and
|
||||
// explained in detail there.
|
||||
drupal_render_root($html['page']);
|
||||
$this->renderer->render($html['page'], TRUE);
|
||||
|
||||
// Add the bare minimum of attachments from the system module and the
|
||||
// current maintenance theme.
|
||||
system_page_attachments($html['page']);
|
||||
return drupal_render($html);
|
||||
return $this->renderer->render($html);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ use Drupal\Core\Controller\TitleResolverInterface;
|
|||
use Drupal\Core\Display\PageVariantInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Render\PageDisplayVariantSelectionEvent;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Render\RenderEvents;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
|
||||
|
@ -54,6 +55,13 @@ class HtmlRenderer implements MainContentRendererInterface {
|
|||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The renderer service.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Constructs a new HtmlRenderer.
|
||||
*
|
||||
|
@ -65,12 +73,15 @@ class HtmlRenderer implements MainContentRendererInterface {
|
|||
* The event dispatcher.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer service.
|
||||
*/
|
||||
public function __construct(TitleResolverInterface $title_resolver, PluginManagerInterface $display_variant_manager, EventDispatcherInterface $event_dispatcher, ModuleHandlerInterface $module_handler) {
|
||||
public function __construct(TitleResolverInterface $title_resolver, PluginManagerInterface $display_variant_manager, EventDispatcherInterface $event_dispatcher, ModuleHandlerInterface $module_handler, RendererInterface $renderer) {
|
||||
$this->titleResolver = $title_resolver;
|
||||
$this->displayVariantManager = $display_variant_manager;
|
||||
$this->eventDispatcher = $event_dispatcher;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,14 +119,14 @@ class HtmlRenderer implements MainContentRendererInterface {
|
|||
// and hence may not execute any #post_render_cache_callbacks (because they
|
||||
// might add yet more assets to be attached), and therefore it must be
|
||||
// rendered with drupal_render(), not drupal_render_root().
|
||||
drupal_render_root($html['page']);
|
||||
$this->renderer->render($html['page'], TRUE);
|
||||
if (isset($html['page_top'])) {
|
||||
drupal_render_root($html['page_top']);
|
||||
$this->renderer->render($html['page_top'], TRUE);
|
||||
}
|
||||
if (isset($html['page_bottom'])) {
|
||||
drupal_render_root($html['page_bottom']);
|
||||
$this->renderer->render($html['page_bottom'], TRUE);
|
||||
}
|
||||
$content = drupal_render($html);
|
||||
$content = $this->renderer->render($html);
|
||||
|
||||
// Store the cache tags associated with this page in a X-Drupal-Cache-Tags
|
||||
// header. Also associate the "rendered" cache tag. This allows us to
|
||||
|
@ -177,7 +188,7 @@ class HtmlRenderer implements MainContentRendererInterface {
|
|||
// ::renderContentIntoResponse().
|
||||
// @todo Remove this once https://www.drupal.org/node/2359901 lands.
|
||||
if (!empty($main_content)) {
|
||||
drupal_render($main_content, FALSE);
|
||||
$this->renderer->render($main_content, FALSE);
|
||||
$main_content = [
|
||||
'#markup' => $main_content['#markup'],
|
||||
'#attached' => $main_content['#attached'],
|
||||
|
|
|
@ -0,0 +1,390 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Render\Renderer.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Render;
|
||||
|
||||
use Drupal\Core\Controller\ControllerResolverInterface;
|
||||
use Drupal\Core\Theme\ThemeManagerInterface;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
|
||||
/**
|
||||
* Turns a render array into a HTML string.
|
||||
*/
|
||||
class Renderer implements RendererInterface {
|
||||
|
||||
/**
|
||||
* The theme manager.
|
||||
*
|
||||
* @var \Drupal\Core\Theme\ThemeManagerInterface
|
||||
*/
|
||||
protected $theme;
|
||||
|
||||
/**
|
||||
* The controller resolver.
|
||||
*
|
||||
* @var \Drupal\Core\Controller\ControllerResolverInterface
|
||||
*/
|
||||
protected $controllerResolver;
|
||||
|
||||
/**
|
||||
* The element info.
|
||||
*
|
||||
* @var \Drupal\Core\Render\ElementInfoManagerInterface
|
||||
*/
|
||||
protected $elementInfo;
|
||||
|
||||
/**
|
||||
* Constructs a new Renderer.
|
||||
*
|
||||
* @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
|
||||
* The controller resolver.
|
||||
* @param \Drupal\Core\Theme\ThemeManagerInterface $theme
|
||||
* The theme manager.
|
||||
* @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
|
||||
* The element info.
|
||||
*/
|
||||
public function __construct(ControllerResolverInterface $controller_resolver, ThemeManagerInterface $theme, ElementInfoManagerInterface $element_info) {
|
||||
$this->controllerResolver = $controller_resolver;
|
||||
$this->theme = $theme;
|
||||
$this->elementInfo = $element_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function renderRoot(&$elements) {
|
||||
return $this->render($elements, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function 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);
|
||||
};
|
||||
|
||||
if (!isset($elements['#access']) && isset($elements['#access_callback'])) {
|
||||
if (is_string($elements['#access_callback']) && strpos($elements['#access_callback'], '::') === FALSE) {
|
||||
$elements['#access_callback'] = $this->controllerResolver->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) {
|
||||
$this->processPostRenderCache($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 += $this->elementInfo->getInfo($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 = $this->controllerResolver->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'] = $this->theme->render($elements['#theme'], $elements);
|
||||
|
||||
// If ThemeManagerInterface::render() 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 Renderer::render() but is inlined for speed.
|
||||
if ((!$theme_is_implemented || isset($elements['#render_children'])) && empty($elements['#children'])) {
|
||||
foreach ($children as $key) {
|
||||
$elements['#children'] .= $this->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 ThemeManagerInterface::render() 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'] = $this->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 = $this->controllerResolver->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 Renderer::render().
|
||||
if ($is_root_call) {
|
||||
// We've already called $update_stack() earlier, which updated both the
|
||||
// element and current stack frame. However,
|
||||
// Renderer::processPostRenderCache() 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());
|
||||
$this->processPostRenderCache($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'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_collect_post_render_cache
|
||||
*/
|
||||
protected function processPostRenderCache(array &$elements) {
|
||||
if (isset($elements['#post_render_cache'])) {
|
||||
|
||||
// Call all #post_render_cache callbacks, passing the provided context.
|
||||
foreach (array_keys($elements['#post_render_cache']) as $callback) {
|
||||
if (strpos($callback, '::') === FALSE) {
|
||||
$callable = $this->controllerResolver->getControllerFromDefinition($callback);
|
||||
}
|
||||
else {
|
||||
$callable = $callback;
|
||||
}
|
||||
foreach ($elements['#post_render_cache'][$callback] as $context) {
|
||||
$elements = call_user_func_array($callable, array($elements, $context));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Render\RendererInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Render;
|
||||
|
||||
/**
|
||||
* Defines an interface for turning a render array into a string.
|
||||
*/
|
||||
interface RendererInterface {
|
||||
|
||||
/**
|
||||
* Renders final HTML given a structured array tree.
|
||||
*
|
||||
* Calls ::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.
|
||||
*
|
||||
* @see ::render()
|
||||
*/
|
||||
public function 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
|
||||
* Renderer::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 Renderer::render()'s cache. To
|
||||
* use Renderer::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 Renderer::render(), such as form_builder() 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 Renderer::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 ThemeManagerInterface::render() is called and
|
||||
* must render both the element and its children. If #render_children is
|
||||
* set, ThemeManagerInterface::render() will not be called.
|
||||
* #render_children is usually only set internally by
|
||||
* ThemeManagerInterface::render() so that we can avoid the situation
|
||||
* where Renderer::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
|
||||
* Renderer::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 ThemeManagerInterface::render()
|
||||
* 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 Renderer::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
|
||||
* ::renderRoot().
|
||||
*
|
||||
* @return string
|
||||
* The rendered HTML.
|
||||
*
|
||||
* @throws \LogicException
|
||||
* If a root call to ::render() does not result in an empty stack, this
|
||||
* indicates an erroneous ::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 \Drupal\Core\Render\ElementInfoManagerInterface::getInfo()
|
||||
* @see \Drupal\Core\Theme\ThemeManagerInterface::render()
|
||||
* @see drupal_process_states()
|
||||
* @see drupal_process_attached()
|
||||
* @see ::renderRoot()
|
||||
*/
|
||||
public function render(&$elements, $is_root_call = FALSE);
|
||||
|
||||
}
|
|
@ -10,6 +10,9 @@ namespace Drupal\Core\Theme;
|
|||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Routing\StackedRouteMatchInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Template\Attribute;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
/**
|
||||
* Provides the default implementation of a theme manager.
|
||||
|
@ -47,9 +50,18 @@ class ThemeManager implements ThemeManagerInterface {
|
|||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* The app root.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $root;
|
||||
|
||||
/**
|
||||
* Constructs a new ThemeManager object.
|
||||
*
|
||||
* @param string $root
|
||||
* The app root.
|
||||
* @param \Drupal\Core\Theme\Registry $theme_registry
|
||||
* The theme registry.
|
||||
* @param \Drupal\Core\Theme\ThemeNegotiatorInterface $theme_negotiator
|
||||
|
@ -58,19 +70,22 @@ class ThemeManager implements ThemeManagerInterface {
|
|||
* The theme initialization.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
*/
|
||||
public function __construct(Registry $theme_registry, ThemeNegotiatorInterface $theme_negotiator, ThemeInitialization $theme_initialization, RequestStack $request_stack) {
|
||||
public function __construct($root, Registry $theme_registry, ThemeNegotiatorInterface $theme_negotiator, ThemeInitialization $theme_initialization, RequestStack $request_stack, ModuleHandlerInterface $module_handler) {
|
||||
$this->root = $root;
|
||||
$this->themeNegotiator = $theme_negotiator;
|
||||
$this->themeRegistry = $theme_registry;
|
||||
$this->themeInitialization = $theme_initialization;
|
||||
$this->requestStack = $request_stack;
|
||||
$this->moduleHandler = $module_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render($hook, array $variables) {
|
||||
return _theme($hook, $variables);
|
||||
return $this->theme($hook, $variables);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -109,6 +124,260 @@ class ThemeManager implements ThemeManagerInterface {
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates themed output (internal use only).
|
||||
*
|
||||
* @see \Drupal\Core\Render\RendererInterface::render();
|
||||
*/
|
||||
protected function theme($hook, $variables = array()) {
|
||||
static $default_attributes;
|
||||
|
||||
$active_theme = $this->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 (!$this->moduleHandler->isLoaded() && !defined('MAINTENANCE_MODE')) {
|
||||
throw new \Exception(t('_theme() may not be called until all modules are loaded.'));
|
||||
}
|
||||
|
||||
$theme_registry = $this->themeRegistry->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"];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$variables[$info['render element']] = $element;
|
||||
// Give a hint to render engines to prevent infinite recursion.
|
||||
$variables[$info['render element']]['#render_children'] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Merge in argument defaults.
|
||||
if (!empty($info['variables'])) {
|
||||
$variables += $info['variables'];
|
||||
}
|
||||
elseif (!empty($info['render element'])) {
|
||||
$variables += array($info['render element'] => array());
|
||||
}
|
||||
// Supply original caller info.
|
||||
$variables += array(
|
||||
'theme_hook_original' => $original_hook,
|
||||
);
|
||||
|
||||
// Set base hook for later use. For example if '#theme' => 'node__article'
|
||||
// is called, we run hook_theme_suggestions_node_alter() rather than
|
||||
// hook_theme_suggestions_node__article_alter(), and also pass in the base
|
||||
// hook as the last parameter to the suggestions alter hooks.
|
||||
if (isset($info['base hook'])) {
|
||||
$base_theme_hook = $info['base hook'];
|
||||
}
|
||||
else {
|
||||
$base_theme_hook = $hook;
|
||||
}
|
||||
|
||||
// Invoke hook_theme_suggestions_HOOK().
|
||||
$suggestions = $this->moduleHandler->invokeAll('theme_suggestions_' . $base_theme_hook, array($variables));
|
||||
// If _theme() was invoked with a direct theme suggestion like
|
||||
// '#theme' => 'node__article', add it to the suggestions array before
|
||||
// invoking suggestion alter hooks.
|
||||
if (isset($info['base hook'])) {
|
||||
$suggestions[] = $hook;
|
||||
}
|
||||
|
||||
// Invoke hook_theme_suggestions_alter() and
|
||||
// hook_theme_suggestions_HOOK_alter().
|
||||
$hooks = array(
|
||||
'theme_suggestions',
|
||||
'theme_suggestions_' . $base_theme_hook,
|
||||
);
|
||||
$this->moduleHandler->alter($hooks, $suggestions, $variables, $base_theme_hook);
|
||||
$this->alter($hooks, $suggestions, $variables, $base_theme_hook);
|
||||
|
||||
// Check if each suggestion exists in the theme registry, and if so,
|
||||
// use it instead of the hook that _theme() was called with. For example, a
|
||||
// function may call _theme('node', ...), but a module can add
|
||||
// 'node__article' as a suggestion via hook_theme_suggestions_HOOK_alter(),
|
||||
// enabling a theme to have an alternate template file for article nodes.
|
||||
foreach (array_reverse($suggestions) as $suggestion) {
|
||||
if ($theme_registry->has($suggestion)) {
|
||||
$info = $theme_registry->get($suggestion);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Include a file if the theme function or variable preprocessor is held
|
||||
// elsewhere.
|
||||
if (!empty($info['includes'])) {
|
||||
foreach ($info['includes'] as $include_file) {
|
||||
include_once $this->root . '/' . $include_file;
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke the variable preprocessors, if any.
|
||||
if (isset($info['base hook'])) {
|
||||
$base_hook = $info['base hook'];
|
||||
$base_hook_info = $theme_registry->get($base_hook);
|
||||
// Include files required by the base hook, since its variable
|
||||
// preprocessors might reside there.
|
||||
if (!empty($base_hook_info['includes'])) {
|
||||
foreach ($base_hook_info['includes'] as $include_file) {
|
||||
include_once $this->root . '/' . $include_file;
|
||||
}
|
||||
}
|
||||
// Replace the preprocess functions with those from the base hook.
|
||||
if (isset($base_hook_info['preprocess functions'])) {
|
||||
// Set a variable for the 'theme_hook_suggestion'. This is used to
|
||||
// maintain backwards compatibility with template engines.
|
||||
$theme_hook_suggestion = $hook;
|
||||
$info['preprocess functions'] = $base_hook_info['preprocess functions'];
|
||||
}
|
||||
}
|
||||
if (isset($info['preprocess functions'])) {
|
||||
foreach ($info['preprocess functions'] as $preprocessor_function) {
|
||||
if (function_exists($preprocessor_function)) {
|
||||
$preprocessor_function($variables, $hook, $info);
|
||||
}
|
||||
}
|
||||
// Allow theme preprocess functions to set $variables['#attached'] and use
|
||||
// it like the #attached property on render arrays. In Drupal 8, this is
|
||||
// the (only) officially supported method of attaching assets from
|
||||
// preprocess functions. Assets attached here should be associated with
|
||||
// the template that we're preprocessing variables for.
|
||||
if (isset($variables['#attached'])) {
|
||||
$preprocess_attached = ['#attached' => $variables['#attached']];
|
||||
drupal_render($preprocess_attached);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the output using either a function or a template.
|
||||
$output = '';
|
||||
if (isset($info['function'])) {
|
||||
if (function_exists($info['function'])) {
|
||||
$output = SafeMarkup::set($info['function']($variables));
|
||||
}
|
||||
}
|
||||
else {
|
||||
$render_function = 'twig_render_template';
|
||||
$extension = '.html.twig';
|
||||
|
||||
// The theme engine may use a different extension and a different
|
||||
// renderer.
|
||||
$theme_engine = $active_theme->getEngine();
|
||||
if (isset($theme_engine)) {
|
||||
if ($info['type'] != 'module') {
|
||||
if (function_exists($theme_engine . '_render_template')) {
|
||||
$render_function = $theme_engine . '_render_template';
|
||||
}
|
||||
$extension_function = $theme_engine . '_extension';
|
||||
if (function_exists($extension_function)) {
|
||||
$extension = $extension_function();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In some cases, a template implementation may not have had
|
||||
// template_preprocess() run (for example, if the default implementation
|
||||
// is a function, but a template overrides that default implementation).
|
||||
// In these cases, a template should still be able to expect to have
|
||||
// access to the variables provided by template_preprocess(), so we add
|
||||
// them here if they don't already exist. We don't want the overhead of
|
||||
// running template_preprocess() twice, so we use the 'directory' variable
|
||||
// to determine if it has already run, which while not completely
|
||||
// intuitive, is reasonably safe, and allows us to save on the overhead of
|
||||
// adding some new variable to track that.
|
||||
if (!isset($variables['directory'])) {
|
||||
$default_template_variables = array();
|
||||
template_preprocess($default_template_variables, $hook, $info);
|
||||
$variables += $default_template_variables;
|
||||
}
|
||||
if (!isset($default_attributes)) {
|
||||
$default_attributes = new Attribute();
|
||||
}
|
||||
foreach (array('attributes', 'title_attributes', 'content_attributes') as $key) {
|
||||
if (isset($variables[$key]) && !($variables[$key] instanceof Attribute)) {
|
||||
if ($variables[$key]) {
|
||||
$variables[$key] = new Attribute($variables[$key]);
|
||||
}
|
||||
else {
|
||||
// Create empty attributes.
|
||||
$variables[$key] = clone $default_attributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render the output using the template file.
|
||||
$template_file = $info['template'] . $extension;
|
||||
if (isset($info['path'])) {
|
||||
$template_file = $info['path'] . '/' . $template_file;
|
||||
}
|
||||
// Add the theme suggestions to the variables array just before rendering
|
||||
// the template for backwards compatibility with template engines.
|
||||
$variables['theme_hook_suggestions'] = $suggestions;
|
||||
// For backwards compatibility, pass 'theme_hook_suggestion' on to the
|
||||
// template engine. This is only set when calling a direct suggestion like
|
||||
// '#theme' => 'menu__shortcut_default' when the template exists in the
|
||||
// current theme.
|
||||
if (isset($theme_hook_suggestion)) {
|
||||
$variables['theme_hook_suggestion'] = $theme_hook_suggestion;
|
||||
}
|
||||
$output = $render_function($template_file, $variables);
|
||||
}
|
||||
|
||||
return (string) $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the active theme for a given route match.
|
||||
*
|
||||
|
|
|
@ -12,6 +12,7 @@ use Drupal\Component\Utility\Xss;
|
|||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Datetime\DateFormatter;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\node\NodeTypeInterface;
|
||||
use Drupal\node\NodeInterface;
|
||||
|
@ -29,21 +30,34 @@ class NodeController extends ControllerBase implements ContainerInjectionInterfa
|
|||
*/
|
||||
protected $dateFormatter;
|
||||
|
||||
/**
|
||||
* The renderer service.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Constructs a NodeController object.
|
||||
*
|
||||
* @param \Drupal\Core\Datetime\DateFormatter $date_formatter
|
||||
* The date formatter service.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer service.
|
||||
*/
|
||||
public function __construct(DateFormatter $date_formatter) {
|
||||
public function __construct(DateFormatter $date_formatter, RendererInterface $renderer) {
|
||||
$this->dateFormatter = $date_formatter;
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static($container->get('date.formatter'));
|
||||
return new static(
|
||||
$container->get('date.formatter'),
|
||||
$container->get('renderer')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
@ -112,7 +126,7 @@ class NodeController extends ControllerBase implements ContainerInjectionInterfa
|
|||
*/
|
||||
public function revisionShow($node_revision) {
|
||||
$node = $this->entityManager()->getStorage('node')->loadRevision($node_revision);
|
||||
$node_view_controller = new NodeViewController($this->entityManager);
|
||||
$node_view_controller = new NodeViewController($this->entityManager, $this->renderer);
|
||||
$page = $node_view_controller->view($node);
|
||||
unset($page['nodes'][$node->id()]['#cache']);
|
||||
return $page;
|
||||
|
|
|
@ -94,6 +94,13 @@ abstract class FieldPluginBase extends HandlerBase {
|
|||
*/
|
||||
protected $linkGenerator;
|
||||
|
||||
/**
|
||||
* Stores the render API renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\Renderer
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Overrides Drupal\views\Plugin\views\HandlerBase::init().
|
||||
*/
|
||||
|
@ -907,7 +914,7 @@ abstract class FieldPluginBase extends HandlerBase {
|
|||
'#items' => $items,
|
||||
'#list_type' => $type,
|
||||
);
|
||||
$output .= drupal_render($item_list);
|
||||
$output .= $this->getRenderer()->render($item_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1153,7 +1160,7 @@ abstract class FieldPluginBase extends HandlerBase {
|
|||
else {
|
||||
$value = $this->render($values);
|
||||
if (is_array($value)) {
|
||||
$value = drupal_render($value);
|
||||
$value = $this->getRenderer()->render($value);
|
||||
}
|
||||
$this->last_render = $value;
|
||||
$this->original_value = $value;
|
||||
|
@ -1166,7 +1173,7 @@ abstract class FieldPluginBase extends HandlerBase {
|
|||
foreach ($raw_items as $count => $item) {
|
||||
$value = $this->render_item($count, $item);
|
||||
if (is_array($value)) {
|
||||
$value = drupal_render($value);
|
||||
$value = $this->getRenderer()->render($value);
|
||||
}
|
||||
$this->last_render = $value;
|
||||
$this->original_value = $this->last_render;
|
||||
|
@ -1184,7 +1191,7 @@ abstract class FieldPluginBase extends HandlerBase {
|
|||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$value = drupal_render($value);
|
||||
$value = $this->getRenderer()->render($value);
|
||||
}
|
||||
// This happens here so that renderAsLink can get the unaltered value of
|
||||
// this field as a token rather than the altered value.
|
||||
|
@ -1640,7 +1647,7 @@ abstract class FieldPluginBase extends HandlerBase {
|
|||
protected function documentSelfTokens(&$tokens) { }
|
||||
|
||||
/**
|
||||
* Pass values to drupal_render() using $this->themeFunctions() as #theme.
|
||||
* Pass values to $this->getRenderer()->render() using $this->themeFunctions() as #theme.
|
||||
*
|
||||
* @param \Drupal\views\ResultRow $values
|
||||
* Holds single row of a view's result set.
|
||||
|
@ -1655,7 +1662,7 @@ abstract class FieldPluginBase extends HandlerBase {
|
|||
'#field' => $this,
|
||||
'#row' => $values,
|
||||
);
|
||||
return drupal_render($build);
|
||||
return $this->getRenderer()->render($build);
|
||||
}
|
||||
|
||||
public function themeFunctions() {
|
||||
|
@ -1745,6 +1752,20 @@ abstract class FieldPluginBase extends HandlerBase {
|
|||
}
|
||||
return $this->linkGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the render API renderer.
|
||||
*
|
||||
* @return \Drupal\Core\Render\Renderer
|
||||
*/
|
||||
protected function getRenderer() {
|
||||
if (!isset($this->renderer)) {
|
||||
$this->renderer = \Drupal::service('renderer');
|
||||
}
|
||||
|
||||
return $this->renderer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -67,7 +67,7 @@ class EntityViewControllerTest extends UnitTestCase{
|
|||
|
||||
|
||||
// Initialize the controller to test.
|
||||
$controller = new EntityViewController($entity_manager);
|
||||
$controller = new EntityViewController($entity_manager, $this->getMock('Drupal\Core\Render\RendererInterface'));
|
||||
|
||||
// Test the view method.
|
||||
$this->assertEquals($controller->view($entity, 'full'), 'Output from rendering the entity');
|
||||
|
|
Loading…
Reference in New Issue