Commit 99b367da authored by Dries's avatar Dries

Issue #2099131 by jessebeach, Wim Leers, catch, Berdir, benjifisher,...

Issue #2099131 by jessebeach, Wim Leers, catch, Berdir, benjifisher, martin107, andypost: Use #pre_render pattern for entity render caching.
parent b0821d88
......@@ -3158,7 +3158,7 @@ function drupal_pre_render_link($element) {
* A typical example comes from node links, which are stored in a renderable
* array similar to this:
* @code
* $node->content['links'] = array(
* $build['links'] = array(
* '#theme' => 'links__node',
* '#pre_render' => array('drupal_pre_render_links'),
* 'comment' => array(
......@@ -3193,7 +3193,7 @@ function drupal_pre_render_link($element) {
* {{ content.links.comment }}
* @endcode
*
* (where $node->content has been transformed into $content before handing
* (where a node's content has been transformed into $content before handing
* control to the node.html.twig template).
*
* The pre_render function defined here allows the above flexibility, but also
......@@ -3529,16 +3529,6 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
return '';
}
// Collect all #post_render_cache callbacks associated with this element when:
// - about to store this element in the render cache, or when;
// - about to apply #post_render_cache callbacks.
if (isset($elements['#cache']) || !$is_recursive_call) {
$post_render_cache = drupal_render_collect_post_render_cache($elements);
if ($post_render_cache) {
$elements['#post_render_cache'] = $post_render_cache;
}
}
// Add any JavaScript state information associated with the element.
if (!empty($elements['#states'])) {
drupal_process_states($elements);
......@@ -3642,6 +3632,15 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
$suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
$elements['#markup'] = $prefix . $elements['#children'] . $suffix;
// Collect all #post_render_cache callbacks associated with this element when:
// - about to store this element in the render cache, or when;
// - about to apply #post_render_cache callbacks.
if (!$is_recursive_call || isset($elements['#cache'])) {
$post_render_cache = drupal_render_collect_post_render_cache($elements);
if ($post_render_cache) {
$elements['#post_render_cache'] = $post_render_cache;
}
}
// Collect all cache tags. This allows the caller of drupal_render() to also
// access the complete list of cache tags.
if (!$is_recursive_call || isset($elements['#cache'])) {
......@@ -3720,6 +3719,10 @@ function render(&$element) {
return NULL;
}
if (is_array($element)) {
// Early return if this element was pre-rendered (no need to re-render).
if (isset($element['#printed']) && $element['#printed'] == TRUE && isset($element['#markup']) && strlen($element['#markup']) > 0) {
return $element['#markup'];
}
show($element);
return drupal_render($element, TRUE);
}
......@@ -3880,6 +3883,9 @@ function drupal_render_cache_set(&$markup, array $elements) {
* @param string $token
* A unique token to uniquely identify the placeholder.
*
* @return string
* The generated placeholder HTML.
*
* @see drupal_render_cache_get()
*/
function drupal_render_cache_generate_placeholder($callback, array $context, $token) {
......@@ -3892,37 +3898,10 @@ function drupal_render_cache_generate_placeholder($callback, array $context, $to
}
/**
* Pre-render callback: Renders a render cache placeholder into #markup.
*
* @param $elements
* A structured array whose keys form the arguments to l():
* - #callback: The #post_render_cache callback that will replace the
* placeholder with its eventual markup.
* - #context: An array providing context for the #post_render_cache callback.
*
* @return
* The passed-in element containing a render cache placeholder in '#markup'
* and a callback with context, keyed by a generated unique token in
* '#post_render_cache'.
*
* @see drupal_render_cache_generate_placeholder()
* Generates a unique token for use in a #post_render_cache placeholder.
*/
function drupal_pre_render_render_cache_placeholder($element) {
$callback = $element['#callback'];
if (!is_callable($callback)) {
throw new Exception(t('#callback must be a callable function.'));
}
$context = $element['#context'];
if (!is_array($context)) {
throw new Exception(t('#context must be an array.'));
}
$token = \Drupal\Component\Utility\Crypt::randomBytesBase64(55);
// Generate placeholder markup and store #post_render_cache callback.
$element['#markup'] = drupal_render_cache_generate_placeholder($callback, $context, $token);
$element['#post_render_cache'][$callback][$token] = $context;
return $element;
function drupal_render_cache_generate_token() {
return \Drupal\Component\Utility\Crypt::randomBytesBase64(55);
}
/**
......@@ -3948,42 +3927,11 @@ function _drupal_render_process_post_render_cache(array &$elements) {
// and if keyed by a number, no token is passed, otherwise, the token string
// is passed to the callback as well. This token is used to uniquely
// identify the placeholder in the markup.
$modified_elements = $elements;
foreach ($elements['#post_render_cache'] as $callback => $options) {
foreach ($elements['#post_render_cache'][$callback] as $token => $context) {
// The advanced option, when setting #post_render_cache directly.
if (is_numeric($token)) {
$modified_elements = call_user_func_array($callback, array($modified_elements, $context));
}
// The simple option, when using the standard placeholders, and hence
// also when using #type => render_cache_placeholder.
else {
// Call #post_render_cache callback to generate the element that will
// fill in the placeholder.
$generated_element = call_user_func_array($callback, array($context));
// Update #attached based on the generated element.
if (isset($generated_element['#attached'])) {
if (!isset($modified_elements['#attached'])) {
$modified_elements['#attached'] = array();
}
$modified_elements['#attached'] = drupal_merge_attached($modified_elements['#attached'], drupal_render_collect_attached($generated_element, TRUE));
}
// Replace the placeholder with the rendered markup of the generated
// element.
$placeholder = drupal_render_cache_generate_placeholder($callback, $context, $token);
$modified_elements['#markup'] = str_replace($placeholder, drupal_render($generated_element), $modified_elements['#markup']);
}
$elements = call_user_func_array($callback, array($elements, $context));
}
}
// Only retain changes to the #markup and #attached properties, as would be
// the case when the render cache was actually being used.
$elements['#markup'] = $modified_elements['#markup'];
if (isset($modified_elements['#attached'])) {
$elements['#attached'] = $modified_elements['#attached'];
}
// Make sure that any attachments added in #post_render_cache callbacks are
// also executed.
if (isset($elements['#attached'])) {
......@@ -4040,13 +3988,6 @@ function drupal_render_collect_post_render_cache(array &$elements, array $callba
}
}
// If this is a render cache placeholder that hasn't been rendered yet, then
// render it now, because we must be able to collect its #post_render_cache
// callback.
if (!isset($elements['#post_render_cache']) && isset($elements['#type']) && $elements['#type'] === 'render_cache_placeholder') {
$elements = drupal_pre_render_render_cache_placeholder($elements);
}
// Collect all #post_render_cache callbacks for this element.
if (isset($elements['#post_render_cache'])) {
$callbacks = NestedArray::mergeDeep($callbacks, $elements['#post_render_cache']);
......
......@@ -34,7 +34,7 @@ public function build(ContentEntityInterface $entity);
*
* This only includes the components handled by the Display object, but
* excludes 'extra fields', that are typically rendered through specific,
* ad-hoc code in EntityViewBuilderInterface::buildContent() or in
* ad-hoc code in EntityViewBuilderInterface::buildComponents() or in
* hook_entity_view() implementations.
*
* hook_entity_display_build_alter() is invoked on each entity, allowing 3rd
......
......@@ -16,8 +16,10 @@
interface EntityViewBuilderInterface {
/**
* Build the structured $content property on the entity.
* Builds the component fields and properties of a set of entities.
*
* @param &$build
* The renderable array representing the entity content.
* @param \Drupal\Core\Entity\EntityInterface[] $entities
* The entities whose content is being built.
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface[] $displays
......@@ -28,11 +30,8 @@ interface EntityViewBuilderInterface {
* @param string $langcode
* (optional) For which language the entity should be build, defaults to
* the current content language.
*
* @return array
* The content array.
*/
public function buildContent(array $entities, array $displays, $view_mode, $langcode = NULL);
public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL);
/**
* Returns the render array for the provided entity.
......
......@@ -90,21 +90,8 @@ public function view(FieldItemListInterface $items) {
'#object' => $entity,
'#items' => $items,
'#formatter' => $this->getPluginId(),
'#cache' => array('tags' => array())
);
// Gather cache tags from reference fields.
foreach ($items as $item) {
if (isset($item->format)) {
$info['#cache']['tags']['filter_format'] = $item->format;
}
if (isset($item->entity)) {
$info['#cache']['tags'][$item->entity->getEntityTypeId()][] = $item->entity->id();
$info['#cache']['tags'][$item->entity->getEntityTypeId() . '_view'] = TRUE;
}
}
$addition = array_merge($info, $elements);
}
......
......@@ -27,7 +27,7 @@
* is hook_block_view_BASE_BLOCK_ID_alter(), which can be used to target a
* specific block or set of similar blocks.
*
* @param array $build
* @param array &$build
* A renderable array of data, as returned from the build() implementation of
* the plugin that defined the block:
* - #title: The default localized title of the block.
......
<?php
/**
* @file
* Contains \Drupal\custom_block\Tests\CustomBlockBuildContentTest.
*/
namespace Drupal\custom_block\Tests;
/**
* Test to ensure that a block's content is always rebuilt.
*/
class CustomBlockBuildContentTest extends CustomBlockTestBase {
/**
* Declares test information.
*/
public static function getInfo() {
return array(
'name' => 'Rebuild content',
'description' => 'Test the rebuilding of content for full view modes.',
'group' => 'Custom Block',
);
}
/**
* Ensures that content is rebuilt in calls to custom_block_build_content().
*/
public function testCustomBlockRebuildContent() {
$block = $this->createCustomBlock();
// Set a property in the content array so we can test for its existence later on.
$block->content['test_content_property'] = array(
'#value' => $this->randomString(),
);
$content = entity_view_multiple(array($block), 'full');
// If the property doesn't exist it means the block->content was rebuilt.
$this->assertFalse(isset($content['test_content_property']), 'Custom block content was emptied prior to being built.');
}
}
......@@ -7,6 +7,7 @@
namespace Drupal\custom_block\Tests;
use Drupal\Core\Entity\EntityInterface;
use Drupal\system\Tests\Entity\EntityCacheTagsTestBase;
/**
......@@ -34,11 +35,23 @@ protected function createEntity() {
$custom_block = entity_create('custom_block', array(
'info' => 'Llama',
'type' => 'basic',
'body' => 'The name "llama" was adopted by European settlers from native Peruvians.',
'body' => array(
'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
'format' => 'plain_text',
),
));
$custom_block->save();
return $custom_block;
}
/**
* {@inheritdoc}
*
* Each comment must have a comment body, which always has a text format.
*/
protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
return array('filter_format:plain_text');
}
}
......@@ -13,9 +13,9 @@
/**
* Implements hook_custom_block_view().
*/
function custom_block_test_custom_block_view(CustomBlock $custom_block, $view_mode) {
function custom_block_test_custom_block_view(array &$build, CustomBlock $custom_block, $view_mode) {
// Add extra content.
$custom_block->content['extra_content'] = array(
$build['extra_content'] = array(
'#markup' => '<blink>Yowser</blink>',
);
}
......
......@@ -22,8 +22,7 @@ class BlockViewBuilder extends EntityViewBuilder {
/**
* {@inheritdoc}
*/
public function buildContent(array $entities, array $displays, $view_mode, $langcode = NULL) {
return array();
public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL) {
}
/**
......
......@@ -247,11 +247,11 @@ function book_node_load($nodes) {
/**
* Implements hook_node_view().
*/
function book_node_view(EntityInterface $node, EntityViewDisplayInterface $display, $view_mode) {
function book_node_view(array &$build, EntityInterface $node, EntityViewDisplayInterface $display, $view_mode) {
if ($view_mode == 'full') {
if (!empty($node->book['bid']) && empty($node->in_preview)) {
$book_navigation = array( '#theme' => 'book_navigation', '#book_link' => $node->book);
$node->content['book_navigation'] = array(
$build['book_navigation'] = array(
'#markup' => drupal_render($book_navigation),
'#weight' => 100,
'#attached' => array(
......
......@@ -84,6 +84,8 @@ function hook_comment_load(Drupal\comment\Comment $comments) {
/**
* Act on a comment that is being assembled before rendering.
*
* @param array &$build
* A renderable array representing the comment content.
* @param \Drupal\comment\Entity\Comment $comment $comment
* Passes in the comment the action is being performed on.
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
......@@ -96,12 +98,12 @@ function hook_comment_load(Drupal\comment\Comment $comments) {
*
* @see hook_entity_view()
*/
function hook_comment_view(\Drupal\comment\Entity\Comment $comment, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
function hook_comment_view(array &$build, \Drupal\comment\Entity\Comment $comment, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
// Only do the extra work if the component is configured to be displayed.
// This assumes a 'mymodule_addition' extra field has been defined for the
// node type in hook_entity_extra_field_info().
if ($display->getComponent('mymodule_addition')) {
$comment->content['mymodule_addition'] = array(
$build['mymodule_addition'] = array(
'#markup' => mymodule_addition($comment),
'#theme' => 'mymodule_my_additional_field',
);
......@@ -120,7 +122,7 @@ function hook_comment_view(\Drupal\comment\Entity\Comment $comment, \Drupal\Core
* callback. Alternatively, it could also implement hook_preprocess_HOOK() for
* comment.html.twig. See drupal_render() documentation for details.
*
* @param $build
* @param array &$build
* A renderable array representing the comment.
* @param \Drupal\comment\Entity\Comment $comment
* The comment being rendered.
......@@ -131,7 +133,7 @@ function hook_comment_view(\Drupal\comment\Entity\Comment $comment, \Drupal\Core
* @see comment_view()
* @see hook_entity_view_alter()
*/
function hook_comment_view_alter(&$build, \Drupal\comment\Entity\Comment $comment, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display) {
function hook_comment_view_alter(array &$build, \Drupal\comment\Entity\Comment $comment, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display) {
// Check for the existence of a field added by another module.
if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) {
// Change its weight.
......
......@@ -13,6 +13,7 @@
use Drupal\comment\CommentInterface;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Url;
......@@ -359,20 +360,22 @@ function comment_new_page_count($num_comments, $new_replies, EntityInterface $en
}
/**
* Implements hook_entity_view_alter().
* Implements hook_entity_build_defaults_alter().
*/
function comment_entity_view_alter(&$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
function comment_entity_build_defaults_alter(array &$build, EntityInterface $entity, $view_mode = 'full', $langcode = NULL) {
// Get the corresponding display settings.
$display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
// Add the comment page number to the cache key if render caching is enabled.
if (isset($build['#cache']) && isset($build['#cache']['keys']) && \Drupal::request()->query->has('page')) {
foreach ($entity->getFieldDefinitions() as $field_name => $definition) {
if (isset($build[$field_name]) && $definition->getType() === 'comment') {
$display_options = $display->getComponent($field_name);
if ($definition->getType() === 'comment' && ($display_options = $display->getComponent($field_name))) {
$pager_id = $display_options['settings']['pager_id'];
$page = pager_find_page($pager_id);
$build['#cache']['keys'][] = $field_name . '-pager-' . $page;
}
}
}
return $build;
}
/**
......@@ -534,7 +537,7 @@ function comment_node_links_alter(array &$node_links, NodeInterface $node, array
/**
* Implements hook_node_view_alter().
*/
function comment_node_view_alter(&$build, EntityInterface $node, EntityViewDisplayInterface $display) {
function comment_node_view_alter(array &$build, EntityInterface $node, EntityViewDisplayInterface $display) {
if (\Drupal::moduleHandler()->moduleExists('history')) {
$build['#attributes']['data-history-node-id'] = $node->id();
}
......@@ -1242,16 +1245,13 @@ function comment_preview(CommentInterface $comment, array &$form_state) {
// therefore the rendered parent entity. This results in an infinite loop of
// parent entity output rendering the comment form and the comment form
// rendering the parent entity. To prevent this infinite loop we temporarily
// set the value of the comment field on the rendered entity to hidden
// set the value of the comment field on a clone of the entity to hidden
// before calling entity_view(). That way when the output of the commented
// entity is rendered, it excludes the comment field output. As objects are
// always addressed by reference we ensure changes are not lost by setting
// the value back to its original state after the call to entity_view().
// entity is rendered, it excludes the comment field output.
$field_name = $comment->getFieldName();
$original_status = $entity->get($field_name)->status;
$entity->get($field_name)->status = CommentItemInterface::HIDDEN;
$entity = clone $entity;
$entity->$field_name->status = CommentItemInterface::HIDDEN;
$build = entity_view($entity, 'full');
$entity->get($field_name)->status = $original_status;
}
$preview_build['comment_output_below'] = $build;
......@@ -1282,7 +1282,7 @@ function comment_preprocess_block(&$variables) {
* A user account, for use with theme_username() or the user_picture template.
*/
function comment_prepare_author(CommentInterface $comment) {
// The account has been pre-loaded by CommentViewBuilder::buildContent().
// The account has been pre-loaded by CommentViewBuilder::buildComponents().
$account = $comment->getOwner();
if (empty($account->uid->value)) {
// @todo Avoid creating a new entity by just creating a new instance
......
......@@ -14,6 +14,8 @@
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityViewBuilder;
use Drupal\entity\Entity\EntityViewDisplay;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\field\FieldInfo;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -78,7 +80,7 @@ public function __construct(EntityTypeInterface $entity_type, EntityManagerInter
}
/**
* Overrides Drupal\Core\Entity\EntityViewBuilder::buildContent().
* Overrides Drupal\Core\Entity\EntityViewBuilder::buildComponents().
*
* In addition to modifying the content key on entities, this implementation
* will also set the comment entity key which all comments carry.
......@@ -86,11 +88,10 @@ public function __construct(EntityTypeInterface $entity_type, EntityManagerInter
* @throws \InvalidArgumentException
* Thrown when a comment is attached to an entity that no longer exists.
*/
public function buildContent(array $entities, array $displays, $view_mode, $langcode = NULL) {
public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL) {
/** @var \Drupal\comment\CommentInterface[] $entities */
$return = array();
if (empty($entities)) {
return $return;
return;
}
// Pre-load associated users into cache to leverage multiple loading.
......@@ -100,7 +101,7 @@ public function buildContent(array $entities, array $displays, $view_mode, $lang
}
$this->entityManager->getStorage('user')->loadMultiple(array_unique($uids));
parent::buildContent($entities, $displays, $view_mode, $langcode);
parent::buildComponents($build, $entities, $displays, $view_mode, $langcode);
// Load all the entities that have comments attached.
$commented_entity_ids = array();
......@@ -114,37 +115,43 @@ public function buildContent(array $entities, array $displays, $view_mode, $lang
$commented_entities[$entity_type] = $this->entityManager->getStorage($entity_type)->loadMultiple($entity_ids);
}
foreach ($entities as $entity) {
foreach ($entities as $id => $entity) {
if (isset($commented_entities[$entity->getCommentedEntityTypeId()][$entity->getCommentedEntityId()])) {
$commented_entity = $commented_entities[$entity->getCommentedEntityTypeId()][$entity->getCommentedEntityId()];
}
else {
throw new \InvalidArgumentException(t('Invalid entity for comment.'));
}
$entity->content['#entity'] = $entity;
$entity->content['#theme'] = 'comment__' . $entity->getFieldId() . '__' . $commented_entity->bundle();
$entity->content['links'] = array(
'#type' => 'render_cache_placeholder',
'#callback' => '\Drupal\comment\CommentViewBuilder::renderLinks',
'#context' => array(
'comment_entity_id' => $entity->id(),
'view_mode' => $view_mode,
'langcode' => $langcode,
'commented_entity_type' => $commented_entity->getEntityTypeId(),
'commented_entity_id' => $commented_entity->id(),
'in_preview' => !empty($entity->in_preview),
$build[$id]['#entity'] = $entity;
$build[$id]['#theme'] = 'comment__' . $entity->getFieldId() . '__' . $commented_entity->bundle();
$callback = '\Drupal\comment\CommentViewBuilder::renderLinks';
$context = array(
'comment_entity_id' => $entity->id(),
'view_mode' => $view_mode,
'langcode' => $langcode,
'commented_entity_type' => $commented_entity->getEntityTypeId(),
'commented_entity_id' => $commented_entity->id(),
'in_preview' => !empty($entity->in_preview),
'token' => drupal_render_cache_generate_token(),
);
$build[$id]['links'] = array(
'#post_render_cache' => array(
$callback => array(
$context,
),
),
'#markup' => drupal_render_cache_generate_placeholder($callback, $context, $context['token']),
);
if (!isset($entity->content['#attached'])) {
$entity->content['#attached'] = array();
if (!isset($build[$id]['#attached'])) {
$build[$id]['#attached'] = array();
}
$entity->content['#attached']['library'][] = 'comment/drupal.comment-by-viewer';
$build[$id]['#attached']['library'][] = 'comment/drupal.comment-by-viewer';
if ($this->moduleHandler->moduleExists('history') && \Drupal::currentUser()->isAuthenticated()) {
$entity->content['#attached']['library'][] = 'comment/drupal.comment-new-indicator';
$build[$id]['#attached']['library'][] = 'comment/drupal.comment-new-indicator';
// Embed the metadata for the comment "new" indicators on this node.
$entity->content['#post_render_cache']['history_attach_timestamp'] = array(
$build[$id]['#post_render_cache']['history_attach_timestamp'] = array(
array('node_id' => $commented_entity->id()),
);
}
......@@ -156,6 +163,8 @@ public function buildContent(array $entities, array $displays, $view_mode, $lang
*
* Renders the links on a comment.
*
* @param array $element
* The renderable array that contains the to be replaced placeholder.
* @param array $context
* An array with the following keys:
* - comment_entity_id: a comment entity ID
......@@ -168,7 +177,9 @@ public function buildContent(array $entities, array $displays, $view_mode, $lang
* @return array
* A renderable array representing the comment links.
*/
public static function renderLinks(array $context) {
public static function renderLinks(array $element, array $context) {
$callback = '\Drupal\comment\CommentViewBuilder::renderLinks';
$placeholder = drupal_render_cache_generate_placeholder($callback, $context, $context['token']);
$links = array(
'#theme' => 'links__comment',
'#pre_render' => array('drupal_pre_render_links'),
......@@ -189,8 +200,10 @@ public static function renderLinks(array $context) {
);
\Drupal::moduleHandler()->alter('comment_links', $links, $entity, $hook_context);
}
$markup = drupal_render($links);
$element['#markup'] = str_replace($placeholder, $markup, $element['#markup']);
return $links;
return $element;
}
/**
......
......@@ -251,11 +251,11 @@ public function getReplyForm(Request $request, $entity_type, $entity_id, $field_
elseif ($entity->access('view', $account)) {
// We make sure the field value isn't set so we don't end up with a
// redirect loop.
$entity = clone $entity;
$entity->{$field_name}->status = CommentItemInterface::HIDDEN;
// Render array of the entity full view mode.
$build['commented_entity'] = $this->entityManager()->getViewBuilder($entity->getEntityTypeId())->view($entity, 'full');
unset($build['commented_entity']['#cache']);
$entity->{$field_name}->status = $status;
}
}
else {
......
......@@ -145,6 +145,20 @@ public function viewElements(FieldItemListInterface $items) {
if ($this->getSetting('pager_id')) {
$build['pager']['#element'] = $this->getSetting('pager_id');
}
// The viewElements() method of entity field formatters is run
// during the #pre_render phase of rendering an entity. A formatter
// builds the content of the field in preparation for theming.
// All entity cache tags must be available after the #pre_render phase.
// This field formatter is highly exceptional: it renders *another*
// entity and this referenced entity has its own #pre_render
// callbacks. In order collect the cache tags associated with the
// referenced entity it must be passed to drupal_render() so that its
// #pre_render callbacks are invoked and its full build array is
// assembled. Rendering the referenced entity in place here will allow
// its cache tags to be bubbled up and included with those of the
// main entity when cache tags are collected for a renderable array
// in drupal_render().
drupal_render($build, TRUE);
$output['comments'] = $build;
}
}
......@@ -162,14 +176,20 @@ public function viewElements(FieldItemListInterface $items) {
// All other users need a user-specific form, which would break the
// render cache: hence use a #post_render_cache callback.
else {
$callback = '\Drupal\comment\Plugin\Field\FieldFormatter\CommentDefaultFormatter::renderForm';
$context = array(
'entity_type' => $entity->getEntityTypeId(),
'entity_id' => $entity->id(),
'field_name' => $field_name,
'token' => drupal_render_cache_generate_token(),
);
$output['comment_form'] = array(
'#type' => 'render_cache_placeholder',
'#callback' => '\Drupal\comment\Plugin\Field\FieldFormatter\CommentDefaultFormatter::renderForm',
'#context' => array(
'entity_type' => $entity->getEntityTypeId(),
'entity_id' => $entity->id(),
'field_name' => $field_name,
'#post_render_cache' => array(
$callback => array(
$context,
),
),
'#markup' => drupal_render_cache_generate_placeholder($callback, $context, $context['token']),
);
}
}
......@@ -190,6 +210,8 @@ public function viewElements(FieldItemListInterface $items) {
/**
* #post_render_cache callback; replaces placeholder with comment form.
*
* @param array $element
* The renderable array that contains the to be replaced placeholder.
* @param array $context
* An array with the following keys:
* - entity_type: an entity type
......@@ -199,9 +221,17 @@ public function viewElements(FieldItemListInterface $items) {
* @return array
* A renderable array containing the comment form.
*/
public static function renderForm(array $context) {
public static function renderForm(array $element, array $context) {
$callback = '\Drupal\comment\Plugin\Field\FieldFormatter\CommentDefaultFormatter::renderForm';
$placeholder = drupal_render_cache_generate_placeholder($callback, $context, $context['token']);
$entity = entity_load($context['entity_type'], $context['entity_id']);
return comment_add($entity, $context['field_name']);
$form = comment_add($entity, $context['field_name']);
// @todo: This only works as long as assets are still tracked in a global
// static variable, see https://drupal.org/node/2238835
$markup = drupal_render($form, TRUE);
$element['#markup'] = str_replace($placeholder