/** * {@inheritdoc} */ public function createPlaceholder(array $element) { $placeholder_render_array = array_intersect_key($element, ['#lazy_builder' => TRUE, '#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="' . Html::escape($callback) . '" arguments="' . Html::escape($arguments) . '" token="' . Html::escape($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; }
/** * 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' => Markup::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; }
/** * {@inheritdoc} */ public function getCacheableRenderArray(array $elements) { $data = ['#markup' => $elements['#markup'], '#attached' => $elements['#attached'], '#cache' => ['contexts' => $elements['#cache']['contexts'], 'tags' => $elements['#cache']['tags'], 'max-age' => $elements['#cache']['max-age']]]; // Preserve cacheable items if specified. If we are preserving any cacheable // children of the element, we assume we are only interested in their // individual markup and not the parent's one, thus we empty it to minimize // the cache entry size. if (!empty($elements['#cache_properties']) && is_array($elements['#cache_properties'])) { $data['#cache_properties'] = $elements['#cache_properties']; // Extract all the cacheable items from the element using cache // properties. $cacheable_items = array_intersect_key($elements, array_flip($elements['#cache_properties'])); $cacheable_children = Element::children($cacheable_items); if ($cacheable_children) { $data['#markup'] = ''; // 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' => Markup::create($cacheable_items[$key]['#markup'])]; } } $data += $cacheable_items; } $data['#markup'] = Markup::create($data['#markup']); return $data; }
/** * 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\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. * * @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'] = 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'] = Markup::create(Xss::filter($elements['#markup'], $tags)); } return $elements; }