Issue #2179083 by Wim Leers, vijaycs85: Rendered menus (e.g. menu blocks) should set cache tags to inform the page cache.

8.0.x
Nathaniel Catchpole 2014-02-18 13:37:58 +00:00
parent 9fc89d90d8
commit 4022a54878
6 changed files with 228 additions and 23 deletions

View File

@ -3572,6 +3572,20 @@ function drupal_prepare_page($page) {
// 'sidebar_first', 'footer', etc.
drupal_alter('page', $page);
// The "main" and "secondary" menus are never part of the page-level render
// array and therefore their cache tags will never bubble up into the page
// cache, even though they should be. This happens because they're rendered
// directly by the theme system.
// @todo Remove this once https://drupal.org/node/1869476 lands.
if (theme_get_setting('features.main_menu') && count(menu_main_menu())) {
$main_links_source = _menu_get_links_source('main_links', 'main');
$page['page_top']['#cache']['tags']['menu'][$main_links_source] = $main_links_source;
}
if (theme_get_setting('features.secondary_menu') && count(menu_secondary_menu())) {
$secondary_links_source = _menu_get_links_source('secondary_links', 'account');
$page['page_top']['#cache']['tags']['menu'][$secondary_links_source] = $secondary_links_source;
}
// If no module has taken care of the main content, add it to the page now.
// This allows the site to still be usable even if no modules that
// control page regions (for example, the Block module) are enabled.

View File

@ -1047,6 +1047,9 @@ function menu_tree_output($tree) {
// Add the theme wrapper for outer markup.
// Allow menu-specific theme overrides.
$build['#theme_wrappers'][] = 'menu_tree__' . strtr($data['link']['menu_name'], '-', '_');
// Set cache tag.
$menu_name = $data['link']['menu_name'];
$build['#cache']['tags']['menu'][$menu_name] = $menu_name;
}
return $build;
@ -1705,10 +1708,7 @@ function menu_list_system_menus() {
* Returns an array of links to be rendered as the Main menu.
*/
function menu_main_menu() {
$config = \Drupal::config('menu.settings');
$menu_enabled = \Drupal::moduleHandler()->moduleExists('menu');
// When menu module is not enabled, we need a hardcoded default value.
$main_links_source = $menu_enabled ? $config->get('main_links') : 'main';
$main_links_source = _menu_get_links_source('main_links', 'main');
return menu_navigation_links($main_links_source);
}
@ -1716,11 +1716,8 @@ function menu_main_menu() {
* Returns an array of links to be rendered as the Secondary links.
*/
function menu_secondary_menu() {
$config = \Drupal::config('menu.settings');
$menu_enabled = \Drupal::moduleHandler()->moduleExists('menu');
// When menu module is not enabled, we need a hardcoded default value.
$main_links_source = $menu_enabled ? $config->get('main_links') : 'main';
$secondary_links_source = $menu_enabled ? $config->get('secondary_links') : 'account';
$main_links_source = _menu_get_links_source('main_links', 'main');
$secondary_links_source = _menu_get_links_source('secondary_links', 'account');
// If the secondary menu source is set as the primary menu, we display the
// second level of the primary menu.
@ -1732,6 +1729,22 @@ function menu_secondary_menu() {
}
}
/**
* Returns the source of links of a menu.
*
* @param string $name
* A string configuration key of menu link source.
* @param string $default
* Default menu name.
*
* @return string
* Returns menu name, if exist
*/
function _menu_get_links_source($name, $default) {
$config = \Drupal::config('menu.settings');
return \Drupal::moduleHandler()->moduleExists('menu') ? $config->get($name) : $default;
}
/**
* Returns an array of links for a navigation menu.
*
@ -2350,15 +2363,6 @@ function menu_get_active_trail() {
return menu_set_active_trail();
}
/**
* Clears the cached cached data for a single named menu.
*/
function menu_cache_clear($menu_name = 'tools') {
Cache::deleteTags(array('menu' => $menu_name));
// Also clear the menu system static caches.
menu_reset_static_cache();
}
/**
* Clears all cached menu data.
*

View File

@ -478,6 +478,66 @@ class MenuTest extends MenuWebTestBase {
$this->assertIdentical($json[$id], '<ul class="contextual-links"><li class="block-configure"><a href="' . base_path() . 'admin/structure/block/manage/' . $block->id() . '">Configure block</a></li><li class="menu-edit"><a href="' . base_path() . 'admin/structure/menu/manage/tools">Edit menu</a></li></ul>');
}
/**
* Test that cache tags are properly set and bubbled up to the page cache.
*
* Ensures that invalidation of the "menu:<menu name>" cache tags works.
*/
public function testMenuBlockPageCacheTags() {
// Enable page caching.
$config = \Drupal::config('system.performance');
$config->set('cache.page.use_internal', 1);
$config->set('cache.page.max_age', 300);
$config->save();
// Create a Llama menu, add a link to it and place the corresponding block.
$menu = entity_create('menu', array(
'id' => 'llama',
'label' => 'Llama',
'description' => 'Description text',
));
$menu->save();
$menu_link = entity_create('menu_link', array(
'link_path' => '<front>',
'link_title' => 'Vicuña',
'menu_name' => 'llama',
));
$menu_link->save();
$block = $this->drupalPlaceBlock('system_menu_block:llama', array('label' => 'Llama', 'module' => 'system', 'region' => 'footer'));
// Prime the page cache.
$this->drupalGet('test-page');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
// Verify a cache hit, but also the presence of the correct cache tags.
$this->drupalGet('test-page');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
$cid_parts = array(url('test-page', array('absolute' => TRUE)), 'html');
$cid = sha1(implode(':', $cid_parts));
$cache_entry = \Drupal::cache('page')->get($cid);
$this->assertIdentical($cache_entry->tags, array('content:1', 'menu:llama'));
// The "Llama" menu is modified.
$menu->label = 'Awesome llama';
$menu->save();
// Verify that after the modified menu, there is a cache miss.
$this->drupalGet('test-page');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
// Verify a cache hit.
$this->drupalGet('test-page');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
// A link in the "Llama" menu is modified.
$menu_link->link_title = 'Guanaco';
$menu_link->save();
// Verify that after the modified menu link, there is a cache miss.
$this->drupalGet('test-page');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
}
/**
* Tests menu link bundles.
*/

View File

@ -7,6 +7,7 @@
namespace Drupal\menu_link\Entity;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\Entity;
use Drupal\Core\Entity\EntityStorageControllerInterface;
use Drupal\Core\Url;
@ -446,9 +447,9 @@ class MenuLink extends Entity implements \ArrayAccess, MenuLinkInterface {
}
}
foreach ($affected_menus as $menu_name) {
menu_cache_clear($menu_name);
}
Cache::invalidateTags(array('menu' => array_keys($affected_menus)));
// Also clear the menu system static caches.
menu_reset_static_cache();
_menu_clear_page_cache();
}
@ -523,10 +524,12 @@ class MenuLink extends Entity implements \ArrayAccess, MenuLinkInterface {
// Check the has_children status of the parent.
$storage_controller->updateParentalStatus($this);
menu_cache_clear($this->menu_name);
Cache::invalidateTags(array('menu' => $this->menu_name));
if (isset($this->original) && $this->menu_name != $this->original->menu_name) {
menu_cache_clear($this->original->menu_name);
Cache::invalidateTags(array('menu' => $this->original->menu_name));
}
// Also clear the menu system static caches.
menu_reset_static_cache();
// Now clear the cache.
_menu_clear_page_cache();

View File

@ -7,6 +7,7 @@
namespace Drupal\system\Entity;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageControllerInterface;
use Drupal\system\MenuInterface;
@ -90,4 +91,22 @@ class Menu extends ConfigEntityBase implements MenuInterface {
return (bool) $this->locked;
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
parent::postSave($storage_controller, $update);
Cache::invalidateTags(array('menu' => $this->id()));
}
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) {
parent::postDelete($storage_controller, $entities);
Cache::invalidateTags(array('menu' => array_keys($entities)));
}
}

View File

@ -0,0 +1,105 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Cache\PageCacheTagsIntegrationTest.
*/
namespace Drupal\system\Tests\Cache;
use Drupal\simpletest\WebTestBase;
use Drupal\Core\Cache\Cache;
/**
* Enables the page cache and tests its cache tags in various scenarios.
*
* @see \Drupal\system\Tests\Bootstrap\PageCacheTest
* @see \Drupal\node\Tests\NodePageCacheTest
* @see \Drupal\menu\Tests\MenuTest::testMenuBlockPageCacheTags()
*/
class PageCacheTagsIntegrationTest extends WebTestBase {
protected $profile = 'standard';
protected $dumpHeaders = TRUE;
public static function getInfo() {
return array(
'name' => 'Page cache tags integration test',
'description' => 'Enable the page cache and test its cache tags in various scenarios.',
'group' => 'Cache',
);
}
function setUp() {
parent::setUp();
$config = \Drupal::config('system.performance');
$config->set('cache.page.use_internal', 1);
$config->set('cache.page.max_age', 300);
$config->save();
}
/**
* Test that cache tags are properly bubbled up to the page level.
*/
function testPageCacheTags() {
// Create two nodes.
$author_1 = $this->drupalCreateUser();
$node_1_path = 'node/' . $this->drupalCreateNode(array(
'uid' => $author_1->id(),
'title' => 'Node 1',
'body' => array(
0 => array('value' => 'Body 1', 'format' => 'basic_html'),
),
'promote' => NODE_PROMOTED,
))->id();
$author_2 = $this->drupalCreateUser();
$node_2_path = 'node/' . $this->drupalCreateNode(array(
'uid' => $author_2->id(),
'title' => 'Node 2',
'body' => array(
0 => array('value' => 'Body 2', 'format' => 'full_html'),
),
'promote' => NODE_PROMOTED,
))->id();
// Full node page 1.
$this->verifyPageCacheTags($node_1_path, array(
'content:1',
'user:' . $author_1->id(),
'filter_format:basic_html',
'menu:footer',
'menu:main',
));
// Full node page 2.
$this->verifyPageCacheTags($node_2_path, array(
'content:1',
'user:' . $author_2->id(),
'filter_format:full_html',
'menu:footer',
'menu:main',
));
}
/**
* Fills page cache for the given path, verify cache tags on page cache hit.
*
* @param $path
* The Drupal page path to test.
* @param $expected_tags
* The expected cache tags for the page cache entry of the given $path.
*/
protected function verifyPageCacheTags($path, $expected_tags) {
$this->drupalGet($path);
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
$this->drupalGet($path);
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
$cid_parts = array(url($path, array('absolute' => TRUE)), 'html');
$cid = sha1(implode(':', $cid_parts));
$cache_entry = \Drupal::cache('page')->get($cid);
$this->assertIdentical($cache_entry->tags, $expected_tags);
}
}