diff --git a/core/modules/field/src/Plugin/views/field/Field.php b/core/modules/field/src/Plugin/views/field/Field.php index 04976b4..f7a2f43 100644 --- a/core/modules/field/src/Plugin/views/field/Field.php +++ b/core/modules/field/src/Plugin/views/field/Field.php @@ -21,6 +21,7 @@ use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Render\Element; use Drupal\Core\Session\AccountInterface; +use Drupal\views\Plugin\CacheablePluginInterface; use Drupal\views\Plugin\views\display\DisplayPluginBase; use Drupal\views\Plugin\views\field\FieldPluginBase; use Drupal\views\ViewExecutable; @@ -34,7 +35,7 @@ * * @ViewsField("field") */ -class Field extends FieldPluginBase { +class Field extends FieldPluginBase implements CacheablePluginInterface { /** * An array to store field renderable arrays for use by renderItems(). @@ -944,5 +945,23 @@ public function getDependencies() { return array('entity' => array($this->getFieldStorageConfig()->getConfigDependencyName())); } + /** + * {@inheritdoc} + */ + public function isCacheable() { + return FALSE; + } + + /** + * @return string[] + */ + public function getCacheContexts() { + // @todo what to do about field access? + $contexts = []; + + $contexts[] = 'cache.context.user'; + + return $contexts; + } } diff --git a/core/modules/history/src/Plugin/views/filter/HistoryUserTimestamp.php b/core/modules/history/src/Plugin/views/filter/HistoryUserTimestamp.php index 70866c8..446e345 100644 --- a/core/modules/history/src/Plugin/views/filter/HistoryUserTimestamp.php +++ b/core/modules/history/src/Plugin/views/filter/HistoryUserTimestamp.php @@ -99,4 +99,12 @@ public function adminSummary() { } } + /** + * {@inheritdoc} + */ + public function isCacheable() { + // This filter depends on the current time. + return FALSE; + } + } diff --git a/core/modules/node/src/Plugin/views/argument_default/Node.php b/core/modules/node/src/Plugin/views/argument_default/Node.php index f232cea..a92b07f 100644 --- a/core/modules/node/src/Plugin/views/argument_default/Node.php +++ b/core/modules/node/src/Plugin/views/argument_default/Node.php @@ -7,6 +7,7 @@ namespace Drupal\node\Plugin\views\argument_default; +use Drupal\views\Plugin\CacheablePluginInterface; use Drupal\views\Plugin\views\argument_default\ArgumentDefaultPluginBase; use Drupal\node\NodeInterface; @@ -20,7 +21,7 @@ * title = @Translation("Content ID from URL") * ) */ -class Node extends ArgumentDefaultPluginBase { +class Node extends ArgumentDefaultPluginBase implements CacheablePluginInterface { /** * {@inheritdoc} @@ -31,4 +32,20 @@ public function getArgument() { } } + /** + * {@inheritdoc} + */ + public function isCacheable() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = ['cache.context.url']; + + return $contexts; + } + } diff --git a/core/modules/node/src/Plugin/views/filter/Access.php b/core/modules/node/src/Plugin/views/filter/Access.php index 8845ca8..5fd6924 100644 --- a/core/modules/node/src/Plugin/views/filter/Access.php +++ b/core/modules/node/src/Plugin/views/filter/Access.php @@ -47,4 +47,16 @@ public function query() { } } + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = parent::getCacheContexts(); + + // Node access is potentially cacheable per user. + $contexts[] = 'cache.context.user'; + + return $contexts; + } + } diff --git a/core/modules/node/src/Plugin/views/filter/Status.php b/core/modules/node/src/Plugin/views/filter/Status.php index 35c818e..9a55860 100644 --- a/core/modules/node/src/Plugin/views/filter/Status.php +++ b/core/modules/node/src/Plugin/views/filter/Status.php @@ -30,4 +30,15 @@ public function query() { $this->query->addWhereExpression($this->options['group'], "$table.status = 1 OR ($table.uid = ***CURRENT_USER*** AND ***CURRENT_USER*** <> 0 AND ***VIEW_OWN_UNPUBLISHED_NODES*** = 1) OR ***BYPASS_NODE_ACCESS*** = 1"); } + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = parent::getCacheContexts(); + + $contexts[] = 'cache.context.user'; + + return $contexts; + } + } diff --git a/core/modules/taxonomy/src/Plugin/views/argument_default/Tid.php b/core/modules/taxonomy/src/Plugin/views/argument_default/Tid.php index d5c9c9d..9b9fc8f 100644 --- a/core/modules/taxonomy/src/Plugin/views/argument_default/Tid.php +++ b/core/modules/taxonomy/src/Plugin/views/argument_default/Tid.php @@ -9,6 +9,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\taxonomy\TermInterface; +use Drupal\views\Plugin\CacheablePluginInterface; use Drupal\views\ViewExecutable; use Drupal\views\Plugin\views\display\DisplayPluginBase; use Drupal\views\Plugin\views\argument_default\ArgumentDefaultPluginBase; @@ -24,7 +25,7 @@ * title = @Translation("Taxonomy term ID from URL") * ) */ -class Tid extends ArgumentDefaultPluginBase { +class Tid extends ArgumentDefaultPluginBase implements CacheablePluginInterface { /** * Overrides \Drupal\views\Plugin\views\Plugin\views\PluginBase::init(). @@ -167,4 +168,20 @@ public function getArgument() { } } + /** + * {@inheritdoc} + */ + public function isCacheable() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = ['cache.context.url']; + + return $contexts; + } + } diff --git a/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php b/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php index 5c3e50a..cf52f5d 100644 --- a/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php +++ b/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php @@ -361,4 +361,16 @@ public function adminSummary() { return parent::adminSummary(); } + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = parent::getCacheContexts(); + // The result potentially depends on term access and so is just cacheable + // per user. + $contexts[] = 'cache.context.user'; + + return $contexts; + } + } diff --git a/core/modules/user/src/Plugin/views/argument_default/CurrentUser.php b/core/modules/user/src/Plugin/views/argument_default/CurrentUser.php index f1d987b..ae0224b 100644 --- a/core/modules/user/src/Plugin/views/argument_default/CurrentUser.php +++ b/core/modules/user/src/Plugin/views/argument_default/CurrentUser.php @@ -7,6 +7,7 @@ namespace Drupal\user\Plugin\views\argument_default; +use Drupal\views\Plugin\CacheablePluginInterface; use Drupal\views\Plugin\views\argument_default\ArgumentDefaultPluginBase; /** @@ -19,10 +20,26 @@ * title = @Translation("User ID from logged in user") * ) */ -class CurrentUser extends ArgumentDefaultPluginBase { +class CurrentUser extends ArgumentDefaultPluginBase implements CacheablePluginInterface { public function getArgument() { return \Drupal::currentUser()->id(); } + /** + * {@inheritdoc} + */ + public function isCacheable() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = ['cache.context.user']; + + return $contexts; + } + } diff --git a/core/modules/user/src/Plugin/views/argument_default/User.php b/core/modules/user/src/Plugin/views/argument_default/User.php index d218e06..9084b38 100644 --- a/core/modules/user/src/Plugin/views/argument_default/User.php +++ b/core/modules/user/src/Plugin/views/argument_default/User.php @@ -8,6 +8,7 @@ namespace Drupal\user\Plugin\views\argument_default; use Drupal\Core\Form\FormStateInterface; +use Drupal\views\Plugin\CacheablePluginInterface; use Drupal\views\Plugin\views\argument_default\ArgumentDefaultPluginBase; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; @@ -22,7 +23,7 @@ * title = @Translation("User ID from route context") * ) */ -class User extends ArgumentDefaultPluginBase { +class User extends ArgumentDefaultPluginBase implements CacheablePluginInterface { /** * {@inheritdoc} @@ -74,4 +75,20 @@ public function getArgument() { } } + /** + * {@inheritdoc} + */ + public function isCacheable() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = ['cache.context.url']; + + return $contexts; + } + } diff --git a/core/modules/user/src/Plugin/views/filter/Current.php b/core/modules/user/src/Plugin/views/filter/Current.php index 3170a6c..013288e 100644 --- a/core/modules/user/src/Plugin/views/filter/Current.php +++ b/core/modules/user/src/Plugin/views/filter/Current.php @@ -47,4 +47,16 @@ public function query() { $this->query->addWhere($this->options['group'], $or); } + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = parent::getCacheContexts(); + + // This filter depends on the current user. + $contexts[] = 'cache.context.user'; + + return $contexts; + } + } diff --git a/core/modules/views/src/Entity/View.php b/core/modules/views/src/Entity/View.php index d64f43b..a5123f8 100644 --- a/core/modules/views/src/Entity/View.php +++ b/core/modules/views/src/Entity/View.php @@ -320,7 +320,8 @@ protected function fillCacheInformation() { $executable->setDisplay($display_id); list($display['cache_information']['contexts'], $display['cache_information']['cacheable']) = $executable->getDisplay()->getCacheInformation(); - // Always include at least the language context. + // Always include at least the language context as there will be most + // probable translatable strings in the view output. $display['cache_information']['contexts'][] = 'cache.context.language'; $display['cache_information']['contexts'] = array_unique($display['cache_information']['contexts']); } diff --git a/core/modules/views/src/Plugin/views/argument/ArgumentPluginBase.php b/core/modules/views/src/Plugin/views/argument/ArgumentPluginBase.php index cc9012b..cdb24ae 100644 --- a/core/modules/views/src/Plugin/views/argument/ArgumentPluginBase.php +++ b/core/modules/views/src/Plugin/views/argument/ArgumentPluginBase.php @@ -10,6 +10,7 @@ use Drupal\Component\Utility\String as UtilityString; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; +use Drupal\views\Plugin\CacheablePluginInterface; use Drupal\views\Plugin\views\PluginBase; use Drupal\views\Plugin\views\display\DisplayPluginBase; use Drupal\views\ViewExecutable; @@ -55,7 +56,7 @@ * - numeric: If set to TRUE this field is numeric and will use %d instead of * %s in queries. */ -abstract class ArgumentPluginBase extends HandlerBase { +abstract class ArgumentPluginBase extends HandlerBase implements CacheablePluginInterface { var $validator = NULL; var $argument = NULL; @@ -1163,6 +1164,53 @@ protected function unpackArgumentValue($force_int = FALSE) { $this->value = $break->value; $this->operator = $break->operator; } + + /** + * {@inheritdoc} + */ + public function isCacheable() { + $result = TRUE; + + if (($plugin = $this->getPlugin('argument_default')) && $plugin instanceof CacheablePluginInterface) { + $result &= $plugin->isCacheable(); + } + + if (($plugin = $this->getPlugin('argument_validator')) && $plugin instanceof CacheablePluginInterface) { + $result &= $plugin->isCacheable(); + } + + if (($plugin = $this->getPlugin('style')) && $plugin instanceof CacheablePluginInterface) { + $result &= $plugin->isCacheable(); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = []; + // By definition arguments depends on the URL. + // @todo Once contexts are properly injected into block views we could pull + // the information from there. + $contexts[] = 'cache.context.url'; + + if (($plugin = $this->getPlugin('argument_default')) && $plugin instanceof CacheablePluginInterface) { + $contexts = array_merge($plugin->getCacheContexts(). $contexts); + } + + if (($plugin = $this->getPlugin('argument_validator')) && $plugin instanceof CacheablePluginInterface) { + $contexts = array_merge($plugin->getCacheContexts(). $contexts); + } + + if (($plugin = $this->getPlugin('style')) && $plugin instanceof CacheablePluginInterface) { + $contexts = array_merge($plugin->getCacheContexts(). $contexts); + } + + return $contexts; + } + } /** diff --git a/core/modules/views/src/Plugin/views/argument_default/Fixed.php b/core/modules/views/src/Plugin/views/argument_default/Fixed.php index 68f3d93..fe41fad 100644 --- a/core/modules/views/src/Plugin/views/argument_default/Fixed.php +++ b/core/modules/views/src/Plugin/views/argument_default/Fixed.php @@ -8,6 +8,7 @@ namespace Drupal\views\Plugin\views\argument_default; use Drupal\Core\Form\FormStateInterface; +use Drupal\views\Plugin\CacheablePluginInterface; /** * The fixed argument default handler. @@ -19,7 +20,7 @@ * title = @Translation("Fixed") * ) */ -class Fixed extends ArgumentDefaultPluginBase { +class Fixed extends ArgumentDefaultPluginBase implements CacheablePluginInterface { protected function defineOptions() { $options = parent::defineOptions(); @@ -44,4 +45,18 @@ public function getArgument() { return $this->options['argument']; } + /** + * {@inheritdoc} + */ + public function isCacheable() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return []; + } + } diff --git a/core/modules/views/src/Plugin/views/argument_default/QueryParameter.php b/core/modules/views/src/Plugin/views/argument_default/QueryParameter.php index 82bf1fa..f6d1bba 100644 --- a/core/modules/views/src/Plugin/views/argument_default/QueryParameter.php +++ b/core/modules/views/src/Plugin/views/argument_default/QueryParameter.php @@ -8,6 +8,7 @@ namespace Drupal\views\Plugin\views\argument_default; use Drupal\Core\Form\FormStateInterface; +use Drupal\views\Plugin\CacheablePluginInterface; /** * A query parameter argument default handler. @@ -19,7 +20,7 @@ * title = @Translation("Query parameter") * ) */ -class QueryParameter extends ArgumentDefaultPluginBase { +class QueryParameter extends ArgumentDefaultPluginBase implements CacheablePluginInterface { /** * {@inheritdoc} @@ -83,4 +84,20 @@ public function getArgument() { } } + /** + * {@inheritdoc} + */ + public function isCacheable() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = ['cache.context.url']; + + return $contexts; + } + } diff --git a/core/modules/views/src/Plugin/views/argument_default/Raw.php b/core/modules/views/src/Plugin/views/argument_default/Raw.php index 022d029..6151277 100644 --- a/core/modules/views/src/Plugin/views/argument_default/Raw.php +++ b/core/modules/views/src/Plugin/views/argument_default/Raw.php @@ -9,6 +9,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Path\AliasManagerInterface; +use Drupal\views\Plugin\CacheablePluginInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; @@ -22,7 +23,7 @@ * title = @Translation("Raw value from URL") * ) */ -class Raw extends ArgumentDefaultPluginBase { +class Raw extends ArgumentDefaultPluginBase implements CacheablePluginInterface { /** * The alias manager. @@ -102,4 +103,20 @@ public function getArgument() { } } + /** + * {@inheritdoc} + */ + public function isCacheable() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = ['cache.context.url']; + + return $contexts; + } + } diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php index 5a63379..4741646 100644 --- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php @@ -2237,7 +2237,7 @@ public function getCacheInformation () { } } - // Iterate over all handles. Note that at least the argument handler will + // Iterate over all handlers. Note that at least the argument handler will // need to ask all its subplugins. foreach (array_keys(Views::getHandlerTypes()) as $handler_type) { $handlers = $this->getHandlers($handler_type); diff --git a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php index 52c76bd..3c17716 100644 --- a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php +++ b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php @@ -1465,6 +1465,9 @@ protected static function arrayFilterZero($var) { */ public function getCacheContexts() { $cache_contexts = []; + // An exposed filter allows the user to change a view's filters. They accept + // input from GET parameters, which are part of the URL. Hence a view with + // an exposed filter is cacheable per URL. if ($this->isExposed()) { $cache_contexts[] = 'cache.context.url'; } diff --git a/core/modules/views/src/Plugin/views/sort/Random.php b/core/modules/views/src/Plugin/views/sort/Random.php index f0223df..6846f36 100644 --- a/core/modules/views/src/Plugin/views/sort/Random.php +++ b/core/modules/views/src/Plugin/views/sort/Random.php @@ -8,13 +8,14 @@ namespace Drupal\views\Plugin\views\sort; use Drupal\Core\Form\FormStateInterface; +use Drupal\views\Plugin\CacheablePluginInterface; /** * Handle a random sort. * * @ViewsSort("random") */ -class Random extends SortPluginBase { +class Random extends SortPluginBase implements CacheablePluginInterface { /** * {@inheritdoc} @@ -32,4 +33,20 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { $form['order']['#access'] = FALSE; } + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + // @todo Is there a cache context which specificies that something is not + // cacheable at all? + return []; + } + + /** + * @return bool + */ + public function isCacheable() { + return FALSE; + } + } diff --git a/core/modules/views/src/Plugin/views/sort/SortPluginBase.php b/core/modules/views/src/Plugin/views/sort/SortPluginBase.php index 5a01ad9..c54da35 100644 --- a/core/modules/views/src/Plugin/views/sort/SortPluginBase.php +++ b/core/modules/views/src/Plugin/views/sort/SortPluginBase.php @@ -8,6 +8,7 @@ namespace Drupal\views\Plugin\views\sort; use Drupal\Core\Form\FormStateInterface; +use Drupal\views\Plugin\CacheablePluginInterface; use Drupal\views\Plugin\views\HandlerBase; /** @@ -26,7 +27,7 @@ /** * Base sort handler that has no options and performs a simple sort. */ -abstract class SortPluginBase extends HandlerBase { +abstract class SortPluginBase extends HandlerBase implements CacheablePluginInterface { /** * Determine if a sort can be exposed. @@ -224,6 +225,27 @@ public function defaultExposeOptions() { ); } + /** + * {@inheritdoc} + */ + public function isCacheable() { + // The result of a sort does not depend on outside information, so by + // default it is cacheable. + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $cache_contexts = []; + // Exposed sorts on the GET parameter, so it depends on the current URL. + if ($this->isExposed()) { + $cache_contexts[] = 'cache.context.url'; + } + return $cache_contexts; + } + } /** diff --git a/core/modules/views/src/Tests/Plugin/RelationshipJoinTestBase.php b/core/modules/views/src/Tests/Plugin/RelationshipJoinTestBase.php index 8016685..1b0b655 100644 --- a/core/modules/views/src/Tests/Plugin/RelationshipJoinTestBase.php +++ b/core/modules/views/src/Tests/Plugin/RelationshipJoinTestBase.php @@ -6,6 +6,7 @@ */ namespace Drupal\views\Tests\Plugin; + use Drupal\views\Views; /** diff --git a/core/modules/views/src/Tests/Plugin/RowEntityTest.php b/core/modules/views/src/Tests/Plugin/RowEntityTest.php index abdb06d..a340158 100644 --- a/core/modules/views/src/Tests/Plugin/RowEntityTest.php +++ b/core/modules/views/src/Tests/Plugin/RowEntityTest.php @@ -24,7 +24,7 @@ class RowEntityTest extends ViewUnitTestBase { * * @var array */ - public static $modules = array('taxonomy', 'text', 'filter', 'field', 'entity', 'system', 'node', 'user'); + public static $modules = ['taxonomy', 'text', 'filter', 'field', 'entity', 'system', 'node', 'user']; /** * Views used by this test.