diff --git a/core/includes/common.inc b/core/includes/common.inc index e70816b..9a24361 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -9,6 +9,7 @@ use Drupal\Component\Utility\Url; use Drupal\Component\Utility\Xss; use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\CacheableHelper; use Drupal\Core\Language\Language; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Request; @@ -4536,9 +4537,15 @@ function drupal_render_cid_create($elements) { return $elements['#cache']['cid']; } elseif (isset($elements['#cache']['keys'])) { + // Add cache context keys when constants are used in the 'keys' parameter. + $cacheable_helper = new CacheableHelper; + $keys = $cacheable_helper->addCacheContextsToKeys($elements['#cache']['keys']); + $granularity = isset($elements['#cache']['granularity']) ? $elements['#cache']['granularity'] : NULL; // Merge in additional cache ID parts based provided by drupal_render_cid_parts(). - $cid_parts = array_merge($elements['#cache']['keys'], drupal_render_cid_parts($granularity)); + + $cid_parts = array_merge($keys, drupal_render_cid_parts($granularity)); + return implode(':', $cid_parts); } return FALSE; diff --git a/core/lib/Drupal/Core/Cache/CacheableHelper.php b/core/lib/Drupal/Core/Cache/CacheableHelper.php new file mode 100644 index 0000000..4a1ddb2 --- /dev/null +++ b/core/lib/Drupal/Core/Cache/CacheableHelper.php @@ -0,0 +1,44 @@ +preExecute(); + $keys = array((string) $query, $query->getArguments()); + return hash('sha256', serialize($keys)); + } + + function addCacheContextsToKeys($keys) { + $keys_with_contexts = array(); + foreach ($keys as $key) { + $keys_with_contexts[] = $this->getContext($key) ?: $key; + } + return $keys_with_contexts; + } + + protected function getContext($context) { + switch ($context) { + case DRUPAL_CACHE_PER_PAGE: + // @todo: Make this use the request properly. + return $this->currentPath(); + case DRUPAL_CACHE_PER_USER: + return "u." . $this->currentUser()->id(); + case DRUPAL_CACHE_PER_ROLE: + return 'r.' . implode(',', $this->currentUser()->getRoles()); + default: + return FALSE; + } + } + + protected function currentUser() { + return \Drupal::currentUser(); + } + + protected function currentPath() { + global $base_root; + return $base_root . request_uri(); + } + +} diff --git a/core/lib/Drupal/Core/CacheableInterface.php b/core/lib/Drupal/Core/CacheableInterface.php new file mode 100644 index 0000000..8a27c24 --- /dev/null +++ b/core/lib/Drupal/Core/CacheableInterface.php @@ -0,0 +1,43 @@ + '', 'module' => $plugin_definition['module'], 'label_display' => BlockInterface::BLOCK_LABEL_VISIBLE, + 'cache' => array( + 'max_age' => 0, + ), ); } @@ -108,6 +113,12 @@ public function buildConfigurationForm(array $form, array &$form_state) { '#return_value' => BlockInterface::BLOCK_LABEL_VISIBLE, ); + $form['cache']['max_age'] = array( + '#type' => 'select', + '#title' => t('Cache: Max age'), + '#default_value' => $this->configuration['cache']['max_age'], + '#options' => drupal_map_assoc(array(0, 60, 300, 1800, 3600, 21600, 518400), 'format_interval'), + ); // Add plugin-specific settings for this block type. $form += $this->blockForm($form, $form_state); return $form; @@ -151,6 +162,7 @@ public function submitConfigurationForm(array &$form, array &$form_state) { $this->configuration['label'] = $form_state['values']['label']; $this->configuration['label_display'] = $form_state['values']['label_display']; $this->configuration['module'] = $form_state['values']['module']; + $this->configuration['cache'] = $form_state['values']['cache']; $this->blockSubmit($form, $form_state); } } @@ -184,4 +196,25 @@ public function getMachineNameSuggestion() { return $transliterated; } + public function cacheKeys() { + return array(str_replace('\\', '_', get_called_class())); + } + + public function cacheTags() { + return array('content' => TRUE); + } + + public function cacheBin() { + return 'block'; + } + + public function cacheMaxAge() { + return (int)$this->configuration['cache']['max_age']; + } + + public function isCacheable() { + return (int)$this->configuration['cache']['max_age'] > 0; + } + + } diff --git a/core/modules/block/lib/Drupal/block/BlockViewBuilder.php b/core/modules/block/lib/Drupal/block/BlockViewBuilder.php index 5aed185..9d37d06 100644 --- a/core/modules/block/lib/Drupal/block/BlockViewBuilder.php +++ b/core/modules/block/lib/Drupal/block/BlockViewBuilder.php @@ -40,21 +40,32 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la $plugin_id = $plugin->getPluginId(); $base_id = $plugin->getBasePluginId(); $derivative_id = $plugin->getDerivativeId(); + $configuration = $plugin->getConfiguration(); - if ($content = $plugin->build()) { - $configuration = $plugin->getConfiguration(); - $build[$entity_id] = array( - '#theme' => 'block', - 'content' => $content, - '#configuration' => $configuration, - '#plugin_id' => $plugin_id, - '#base_plugin_id' => $base_id, - '#derivative_plugin_id' => $derivative_id, + $build[$entity_id] = array( + '#theme' => 'block', + '#weight' => $entity->get('weight'), + '#configuration' => $configuration + array('label' => check_plain($configuration['label'])), + '#plugin_id' => $plugin_id, + '#base_plugin_id' => $base_id, + '#derivative_plugin_id' => $derivative_id, + ); + + if ($plugin->isCacheable()) { + $build[$entity_id] += array( + 'content' => array( + '#pre_render' => array(array($plugin, 'build')), + ), + '#cache' => array( + 'keys' => $plugin->cacheKeys(), + 'bin' => $plugin->cacheBin(), + 'tags' => $plugin->cacheTags(), + 'expire' => $plugin->cacheMaxAge(), + ), ); - $build[$entity_id]['#configuration']['label'] = check_plain($configuration['label']); } else { - $build[$entity_id] = array(); + $build[$entity_id]['content'] = $plugin->build(); } drupal_alter(array('block_view', "block_view_$base_id"), $build[$entity_id], $plugin); diff --git a/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php b/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php index 42d880a..64f713e 100644 --- a/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php +++ b/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php @@ -60,13 +60,7 @@ public function blockSubmit($form, &$form_state) { */ public function build() { $current_bid = 0; - $cache['#cache'] = array( - 'keys' => array('book_navigation', 'book'), - 'granularity' => DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE, - 'bin' => 'block', - // @todo Save tags for each entity in the book. - 'tags' => array('content' => TRUE), - ); + if ($node = menu_get_object()) { $current_bid = empty($node->book['bid']) ? 0 : $node->book['bid']; } @@ -94,7 +88,7 @@ public function build() { if ($book_menus) { return array( '#theme' => 'book_all_books_block', - ) + $book_menus + $cache; + ) + $book_menus; } } elseif ($current_bid) { @@ -112,7 +106,7 @@ public function build() { $below = \Drupal::service('book.manager')->bookTreeOutput($data['below']); if (!empty($below)) { $book_title_link = array('#theme' => 'book_title_link', '#link' => $data['link']); - return $cache + array( + return array( '#title' => drupal_render($book_title_link), $below, ); @@ -122,4 +116,12 @@ public function build() { return array(); } + public function cacheKeys() { + return array('book_navigation', 'book', DRUPAL_CACHE_PER_PAGE, DRUPAL_CACHE_PER_ROLE); + } + + public function cacheTags() { + // @todo Save tags for each entity in the book. + return array('content' => TRUE); + } } diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index 95ebe76..92ea2a2 100644 --- a/core/modules/forum/forum.module +++ b/core/modules/forum/forum.module @@ -553,23 +553,6 @@ function forum_form_node_form_alter(&$form, &$form_state, $form_id) { } /** - * Render API callback: Lists nodes based on the element's #query property. - * - * This function can be used as a #pre_render callback. - * - * @see \Drupal\forum\Plugin\block\block\NewTopicsBlock::build() - * @see \Drupal\forum\Plugin\block\block\ActiveTopicsBlock::build() - */ -function forum_block_view_pre_render($elements) { - $result = $elements['#query']->execute(); - if ($node_title_list = node_title_list($result)) { - $elements['forum_list'] = $node_title_list; - $elements['forum_more'] = array('#theme' => 'more_link', '#url' => 'forum', '#title' => t('Read the latest forum topics.')); - } - return $elements; -} - -/** * Implements hook_preprocess_HOOK() for block templates. */ function forum_preprocess_block(&$variables) { diff --git a/core/modules/forum/lib/Drupal/forum/Plugin/Block/ActiveTopicsBlock.php b/core/modules/forum/lib/Drupal/forum/Plugin/Block/ActiveTopicsBlock.php index b353d09..0ed096a 100644 --- a/core/modules/forum/lib/Drupal/forum/Plugin/Block/ActiveTopicsBlock.php +++ b/core/modules/forum/lib/Drupal/forum/Plugin/Block/ActiveTopicsBlock.php @@ -21,19 +21,13 @@ class ActiveTopicsBlock extends ForumBlockBase { /** * {@inheritdoc} */ - public function build() { - $query = db_select('forum_index', 'f') + protected function forumQuery() { + return db_select('forum_index', 'f') ->fields('f') ->addTag('node_access') ->addMetaData('base_table', 'forum_index') ->orderBy('f.last_comment_timestamp', 'DESC') ->range(0, $this->configuration['block_count']); - - return array( - // @todo Needs cache tags. Fix by converting to a View with result cache. - // Then remove drupal_render_cache_by_query(). - drupal_render_cache_by_query($query, 'forum_block_view'), - ); } } diff --git a/core/modules/forum/lib/Drupal/forum/Plugin/Block/ForumBlockBase.php b/core/modules/forum/lib/Drupal/forum/Plugin/Block/ForumBlockBase.php index 4cdb94a..9b63a76 100644 --- a/core/modules/forum/lib/Drupal/forum/Plugin/Block/ForumBlockBase.php +++ b/core/modules/forum/lib/Drupal/forum/Plugin/Block/ForumBlockBase.php @@ -9,6 +9,7 @@ use Drupal\block\BlockBase; use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Cache\CacheableHelper; /** * Provides a base class for Forum blocks. @@ -18,6 +19,24 @@ /** * {@inheritdoc} */ + public function build() { + $result = $this->forumQuery()->execute(); + if ($node_title_list = node_title_list($result)) { + $elements['forum_list'] = $node_title_list; + $elements['forum_more'] = array( + '#theme' => 'more_link', + '#url' => 'forum', + '#title' => t('Read the latest forum topics.') + ); + } + return $elements; + } + + abstract protected function forumQuery(); + + /** + * {@inheritdoc} + */ public function defaultConfiguration() { return array( 'properties' => array( @@ -54,4 +73,14 @@ public function blockSubmit($form, &$form_state) { $this->configuration['block_count'] = $form_state['values']['block_count']; } + + public function cacheKeys() { + return array('forum_block_view', $this->cacheKeyFromQuery($this->forumQuery())); + } + + protected function cacheKeyFromQuery($query) { + $cacheable_helper = new CacheableHelper; + return $cacheable_helper->keyFromQuery($query); + } + } diff --git a/core/modules/forum/lib/Drupal/forum/Plugin/Block/NewTopicsBlock.php b/core/modules/forum/lib/Drupal/forum/Plugin/Block/NewTopicsBlock.php index 8b75a83..a61970f 100644 --- a/core/modules/forum/lib/Drupal/forum/Plugin/Block/NewTopicsBlock.php +++ b/core/modules/forum/lib/Drupal/forum/Plugin/Block/NewTopicsBlock.php @@ -21,17 +21,12 @@ class NewTopicsBlock extends ForumBlockBase { /** * {@inheritdoc} */ - public function build() { - $query = db_select('forum_index', 'f') + protected function forumQuery() { + return db_select('forum_index', 'f') ->fields('f') ->addTag('node_access') ->addMetaData('base_table', 'forum_index') ->orderBy('f.created', 'DESC') ->range(0, $this->configuration['block_count']); - - return array( - drupal_render_cache_by_query($query, 'forum_block_view'), - ); } - } diff --git a/core/modules/views/lib/Drupal/views/Plugin/Block/ViewsBlock.php b/core/modules/views/lib/Drupal/views/Plugin/Block/ViewsBlock.php index 4d521a9..e944823 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/Block/ViewsBlock.php +++ b/core/modules/views/lib/Drupal/views/Plugin/Block/ViewsBlock.php @@ -97,4 +97,8 @@ public function getMachineNameSuggestion() { return 'views_block__' . $this->view->storage->id() . '_' . $this->view->current_display; } + public function cacheKeys() { + return array($this->getMachineNameSuggestion()); + } + }