Update to Drupal 8.0.0-beta15. For more information, see: https://www.drupal.org/node/2563023
This commit is contained in:
parent
2720a9ec4b
commit
f3791f1da3
1898 changed files with 54300 additions and 11481 deletions
|
@ -40,6 +40,8 @@ class Date extends FormElement {
|
|||
'#process' => [[$class, 'processDate']],
|
||||
'#pre_render' => [[$class, 'preRenderDate']],
|
||||
'#theme_wrappers' => ['form_element'],
|
||||
'#attributes' => ['type' => 'date'],
|
||||
'#date_date_format' => 'Y-m-d',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
|
||||
namespace Drupal\Core\Render\Element;
|
||||
|
||||
use Drupal\Component\Utility\Html as HtmlUtility;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Render\SafeString;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Template\Attribute;
|
||||
|
||||
|
@ -46,30 +48,17 @@ class HtmlTag extends RenderElement {
|
|||
/**
|
||||
* Pre-render callback: Renders a generic HTML tag with attributes into #markup.
|
||||
*
|
||||
* Note: It is the caller's responsibility to sanitize any input parameters.
|
||||
* This callback does not perform sanitization. Despite the result of this
|
||||
* pre-render callback being a #markup element, it is not passed through
|
||||
* \Drupal\Component\Utility\Xss::filterAdmin(). This is because it is marked
|
||||
* safe here, which causes
|
||||
* \Drupal\Core\Render\Renderer::xssFilterAdminIfUnsafe() to regard it as safe
|
||||
* and bypass the call to \Drupal\Component\Utility\Xss::filterAdmin().
|
||||
*
|
||||
* @param array $element
|
||||
* An associative array containing:
|
||||
* - #tag: The tag name to output. Typical tags added to the HTML HEAD:
|
||||
* - meta: To provide meta information, such as a page refresh.
|
||||
* - link: To refer to stylesheets and other contextual information.
|
||||
* - script: To load JavaScript.
|
||||
* The value of #tag is not escaped or sanitized, so do not pass in user
|
||||
* input.
|
||||
* The value of #tag is escaped.
|
||||
* - #attributes: (optional) An array of HTML attributes to apply to the
|
||||
* tag.
|
||||
* tag. The attributes are escaped, see \Drupal\Core\Template\Attribute.
|
||||
* - #value: (optional) A string containing tag content, such as inline
|
||||
* CSS.
|
||||
* - #value_prefix: (optional) A string to prepend to #value, e.g. a CDATA
|
||||
* wrapper prefix.
|
||||
* - #value_suffix: (optional) A string to append to #value, e.g. a CDATA
|
||||
* wrapper suffix.
|
||||
* CSS. The value of #value will be XSS admin filtered if it is not safe.
|
||||
* - #noscript: (optional) If TRUE, the markup (including any prefix or
|
||||
* suffix) will be wrapped in a <noscript> element. (Note that passing
|
||||
* any non-empty value here will add the <noscript> tag.)
|
||||
|
@ -79,35 +68,24 @@ class HtmlTag extends RenderElement {
|
|||
public static function preRenderHtmlTag($element) {
|
||||
$attributes = isset($element['#attributes']) ? new Attribute($element['#attributes']) : '';
|
||||
|
||||
// An HTML tag should not contain any special characters. Escape them to
|
||||
// ensure this cannot be abused.
|
||||
$escaped_tag = HtmlUtility::escape($element['#tag']);
|
||||
$markup = '<' . $escaped_tag . $attributes;
|
||||
// Construct a void element.
|
||||
if (in_array($element['#tag'], self::$voidElements)) {
|
||||
// This function is intended for internal use, so we assume that no unsafe
|
||||
// values are passed in #tag. The attributes are already safe because
|
||||
// Attribute output is already automatically sanitized.
|
||||
// @todo Escape this properly instead? https://www.drupal.org/node/2296101
|
||||
$markup = SafeMarkup::set('<' . $element['#tag'] . $attributes . " />\n");
|
||||
$markup .= " />\n";
|
||||
}
|
||||
// Construct all other elements.
|
||||
else {
|
||||
$markup = '<' . $element['#tag'] . $attributes . '>';
|
||||
if (isset($element['#value_prefix'])) {
|
||||
$markup .= $element['#value_prefix'];
|
||||
}
|
||||
$markup .= $element['#value'];
|
||||
if (isset($element['#value_suffix'])) {
|
||||
$markup .= $element['#value_suffix'];
|
||||
}
|
||||
$markup .= '</' . $element['#tag'] . ">\n";
|
||||
// @todo We cannot actually guarantee this markup is safe. Consider a fix
|
||||
// in: https://www.drupal.org/node/2296101
|
||||
$markup = SafeMarkup::set($markup);
|
||||
$markup .= '>';
|
||||
$markup .= SafeMarkup::isSafe($element['#value']) ? $element['#value'] : Xss::filterAdmin($element['#value']);
|
||||
$markup .= '</' . $escaped_tag . ">\n";
|
||||
}
|
||||
if (!empty($element['#noscript'])) {
|
||||
$element['#markup'] = SafeMarkup::format('<noscript>@markup</noscript>', ['@markup' => $markup]);
|
||||
}
|
||||
else {
|
||||
$element['#markup'] = $markup;
|
||||
$markup = "<noscript>$markup</noscript>";
|
||||
}
|
||||
$element['#markup'] = SafeString::create($markup);
|
||||
return $element;
|
||||
}
|
||||
|
||||
|
@ -174,20 +152,27 @@ class HtmlTag extends RenderElement {
|
|||
|
||||
// Ensure what we are dealing with is safe.
|
||||
// This would be done later anyway in drupal_render().
|
||||
$prefix = isset($elements['#prefix']) ? Xss::filterAdmin($elements['#prefix']) : '';
|
||||
$suffix = isset($elements['#suffix']) ? Xss::filterAdmin($elements['#suffix']) : '';
|
||||
$prefix = isset($element['#prefix']) ? $element['#prefix'] : '';
|
||||
if ($prefix && !SafeMarkup::isSafe($prefix)) {
|
||||
$prefix = Xss::filterAdmin($prefix);
|
||||
}
|
||||
$suffix = isset($element['#suffix']) ? $element['#suffix'] : '';
|
||||
if ($suffix && !SafeMarkup::isSafe($suffix)) {
|
||||
$suffix = Xss::filterAdmin($suffix);
|
||||
}
|
||||
|
||||
// Now calling SafeMarkup::set is safe, because we ensured the
|
||||
// data coming in was at least admin escaped.
|
||||
// We ensured above that $expression is either a string we created or is
|
||||
// admin XSS filtered, and that $prefix and $suffix are also admin XSS
|
||||
// filtered if they are unsafe. Thus, all these strings are safe.
|
||||
if (!$browsers['!IE']) {
|
||||
// "downlevel-hidden".
|
||||
$element['#prefix'] = SafeMarkup::set("\n<!--[if $expression]>\n" . $prefix);
|
||||
$element['#suffix'] = SafeMarkup::set($suffix . "<![endif]-->\n");
|
||||
$element['#prefix'] = SafeString::create("\n<!--[if $expression]>\n" . $prefix);
|
||||
$element['#suffix'] = SafeString::create($suffix . "<![endif]-->\n");
|
||||
}
|
||||
else {
|
||||
// "downlevel-revealed".
|
||||
$element['#prefix'] = SafeMarkup::set("\n<!--[if $expression]><!-->\n" . $prefix);
|
||||
$element['#suffix'] = SafeMarkup::set($suffix . "<!--<![endif]-->\n");
|
||||
$element['#prefix'] = SafeString::create("\n<!--[if $expression]><!-->\n" . $prefix);
|
||||
$element['#suffix'] = SafeString::create($suffix . "<!--<![endif]-->\n");
|
||||
}
|
||||
|
||||
return $element;
|
||||
|
|
|
@ -104,6 +104,11 @@ class MachineName extends Textfield {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
|
||||
if ($input !== FALSE && $input !== NULL) {
|
||||
// This should be a string, but allow other scalars since they might be
|
||||
// valid input in programmatic form submissions.
|
||||
return is_scalar($input) ? (string) $input : '';
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -135,7 +140,7 @@ class MachineName extends Textfield {
|
|||
);
|
||||
// A form element that only wants to set one #machine_name property (usually
|
||||
// 'source' only) would leave all other properties undefined, if the defaults
|
||||
// were defined in hook_element_info(). Therefore, we apply the defaults here.
|
||||
// were defined by an element plugin. Therefore, we apply the defaults here.
|
||||
$element['#machine_name'] += array(
|
||||
'source' => array('label'),
|
||||
'target' => '#' . $element['#id'],
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\Core\Render\Element;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
|
||||
/**
|
||||
|
@ -68,4 +69,16 @@ class Password extends FormElement {
|
|||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
|
||||
if ($input !== FALSE && $input !== NULL) {
|
||||
// This should be a string, but allow other scalars since they might be
|
||||
// valid input in programmatic form submissions.
|
||||
return is_scalar($input) ? (string) $input : '';
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -50,9 +50,20 @@ class PasswordConfirm extends FormElement {
|
|||
*/
|
||||
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
|
||||
if ($input === FALSE) {
|
||||
$element += array('#default_value' => array());
|
||||
return $element['#default_value'] + array('pass1' => '', 'pass2' => '');
|
||||
$element += ['#default_value' => []];
|
||||
return $element['#default_value'] + ['pass1' => '', 'pass2' => ''];
|
||||
}
|
||||
$value = ['pass1' => '', 'pass2' => ''];
|
||||
// Throw out all invalid array keys; we only allow pass1 and pass2.
|
||||
foreach ($value as $allowed_key => $default) {
|
||||
// These should be strings, but allow other scalars since they might be
|
||||
// valid input in programmatic form submissions. Any nested array values
|
||||
// are ignored.
|
||||
if (isset($input[$allowed_key]) && is_scalar($input[$allowed_key])) {
|
||||
$value[$allowed_key] = (string) $input[$allowed_key];
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -304,6 +304,7 @@ abstract class RenderElement extends PluginBase implements ElementInterface {
|
|||
}
|
||||
|
||||
$element['#attached']['drupalSettings']['ajax'][$element['#id']] = $settings;
|
||||
$element['#attached']['drupalSettings']['ajaxTrustedUrl'][$settings['url']] = TRUE;
|
||||
|
||||
// Indicate that Ajax processing was successful.
|
||||
$element['#ajax_processed'] = TRUE;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\Core\Render\Element;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
|
||||
/**
|
||||
|
@ -55,4 +56,15 @@ class Textarea extends FormElement {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
|
||||
if ($input !== FALSE && $input !== NULL) {
|
||||
// This should be a string, but allow other scalars since they might be
|
||||
// valid input in programmatic form submissions.
|
||||
return is_scalar($input) ? (string) $input : '';
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,10 +77,14 @@ class Textfield extends FormElement {
|
|||
*/
|
||||
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
|
||||
if ($input !== FALSE && $input !== NULL) {
|
||||
// Equate $input to the form value to ensure it's marked for
|
||||
// validation.
|
||||
// This should be a string, but allow other scalars since they might be
|
||||
// valid input in programmatic form submissions.
|
||||
if (!is_scalar($input)) {
|
||||
$input = '';
|
||||
}
|
||||
return str_replace(array("\r", "\n"), '', $input);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -39,9 +39,12 @@ class Token extends Hidden {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
|
||||
if ($input !== FALSE) {
|
||||
return (string) $input;
|
||||
if ($input !== FALSE && $input !== NULL) {
|
||||
// This should be a string, but allow other scalars since they might be
|
||||
// valid input in programmatic form submissions.
|
||||
return is_scalar($input) ? (string) $input : '';
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ use Drupal\Core\Render\Element;
|
|||
* '#size' => 30,
|
||||
* ...
|
||||
* );
|
||||
* @end_code
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\Core\Render\Element\Textfield
|
||||
*
|
||||
|
|
|
@ -71,6 +71,10 @@ class VerticalTabs extends RenderElement {
|
|||
* The processed element.
|
||||
*/
|
||||
public static function processVerticalTabs(&$element, FormStateInterface $form_state, &$complete_form) {
|
||||
if (isset($element['#access']) && !$element['#access']) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
// Inject a new details as child, so that form_process_details() processes
|
||||
// this details element like any other details.
|
||||
$element['group'] = array(
|
||||
|
|
|
@ -110,10 +110,7 @@ class ElementInfoManager extends DefaultPluginManager implements ElementInfoMana
|
|||
}
|
||||
|
||||
// Otherwise, rebuild and cache.
|
||||
// @todo Remove this hook once all elements are converted to plugins in
|
||||
// https://www.drupal.org/node/2311393.
|
||||
$info = $this->moduleHandler->invokeAll('element_info');
|
||||
|
||||
$info = [];
|
||||
foreach ($this->getDefinitions() as $element_type => $definition) {
|
||||
$element = $this->createInstance($element_type);
|
||||
$element_info = $element->getInfo();
|
||||
|
|
|
@ -44,8 +44,6 @@ interface ElementInfoManagerInterface {
|
|||
* - #title_display: optional string indicating if and how #title should be
|
||||
* displayed (see form-element.html.twig).
|
||||
*
|
||||
* @see hook_element_info()
|
||||
* @see hook_element_info_alter()
|
||||
* @see \Drupal\Core\Render\Element\ElementInterface
|
||||
* @see \Drupal\Core\Render\Element\ElementInterface::getInfo()
|
||||
*/
|
||||
|
@ -55,7 +53,7 @@ interface ElementInfoManagerInterface {
|
|||
* Retrieves a single property for the defined element type.
|
||||
*
|
||||
* @param string $type
|
||||
* An element type as defined by hook_element_info().
|
||||
* An element type as defined by an element plugin.
|
||||
* @param string $property_name
|
||||
* The property within the element type that should be returned.
|
||||
* @param $default
|
||||
|
|
|
@ -36,12 +36,16 @@ class HtmlResponse extends Response implements CacheableResponseInterface, Attac
|
|||
// A render array can automatically be converted to a string and set the
|
||||
// necessary metadata.
|
||||
if (is_array($content) && (isset($content['#markup']))) {
|
||||
$content += ['#attached' => ['html_response_placeholders' => []]];
|
||||
$content += ['#attached' => [
|
||||
'html_response_attachment_placeholders' => [],
|
||||
'placeholders' => []],
|
||||
];
|
||||
$this->addCacheableDependency(CacheableMetadata::createFromRenderArray($content));
|
||||
$this->setAttachments($content['#attached']);
|
||||
$content = $content['#markup'];
|
||||
}
|
||||
|
||||
parent::setContent($content);
|
||||
return parent::setContent($content);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ use Drupal\Core\Asset\AssetCollectionRendererInterface;
|
|||
use Drupal\Core\Asset\AssetResolverInterface;
|
||||
use Drupal\Core\Asset\AttachedAssets;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Form\EnforcedResponseException;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
|
@ -99,30 +100,96 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
|
|||
throw new \InvalidArgumentException('\Drupal\Core\Render\HtmlResponse instance expected.');
|
||||
}
|
||||
|
||||
// First, render the actual placeholders; this may cause additional
|
||||
// attachments to be added to the response, which the attachment
|
||||
// placeholders rendered by renderHtmlResponseAttachmentPlaceholders() will
|
||||
// need to include.
|
||||
//
|
||||
// @todo Exceptions should not be used for code flow control. However, the
|
||||
// Form API does not integrate with the HTTP Kernel based architecture of
|
||||
// Drupal 8. In order to resolve this issue properly it is necessary to
|
||||
// completely separate form submission from rendering.
|
||||
// @see https://www.drupal.org/node/2367555
|
||||
try {
|
||||
$response = $this->renderPlaceholders($response);
|
||||
}
|
||||
catch (EnforcedResponseException $e) {
|
||||
return $e->getResponse();
|
||||
}
|
||||
|
||||
$attached = $response->getAttachments();
|
||||
|
||||
// Get the placeholders from attached and then remove them.
|
||||
$placeholders = $attached['html_response_placeholders'];
|
||||
unset($attached['html_response_placeholders']);
|
||||
$attachment_placeholders = $attached['html_response_attachment_placeholders'];
|
||||
unset($attached['html_response_attachment_placeholders']);
|
||||
|
||||
$variables = $this->processAssetLibraries($attached, $placeholders);
|
||||
$variables = $this->processAssetLibraries($attached, $attachment_placeholders);
|
||||
|
||||
// Handle all non-asset attachments. This populates drupal_get_html_head()
|
||||
// and drupal_get_http_header().
|
||||
// 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($placeholders['head'])) {
|
||||
if (isset($attachment_placeholders['head'])) {
|
||||
$variables['head'] = drupal_get_html_head(FALSE);
|
||||
}
|
||||
|
||||
// Now replace the placeholders in the response content with the real data.
|
||||
$this->renderPlaceholders($response, $placeholders, $variables);
|
||||
// Now replace the attachment placeholders.
|
||||
$this->renderHtmlResponseAttachmentPlaceholders($response, $attachment_placeholders, $variables);
|
||||
|
||||
// Finally set the headers on the response.
|
||||
$headers = drupal_get_http_header();
|
||||
$this->setHeaders($response, $headers);
|
||||
// Finally set the headers on the response if any bubbled.
|
||||
if (!empty($attached['http_header'])) {
|
||||
$this->setHeaders($response, $attached['http_header']);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders placeholders (#attached['placeholders']).
|
||||
*
|
||||
* First, the HTML response object is converted to an equivalent render array,
|
||||
* with #markup being set to the response's content and #attached being set to
|
||||
* the response's attachments. Among these attachments, there may be
|
||||
* placeholders that need to be rendered (replaced).
|
||||
*
|
||||
* Next, RendererInterface::renderRoot() is called, which renders the
|
||||
* placeholders into their final markup.
|
||||
*
|
||||
* The markup that results from RendererInterface::renderRoot() is now the
|
||||
* original HTML response's content, but with the placeholders rendered. We
|
||||
* overwrite the existing content in the original HTML response object with
|
||||
* this markup. The markup that was rendered for the placeholders may also
|
||||
* have attachments (e.g. for CSS/JS assets) itself, and cacheability metadata
|
||||
* that indicates what that markup depends on. That metadata is also added to
|
||||
* the HTML response object.
|
||||
*
|
||||
* @param \Drupal\Core\Render\HtmlResponse $response
|
||||
* The HTML response whose placeholders are being replaced.
|
||||
*
|
||||
* @return \Drupal\Core\Render\HtmlResponse
|
||||
* The updated HTML response, with replaced placeholders.
|
||||
*
|
||||
* @see \Drupal\Core\Render\Renderer::replacePlaceholders()
|
||||
* @see \Drupal\Core\Render\Renderer::renderPlaceholder()
|
||||
*/
|
||||
protected function renderPlaceholders(HtmlResponse $response) {
|
||||
$build = [
|
||||
'#markup' => SafeString::create($response->getContent()),
|
||||
'#attached' => $response->getAttachments(),
|
||||
];
|
||||
// RendererInterface::renderRoot() renders the $build render array and
|
||||
// updates it in place. We don't care about the return value (which is just
|
||||
// $build['#markup']), but about the resulting render array.
|
||||
// @todo Simplify this when https://www.drupal.org/node/2495001 lands.
|
||||
$this->renderer->renderRoot($build);
|
||||
|
||||
// Update the Response object now that the placeholders have been rendered.
|
||||
$placeholders_bubbleable_metadata = BubbleableMetadata::createFromRenderArray($build);
|
||||
$response
|
||||
->setContent($build['#markup'])
|
||||
->addCacheableDependency($placeholders_bubbleable_metadata)
|
||||
->setAttachments($placeholders_bubbleable_metadata->getAttachments());
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
@ -164,7 +231,7 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
|
|||
// Print scripts - if any are present.
|
||||
if (isset($placeholders['scripts']) || isset($placeholders['scripts_bottom'])) {
|
||||
// Optimize JS if necessary, but only during normal site operation.
|
||||
$optimize_js = !defined('MAINTENANCE_MODE') && $this->config->get('js.preprocess');
|
||||
$optimize_js = !defined('MAINTENANCE_MODE') && !\Drupal::state()->get('system.maintenance_mode') && $this->config->get('js.preprocess');
|
||||
list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, $optimize_js);
|
||||
$variables['scripts'] = $this->jsCollectionRenderer->render($js_assets_header);
|
||||
$variables['scripts_bottom'] = $this->jsCollectionRenderer->render($js_assets_footer);
|
||||
|
@ -174,8 +241,7 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
|
|||
}
|
||||
|
||||
/**
|
||||
* Renders variables into HTML markup and replaces placeholders in the
|
||||
* response content.
|
||||
* Renders HTML response attachment placeholders.
|
||||
*
|
||||
* @param \Drupal\Core\Render\HtmlResponse $response
|
||||
* The HTML response to update.
|
||||
|
@ -186,7 +252,7 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
|
|||
* The variables to render and replace, keyed by type with renderable
|
||||
* arrays as values.
|
||||
*/
|
||||
protected function renderPlaceholders(HtmlResponse $response, array $placeholders, array $variables) {
|
||||
protected function renderHtmlResponseAttachmentPlaceholders(HtmlResponse $response, array $placeholders, array $variables) {
|
||||
$content = $response->getContent();
|
||||
foreach ($placeholders as $type => $placeholder) {
|
||||
if (isset($variables[$type])) {
|
||||
|
@ -205,13 +271,17 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
|
|||
* The headers to set.
|
||||
*/
|
||||
protected function setHeaders(HtmlResponse $response, array $headers) {
|
||||
foreach ($headers as $name => $value) {
|
||||
foreach ($headers as $values) {
|
||||
$name = $values[0];
|
||||
$value = $values[1];
|
||||
$replace = !empty($values[2]);
|
||||
|
||||
// Drupal treats the HTTP response status code like a header, even though
|
||||
// it really is not.
|
||||
if ($name === 'status') {
|
||||
if (strtolower($name) === 'status') {
|
||||
$response->setStatusCode($value);
|
||||
}
|
||||
$response->headers->set($name, $value, FALSE);
|
||||
$response->headers->set($name, $value, $replace);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ class AjaxRenderer implements MainContentRendererInterface {
|
|||
}
|
||||
}
|
||||
|
||||
$html = (string) $this->drupalRenderRoot($main_content);
|
||||
$html = $this->drupalRenderRoot($main_content);
|
||||
$response->setAttachments($main_content['#attached']);
|
||||
|
||||
// The selector for the insert command is NULL as the new content will
|
||||
|
@ -72,7 +72,7 @@ class AjaxRenderer implements MainContentRendererInterface {
|
|||
// behavior can be changed with #ajax['method'].
|
||||
$response->addCommand(new InsertCommand(NULL, $html));
|
||||
$status_messages = array('#type' => 'status_messages');
|
||||
$output = (string) $this->drupalRenderRoot($status_messages);
|
||||
$output = $this->drupalRenderRoot($status_messages);
|
||||
if (!empty($output)) {
|
||||
$response->addCommand(new PrependCommand(NULL, $output));
|
||||
}
|
||||
|
|
|
@ -8,9 +8,11 @@
|
|||
namespace Drupal\Core\Render\MainContent;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Controller\TitleResolverInterface;
|
||||
use Drupal\Core\Display\PageVariantInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\Render\HtmlResponse;
|
||||
use Drupal\Core\Render\PageDisplayVariantSelectionEvent;
|
||||
use Drupal\Core\Render\RenderCacheInterface;
|
||||
|
@ -74,6 +76,15 @@ class HtmlRenderer implements MainContentRendererInterface {
|
|||
*/
|
||||
protected $renderCache;
|
||||
|
||||
/**
|
||||
* The renderer configuration array.
|
||||
*
|
||||
* @see sites/default/default.services.yml
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $rendererConfig;
|
||||
|
||||
/**
|
||||
* Constructs a new HtmlRenderer.
|
||||
*
|
||||
|
@ -89,14 +100,17 @@ class HtmlRenderer implements MainContentRendererInterface {
|
|||
* The renderer service.
|
||||
* @param \Drupal\Core\Render\RenderCacheInterface $render_cache
|
||||
* The render cache service.
|
||||
* @param array $renderer_config
|
||||
* The renderer configuration array.
|
||||
*/
|
||||
public function __construct(TitleResolverInterface $title_resolver, PluginManagerInterface $display_variant_manager, EventDispatcherInterface $event_dispatcher, ModuleHandlerInterface $module_handler, RendererInterface $renderer, RenderCacheInterface $render_cache) {
|
||||
public function __construct(TitleResolverInterface $title_resolver, PluginManagerInterface $display_variant_manager, EventDispatcherInterface $event_dispatcher, ModuleHandlerInterface $module_handler, RendererInterface $renderer, RenderCacheInterface $render_cache, array $renderer_config) {
|
||||
$this->titleResolver = $title_resolver;
|
||||
$this->displayVariantManager = $display_variant_manager;
|
||||
$this->eventDispatcher = $event_dispatcher;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->renderer = $renderer;
|
||||
$this->renderCache = $render_cache;
|
||||
$this->rendererConfig = $renderer_config;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -125,11 +139,29 @@ class HtmlRenderer implements MainContentRendererInterface {
|
|||
// page.html.twig, hence add them here, just before rendering html.html.twig.
|
||||
$this->buildPageTopAndBottom($html);
|
||||
|
||||
// @todo https://www.drupal.org/node/2495001 Make renderRoot return a
|
||||
// cacheable render array directly.
|
||||
$this->renderer->renderRoot($html);
|
||||
// Render, but don't replace placeholders yet, because that happens later in
|
||||
// the render pipeline. To not replace placeholders yet, we use
|
||||
// RendererInterface::render() instead of RendererInterface::renderRoot().
|
||||
// @see \Drupal\Core\Render\HtmlResponseAttachmentsProcessor.
|
||||
$render_context = new RenderContext();
|
||||
$this->renderer->executeInRenderContext($render_context, function() use (&$html) {
|
||||
// RendererInterface::render() renders the $html render array and updates
|
||||
// it in place. We don't care about the return value (which is just
|
||||
// $html['#markup']), but about the resulting render array.
|
||||
// @todo Simplify this when https://www.drupal.org/node/2495001 lands.
|
||||
$this->renderer->render($html);
|
||||
});
|
||||
// RendererInterface::render() always causes bubbleable metadata to be
|
||||
// stored in the render context, no need to check it conditionally.
|
||||
$bubbleable_metadata = $render_context->pop();
|
||||
$bubbleable_metadata->applyTo($html);
|
||||
$content = $this->renderCache->getCacheableRenderArray($html);
|
||||
|
||||
// Also associate the required cache contexts.
|
||||
// (Because we use ::render() above and not ::renderRoot(), we manually must
|
||||
// ensure the HTML response varies by the required cache contexts.)
|
||||
$content['#cache']['contexts'] = Cache::mergeContexts($content['#cache']['contexts'], $this->rendererConfig['required_cache_contexts']);
|
||||
|
||||
// Also associate the "rendered" cache tag. This allows us to invalidate the
|
||||
// entire render cache, regardless of the cache bin.
|
||||
$content['#cache']['tags'][] = 'rendered';
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\Core\Render;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
|
@ -170,12 +171,15 @@ class Renderer implements RendererInterface {
|
|||
// Get the render array for the given placeholder
|
||||
$placeholder_elements = $elements['#attached']['placeholders'][$placeholder];
|
||||
|
||||
// Prevent the render array from being auto-placeholdered again.
|
||||
$placeholder_elements['#create_placeholder'] = FALSE;
|
||||
|
||||
// Render the placeholder into markup.
|
||||
$markup = $this->renderPlain($placeholder_elements);
|
||||
|
||||
// Replace the placeholder with its rendered markup, and merge its
|
||||
// bubbleable metadata with the main elements'.
|
||||
$elements['#markup'] = str_replace($placeholder, $markup, $elements['#markup']);
|
||||
$elements['#markup'] = SafeString::create(str_replace($placeholder, $markup, $elements['#markup']));
|
||||
$elements = $this->mergeBubbleableMetadata($elements, $placeholder_elements);
|
||||
|
||||
// Remove the placeholder that we've just rendered.
|
||||
|
@ -337,6 +341,10 @@ class Renderer implements RendererInterface {
|
|||
throw new \DomainException(sprintf('When a #lazy_builder callback is specified, no properties can exist; all properties must be generated by the #lazy_builder callback. You specified the following properties: %s.', implode(', ', $unsupported_keys)));
|
||||
}
|
||||
}
|
||||
// Determine whether to do auto-placeholdering.
|
||||
if (isset($elements['#lazy_builder']) && (!isset($elements['#create_placeholder']) || $elements['#create_placeholder'] !== FALSE) && $this->shouldAutomaticallyPlaceholder($elements)) {
|
||||
$elements['#create_placeholder'] = TRUE;
|
||||
}
|
||||
// If instructed to create a placeholder, and a #lazy_builder callback is
|
||||
// present (without such a callback, it would be impossible to replace the
|
||||
// placeholder), replace the current element with a placeholder.
|
||||
|
@ -364,6 +372,12 @@ class Renderer implements RendererInterface {
|
|||
$elements = $new_elements;
|
||||
$elements['#lazy_builder_built'] = TRUE;
|
||||
}
|
||||
|
||||
// All render elements support #markup and #plain_text.
|
||||
if (!empty($elements['#markup']) || !empty($elements['#plain_text'])) {
|
||||
$elements = $this->ensureMarkupIsSafe($elements);
|
||||
}
|
||||
|
||||
// Make any final changes to the element before it is rendered. This means
|
||||
// that the $element or the children can be altered or corrected before the
|
||||
// element is rendered into the final text.
|
||||
|
@ -405,12 +419,6 @@ class Renderer implements RendererInterface {
|
|||
$elements['#children'] = '';
|
||||
}
|
||||
|
||||
if (!empty($elements['#markup'])) {
|
||||
// @todo Decide how to support non-HTML in the render API in
|
||||
// https://www.drupal.org/node/2501313.
|
||||
$elements['#markup'] = $this->xssFilterAdminIfUnsafe($elements['#markup']);
|
||||
}
|
||||
|
||||
// Assume that if #theme is set it represents an implemented hook.
|
||||
$theme_is_implemented = isset($elements['#theme']);
|
||||
// Check the elements for insecure HTML and pass through sanitization.
|
||||
|
@ -511,7 +519,7 @@ class Renderer implements RendererInterface {
|
|||
$prefix = isset($elements['#prefix']) ? $this->xssFilterAdminIfUnsafe($elements['#prefix']) : '';
|
||||
$suffix = isset($elements['#suffix']) ? $this->xssFilterAdminIfUnsafe($elements['#suffix']) : '';
|
||||
|
||||
$elements['#markup'] = $prefix . $elements['#children'] . $suffix;
|
||||
$elements['#markup'] = SafeString::create($prefix . $elements['#children'] . $suffix);
|
||||
|
||||
// We've rendered this element (and its subtree!), now update the context.
|
||||
$context->update($elements);
|
||||
|
@ -546,7 +554,7 @@ class Renderer implements RendererInterface {
|
|||
$context->bubble();
|
||||
|
||||
$elements['#printed'] = TRUE;
|
||||
return SafeString::create($elements['#markup']);
|
||||
return $elements['#markup'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -635,6 +643,37 @@ 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.
|
||||
*
|
||||
|
@ -669,7 +708,7 @@ class Renderer implements RendererInterface {
|
|||
$attributes = new Attribute();
|
||||
$attributes['callback'] = $placeholder_render_array['#lazy_builder'][0];
|
||||
$attributes['arguments'] = UrlHelper::buildQuery($placeholder_render_array['#lazy_builder'][1]);
|
||||
$attributes['token'] = hash('sha1', serialize($placeholder_render_array));
|
||||
$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.
|
||||
|
@ -718,4 +757,48 @@ class Renderer implements RendererInterface {
|
|||
return SafeString::create($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes #plain_text or filters #markup as required.
|
||||
*
|
||||
* Drupal uses Twig's auto-escape feature to improve security. This feature
|
||||
* automatically escapes any HTML that is not known to be safe. Due to this
|
||||
* the render system needs to ensure that all markup it generates is marked
|
||||
* safe so that Twig does not do any additional escaping.
|
||||
*
|
||||
* By default all #markup is filtered to protect against XSS using the admin
|
||||
* tag list. Render arrays can alter the list of tags allowed by the filter
|
||||
* using the #allowed_tags property. This value should be an array of tags
|
||||
* that Xss::filter() would accept. Render arrays can escape text instead
|
||||
* of XSS filtering by setting the #plain_text property instead of #markup. If
|
||||
* #plain_text is used #allowed_tags is ignored.
|
||||
*
|
||||
* @param array $elements
|
||||
* A render array with #markup set.
|
||||
*
|
||||
* @return \Drupal\Component\Utility\SafeStringInterface|string
|
||||
* The escaped markup wrapped in a SafeString object. If
|
||||
* SafeMarkup::isSafe($elements['#markup']) returns TRUE, it won't be
|
||||
* escaped or filtered again.
|
||||
*
|
||||
* @see \Drupal\Component\Utility\Html::escape()
|
||||
* @see \Drupal\Component\Utility\Xss::filter()
|
||||
* @see \Drupal\Component\Utility\Xss::adminFilter()
|
||||
*/
|
||||
protected function ensureMarkupIsSafe(array $elements) {
|
||||
if (empty($elements['#markup']) && empty($elements['#plain_text'])) {
|
||||
return $elements;
|
||||
}
|
||||
|
||||
if (!empty($elements['#plain_text'])) {
|
||||
$elements['#markup'] = SafeString::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));
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -148,7 +148,7 @@ interface RendererInterface {
|
|||
* $pre_bubbling_cid.
|
||||
* - 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(),
|
||||
* the defaults for this type of element, defined by an element plugin,
|
||||
* are merged into the array. #defaults_loaded is set by functions that
|
||||
* process render arrays and call the element info service before passing
|
||||
* the array to Renderer::render(), such as form_builder() in the Form
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
namespace Drupal\Core\Render;
|
||||
|
||||
use Drupal\Component\Utility\SafeStringInterface;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Component\Utility\SafeStringTrait;
|
||||
|
||||
/**
|
||||
* Defines an object that passes safe strings through the render system.
|
||||
|
@ -18,67 +18,13 @@ use Drupal\Component\Utility\Unicode;
|
|||
* filtered first, it must not be used.
|
||||
*
|
||||
* @internal
|
||||
* This object is marked as internal because it should only be used during
|
||||
* rendering. Currently, there is no use case for this object by contrib or
|
||||
* custom code.
|
||||
* This object is marked as internal because it should only be used whilst
|
||||
* rendering.
|
||||
*
|
||||
* @see \Drupal\Core\Template\TwigExtension::escapeFilter
|
||||
* @see \Twig_Markup
|
||||
* @see \Drupal\Component\Utility\SafeMarkup
|
||||
*/
|
||||
class SafeString implements SafeStringInterface, \Countable {
|
||||
|
||||
/**
|
||||
* The safe string.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $string;
|
||||
|
||||
/**
|
||||
* Creates a SafeString object if necessary.
|
||||
*
|
||||
* If $string is equal to a blank string then it is not necessary to create a
|
||||
* SafeString object. If $string is an object that implements
|
||||
* SafeStringInterface it is returned unchanged.
|
||||
*
|
||||
* @param mixed $string
|
||||
* The string to mark as safe. This value will be cast to a string.
|
||||
*
|
||||
* @return string|\Drupal\Component\Utility\SafeStringInterface
|
||||
* A safe string.
|
||||
*/
|
||||
public static function create($string) {
|
||||
if ($string instanceof SafeStringInterface) {
|
||||
return $string;
|
||||
}
|
||||
$string = (string) $string;
|
||||
if ($string === '') {
|
||||
return '';
|
||||
}
|
||||
$safe_string = new static();
|
||||
$safe_string->string = $string;
|
||||
return $safe_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string version of the SafeString object.
|
||||
*
|
||||
* @return string
|
||||
* The safe string content.
|
||||
*/
|
||||
public function __toString() {
|
||||
return $this->string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string length.
|
||||
*
|
||||
* @return int
|
||||
* The length of the string.
|
||||
*/
|
||||
public function count() {
|
||||
return Unicode::strlen($this->string);
|
||||
}
|
||||
|
||||
final class SafeString implements SafeStringInterface, \Countable {
|
||||
use SafeStringTrait;
|
||||
}
|
||||
|
|
|
@ -99,10 +99,12 @@
|
|||
* before the template file is invoked to modify the variables that are passed
|
||||
* to the template. These make up the "preprocessing" phase, and are executed
|
||||
* (if they exist), in the following order (note that in the following list,
|
||||
* HOOK indicates the theme hook name, MODULE indicates a module name, THEME
|
||||
* indicates a theme name, and ENGINE indicates a theme engine name). Modules,
|
||||
* themes, and theme engines can provide these functions to modify how the
|
||||
* data is preprocessed, before it is passed to the theme template:
|
||||
* HOOK indicates the hook being called or a less specific hook. For example, if
|
||||
* '#theme' => 'node__article' is called, hook is node__article and node. MODULE
|
||||
* indicates a module name, THEME indicates a theme name, and ENGINE indicates a
|
||||
* theme engine name). Modules, themes, and theme engines can provide these
|
||||
* functions to modify how the data is preprocessed, before it is passed to the
|
||||
* theme template:
|
||||
* - template_preprocess(&$variables, $hook): Creates a default set of variables
|
||||
* for all theme hooks with template implementations. Provided by Drupal Core.
|
||||
* - template_preprocess_HOOK(&$variables): Should be implemented by the module
|
||||
|
@ -271,8 +273,29 @@
|
|||
* vectors. (I.e, <script> and <style> are not allowed.) See
|
||||
* \Drupal\Component\Utility\Xss::$adminTags for the list of tags that will
|
||||
* be allowed. If your markup needs any of the tags that are not in this
|
||||
* whitelist, then you should implement a theme hook and template file and/or
|
||||
* an asset library.
|
||||
* whitelist, then you can implement a theme hook and template file and/or
|
||||
* an asset library. Aternatively, you can use the render array key
|
||||
* #allowed_tags to alter which tags are filtered.
|
||||
* - #plain_text: Specifies that the array provides text that needs to be
|
||||
* escaped. This value takes precedence over #markup if present.
|
||||
* - #allowed_tags: If #markup is supplied this can be used to change which tags
|
||||
* are using to filter the markup. The value should be an array of tags that
|
||||
* Xss::filter() would accept. If #plain_text is set this value is ignored.
|
||||
*
|
||||
* Usage example:
|
||||
* @code
|
||||
* $output['admin_filtered_string'] = array(
|
||||
* '#markup' => '<em>This is filtered using the admin tag list</em>',
|
||||
* );
|
||||
* $output['filtered_string'] = array(
|
||||
* '#markup' => '<em>This is filtered</em>',
|
||||
* '#allowed_tags' => ['strong'],
|
||||
* );
|
||||
* $output['escaped_string'] = array(
|
||||
* '#plain_text' => '<em>This is escaped</em>',
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* @see core.libraries.yml
|
||||
* @see hook_theme()
|
||||
*
|
||||
|
@ -297,10 +320,7 @@
|
|||
* on plugins, and look for classes with the RenderElement or FormElement
|
||||
* annotation to discover what render elements are available.
|
||||
*
|
||||
* Modules can also currently define render elements by implementing
|
||||
* hook_element_info(), although defining a plugin is preferred.
|
||||
* properties. Look through implementations of hook_element_info() to discover
|
||||
* elements defined this way.
|
||||
* Modules can define render elements by defining an element plugin.
|
||||
*
|
||||
* @section sec_caching Caching
|
||||
* The Drupal rendering process has the ability to cache rendered output at any
|
||||
|
@ -693,32 +713,6 @@ function hook_render_template($template_file, $variables) {
|
|||
return $twig_service->loadTemplate($template_file)->render($variables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows modules to declare their own Form API element types and specify their
|
||||
* default values.
|
||||
*
|
||||
* This hook allows modules to declare their own form element types and to
|
||||
* specify their default values. The values returned by this hook will be
|
||||
* merged with the elements returned by form constructor implementations and so
|
||||
* can return defaults for any Form APIs keys in addition to those explicitly
|
||||
* documented by \Drupal\Core\Render\ElementInfoManagerInterface::getInfo().
|
||||
*
|
||||
* @return array
|
||||
* An associative array with structure identical to that of the return value
|
||||
* of \Drupal\Core\Render\ElementInfoManagerInterface::getInfo().
|
||||
*
|
||||
* @deprecated Use an annotated class instead, see
|
||||
* \Drupal\Core\Render\Element\ElementInterface.
|
||||
*
|
||||
* @see hook_element_info_alter()
|
||||
*/
|
||||
function hook_element_info() {
|
||||
$types['filter_format'] = array(
|
||||
'#input' => TRUE,
|
||||
);
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the element type information returned from modules.
|
||||
*
|
||||
|
@ -729,7 +723,8 @@ function hook_element_info() {
|
|||
* An associative array with structure identical to that of the return value
|
||||
* of \Drupal\Core\Render\ElementInfoManagerInterface::getInfo().
|
||||
*
|
||||
* @see hook_element_info()
|
||||
* @see \Drupal\Core\Render\ElementInfoManager
|
||||
* @see \Drupal\Core\Render\Element\ElementInterface
|
||||
*/
|
||||
function hook_element_info_alter(array &$types) {
|
||||
// Decrease the default size of textfields.
|
||||
|
|
Reference in a new issue