diff --cc core/includes/theme.inc index 1015f67,920946f..0000000 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index 30b77e8..1b0811f 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -8,7 +8,9 @@ use Drupal\Core\Cache\Cache; use Drupal\Component\Utility\NestedArray; - +/** + * Converts a render array into a string. + */ class Renderer { /** @@ -26,16 +28,214 @@ class Renderer { protected $controllerResolver; /** - * @param \Drupal\Core\Controller\ControllerResolverInterface $this->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_manager + * @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) { + public function __construct(ControllerResolverInterface $controller_resolver, ThemeManagerInterface $theme, ElementInfoManagerInterface $element_info) { $this->controllerResolver = $controller_resolver; $this->theme = $theme; + $this->elementInfo = $element_info; } + /** + * Renders HTML given a structured array tree. + * + * Renderable arrays have two kinds of key/value pairs: properties and children. + * Properties have keys starting with '#' and their values influence how the + * array will be rendered. Children are all elements whose keys do not start + * with a '#'. Their values should be renderable arrays themselves, which will + * be rendered during the rendering of the parent array. The markup provided by + * the children is typically inserted into the markup generated by the parent + * array. + * + * An important aspect of rendering is the bubbling of rendering metadata: cache + * tags, attached assets and #post_render_cache metadata all need to be bubbled + * up. That information is needed once the rendering to a HTML string is + * completed: the resulting HTML for the page must know by which cache tags it + * should be invalidated, which (CSS and JavaScript) assets must be loaded, and + * which #post_render_cache callbacks should be executed. A stack data structure + * is used to perform this bubbling. + * + * The process of rendering an element is recursive unless the element defines + * an implemented theme hook in #theme. During each call to drupal_render(), the + * outermost renderable array (also known as an "element") is processed using + * the following steps: + * - If this element has already been printed (#printed = TRUE) or the user + * does not have access to it (#access = FALSE), then an empty string is + * returned. + * - If no stack data structure has been created yet, it is done now. Next, + * an empty \Drupal\Core\Render\RenderStackFrame is pushed onto the stack. + * - If this element has #cache defined then the cached markup for this + * element will be returned if it exists in drupal_render()'s cache. To use + * drupal_render() caching, set the element's #cache property to an + * associative array with one or several of the following keys: + * - 'keys': An array of one or more keys that identify the element. If + * 'keys' is set, the cache ID is created automatically from these keys. + * Cache keys may either be static (just strings) or tokens (placeholders + * that are converted to static keys by the @cache_contexts service, + * depending on the request). See drupal_render_cid_create(). + * - 'cid': Specify the cache ID directly. Either 'keys' or 'cid' is + * required. If 'cid' is set, 'keys' is ignored. Use only if you have + * special requirements. + * - 'expire': Set to one of the cache lifetime constants. + * - 'bin': Specify a cache bin to cache the element in. Default is + * 'default'. + * When there is a render cache hit, there is no rendering work left to be + * done, so the stack must be updated. The empty (and topmost) frame that + * was just pushed onto the stack is updated with all bubbleable rendering + * metadata from the element retrieved from render cache. Then, this stack + * frame is bubbled: the two topmost frames are popped from the stack, they + * are merged, and the result is pushed back onto the stack. + * - If this element has #type defined and the default attributes for this + * element have not already been merged in (#defaults_loaded = TRUE) then + * the defaults for this type of element, defined in hook_element_info(), + * are merged into the array. #defaults_loaded is set by functions that + * process render arrays and call element_info() before passing the array to + * drupal_render(), such as 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 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_recursive_call + * Whether this is a recursive call or not, for internal use. + * + * @return string + * The rendered HTML. + * + * @see \Drupal\Core\Render\ElementInfoManagerInterface::getInfo() + * @see \Drupal\Core\Theme\ThemeManagerInterface::render() + * @see drupal_process_states() + * @see drupal_process_attached() + */ public function render(&$elements, $is_recursive_call = FALSE) { static $stack; @@ -113,7 +313,7 @@ public function render(&$elements, $is_recursive_call = FALSE) { // 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']); + $elements += $this->elementInfo->getInfo($elements['#type']); } // Make any final changes to the element before it is rendered. This means @@ -324,4 +524,5 @@ protected function processPostRenderCache(array &$elements) { } } } + } diff --git a/core/lib/Drupal/Core/Theme/ThemeManager.php b/core/lib/Drupal/Core/Theme/ThemeManager.php index 8a5d492..32babb2 100644 --- a/core/lib/Drupal/Core/Theme/ThemeManager.php +++ b/core/lib/Drupal/Core/Theme/ThemeManager.php @@ -113,72 +113,25 @@ public function setActiveTheme(ActiveTheme $active_theme) { return $this; } - /** * 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() + * @see ::render for the full documentation. */ protected function theme($hook, $variables = array()) { static $default_attributes; - $theme_registry = $this->themeRegistry->getRuntime(); + $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.')); + 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)) { @@ -324,6 +277,15 @@ protected function theme($hook, $variables = array()) { $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, TRUE); + } } // Generate the output using either a function or a template. @@ -338,7 +300,7 @@ protected function theme($hook, $variables = array()) { $extension = '.html.twig'; // The theme engine may use a different extension and a different renderer. - $theme_engine = $this->getActiveTheme()->getEngine(); + $theme_engine = $active_theme->getEngine(); if (isset($theme_engine)) { if ($info['type'] != 'module') { if (function_exists($theme_engine . '_render_template')) { diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php index 238aed3..0d5144e 100644 --- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php @@ -87,6 +87,13 @@ var $additional_fields = array(); /** + * Stores the render API renderer. + * + * @var \Drupal\Core\Render\Renderer + */ + protected $renderer; + + /** * Overrides Drupal\views\Plugin\views\HandlerBase::init(). */ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) { @@ -1717,9 +1724,14 @@ public static function trimText($alter, $value) { return $value; } + /** + * Returns the render API renderer. + * + * @return \Drupal\Core\Render\Renderer + */ protected function getRenderer() { - if (!$this->renderer) { - $this->renderer = \Drupal::service("renderer"); + if (!isset($this->renderer)) { + $this->renderer = \Drupal::service('renderer'); } return $this->renderer;