Update to drupal 8.0.0-rc1. For more information, see https://www.drupal.org/node/2582663
This commit is contained in:
parent
eb34d130a8
commit
f32e58e4b1
8476 changed files with 211648 additions and 170042 deletions
|
@ -9,6 +9,8 @@ namespace Drupal\Core\Render;
|
|||
/**
|
||||
* Defines an interface for processing attachments of responses that have them.
|
||||
*
|
||||
* @see \Drupal\Core\Ajax\AjaxResponse
|
||||
* @see \Drupal\Core\Ajax\AjaxResponseAttachmentsProcessor
|
||||
* @see \Drupal\Core\Render\HtmlResponse
|
||||
* @see \Drupal\Core\Render\HtmlResponseAttachmentsProcessor
|
||||
*/
|
||||
|
@ -17,13 +19,38 @@ interface AttachmentsResponseProcessorInterface {
|
|||
/**
|
||||
* Processes the attachments of a response that has attachments.
|
||||
*
|
||||
* Libraries, JavaScript settings, feeds, HTML <head> tags, HTML <head> links,
|
||||
* HTTP headers, and the HTTP status code are attached to render arrays using
|
||||
* the #attached property. The #attached property is an associative array,
|
||||
* where the keys are the attachment types and the values are the attached
|
||||
* data. For example:
|
||||
*
|
||||
* @code
|
||||
* $build['#attached']['library'][] = [
|
||||
* 'library' => ['core/jquery']
|
||||
* ];
|
||||
* $build['#attached']['http_header'][] = [
|
||||
* ['Content-Type', 'application/rss+xml; charset=utf-8'],
|
||||
* ];
|
||||
* @endcode
|
||||
*
|
||||
* The available keys are:
|
||||
* - 'library' (asset libraries)
|
||||
* - 'drupalSettings' (JavaScript settings)
|
||||
* - 'feed' (RSS feeds)
|
||||
* - 'html_head' (tags in HTML <head>)
|
||||
* - 'html_head_link' (<link> tags in HTML <head>)
|
||||
* - 'http_header' (HTTP headers and status code)
|
||||
*
|
||||
* @param \Drupal\Core\Render\AttachmentsInterface $response
|
||||
* The response to process the attachments for.
|
||||
* The response to process.
|
||||
*
|
||||
* @return \Drupal\Core\Render\AttachmentsInterface
|
||||
* The processed response.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown when the $response parameter is not the type of response object
|
||||
* the processor expects.
|
||||
*/
|
||||
public function processAttachments(AttachmentsInterface $response);
|
||||
|
||||
|
|
|
@ -13,8 +13,18 @@ use Drupal\Core\Render\Element;
|
|||
/**
|
||||
* Provides a wrapper element to group one or more buttons in a form.
|
||||
*
|
||||
* Use of the 'actions' element as an array key helps to ensure proper styling
|
||||
* in themes and to enable other modules to properly alter a form's actions.
|
||||
* Use of a single Actions element with an array key of 'actions' to group the
|
||||
* primary submit buttons on a form helps to ensure proper styling in themes,
|
||||
* and enables other modules to properly alter a form's actions.
|
||||
*
|
||||
* Usage example:
|
||||
* @code
|
||||
* $form['actions'] = array('#type' => 'actions');
|
||||
* $form['actions']['submit'] = array(
|
||||
* '#type' => 'submit',
|
||||
* '#value' => t('Save'),
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* @RenderElement("actions")
|
||||
*/
|
||||
|
|
|
@ -16,6 +16,31 @@ use Drupal\Core\Form\FormStateInterface;
|
|||
* Surrounds child elements with a <div> and adds attributes such as classes or
|
||||
* an HTML ID.
|
||||
*
|
||||
* Usage example:
|
||||
* @code
|
||||
* $form['needs_accommodation'] = array(
|
||||
* '#type' => 'checkbox',
|
||||
* '#title' => 'Need Special Accommodations?',
|
||||
* );
|
||||
*
|
||||
* $form['accommodation'] = array(
|
||||
* '#type' => 'container',
|
||||
* '#attributes' => array(
|
||||
* 'class' => 'accommodation',
|
||||
* ),
|
||||
* '#states' => array(
|
||||
* 'invisible' => array(
|
||||
* 'input[name="needs_accommodation"]' => array('checked' => FALSE),
|
||||
* ),
|
||||
* ),
|
||||
* );
|
||||
*
|
||||
* $form['accommodation']['diet'] = array(
|
||||
* '#type' => 'textfield',
|
||||
* '#title' => t('Dietary Restrictions'),
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* @RenderElement("container")
|
||||
*/
|
||||
class Container extends RenderElement {
|
||||
|
|
|
@ -13,9 +13,29 @@ use Drupal\Core\Render\Element;
|
|||
* Provides a render element for a details element, similar to a fieldset.
|
||||
*
|
||||
* Fieldsets can only be used in forms, while details elements can be used
|
||||
* outside of forms.
|
||||
* outside of forms. Users click on the the title to open or close the details
|
||||
* element, showing or hiding the contained elements.
|
||||
*
|
||||
* Properties:
|
||||
* - #title: The title of the details container. Defaults to "Details".
|
||||
* - #open: Indicates whether the container should be open by default.
|
||||
* Defaults to FALSE.
|
||||
*
|
||||
* Usage example:
|
||||
* @code
|
||||
* $form['author'] = array(
|
||||
* '#type' => 'details',
|
||||
* '#title' => 'Author',
|
||||
* );
|
||||
*
|
||||
* $form['author']['name'] = array(
|
||||
* '#type' => 'textfield',
|
||||
* '#title' => t('Name'),
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\Core\Render\Element\Fieldset
|
||||
* @see \Drupal]Core\Render\Element\VerticalTabs
|
||||
*
|
||||
* @RenderElement("details")
|
||||
*/
|
||||
|
|
|
@ -11,7 +11,10 @@ namespace Drupal\Core\Render\Element;
|
|||
* Provides a render element for a group of form elements.
|
||||
*
|
||||
* In default rendering, the only difference between a 'fieldgroup' and a
|
||||
* 'fieldset' is the CSS class applied to the containing HTML element.
|
||||
* 'fieldset' is the CSS class applied to the containing HTML element. Normally
|
||||
* use a fieldset.
|
||||
*
|
||||
* @see \Drupal\Core\Render\Element\Fieldset for documentation and usage.
|
||||
*
|
||||
* @see \Drupal\Core\Render\Element\Fieldset
|
||||
* @see \Drupal\Core\Render\Element\Details
|
||||
|
|
|
@ -12,8 +12,18 @@ use Drupal\Core\Render\Element;
|
|||
/**
|
||||
* Provides a render element for a group of form elements.
|
||||
*
|
||||
* In default rendering, the only difference between a 'fieldgroup' and a
|
||||
* 'fieldset' is the CSS class applied to the containing HTML element.
|
||||
* Usage example:
|
||||
* @code
|
||||
* $form['author'] = array(
|
||||
* '#type' => 'fieldset',
|
||||
* '#title' => 'Author',
|
||||
* );
|
||||
*
|
||||
* $form['author']['name'] = array(
|
||||
* '#type' => 'textfield',
|
||||
* '#title' => t('Name'),
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\Core\Render\Element\Fieldgroup
|
||||
* @see \Drupal\Core\Render\Element\Details
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace Drupal\Core\Render\Element;
|
|||
|
||||
use Drupal\Component\Utility\Html as HtmlUtility;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Render\SafeString;
|
||||
use Drupal\Core\Render\Markup;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Template\Attribute;
|
||||
|
||||
|
@ -85,7 +85,7 @@ class HtmlTag extends RenderElement {
|
|||
if (!empty($element['#noscript'])) {
|
||||
$markup = "<noscript>$markup</noscript>";
|
||||
}
|
||||
$element['#markup'] = SafeString::create($markup);
|
||||
$element['#markup'] = Markup::create($markup);
|
||||
return $element;
|
||||
}
|
||||
|
||||
|
@ -166,13 +166,13 @@ class HtmlTag extends RenderElement {
|
|||
// filtered if they are unsafe. Thus, all these strings are safe.
|
||||
if (!$browsers['!IE']) {
|
||||
// "downlevel-hidden".
|
||||
$element['#prefix'] = SafeString::create("\n<!--[if $expression]>\n" . $prefix);
|
||||
$element['#suffix'] = SafeString::create($suffix . "<![endif]-->\n");
|
||||
$element['#prefix'] = Markup::create("\n<!--[if $expression]>\n" . $prefix);
|
||||
$element['#suffix'] = Markup::create($suffix . "<![endif]-->\n");
|
||||
}
|
||||
else {
|
||||
// "downlevel-revealed".
|
||||
$element['#prefix'] = SafeString::create("\n<!--[if $expression]><!-->\n" . $prefix);
|
||||
$element['#suffix'] = SafeString::create($suffix . "<!--<![endif]-->\n");
|
||||
$element['#prefix'] = Markup::create("\n<!--[if $expression]><!-->\n" . $prefix);
|
||||
$element['#suffix'] = Markup::create($suffix . "<!--<![endif]-->\n");
|
||||
}
|
||||
|
||||
return $element;
|
||||
|
|
|
@ -38,17 +38,18 @@ class Link extends RenderElement {
|
|||
* Doing so during pre_render gives modules a chance to alter the link parts.
|
||||
*
|
||||
* @param array $element
|
||||
* A structured array whose keys form the arguments to _l():
|
||||
* - #title: The link text to pass as argument to _l().
|
||||
* A structured array whose keys form the arguments to
|
||||
* \Drupal\Core\Utility\LinkGeneratorInterface::generate():
|
||||
* - #title: The link text.
|
||||
* - #url: The URL info either pointing to a route or a non routed path.
|
||||
* - #options: (optional) An array of options to pass to _l() or the link
|
||||
* generator.
|
||||
* - #options: (optional) An array of options to pass to the link generator.
|
||||
*
|
||||
* @return array
|
||||
* The passed-in element containing a rendered link in '#markup'.
|
||||
*/
|
||||
public static function preRenderLink($element) {
|
||||
// By default, link options to pass to _l() are normally set in #options.
|
||||
// By default, link options to pass to the link generator are normally set
|
||||
// in #options.
|
||||
$element += array('#options' => array());
|
||||
// However, within the scope of renderable elements, #attributes is a valid
|
||||
// way to specify attributes, too. Take them into account, but do not override
|
||||
|
@ -82,7 +83,7 @@ class Link extends RenderElement {
|
|||
$options = NestedArray::mergeDeep($element['#url']->getOptions(), $element['#options']);
|
||||
/** @var \Drupal\Core\Utility\LinkGenerator $link_generator */
|
||||
$link_generator = \Drupal::service('link_generator');
|
||||
$generated_link = $link_generator->generate($element['#title'], $element['#url']->setOptions($options), TRUE);
|
||||
$generated_link = $link_generator->generate($element['#title'], $element['#url']->setOptions($options));
|
||||
$element['#markup'] = $generated_link->getGeneratedLink();
|
||||
$generated_link->merge(BubbleableMetadata::createFromRenderArray($element))
|
||||
->applyTo($element);
|
||||
|
|
31
core/lib/Drupal/Core/Render/Element/PageTitle.php
Normal file
31
core/lib/Drupal/Core/Render/Element/PageTitle.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Render\Element\PageTitle.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Render\Element;
|
||||
|
||||
/**
|
||||
* Provides a render element for the title of an HTML page.
|
||||
*
|
||||
* This represents the title of the HTML page's body.
|
||||
*
|
||||
* @RenderElement("page_title")
|
||||
*/
|
||||
class PageTitle extends RenderElement {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo() {
|
||||
return [
|
||||
'#theme' => 'page_title',
|
||||
// The page title: either a string for plain titles or a render array for
|
||||
// formatted titles.
|
||||
'#title' => NULL,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -102,7 +102,7 @@ class PasswordConfirm extends FormElement {
|
|||
public static function validatePasswordConfirm(&$element, FormStateInterface $form_state, &$complete_form) {
|
||||
$pass1 = trim($element['pass1']['#value']);
|
||||
$pass2 = trim($element['pass2']['#value']);
|
||||
if (!empty($pass1) || !empty($pass2)) {
|
||||
if (strlen($pass1) > 0 || strlen($pass2) > 0) {
|
||||
if (strcmp($pass1, $pass2)) {
|
||||
$form_state->setError($element, t('The specified passwords do not match.'));
|
||||
}
|
||||
|
|
|
@ -17,6 +17,41 @@ use Drupal\Component\Utility\Html as HtmlUtility;
|
|||
* Note: Although this extends FormElement, it can be used outside the
|
||||
* context of a form.
|
||||
*
|
||||
* Properties:
|
||||
* - #header: An array of table header labels.
|
||||
* - #rows: An array of the rows to be displayed. Each row is either an array
|
||||
* of cell contents or an array of properties as described in table.html.twig
|
||||
* Alternatively specify the data for the table as child elements of the table
|
||||
* element. Table elements would contain rows elements that would in turn
|
||||
* contain column elements.
|
||||
* - #empty: Text to display when no rows are present.
|
||||
* - #responsive: Indicates whether to add the drupal.responsive_table library
|
||||
* providing responsive tables. Defaults to TRUE.
|
||||
* - #sticky: Indicates whether to add the drupal.tableheader library that makes
|
||||
* table headers always visible at the top of the page. Defaults to FALSE.
|
||||
*
|
||||
* Usage example:
|
||||
* @code
|
||||
* $form['contacts'] = array(
|
||||
* '#type' => 'table',
|
||||
* '#title' => 'Sample Table',
|
||||
* '#header' => array('Name', 'Phone'),
|
||||
* );
|
||||
*
|
||||
* for ($i=1; $i<=4; $i++) {
|
||||
* $form['contacts'][$i]['name'] = array(
|
||||
* '#type' => 'textfield',
|
||||
* '#title' => t('Name'),
|
||||
* '#title_display' => 'invisible',
|
||||
* );
|
||||
*
|
||||
* $form['contacts'][$i]['phone'] = array(
|
||||
* '#type' => 'tel',
|
||||
* '#title' => t('Phone'),
|
||||
* '#title_display' => 'invisible',
|
||||
* );
|
||||
* }
|
||||
* @endcode
|
||||
* @see \Drupal\Core\Render\Element\Tableselect
|
||||
*
|
||||
* @FormElement("table")
|
||||
|
@ -165,7 +200,7 @@ class Table extends FormElement {
|
|||
}
|
||||
}
|
||||
if (isset($title) && $title !== '') {
|
||||
$title = t('Update !title', array('!title' => $title));
|
||||
$title = t('Update @title', array('@title' => $title));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -297,7 +332,7 @@ class Table extends FormElement {
|
|||
* @return array
|
||||
*
|
||||
* @see template_preprocess_table()
|
||||
* @see drupal_process_attached()
|
||||
* @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments()
|
||||
* @see drupal_attach_tabledrag()
|
||||
*/
|
||||
public static function preRenderTable($element) {
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace Drupal\Core\Render\Element;
|
|||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Component\Utility\Html as HtmlUtility;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Provides a form element for a table with radios or checkboxes in left column.
|
||||
|
@ -217,10 +218,12 @@ class Tableselect extends Table {
|
|||
if (!isset($element[$key])) {
|
||||
if ($element['#multiple']) {
|
||||
$title = '';
|
||||
if (!empty($element['#options'][$key]['title']['data']['#title'])) {
|
||||
$title = t('Update @title', array(
|
||||
'@title' => $element['#options'][$key]['title']['data']['#title'],
|
||||
));
|
||||
if (isset($element['#options'][$key]['title']) && is_array($element['#options'][$key]['title'])) {
|
||||
if (!empty($element['#options'][$key]['title']['data']['#title'])) {
|
||||
$title = new TranslatableMarkup('Update @title', array(
|
||||
'@title' => $element['#options'][$key]['title']['data']['#title'],
|
||||
));
|
||||
}
|
||||
}
|
||||
$element[$key] = array(
|
||||
'#type' => 'checkbox',
|
||||
|
|
|
@ -13,8 +13,42 @@ use Drupal\Core\Render\Element;
|
|||
/**
|
||||
* Provides a render element for vertical tabs in a form.
|
||||
*
|
||||
* Formats all child fieldsets and all non-child fieldsets whose #group is
|
||||
* assigned this element's name as vertical tabs.
|
||||
* Formats all child and non-child details elements whose #group is assigned
|
||||
* this element's name as vertical tabs.
|
||||
*
|
||||
* Properties:
|
||||
* - #default_tab: The HTML ID of the rendered details element to be used as
|
||||
* the default tab. View the source of the rendered page to determine the ID.
|
||||
*
|
||||
* Usage example:
|
||||
* @code
|
||||
* $form['information'] = array(
|
||||
* '#type' => 'vertical_tabs',
|
||||
* '#default_tab' => 'edit-publication',
|
||||
* );
|
||||
*
|
||||
* $form['author'] = array(
|
||||
* '#type' => 'details',
|
||||
* '#title' => 'Author',
|
||||
* '#group' => 'information',
|
||||
* );
|
||||
*
|
||||
* $form['author']['name'] = array(
|
||||
* '#type' => 'textfield',
|
||||
* '#title' => t('Name'),
|
||||
* );
|
||||
*
|
||||
* $form['publication'] = array(
|
||||
* '#type' => 'details',
|
||||
* '#title' => t('Publication'),
|
||||
* '#group' => 'information',
|
||||
* );
|
||||
*
|
||||
* $form['publication']['publisher'] = array(
|
||||
* '#type' => 'textfield',
|
||||
* '#title' => t('Publisher'),
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* @FormElement("vertical_tabs")
|
||||
*/
|
||||
|
|
|
@ -11,11 +11,23 @@ use Drupal\Core\Asset\AssetResolverInterface;
|
|||
use Drupal\Core\Asset\AttachedAssets;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Form\EnforcedResponseException;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Processes attachments of HTML responses.
|
||||
*
|
||||
* This class is used by the rendering service to process the #attached part of
|
||||
* the render array, for HTML responses.
|
||||
*
|
||||
* To render attachments to HTML for testing without a controller, use the
|
||||
* 'bare_html_page_renderer' service to generate a
|
||||
* Drupal\Core\Render\HtmlResponse object. Then use its getContent(),
|
||||
* getStatusCode(), and/or the headers property to access the result.
|
||||
*
|
||||
* @see template_preprocess_html()
|
||||
* @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface
|
||||
* @see \Drupal\Core\Render\BareHtmlPageRenderer
|
||||
|
@ -66,6 +78,13 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
|
|||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* The module handler service.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* Constructs a HtmlResponseAttachmentsProcessor object.
|
||||
*
|
||||
|
@ -81,14 +100,17 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
|
|||
* The request stack.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler service.
|
||||
*/
|
||||
public function __construct(AssetResolverInterface $asset_resolver, ConfigFactoryInterface $config_factory, AssetCollectionRendererInterface $css_collection_renderer, AssetCollectionRendererInterface $js_collection_renderer, RequestStack $request_stack, RendererInterface $renderer) {
|
||||
public function __construct(AssetResolverInterface $asset_resolver, ConfigFactoryInterface $config_factory, AssetCollectionRendererInterface $css_collection_renderer, AssetCollectionRendererInterface $js_collection_renderer, RequestStack $request_stack, RendererInterface $renderer, ModuleHandlerInterface $module_handler) {
|
||||
$this->assetResolver = $asset_resolver;
|
||||
$this->config = $config_factory->get('system.performance');
|
||||
$this->cssCollectionRenderer = $css_collection_renderer;
|
||||
$this->jsCollectionRenderer = $js_collection_renderer;
|
||||
$this->requestStack = $request_stack;
|
||||
$this->renderer = $renderer;
|
||||
$this->moduleHandler = $module_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -117,27 +139,65 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
|
|||
return $e->getResponse();
|
||||
}
|
||||
|
||||
// Get a reference to the attachments.
|
||||
$attached = $response->getAttachments();
|
||||
|
||||
// Get the placeholders from attached and then remove them.
|
||||
$attachment_placeholders = $attached['html_response_attachment_placeholders'];
|
||||
unset($attached['html_response_attachment_placeholders']);
|
||||
|
||||
$variables = $this->processAssetLibraries($attached, $attachment_placeholders);
|
||||
|
||||
// Handle all non-asset attachments. This populates drupal_get_html_head().
|
||||
$all_attached = ['#attached' => $attached];
|
||||
drupal_process_attached($all_attached);
|
||||
|
||||
// Get HTML head elements - if present.
|
||||
if (isset($attachment_placeholders['head'])) {
|
||||
$variables['head'] = drupal_get_html_head(FALSE);
|
||||
// Send a message back if the render array has unsupported #attached types.
|
||||
$unsupported_types = array_diff(
|
||||
array_keys($attached),
|
||||
['html_head', 'feed', 'html_head_link', 'http_header', 'library', 'html_response_attachment_placeholders', 'placeholders', 'drupalSettings']
|
||||
);
|
||||
if (!empty($unsupported_types)) {
|
||||
throw new \LogicException(sprintf('You are not allowed to use %s in #attached.', implode(', ', $unsupported_types)));
|
||||
}
|
||||
|
||||
// Now replace the attachment placeholders.
|
||||
$this->renderHtmlResponseAttachmentPlaceholders($response, $attachment_placeholders, $variables);
|
||||
// If we don't have any placeholders, there is no need to proceed.
|
||||
if (!empty($attached['html_response_attachment_placeholders'])) {
|
||||
// Get the placeholders from attached and then remove them.
|
||||
$attachment_placeholders = $attached['html_response_attachment_placeholders'];
|
||||
unset($attached['html_response_attachment_placeholders']);
|
||||
|
||||
// Finally set the headers on the response if any bubbled.
|
||||
$variables = $this->processAssetLibraries($attached, $attachment_placeholders);
|
||||
|
||||
// Since we can only replace content in the HTML head section if there's a
|
||||
// placeholder for it, we can safely avoid processing the render array if
|
||||
// it's not present.
|
||||
if (!empty($attachment_placeholders['head'])) {
|
||||
// 'feed' is a special case of 'html_head_link'. We process them into
|
||||
// 'html_head_link' entries and merge them.
|
||||
if (!empty($attached['feed'])) {
|
||||
$attached = BubbleableMetadata::mergeAttachments(
|
||||
$attached,
|
||||
$this->processFeed($attached['feed'])
|
||||
);
|
||||
}
|
||||
// 'html_head_link' is a special case of 'html_head' which can be present
|
||||
// as a head element, but also as a Link: HTTP header depending on
|
||||
// settings in the render array. Processing it can add to both the
|
||||
// 'html_head' and 'http_header' keys of '#attached', so we must address
|
||||
// it before 'html_head'.
|
||||
if (!empty($attached['html_head_link'])) {
|
||||
// Merge the processed 'html_head_link' into $attached so that its
|
||||
// 'html_head' and 'http_header' values are present for further
|
||||
// processing.
|
||||
$attached = BubbleableMetadata::mergeAttachments(
|
||||
$attached,
|
||||
$this->processHtmlHeadLink($attached['html_head_link'])
|
||||
);
|
||||
}
|
||||
|
||||
// Now we can process 'html_head', which contains both 'feed' and
|
||||
// 'html_head_link'.
|
||||
if (!empty($attached['html_head'])) {
|
||||
$variables['head'] = $this->processHtmlHead($attached['html_head']);
|
||||
}
|
||||
}
|
||||
|
||||
// Now replace the attachment placeholders.
|
||||
$this->renderHtmlResponseAttachmentPlaceholders($response, $attachment_placeholders, $variables);
|
||||
}
|
||||
|
||||
// Set the HTTP headers and status code on the response if any bubbled.
|
||||
if (!empty($attached['http_header'])) {
|
||||
$this->setHeaders($response, $attached['http_header']);
|
||||
}
|
||||
|
@ -175,7 +235,7 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
|
|||
*/
|
||||
protected function renderPlaceholders(HtmlResponse $response) {
|
||||
$build = [
|
||||
'#markup' => SafeString::create($response->getContent()),
|
||||
'#markup' => Markup::create($response->getContent()),
|
||||
'#attached' => $response->getAttachments(),
|
||||
];
|
||||
// RendererInterface::renderRoot() renders the $build render array and
|
||||
|
@ -215,8 +275,7 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
|
|||
// Take Ajax page state into account, to allow for something like Turbolinks
|
||||
// to be implemented without altering core.
|
||||
// @see https://github.com/rails/turbolinks/
|
||||
// @todo https://www.drupal.org/node/2497115 - Below line is broken due to ->request.
|
||||
$ajax_page_state = $this->requestStack->getCurrentRequest()->request->get('ajax_page_state');
|
||||
$ajax_page_state = $this->requestStack->getCurrentRequest()->get('ajax_page_state');
|
||||
$assets->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : []);
|
||||
|
||||
$variables = [];
|
||||
|
@ -243,6 +302,9 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
|
|||
/**
|
||||
* Renders HTML response attachment placeholders.
|
||||
*
|
||||
* This is the last step where all of the attachments are placed into the
|
||||
* response object's contents.
|
||||
*
|
||||
* @param \Drupal\Core\Render\HtmlResponse $response
|
||||
* The HTML response to update.
|
||||
* @param array $placeholders
|
||||
|
@ -268,7 +330,13 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
|
|||
* @param \Drupal\Core\Render\HtmlResponse $response
|
||||
* The HTML response to update.
|
||||
* @param array $headers
|
||||
* The headers to set.
|
||||
* The headers to set, as an array. The items in this array should be as
|
||||
* follows:
|
||||
* - The header name.
|
||||
* - The header value.
|
||||
* - (optional) Whether to replace a current value with the new one, or add
|
||||
* it to the others. If the value is not replaced, it will be appended,
|
||||
* resulting in a header like this: 'Header: value1,value2'
|
||||
*/
|
||||
protected function setHeaders(HtmlResponse $response, array $headers) {
|
||||
foreach ($headers as $values) {
|
||||
|
@ -281,8 +349,105 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
|
|||
if (strtolower($name) === 'status') {
|
||||
$response->setStatusCode($value);
|
||||
}
|
||||
$response->headers->set($name, $value, $replace);
|
||||
else {
|
||||
$response->headers->set($name, $value, $replace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure proper key/data order and defaults for renderable head items.
|
||||
*
|
||||
* @param array $html_head
|
||||
* The ['#attached']['html_head'] portion of a render array.
|
||||
*
|
||||
* @return array
|
||||
* The ['#attached']['html_head'] portion of a render array with #type of
|
||||
* html_tag added for items without a #type.
|
||||
*/
|
||||
protected function processHtmlHead(array $html_head) {
|
||||
$head = [];
|
||||
foreach ($html_head as $item) {
|
||||
list($data, $key) = $item;
|
||||
if (!isset($data['#type'])) {
|
||||
$data['#type'] = 'html_tag';
|
||||
}
|
||||
$head[$key] = $data;
|
||||
}
|
||||
return $head;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a html_head_link array into html_head and http_header arrays.
|
||||
*
|
||||
* html_head_link is a special case of html_head which can be present as
|
||||
* a link item in the HTML head section, and also as a Link: HTTP header,
|
||||
* depending on options in the render array. Processing it can add to both the
|
||||
* html_head and http_header sections.
|
||||
*
|
||||
* @param array $html_head_link
|
||||
* The 'html_head_link' value of a render array. Each head link is specified
|
||||
* by a two-element array:
|
||||
* - An array specifying the attributes of the link.
|
||||
* - A boolean specifying whether the link should also be a Link: HTTP
|
||||
* header.
|
||||
*
|
||||
* @return array
|
||||
* An ['#attached'] section of a render array. This allows us to easily
|
||||
* merge the results with other render arrays. The array could contain the
|
||||
* following keys:
|
||||
* - http_header
|
||||
* - html_head
|
||||
*/
|
||||
protected function processHtmlHeadLink(array $html_head_link) {
|
||||
$attached = [];
|
||||
|
||||
foreach ($html_head_link as $item) {
|
||||
$attributes = $item[0];
|
||||
$should_add_header = isset($item[1]) ? $item[1] : FALSE;
|
||||
|
||||
$element = array(
|
||||
'#tag' => 'link',
|
||||
'#attributes' => $attributes,
|
||||
);
|
||||
$href = $attributes['href'];
|
||||
$attached['html_head'][] = [$element, 'html_head_link:' . $attributes['rel'] . ':' . $href];
|
||||
|
||||
if ($should_add_header) {
|
||||
// Also add a HTTP header "Link:".
|
||||
$href = '<' . Html::escape($attributes['href'] . '>');
|
||||
unset($attributes['href']);
|
||||
$attached['http_header'][] = ['Link', $href . drupal_http_header_attributes($attributes), TRUE];
|
||||
}
|
||||
}
|
||||
return $attached;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a 'feed' attachment into an 'html_head_link' attachment.
|
||||
*
|
||||
* The RSS feed is a special case of 'html_head_link', so we just turn it into
|
||||
* one.
|
||||
*
|
||||
* @param array $attached_feed
|
||||
* The ['#attached']['feed'] portion of a render array.
|
||||
*
|
||||
* @return array
|
||||
* An ['#attached']['html_head_link'] array, suitable for merging with
|
||||
* another 'html_head_link' array.
|
||||
*/
|
||||
protected function processFeed($attached_feed) {
|
||||
$html_head_link = [];
|
||||
foreach($attached_feed as $item) {
|
||||
$feed_link = [
|
||||
'href' => $item[0],
|
||||
'rel' => 'alternate',
|
||||
'title' => empty($item[1]) ? '' : $item[1],
|
||||
'type' => 'application/rss+xml',
|
||||
];
|
||||
$html_head_link[] = [$feed_link, FALSE];
|
||||
}
|
||||
return ['html_head_link' => $html_head_link];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ use Drupal\Core\Cache\Cache;
|
|||
use Drupal\Core\Controller\TitleResolverInterface;
|
||||
use Drupal\Core\Display\PageVariantInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Display\ContextAwareVariantInterface;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\Render\HtmlResponse;
|
||||
use Drupal\Core\Render\PageDisplayVariantSelectionEvent;
|
||||
|
@ -192,11 +193,18 @@ class HtmlRenderer implements MainContentRendererInterface {
|
|||
* If the selected display variant does not implement PageVariantInterface.
|
||||
*/
|
||||
protected function prepare(array $main_content, Request $request, RouteMatchInterface $route_match) {
|
||||
// Determine the title: use the title provided by the main content if any,
|
||||
// otherwise get it from the routing information.
|
||||
$get_title = function (array $main_content) use ($request, $route_match) {
|
||||
return isset($main_content['#title']) ? $main_content['#title'] : $this->titleResolver->getTitle($request, $route_match->getRouteObject());
|
||||
};
|
||||
|
||||
// If the _controller result already is #type => page,
|
||||
// we have no work to do: The "main content" already is an entire "page"
|
||||
// (see html.html.twig).
|
||||
if (isset($main_content['#type']) && $main_content['#type'] === 'page') {
|
||||
$page = $main_content;
|
||||
$title = $get_title($page);
|
||||
}
|
||||
// Otherwise, render it as the main content of a #type => page, by selecting
|
||||
// page display variant to do that and building that page display variant.
|
||||
|
@ -228,6 +236,8 @@ class HtmlRenderer implements MainContentRendererInterface {
|
|||
];
|
||||
}
|
||||
|
||||
$title = $get_title($main_content);
|
||||
|
||||
// Instantiate the page display, and give it the main content.
|
||||
$page_display = $this->displayVariantManager->createInstance($variant_id);
|
||||
if (!$page_display instanceof PageVariantInterface) {
|
||||
|
@ -235,7 +245,17 @@ class HtmlRenderer implements MainContentRendererInterface {
|
|||
}
|
||||
$page_display
|
||||
->setMainContent($main_content)
|
||||
->setTitle($title)
|
||||
->addCacheableDependency($event)
|
||||
->setConfiguration($event->getPluginConfiguration());
|
||||
// Some display variants need to be passed an array of contexts with
|
||||
// values because they can't get all their contexts globally. For example,
|
||||
// in Page Manager, you can create a Page which has a specific static
|
||||
// context (e.g. a context that refers to the Node with nid 6), if any
|
||||
// such contexts were added to the $event, pass them to the $page_display.
|
||||
if ($page_display instanceof ContextAwareVariantInterface) {
|
||||
$page_display->setContexts($event->getContexts());
|
||||
}
|
||||
|
||||
// Generate a #type => page render array using the page display variant,
|
||||
// the page display will build the content for the various page regions.
|
||||
|
@ -258,10 +278,6 @@ class HtmlRenderer implements MainContentRendererInterface {
|
|||
// Allow hooks to add attachments to $page['#attached'].
|
||||
$this->invokePageAttachmentHooks($page);
|
||||
|
||||
// Determine the title: use the title provided by the main content if any,
|
||||
// otherwise get it from the routing information.
|
||||
$title = isset($main_content['#title']) ? $main_content['#title'] : $this->titleResolver->getTitle($request, $route_match->getRouteObject());
|
||||
|
||||
return [$page, $title];
|
||||
}
|
||||
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Render\SafeString.
|
||||
* Contains \Drupal\Core\Render\Markup.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Render;
|
||||
|
||||
use Drupal\Component\Utility\SafeStringInterface;
|
||||
use Drupal\Component\Utility\SafeStringTrait;
|
||||
use Drupal\Component\Render\MarkupInterface;
|
||||
use Drupal\Component\Render\MarkupTrait;
|
||||
|
||||
/**
|
||||
* Defines an object that passes safe strings through the render system.
|
||||
|
@ -25,6 +25,6 @@ use Drupal\Component\Utility\SafeStringTrait;
|
|||
* @see \Twig_Markup
|
||||
* @see \Drupal\Component\Utility\SafeMarkup
|
||||
*/
|
||||
final class SafeString implements SafeStringInterface, \Countable {
|
||||
use SafeStringTrait;
|
||||
final class Markup implements MarkupInterface, \Countable {
|
||||
use MarkupTrait;
|
||||
}
|
|
@ -114,17 +114,6 @@ class MetadataBubblingUrlGenerator implements UrlGeneratorInterface {
|
|||
return $collect_bubbleable_metadata ? $generated_url : $generated_url->getGeneratedUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generateFromPath($path = NULL, $options = array(), $collect_bubbleable_metadata = FALSE) {
|
||||
$generated_url = $this->urlGenerator->generateFromPath($path, $options, TRUE);
|
||||
if (!$collect_bubbleable_metadata) {
|
||||
$this->bubble($generated_url, $options);
|
||||
}
|
||||
return $collect_bubbleable_metadata ? $generated_url : $generated_url->getGeneratedUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -7,16 +7,26 @@
|
|||
|
||||
namespace Drupal\Core\Render;
|
||||
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event fired when rendering main content, to select a page display variant.
|
||||
*
|
||||
* Subscribers of this event can call the following setters to pass additional
|
||||
* information along to the selected variant:
|
||||
* - self::setPluginConfiguration()
|
||||
* - self::setContexts()
|
||||
* - self::addCacheableDependency()
|
||||
*
|
||||
* @see \Drupal\Core\Render\RenderEvents::SELECT_PAGE_DISPLAY_VARIANT
|
||||
* @see \Drupal\Core\Render\MainContent\HtmlRenderer
|
||||
*/
|
||||
class PageDisplayVariantSelectionEvent extends Event {
|
||||
class PageDisplayVariantSelectionEvent extends Event implements RefinableCacheableDependencyInterface {
|
||||
|
||||
use RefinableCacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* The selected page display variant plugin ID.
|
||||
|
@ -39,6 +49,13 @@ class PageDisplayVariantSelectionEvent extends Event {
|
|||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
/**
|
||||
* An array of collected contexts to pass to the page display variant.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\Context\ContextInterface[]
|
||||
*/
|
||||
protected $contexts = [];
|
||||
|
||||
/**
|
||||
* Constructs the page display variant plugin selection event.
|
||||
*
|
||||
|
@ -106,4 +123,27 @@ class PageDisplayVariantSelectionEvent extends Event {
|
|||
return $this->routeMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the contexts that were set during event dispatch.
|
||||
*
|
||||
* @return \Drupal\Component\Plugin\Context\ContextInterface[]
|
||||
* An array of set contexts, keyed by context name.
|
||||
*/
|
||||
public function getContexts() {
|
||||
return $this->contexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the contexts to be passed to the page display variant.
|
||||
*
|
||||
* @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
|
||||
* An array of contexts, keyed by context name.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setContexts(array $contexts) {
|
||||
$this->contexts = $contexts;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Render\Placeholder\ChainedPlaceholderStrategy.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Render\Placeholder;
|
||||
|
||||
/**
|
||||
* Renders placeholders using a chain of placeholder strategies.
|
||||
*/
|
||||
class ChainedPlaceholderStrategy implements PlaceholderStrategyInterface {
|
||||
|
||||
/**
|
||||
* An ordered list of placeholder strategy services.
|
||||
*
|
||||
* Ordered according to service priority.
|
||||
*
|
||||
* @var \Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface[]
|
||||
*/
|
||||
protected $placeholderStrategies = [];
|
||||
|
||||
/**
|
||||
* Adds a placeholder strategy to use.
|
||||
*
|
||||
* @param \Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface $strategy
|
||||
* The strategy to add to the placeholder strategies.
|
||||
*/
|
||||
public function addPlaceholderStrategy(PlaceholderStrategyInterface $strategy) {
|
||||
$this->placeholderStrategies[] = $strategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPlaceholders(array $placeholders) {
|
||||
if (empty($placeholders)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Assert that there is at least one strategy.
|
||||
assert('!empty($this->placeholderStrategies)', 'At least one placeholder strategy must be present; by default the fallback strategy \Drupal\Core\Render\Placeholder\SingleFlushStrategy is always present.');
|
||||
|
||||
$new_placeholders = [];
|
||||
|
||||
// Give each placeholder strategy a chance to replace all not-yet replaced
|
||||
// placeholders. The order of placeholder strategies is well defined
|
||||
// and this uses a variation of the "chain of responsibility" design pattern.
|
||||
foreach ($this->placeholderStrategies as $strategy) {
|
||||
$processed_placeholders = $strategy->processPlaceholders($placeholders);
|
||||
assert('array_intersect_key($processed_placeholders, $placeholders) === $processed_placeholders', 'Processed placeholders must be a subset of all placeholders.');
|
||||
$placeholders = array_diff_key($placeholders, $processed_placeholders);
|
||||
$new_placeholders += $processed_placeholders;
|
||||
|
||||
if (empty($placeholders)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $new_placeholders;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Render\Placeholder;
|
||||
|
||||
/**
|
||||
* Provides an interface for defining a placeholder strategy service.
|
||||
*/
|
||||
interface PlaceholderStrategyInterface {
|
||||
|
||||
/**
|
||||
* Processes placeholders to render them with different strategies.
|
||||
*
|
||||
* @param array $placeholders
|
||||
* The placeholders to process, with the keys being the markup for the
|
||||
* placeholders and the values the corresponding render array describing the
|
||||
* data to be rendered.
|
||||
*
|
||||
* @return array
|
||||
* The resulting placeholders, with a subset of the keys of $placeholders
|
||||
* (and those being the markup for the placeholders) but with the
|
||||
* corresponding render array being potentially modified to render e.g. an
|
||||
* ESI or BigPipe placeholder.
|
||||
*/
|
||||
public function processPlaceholders(array $placeholders);
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Render\Placeholder\SingleFlushStrategy
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Render\Placeholder;
|
||||
|
||||
/**
|
||||
* Defines the 'single_flush' placeholder strategy.
|
||||
*
|
||||
* This is designed to be the fallback strategy, so should have the lowest
|
||||
* priority. All placeholders that are not yet replaced at this point will be
|
||||
* rendered as is and delivered directly.
|
||||
*/
|
||||
class SingleFlushStrategy implements PlaceholderStrategyInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPlaceholders(array $placeholders) {
|
||||
// Return all placeholders as is; they should be rendered directly.
|
||||
return $placeholders;
|
||||
}
|
||||
}
|
101
core/lib/Drupal/Core/Render/PlaceholderGenerator.php
Normal file
101
core/lib/Drupal/Core/Render/PlaceholderGenerator.php
Normal file
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Render\Placeholder.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Render;
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
|
||||
/**
|
||||
* Turns a render array into a placeholder.
|
||||
*/
|
||||
class PlaceholderGenerator implements PlaceholderGeneratorInterface {
|
||||
|
||||
/**
|
||||
* The renderer configuration array.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $rendererConfig;
|
||||
|
||||
/**
|
||||
* Constructs a new Placeholder service.
|
||||
*
|
||||
* @param array $renderer_config
|
||||
* The renderer configuration array.
|
||||
*/
|
||||
public function __construct(array $renderer_config) {
|
||||
$this->rendererConfig = $renderer_config;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function canCreatePlaceholder(array $element) {
|
||||
return
|
||||
// If generated by a #lazy_builder callback, placeholdering is possible.
|
||||
isset($element['#lazy_builder'])
|
||||
&&
|
||||
// If #create_placeholder === FALSE, placeholdering is disallowed.
|
||||
(!isset($element['#create_placeholder']) || $element['#create_placeholder'] !== FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function shouldAutomaticallyPlaceholder(array $element) {
|
||||
$conditions = $this->rendererConfig['auto_placeholder_conditions'];
|
||||
|
||||
// Auto-placeholder if max-age is at or below the configured threshold.
|
||||
if (isset($element['#cache']['max-age']) && $element['#cache']['max-age'] !== Cache::PERMANENT && $element['#cache']['max-age'] <= $conditions['max-age']) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Auto-placeholder if a high-cardinality cache context is set.
|
||||
if (isset($element['#cache']['contexts']) && array_intersect($element['#cache']['contexts'], $conditions['contexts'])) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Auto-placeholder if a high-invalidation frequency cache tag is set.
|
||||
if (isset($element['#cache']['tags']) && array_intersect($element['#cache']['tags'], $conditions['tags'])) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createPlaceholder(array $element) {
|
||||
$placeholder_render_array = array_intersect_key($element, [
|
||||
// Placeholders are replaced with markup by executing the associated
|
||||
// #lazy_builder callback, which generates a render array, and which the
|
||||
// Renderer will render and replace the placeholder with.
|
||||
'#lazy_builder' => TRUE,
|
||||
// The cacheability metadata for the placeholder. The rendered result of
|
||||
// the placeholder may itself be cached, if [#cache][keys] are specified.
|
||||
'#cache' => TRUE,
|
||||
]);
|
||||
|
||||
// Generate placeholder markup. Note that the only requirement is that this
|
||||
// is unique markup that isn't easily guessable. The #lazy_builder callback
|
||||
// and its arguments are put in the placeholder markup solely to simplify<<<
|
||||
// debugging.
|
||||
$callback = $placeholder_render_array['#lazy_builder'][0];
|
||||
$arguments = UrlHelper::buildQuery($placeholder_render_array['#lazy_builder'][1]);
|
||||
$token = hash('crc32b', serialize($placeholder_render_array));
|
||||
$placeholder_markup = '<drupal-render-placeholder callback="' . $callback . '" arguments="' . $arguments . '" token="' . $token . '"></drupal-render-placeholder>';
|
||||
|
||||
// Build the placeholder element to return.
|
||||
$placeholder_element = [];
|
||||
$placeholder_element['#markup'] = Markup::create($placeholder_markup);
|
||||
$placeholder_element['#attached']['placeholders'][$placeholder_markup] = $placeholder_render_array;
|
||||
return $placeholder_element;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Render\PlaceholderGeneratorInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Render;
|
||||
|
||||
/**
|
||||
* Defines an interface for turning a render array into a placeholder.
|
||||
*
|
||||
* This encapsulates logic related to generating placeholders.
|
||||
*
|
||||
* Makes it possible to determine whether a render array can be placeholdered
|
||||
* (it can be reconstructed independently of the request context), whether a
|
||||
* render array should be placeholdered (its cacheability meets the conditions),
|
||||
* and to create a placeholder.
|
||||
*
|
||||
* @see \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
interface PlaceholderGeneratorInterface {
|
||||
|
||||
/**
|
||||
* Analyzes whether the given render array can be placeholdered.
|
||||
*
|
||||
* @param array $element
|
||||
* A render array. Its #lazy_builder and #create_placeholder properties are
|
||||
* analyzed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canCreatePlaceholder(array $element);
|
||||
|
||||
/**
|
||||
* Whether the given render array should be automatically placeholdered.
|
||||
*
|
||||
* @param array $element
|
||||
* The render array whose cacheability to analyze.
|
||||
*
|
||||
* @return bool
|
||||
* Whether the given render array's cacheability meets the placeholdering
|
||||
* conditions.
|
||||
*/
|
||||
public function shouldAutomaticallyPlaceholder(array $element);
|
||||
|
||||
/**
|
||||
* Turns the given element into a placeholder.
|
||||
*
|
||||
* Placeholdering allows us to avoid "poor cacheability contamination": this
|
||||
* maps the current render array to one that only has #markup and #attached,
|
||||
* and #attached contains a placeholder with this element's prior cacheability
|
||||
* metadata. In other words: this placeholder is perfectly cacheable, the
|
||||
* placeholder replacement logic effectively cordons off poor cacheability.
|
||||
*
|
||||
* @param array $element
|
||||
* The render array to create a placeholder for.
|
||||
*
|
||||
* @return array
|
||||
* Render array with placeholder markup and the attached placeholder
|
||||
* replacement metadata.
|
||||
*/
|
||||
public function createPlaceholder(array $element);
|
||||
|
||||
}
|
183
core/lib/Drupal/Core/Render/PlaceholderingRenderCache.php
Normal file
183
core/lib/Drupal/Core/Render/PlaceholderingRenderCache.php
Normal file
|
@ -0,0 +1,183 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Render\PlaceholderingRenderCache.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Render;
|
||||
|
||||
use Drupal\Core\Cache\CacheFactoryInterface;
|
||||
use Drupal\Core\Cache\Context\CacheContextsManager;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Adds automatic placeholdering to the RenderCache.
|
||||
*
|
||||
* This automatic placeholdering is performed to ensure the containing elements
|
||||
* and overarching response are as cacheable as possible. Elements whose subtree
|
||||
* bubble either max-age=0 or high-cardinality cache contexts (such as 'user'
|
||||
* and 'session') are considered poorly cacheable.
|
||||
*
|
||||
* @see sites/default/default.services.yml
|
||||
*
|
||||
* Automatic placeholdering is performed only on elements whose subtree was
|
||||
* generated using a #lazy_builder callback and whose bubbled cacheability meets
|
||||
* the auto-placeholdering conditions as configured in the renderer.config
|
||||
* container parameter.
|
||||
*
|
||||
* This RenderCache implementation automatically replaces an element with a
|
||||
* placeholder:
|
||||
* - on render cache hit, i.e. ::get()
|
||||
* - on render cache miss, i.e. ::set() (in subsequent requests, it will be a
|
||||
* cache hit)
|
||||
*
|
||||
* In either case, the render cache is guaranteed to contain the to-be-rendered
|
||||
* placeholder, so replacing (rendering) the placeholder will be very fast.
|
||||
*
|
||||
* Finally, in case the render cache item disappears between the time it is
|
||||
* decided to automatically placeholder the element and the time where the
|
||||
* placeholder is replaced (rendered), that is guaranteed to not be problematic.
|
||||
* Because this only automatically placeholders elements that have a
|
||||
* #lazy_builder callback set, which means that in the worst case, it will need
|
||||
* to be re-rendered.
|
||||
*/
|
||||
class PlaceholderingRenderCache extends RenderCache {
|
||||
|
||||
/**
|
||||
* The placeholder generator.
|
||||
*
|
||||
* @var \Drupal\Core\Render\PlaceholderGeneratorInterface
|
||||
*/
|
||||
protected $placeholderGenerator;
|
||||
|
||||
/**
|
||||
* Stores rendered results for automatically placeholdered elements.
|
||||
*
|
||||
* This allows us to avoid talking to the cache twice per auto-placeholdered
|
||||
* element, or in case of an uncacheable element, to render it twice.
|
||||
*
|
||||
* Scenario A. The double cache read would happen because:
|
||||
* 1. when rendering, cache read, but auto-placeholdered
|
||||
* 2. when rendering placeholders, again cache read
|
||||
*
|
||||
* Scenario B. The cache write plus read would happen because:
|
||||
* 1. when rendering, cache write, but auto-placeholdered
|
||||
* 2. when rendering placeholders, cache read
|
||||
*
|
||||
* Scenario C. The double rendering for an uncacheable element would happen because:
|
||||
* 1. when rendering, not cacheable, but auto-placeholdered
|
||||
* 2. when rendering placeholders, rendered again
|
||||
*
|
||||
* In all three scenarios, this static cache avoids the second step, thus
|
||||
* avoiding expensive work.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $placeholderResultsCache = [];
|
||||
|
||||
/**
|
||||
* Constructs a new PlaceholderingRenderCache object.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack.
|
||||
* @param \Drupal\Core\Cache\CacheFactoryInterface $cache_factory
|
||||
* The cache factory.
|
||||
* @param \Drupal\Core\Cache\Context\CacheContextsManager $cache_contexts_manager
|
||||
* The cache contexts manager.
|
||||
* @param \Drupal\Core\Render\PlaceholderGeneratorInterface $placeholder_generator
|
||||
* The placeholder generator.
|
||||
*/
|
||||
public function __construct(RequestStack $request_stack, CacheFactoryInterface $cache_factory, CacheContextsManager $cache_contexts_manager, PlaceholderGeneratorInterface $placeholder_generator) {
|
||||
parent::__construct($request_stack, $cache_factory, $cache_contexts_manager);
|
||||
$this->placeholderGenerator = $placeholder_generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(array $elements) {
|
||||
// When rendering placeholders, special case auto-placeholdered elements:
|
||||
// avoid retrieving them from cache again, or rendering them again.
|
||||
if (isset($elements['#create_placeholder']) && $elements['#create_placeholder'] === FALSE) {
|
||||
$cached_placeholder_result = $this->getFromPlaceholderResultsCache($elements);
|
||||
if ($cached_placeholder_result !== FALSE) {
|
||||
return $cached_placeholder_result;
|
||||
}
|
||||
}
|
||||
|
||||
$cached_element = parent::get($elements);
|
||||
|
||||
if ($cached_element === FALSE) {
|
||||
return FALSE;
|
||||
}
|
||||
else {
|
||||
if ($this->placeholderGenerator->canCreatePlaceholder($elements) && $this->placeholderGenerator->shouldAutomaticallyPlaceholder($cached_element)) {
|
||||
return $this->createPlaceholderAndRemember($cached_element, $elements);
|
||||
}
|
||||
|
||||
return $cached_element;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set(array &$elements, array $pre_bubbling_elements) {
|
||||
$result = parent::set($elements, $pre_bubbling_elements);
|
||||
|
||||
if ($this->placeholderGenerator->canCreatePlaceholder($pre_bubbling_elements) && $this->placeholderGenerator->shouldAutomaticallyPlaceholder($elements)) {
|
||||
// Overwrite $elements with a placeholder. The Renderer (which called this
|
||||
// method) will update the context with the bubbleable metadata of the
|
||||
// overwritten $elements.
|
||||
$elements = $this->createPlaceholderAndRemember($this->getCacheableRenderArray($elements), $pre_bubbling_elements);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a placeholder for a renderable array and remember in a static cache.
|
||||
*
|
||||
* @param array $rendered_elements
|
||||
* A fully rendered renderable array.
|
||||
* @param array $pre_bubbling_elements
|
||||
* A renderable array corresponding to the state (in particular, the
|
||||
* cacheability metadata) of $rendered_elements prior to the beginning of
|
||||
* its rendering process, and therefore before any bubbling of child
|
||||
* information has taken place. Only the #cache property is used by this
|
||||
* function, so the caller may omit all other properties and children from
|
||||
* this array.
|
||||
*
|
||||
* @return array
|
||||
* Renderable array with placeholder markup and the attached placeholder
|
||||
* replacement metadata.
|
||||
*/
|
||||
protected function createPlaceholderAndRemember(array $rendered_elements, array $pre_bubbling_elements) {
|
||||
$placeholder_element = $this->placeholderGenerator->createPlaceholder($pre_bubbling_elements);
|
||||
// Remember the result for this placeholder to avoid double work.
|
||||
$placeholder = (string) $placeholder_element['#markup'];
|
||||
$this->placeholderResultsCache[$placeholder] = $rendered_elements;
|
||||
return $placeholder_element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an auto-placeholdered renderable array from the static cache.
|
||||
*
|
||||
* @param array $elements
|
||||
* A renderable array.
|
||||
*
|
||||
* @return array|false
|
||||
* A renderable array, with the original element and all its children pre-
|
||||
* rendered, or FALSE if no cached copy of the element is available.
|
||||
*/
|
||||
protected function getFromPlaceholderResultsCache(array $elements) {
|
||||
$placeholder_element = $this->placeholderGenerator->createPlaceholder($elements);
|
||||
$placeholder = (string) $placeholder_element['#markup'];
|
||||
if (isset($this->placeholderResultsCache[$placeholder])) {
|
||||
return $this->placeholderResultsCache[$placeholder];
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
|
@ -27,6 +27,13 @@ class SimplePageVariant extends VariantBase implements PageVariantInterface {
|
|||
*/
|
||||
protected $mainContent;
|
||||
|
||||
/**
|
||||
* The page title: a string (plain title) or a render array (formatted title).
|
||||
*
|
||||
* @var string|array
|
||||
*/
|
||||
protected $title = '';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -35,17 +42,30 @@ class SimplePageVariant extends VariantBase implements PageVariantInterface {
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setTitle($title) {
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
$build = [
|
||||
'content' => [
|
||||
'main_content' => $this->mainContent,
|
||||
'messages' => [
|
||||
'#type' => 'status_messages',
|
||||
'#weight' => -1000,
|
||||
],
|
||||
'page_title' => [
|
||||
'#type' => 'page_title',
|
||||
'#title' => $this->title,
|
||||
'#weight' => -900,
|
||||
],
|
||||
'main_content' => ['#weight' => -800] + $this->mainContent,
|
||||
],
|
||||
];
|
||||
return $build;
|
||||
|
|
|
@ -16,6 +16,11 @@ use Symfony\Component\HttpFoundation\RequestStack;
|
|||
|
||||
/**
|
||||
* Wraps the caching logic for the render caching system.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @todo Refactor this out into a generic service capable of cache redirects,
|
||||
* and let RenderCache use that. https://www.drupal.org/node/2551419
|
||||
*/
|
||||
class RenderCache implements RenderCacheInterface {
|
||||
|
||||
|
@ -336,10 +341,10 @@ class RenderCache implements RenderCacheInterface {
|
|||
// the cache entry size.
|
||||
if (!empty($elements['#cache_properties']) && is_array($elements['#cache_properties'])) {
|
||||
$data['#cache_properties'] = $elements['#cache_properties'];
|
||||
// Ensure that any safe strings are a SafeString object.
|
||||
// Ensure that any safe strings are a Markup object.
|
||||
foreach (Element::properties(array_flip($elements['#cache_properties'])) as $cache_property) {
|
||||
if (isset($elements[$cache_property]) && is_scalar($elements[$cache_property]) && SafeMarkup::isSafe($elements[$cache_property])) {
|
||||
$elements[$cache_property] = SafeString::create($elements[$cache_property]);
|
||||
$elements[$cache_property] = Markup::create($elements[$cache_property]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -352,13 +357,13 @@ class RenderCache implements RenderCacheInterface {
|
|||
// Cache only cacheable children's markup.
|
||||
foreach ($cacheable_children as $key) {
|
||||
// We can assume that #markup is safe at this point.
|
||||
$cacheable_items[$key] = ['#markup' => SafeString::create($cacheable_items[$key]['#markup'])];
|
||||
$cacheable_items[$key] = ['#markup' => Markup::create($cacheable_items[$key]['#markup'])];
|
||||
}
|
||||
}
|
||||
$data += $cacheable_items;
|
||||
}
|
||||
|
||||
$data['#markup'] = SafeString::create($data['#markup']);
|
||||
$data['#markup'] = Markup::create($data['#markup']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,8 +10,9 @@ namespace Drupal\Core\Render;
|
|||
/**
|
||||
* Defines an interface for caching rendered render arrays.
|
||||
*
|
||||
* @see sec_caching
|
||||
* @internal
|
||||
*
|
||||
* @see sec_caching
|
||||
* @see \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
interface RenderCacheInterface {
|
||||
|
|
23
core/lib/Drupal/Core/Render/RenderableInterface.php
Normal file
23
core/lib/Drupal/Core/Render/RenderableInterface.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Render\RenderableInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Render;
|
||||
|
||||
/**
|
||||
* Defines an object which can be rendered by the Render API.
|
||||
*/
|
||||
interface RenderableInterface {
|
||||
|
||||
/**
|
||||
* Returns a render array representation of the object.
|
||||
*
|
||||
* @return mixed[]
|
||||
* A render array.
|
||||
*/
|
||||
public function toRenderable();
|
||||
|
||||
}
|
|
@ -9,13 +9,11 @@ namespace Drupal\Core\Render;
|
|||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Access\AccessResultInterface;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Controller\ControllerResolverInterface;
|
||||
use Drupal\Core\Template\Attribute;
|
||||
use Drupal\Core\Theme\ThemeManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
|
@ -45,6 +43,13 @@ class Renderer implements RendererInterface {
|
|||
*/
|
||||
protected $elementInfo;
|
||||
|
||||
/**
|
||||
* The placeholder generator.
|
||||
*
|
||||
* @var \Drupal\Core\Render\PlaceholderGeneratorInterface
|
||||
*/
|
||||
protected $placeholderGenerator;
|
||||
|
||||
/**
|
||||
* The render cache service.
|
||||
*
|
||||
|
@ -99,6 +104,8 @@ class Renderer implements RendererInterface {
|
|||
* The theme manager.
|
||||
* @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
|
||||
* The element info.
|
||||
* @param \Drupal\Core\Render\PlaceholderGeneratorInterface $placeholder_generator
|
||||
* The placeholder generator.
|
||||
* @param \Drupal\Core\Render\RenderCacheInterface $render_cache
|
||||
* The render cache service.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
|
@ -106,10 +113,11 @@ class Renderer implements RendererInterface {
|
|||
* @param array $renderer_config
|
||||
* The renderer configuration array.
|
||||
*/
|
||||
public function __construct(ControllerResolverInterface $controller_resolver, ThemeManagerInterface $theme, ElementInfoManagerInterface $element_info, RenderCacheInterface $render_cache, RequestStack $request_stack, array $renderer_config) {
|
||||
public function __construct(ControllerResolverInterface $controller_resolver, ThemeManagerInterface $theme, ElementInfoManagerInterface $element_info, PlaceholderGeneratorInterface $placeholder_generator, RenderCacheInterface $render_cache, RequestStack $request_stack, array $renderer_config) {
|
||||
$this->controllerResolver = $controller_resolver;
|
||||
$this->theme = $theme;
|
||||
$this->elementInfo = $element_info;
|
||||
$this->placeholderGenerator = $placeholder_generator;
|
||||
$this->renderCache = $render_cache;
|
||||
$this->rendererConfig = $renderer_config;
|
||||
$this->requestStack = $request_stack;
|
||||
|
@ -179,7 +187,7 @@ class Renderer implements RendererInterface {
|
|||
|
||||
// Replace the placeholder with its rendered markup, and merge its
|
||||
// bubbleable metadata with the main elements'.
|
||||
$elements['#markup'] = SafeString::create(str_replace($placeholder, $markup, $elements['#markup']));
|
||||
$elements['#markup'] = Markup::create(str_replace($placeholder, $markup, $elements['#markup']));
|
||||
$elements = $this->mergeBubbleableMetadata($elements, $placeholder_elements);
|
||||
|
||||
// Remove the placeholder that we've just rendered.
|
||||
|
@ -284,7 +292,7 @@ class Renderer implements RendererInterface {
|
|||
}
|
||||
// Mark the element markup as safe if is it a string.
|
||||
if (is_string($elements['#markup'])) {
|
||||
$elements['#markup'] = SafeString::create($elements['#markup']);
|
||||
$elements['#markup'] = Markup::create($elements['#markup']);
|
||||
}
|
||||
// The render cache item contains all the bubbleable rendering metadata
|
||||
// for the subtree.
|
||||
|
@ -295,12 +303,15 @@ class Renderer implements RendererInterface {
|
|||
return $elements['#markup'];
|
||||
}
|
||||
}
|
||||
// Two-tier caching: track pre-bubbling elements' #cache for later
|
||||
// comparison.
|
||||
// Two-tier caching: track pre-bubbling elements' #cache, #lazy_builder and
|
||||
// #create_placeholder for later comparison.
|
||||
// @see \Drupal\Core\Render\RenderCacheInterface::get()
|
||||
// @see \Drupal\Core\Render\RenderCacheInterface::set()
|
||||
$pre_bubbling_elements = [];
|
||||
$pre_bubbling_elements['#cache'] = isset($elements['#cache']) ? $elements['#cache'] : [];
|
||||
$pre_bubbling_elements = array_intersect_key($elements, [
|
||||
'#cache' => TRUE,
|
||||
'#lazy_builder' => TRUE,
|
||||
'#create_placeholder' => TRUE,
|
||||
]);
|
||||
|
||||
// If the default values for this element have not been loaded yet, populate
|
||||
// them.
|
||||
|
@ -342,7 +353,7 @@ class Renderer implements RendererInterface {
|
|||
}
|
||||
}
|
||||
// Determine whether to do auto-placeholdering.
|
||||
if (isset($elements['#lazy_builder']) && (!isset($elements['#create_placeholder']) || $elements['#create_placeholder'] !== FALSE) && $this->shouldAutomaticallyPlaceholder($elements)) {
|
||||
if ($this->placeholderGenerator->canCreatePlaceholder($elements) && $this->placeholderGenerator->shouldAutomaticallyPlaceholder($elements)) {
|
||||
$elements['#create_placeholder'] = TRUE;
|
||||
}
|
||||
// If instructed to create a placeholder, and a #lazy_builder callback is
|
||||
|
@ -352,7 +363,7 @@ class Renderer implements RendererInterface {
|
|||
if (!isset($elements['#lazy_builder'])) {
|
||||
throw new \LogicException('When #create_placeholder is set, a #lazy_builder callback must be present as well.');
|
||||
}
|
||||
$elements = $this->createPlaceholder($elements);
|
||||
$elements = $this->placeholderGenerator->createPlaceholder($elements);
|
||||
}
|
||||
// Build the element if it is still empty.
|
||||
if (isset($elements['#lazy_builder'])) {
|
||||
|
@ -455,7 +466,7 @@ class Renderer implements RendererInterface {
|
|||
foreach ($children as $key) {
|
||||
$elements['#children'] .= $this->doRender($elements[$key]);
|
||||
}
|
||||
$elements['#children'] = SafeString::create($elements['#children']);
|
||||
$elements['#children'] = Markup::create($elements['#children']);
|
||||
}
|
||||
|
||||
// If #theme is not implemented and the element has raw #markup as a
|
||||
|
@ -466,7 +477,7 @@ class Renderer implements RendererInterface {
|
|||
// 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'] = SafeString::create($elements['#markup'] . $elements['#children']);
|
||||
$elements['#children'] = Markup::create($elements['#markup'] . $elements['#children']);
|
||||
}
|
||||
|
||||
// Let the theme functions in #theme_wrappers add markup around the rendered
|
||||
|
@ -519,7 +530,7 @@ class Renderer implements RendererInterface {
|
|||
$prefix = isset($elements['#prefix']) ? $this->xssFilterAdminIfUnsafe($elements['#prefix']) : '';
|
||||
$suffix = isset($elements['#suffix']) ? $this->xssFilterAdminIfUnsafe($elements['#suffix']) : '';
|
||||
|
||||
$elements['#markup'] = SafeString::create($prefix . $elements['#children'] . $suffix);
|
||||
$elements['#markup'] = Markup::create($prefix . $elements['#children'] . $suffix);
|
||||
|
||||
// We've rendered this element (and its subtree!), now update the context.
|
||||
$context->update($elements);
|
||||
|
@ -531,6 +542,12 @@ class Renderer implements RendererInterface {
|
|||
throw new \LogicException('Cache keys may not be changed after initial setup. Use the contexts property instead to bubble additional metadata.');
|
||||
}
|
||||
$this->renderCache->set($elements, $pre_bubbling_elements);
|
||||
// Update the render context; the render cache implementation may update
|
||||
// the element, and it may have different bubbleable metadata now.
|
||||
// @see \Drupal\Core\Render\PlaceholderingRenderCache::set()
|
||||
$context->pop();
|
||||
$context->push(new BubbleableMetadata());
|
||||
$context->update($elements);
|
||||
}
|
||||
|
||||
// Only when we're in a root (non-recursive) Renderer::render() call,
|
||||
|
@ -643,81 +660,6 @@ class Renderer implements RendererInterface {
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given render array should be automatically placeholdered.
|
||||
*
|
||||
* @param array $element
|
||||
* The render array whose cacheability to analyze.
|
||||
*
|
||||
* @return bool
|
||||
* Whether the given render array's cacheability meets the placeholdering
|
||||
* conditions.
|
||||
*/
|
||||
protected function shouldAutomaticallyPlaceholder(array $element) {
|
||||
$conditions = $this->rendererConfig['auto_placeholder_conditions'];
|
||||
|
||||
// Auto-placeholder if max-age is at or below the configured threshold.
|
||||
if (isset($element['#cache']['max-age']) && $element['#cache']['max-age'] !== Cache::PERMANENT && $element['#cache']['max-age'] <= $conditions['max-age']) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Auto-placeholder if a high-cardinality cache context is set.
|
||||
if (isset($element['#cache']['contexts']) && array_intersect($element['#cache']['contexts'], $conditions['contexts'])) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Auto-placeholder if a high-invalidation frequency cache tag is set.
|
||||
if (isset($element['#cache']['tags']) && array_intersect($element['#cache']['tags'], $conditions['tags'])) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns this element into a placeholder.
|
||||
*
|
||||
* Placeholdering allows us to avoid "poor cacheability contamination": this
|
||||
* maps the current render array to one that only has #markup and #attached,
|
||||
* and #attached contains a placeholder with this element's prior cacheability
|
||||
* metadata. In other words: this placeholder is perfectly cacheable, the
|
||||
* placeholder replacement logic effectively cordons off poor cacheability.
|
||||
*
|
||||
* @param array $element
|
||||
* The render array to create a placeholder for.
|
||||
*
|
||||
* @return array
|
||||
* Render array with placeholder markup and the attached placeholder
|
||||
* replacement metadata.
|
||||
*/
|
||||
protected function createPlaceholder(array $element) {
|
||||
$placeholder_render_array = array_intersect_key($element, [
|
||||
// Placeholders are replaced with markup by executing the associated
|
||||
// #lazy_builder callback, which generates a render array, and which the
|
||||
// Renderer will render and replace the placeholder with.
|
||||
'#lazy_builder' => TRUE,
|
||||
// The cacheability metadata for the placeholder. The rendered result of
|
||||
// the placeholder may itself be cached, if [#cache][keys] are specified.
|
||||
'#cache' => TRUE,
|
||||
]);
|
||||
|
||||
// Generate placeholder markup. Note that the only requirement is that this
|
||||
// is unique markup that isn't easily guessable. The #lazy_builder callback
|
||||
// and its arguments are put in the placeholder markup solely to simplify
|
||||
// debugging.
|
||||
$attributes = new Attribute();
|
||||
$attributes['callback'] = $placeholder_render_array['#lazy_builder'][0];
|
||||
$attributes['arguments'] = UrlHelper::buildQuery($placeholder_render_array['#lazy_builder'][1]);
|
||||
$attributes['token'] = hash('crc32b', serialize($placeholder_render_array));
|
||||
$placeholder_markup = SafeMarkup::format('<drupal-render-placeholder@attributes></drupal-render-placeholder>', ['@attributes' => $attributes]);
|
||||
|
||||
// Build the placeholder element to return.
|
||||
$placeholder_element = [];
|
||||
$placeholder_element['#markup'] = $placeholder_markup;
|
||||
$placeholder_element['#attached']['placeholders'][$placeholder_markup] = $placeholder_render_array;
|
||||
return $placeholder_element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -743,18 +685,18 @@ class Renderer implements RendererInterface {
|
|||
* Note: This method only filters if $string is not marked safe already. This
|
||||
* ensures that HTML intended for display is not filtered.
|
||||
*
|
||||
* @param string|\Drupal\Core\Render\SafeString $string
|
||||
* @param string|\Drupal\Core\Render\Markup $string
|
||||
* A string.
|
||||
*
|
||||
* @return \Drupal\Core\Render\SafeString
|
||||
* The escaped string wrapped in a SafeString object. If
|
||||
* @return \Drupal\Core\Render\Markup
|
||||
* The escaped string wrapped in a Markup object. If
|
||||
* SafeMarkup::isSafe($string) returns TRUE, it won't be escaped again.
|
||||
*/
|
||||
protected function xssFilterAdminIfUnsafe($string) {
|
||||
if (!SafeMarkup::isSafe($string)) {
|
||||
$string = Xss::filterAdmin($string);
|
||||
}
|
||||
return SafeString::create($string);
|
||||
return Markup::create($string);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -775,8 +717,8 @@ class Renderer implements RendererInterface {
|
|||
* @param array $elements
|
||||
* A render array with #markup set.
|
||||
*
|
||||
* @return \Drupal\Component\Utility\SafeStringInterface|string
|
||||
* The escaped markup wrapped in a SafeString object. If
|
||||
* @return \Drupal\Component\Render\MarkupInterface|string
|
||||
* The escaped markup wrapped in a Markup object. If
|
||||
* SafeMarkup::isSafe($elements['#markup']) returns TRUE, it won't be
|
||||
* escaped or filtered again.
|
||||
*
|
||||
|
@ -790,12 +732,12 @@ class Renderer implements RendererInterface {
|
|||
}
|
||||
|
||||
if (!empty($elements['#plain_text'])) {
|
||||
$elements['#markup'] = SafeString::create(Html::escape($elements['#plain_text']));
|
||||
$elements['#markup'] = Markup::create(Html::escape($elements['#plain_text']));
|
||||
}
|
||||
elseif (!SafeMarkup::isSafe($elements['#markup'])) {
|
||||
// The default behaviour is to XSS filter using the admin tag list.
|
||||
$tags = isset($elements['#allowed_tags']) ? $elements['#allowed_tags'] : Xss::getAdminTagList();
|
||||
$elements['#markup'] = SafeString::create(Xss::filter($elements['#markup'], $tags));
|
||||
$elements['#markup'] = Markup::create(Xss::filter($elements['#markup'], $tags));
|
||||
}
|
||||
|
||||
return $elements;
|
||||
|
|
|
@ -27,7 +27,7 @@ interface RendererInterface {
|
|||
* @param array $elements
|
||||
* The structured array describing the data to be rendered.
|
||||
*
|
||||
* @return \Drupal\Component\Utility\SafeStringInterface
|
||||
* @return \Drupal\Component\Render\MarkupInterface
|
||||
* The rendered HTML.
|
||||
*
|
||||
* @see ::render()
|
||||
|
@ -42,7 +42,7 @@ interface RendererInterface {
|
|||
*
|
||||
* Calls ::render() in such a way that placeholders are replaced.
|
||||
*
|
||||
* Useful for e.g. rendering the values of tokens or e-mails, which need a
|
||||
* Useful for e.g. rendering the values of tokens or emails, which need a
|
||||
* render array being turned into a string, but don't need any of the
|
||||
* bubbleable metadata (the attached assets the cache tags).
|
||||
*
|
||||
|
@ -58,7 +58,7 @@ interface RendererInterface {
|
|||
* @param array $elements
|
||||
* The structured array describing the data to be rendered.
|
||||
*
|
||||
* @return \Drupal\Component\Utility\SafeStringInterface
|
||||
* @return \Drupal\Component\Render\MarkupInterface
|
||||
* The rendered HTML.
|
||||
*
|
||||
* @see ::renderRoot()
|
||||
|
@ -209,7 +209,7 @@ interface RendererInterface {
|
|||
* 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().
|
||||
* \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments().
|
||||
* - 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()
|
||||
|
@ -302,7 +302,7 @@ interface RendererInterface {
|
|||
* (Internal use only.) Whether this is a recursive call or not. See
|
||||
* ::renderRoot().
|
||||
*
|
||||
* @return \Drupal\Component\Utility\SafeStringInterface
|
||||
* @return \Drupal\Component\Render\MarkupInterface
|
||||
* The rendered HTML.
|
||||
*
|
||||
* @throws \LogicException
|
||||
|
@ -316,7 +316,7 @@ interface RendererInterface {
|
|||
* @see \Drupal\Core\Render\ElementInfoManagerInterface::getInfo()
|
||||
* @see \Drupal\Core\Theme\ThemeManagerInterface::render()
|
||||
* @see drupal_process_states()
|
||||
* @see drupal_process_attached()
|
||||
* @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments()
|
||||
* @see ::renderRoot()
|
||||
*/
|
||||
public function render(&$elements, $is_root_call = FALSE);
|
||||
|
|
|
@ -70,7 +70,10 @@
|
|||
* hook_theme() implementations can also specify that a theme hook
|
||||
* implementation is a theme function, but that is uncommon. It is only used for
|
||||
* special cases, for performance reasons, because rendering using theme
|
||||
* functions is somewhat faster than theme templates.
|
||||
* functions is somewhat faster than theme templates. Note that while Twig
|
||||
* templates will auto-escape variables, theme functions must explicitly escape
|
||||
* any variables by using theme_render_and_autoescape(). Failure to do so is
|
||||
* likely to result in security vulnerabilities.
|
||||
*
|
||||
* @section sec_overriding_theme_hooks Overriding Theme Hooks
|
||||
* Themes may register new theme hooks within a hook_theme() implementation, but
|
||||
|
@ -93,6 +96,9 @@
|
|||
* bartik_search_result() in the bartik.theme file, if the search_result hook
|
||||
* implementation was a function instead of a template). Normally, copying the
|
||||
* default function is again a good starting point for overriding its behavior.
|
||||
* Again, note that theme functions (unlike templates) must explicitly escape
|
||||
* variables using theme_render_and_autoescape() or risk security
|
||||
* vulnerabilities.
|
||||
*
|
||||
* @section sec_preprocess_templates Preprocessing for Template Files
|
||||
* If the theme implementation is a template file, several functions are called
|
||||
|
@ -375,7 +381,10 @@
|
|||
* Libraries, JavaScript settings, feeds, HTML <head> tags and HTML <head> links
|
||||
* are attached to elements using the #attached property. The #attached property
|
||||
* is an associative array, where the keys are the attachment types and the
|
||||
* values are the attached data. For example:
|
||||
* values are the attached data.
|
||||
*
|
||||
* The #attached property can also be used to specify HTTP headers and the
|
||||
* response status code.
|
||||
*
|
||||
* The #attached property allows loading of asset libraries (which may contain
|
||||
* CSS assets, JavaScript assets, and JavaScript setting assets), JavaScript
|
||||
|
@ -386,10 +395,11 @@
|
|||
* @code
|
||||
* $build['#attached']['library'][] = 'core/jquery';
|
||||
* $build['#attached']['drupalSettings']['foo'] = 'bar';
|
||||
* $build['#attached']['feed'][] = ['aggregator/rss', $this->t('Feed title')];
|
||||
* $build['#attached']['feed'][] = [$url, $this->t('Feed title')];
|
||||
* @endcode
|
||||
*
|
||||
* See drupal_process_attached() for additional information.
|
||||
* See \Drupal\Core\Render\AttachmentsResponseProcessorInterface for additional
|
||||
* information.
|
||||
*
|
||||
* See \Drupal\Core\Asset\LibraryDiscoveryParser::parseLibraryInfo() for more
|
||||
* information on how to define libraries.
|
||||
|
|
Reference in a new issue