Commit bb4367b4 authored by alexpott's avatar alexpott

Issue #2372855 by Wim Leers, dawehner: Add content & config entity dependencies to views

parent 88a46ae9
......@@ -550,6 +550,13 @@ public function __sleep() {
return $this->traitSleep();
}
/**
* {@inheritdoc}
*/
public function getConfigDependencyKey() {
return $this->getEntityType()->getConfigDependencyKey();
}
/**
* {@inheritdoc}
*/
......
......@@ -404,6 +404,17 @@ public function getTypedData();
*/
public function getCacheTags();
/**
* Gets the key that is used to store configuration dependencies.
*
* @return string
* The key to be used in configuration dependencies when storing
* dependencies on entities of this type.
*
* @see \Drupal\Core\Entity\EntityTypeInterface::getConfigDependencyKey()
*/
public function getConfigDependencyKey();
/**
* Gets the configuration dependency name.
*
......
......@@ -7,8 +7,7 @@
namespace Drupal\aggregator\Plugin\views\row;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\row\RowPluginBase;
use Drupal\views\Plugin\views\row\RssPluginBase;
/**
* Defines a row plugin which loads an aggregator item and renders as RSS.
......@@ -22,7 +21,7 @@
* display_types = {"feed"}
* )
*/
class Rss extends RowPluginBase {
class Rss extends RssPluginBase {
/**
* The table the aggregator item is using for storage.
......@@ -41,30 +40,7 @@ class Rss extends RowPluginBase {
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['view_mode'] = array('default' => 'default');
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$form['view_mode'] = array(
'#type' => 'select',
'#title' => $this->t('Display type'),
'#options' => array(
'fulltext' => $this->t('Full text'),
'teaser' => $this->t('Title plus teaser'),
'title' => $this->t('Title only'),
'default' => $this->t('Use default RSS settings'),
),
'#default_value' => $this->options['view_mode'],
);
}
protected $entityTypeId = 'aggregator_item';
/**
* {@inheritdoc}
......
......@@ -7,8 +7,7 @@
namespace Drupal\comment\Plugin\views\row;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\row\RowPluginBase;
use Drupal\views\Plugin\views\row\RssPluginBase;
/**
* Plugin which formats the comments as RSS items.
......@@ -23,29 +22,15 @@
* display_types = {"feed"}
* )
*/
class Rss extends RowPluginBase {
class Rss extends RssPluginBase {
var $base_table = 'comment';
var $base_field = 'cid';
protected function defineOptions() {
$options = parent::defineOptions();
$options['view_mode'] = array('default' => 'default');
return $options;
}
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$form['view_mode'] = array(
'#type' => 'select',
'#title' => $this->t('Display type'),
'#options' => $this->options_form_summary_options(),
'#default_value' => $this->options['view_mode'],
);
}
/**
* {@inheritdoc}
*/
protected $entityTypeId = 'comment';
public function preRender($result) {
$cids = array();
......@@ -62,18 +47,10 @@ public function preRender($result) {
}
/**
* Return the main options, which are shown in the summary title
*
* @see views_plugin_row_node_rss::options_form_summary_options()
* @todo: Maybe provide a views_plugin_row_rss_entity and reuse this method
* in views_plugin_row_comment|node_rss.inc
* {@inheritdoc}
*/
function options_form_summary_options() {
$view_modes = \Drupal::entityManager()->getViewModes('node');
$options = array();
foreach ($view_modes as $mode => $settings) {
$options[$mode] = $settings['label'];
}
public function buildOptionsForm_summary_options() {
$options = parent::buildOptionsForm_summary_options();
$options['title'] = $this->t('Title only');
$options['default'] = $this->t('Use site default RSS settings');
return $options;
......
......@@ -942,8 +942,16 @@ function field_langcode(EntityInterface $entity) {
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
// Add the module providing the configured field storage as a dependency.
return array('config' => array($this->getFieldStorageConfig()->getConfigDependencyName()));
$dependencies['config'][] = $this->getFieldStorageConfig()->getConfigDependencyName();
// Add the module providing the formatter.
if ($this->options['type']) {
$dependencies['module'][] = $this->formatterPluginManager->getDefinition($this->options['type'])['provider'];
}
return $dependencies;
}
/**
......
......@@ -9,8 +9,8 @@
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\String;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\row\RowPluginBase;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\views\Plugin\views\row\RssPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\node\NodeStorageInterface;
......@@ -28,7 +28,7 @@
* display_types = {"feed"}
* )
*/
class Rss extends RowPluginBase {
class Rss extends RssPluginBase {
// Basic properties that let the row style follow relationships.
var $base_table = 'node';
......@@ -38,6 +38,11 @@ class Rss extends RowPluginBase {
// Stores the nodes loaded with preRender.
var $nodes = array();
/**
* {@inheritdoc}
*/
protected $entityTypeId = 'node';
/**
* The node storage
*
......@@ -54,54 +59,19 @@ class Rss extends RowPluginBase {
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param NodeStorageInterface $node_storage
* The node storage.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, NodeStorageInterface $node_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->nodeStorage = $node_storage;
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_manager);
$this->nodeStorage = $entity_manager->getStorage('node');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager')->getStorage('node')
);
}
protected function defineOptions() {
$options = parent::defineOptions();
$options['view_mode'] = array('default' => 'default');
return $options;
}
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$form['view_mode'] = array(
'#type' => 'select',
'#title' => $this->t('Display type'),
'#options' => $this->buildOptionsForm_summary_options(),
'#default_value' => $this->options['view_mode'],
);
}
/**
* Return the main options, which are shown in the summary title.
*/
public function buildOptionsForm_summary_options() {
$view_modes = \Drupal::entityManager()->getViewModes('node');
$options = array();
foreach ($view_modes as $mode => $settings) {
$options[$mode] = $settings['label'];
}
$options = parent::buildOptionsForm_summary_options();
$options['title'] = $this->t('Title only');
$options['default'] = $this->t('Use site default RSS settings');
return $options;
......
......@@ -29,6 +29,9 @@ abstract class NodeTestBase extends WebTestBase {
*/
protected $accessHandler;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
......
......@@ -49,6 +49,20 @@ public function testFrontPage() {
->save();
$view = Views::getView('frontpage');
// Tests \Drupal\node\Plugin\views\row\RssPluginBase::calculateDependencies().
$expected = [
'config' => [
'core.entity_view_mode.node.rss',
'core.entity_view_mode.node.teaser',
],
'module' => [
'node',
'user',
],
];
$this->assertIdentical($expected, $view->calculateDependencies());
$view->setDisplay('page_1');
$this->executeView($view);
$view->preview();
......
......@@ -10,6 +10,7 @@
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\views\Plugin\views\PluginBase;
use Drupal\views\Tests\ViewTestData;
/**
* Tests node language fields, filters, and sorting.
......@@ -41,11 +42,12 @@ class NodeLanguageTest extends NodeTestBase {
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
parent::setUp(FALSE);
// Create Page content type.
if ($this->profile != 'standard') {
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
ViewTestData::createTestViews(get_class($this), array('node_test_views'));
}
// Add two new languages.
......
......@@ -22,10 +22,12 @@ abstract class NodeTestBase extends ViewTestBase {
*/
public static $modules = array('node_test_views');
protected function setUp() {
parent::setUp();
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
ViewTestData::createTestViews(get_class($this), array('node_test_views'));
if ($import_test_views) {
ViewTestData::createTestViews(get_class($this), array('node_test_views'));
}
}
}
......@@ -2,18 +2,22 @@
/**
* @file
* Definition of views_handler_filter_term_node_tid.
* Contains \Drupal\taxonomy\Plugin\views\filter\TaxonomyIndexTid.
*/
namespace Drupal\taxonomy\Plugin\views\filter;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\TermStorageInterface;
use Drupal\taxonomy\VocabularyStorageInterface;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\filter\ManyToOne;
use Drupal\Component\Utility\String;
use Drupal\Component\Utility\Tags;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Filter by term id.
......@@ -27,6 +31,53 @@ class TaxonomyIndexTid extends ManyToOne {
// Stores the exposed input for this filter.
var $validated_exposed_input = NULL;
/**
* The vocabulary storage.
*
* @var \Drupal\taxonomy\VocabularyStorageInterface
*/
protected $vocabularyStorage;
/**
* The term storage.
*
* @var \Drupal\taxonomy\TermStorageInterface
*/
protected $termStorage;
/**
* Constructs a TaxonomyIndexTid object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\taxonomy\VocabularyStorageInterface $vocabulary_storage
* The vocabulary storage.
* @param \Drupal\taxonomy\TermStorageInterface $term_storage
* The term storage.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, VocabularyStorageInterface $vocabulary_storage, TermStorageInterface $term_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->vocabularyStorage = $vocabulary_storage;
$this->termStorage = $term_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager')->getStorage('taxonomy_vocabulary'),
$container->get('entity.manager')->getStorage('taxonomy_term')
);
}
/**
* Overrides \Drupal\views\Plugin\views\filter\ManyToOne::init().
*/
......@@ -374,4 +425,21 @@ public function getCacheContexts() {
return $contexts;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
$vocabulary = $this->vocabularyStorage->load($this->options['vid']);
$dependencies[$vocabulary->getConfigDependencyKey()][] = $vocabulary->getConfigDependencyName();
foreach ($this->options['value'] as $tid) {
$term = $this->termStorage->load($tid);
$dependencies[$term->getConfigDependencyKey()][] = $term->getConfigDependencyName();
}
return $dependencies;
}
}
......@@ -2,15 +2,17 @@
/**
* @file
* Definition of Drupal\taxonomy\Plugin\views\relationship\NodeTermData.
* Contains \Drupal\taxonomy\Plugin\views\relationship\NodeTermData.
*/
namespace Drupal\taxonomy\Plugin\views\relationship;
use Drupal\Core\Form\FormStateInterface;
use Drupal\taxonomy\VocabularyStorageInterface;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\relationship\RelationshipPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Relationship handler to return the taxonomy terms of nodes.
......@@ -21,6 +23,42 @@
*/
class NodeTermData extends RelationshipPluginBase {
/**
* The vocabulary storage.
*
* @var \Drupal\taxonomy\VocabularyStorageInterface
*/
protected $vocabularyStorage;
/**
* Constructs a NodeTermData object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\taxonomy\VocabularyStorageInterface $vocabulary_storage
* The vocabulary storage.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, VocabularyStorageInterface $vocabulary_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->vocabularyStorage = $vocabulary_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager')->getStorage('taxonomy_vocabulary')
);
}
/**
* Overrides \Drupal\views\Plugin\views\relationship\RelationshipPluginBase::init().
*/
......@@ -62,6 +100,16 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitOptionsForm(&$form, FormStateInterface $form_state) {
// Transform the #type = checkboxes value to a numerically indexed array,
// because the config schema expects a sequence, not a mapping.
$vids = $form_state->getValue(['options', 'vids']);
$form_state->setValue(['options', 'vids'], array_values(array_filter($vids)));
}
/**
* Called to implement a relationship in a query.
*/
......@@ -103,4 +151,19 @@ public function query() {
$this->alias = $this->query->addRelationship($alias, $join, 'taxonomy_term_data', $this->relationship);
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
foreach ($this->options['vids'] as $vocabulary_id) {
if ($vocabulary = $this->vocabularyStorage->load($vocabulary_id)) {
$dependencies[$vocabulary->getConfigDependencyKey()][] = $vocabulary->getConfigDependencyName();
}
}
return $dependencies;
}
}
......@@ -25,6 +25,16 @@ class RelationshipNodeTermDataTest extends TaxonomyTestBase {
function testViewsHandlerRelationshipNodeTermData() {
$view = Views::getView('test_taxonomy_node_term_data');
// Tests \Drupal\taxonomy\Plugin\views\relationship\NodeTermData::calculateDependencies().
$expected = [
'config' => ['core.entity_view_mode.node.teaser'],
'module' => [
'node',
'taxonomy',
'user',
],
];
$this->assertIdentical($expected, $view->calculateDependencies());
$this->executeView($view, array($this->term1->id(), $this->term2->id()));
$expected_result = array(
array(
......@@ -39,10 +49,13 @@ function testViewsHandlerRelationshipNodeTermData() {
// Change the view to test relation limited by vocabulary.
\Drupal::config('views.view.test_taxonomy_node_term_data')
->set('display.default.display_options.relationships.term_node_tid.vids.tags', 'views_testing_tags')
->set('display.default.display_options.relationships.term_node_tid.vids', ['views_testing_tags'])
->save();
$view = Views::getView('test_taxonomy_node_term_data');
// Tests \Drupal\taxonomy\Plugin\views\relationship\NodeTermData::calculateDependencies().
$expected['config'][] = 'taxonomy.vocabulary.views_testing_tags';
$this->assertIdentical($expected, $view->calculateDependencies());
$this->executeView($view, array($this->term1->id(), $this->term2->id()));
$this->assertIdenticalResultset($view, $expected_result, $column_map);
}
......
......@@ -7,6 +7,8 @@
namespace Drupal\taxonomy\Tests\Views;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\views\Tests\ViewTestData;
use Drupal\views_ui\Tests\UITestBase;
......@@ -32,25 +34,24 @@ class TaxonomyIndexTidUiTest extends UITestBase {
*/
public static $modules = array('node', 'taxonomy', 'taxonomy_test_views');
/**
* A nested array of \Drupal\taxonomy\TermInterface objects.
*
* @var \Drupal\taxonomy\TermInterface[][]
*/
protected $terms = [];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
ViewTestData::createTestViews(get_class($this), array('taxonomy_test_views'));
}
/**
* Tests the filter UI.
*/
public function testFilterUI() {
entity_create('taxonomy_vocabulary', array(
Vocabulary::create([
'vid' => 'tags',
'name' => 'Tags',
))->save();
])->save();
$terms = array();
// Setup a hierarchy which looks like this:
// term 0.0
// term 1.0
......@@ -60,15 +61,21 @@ public function testFilterUI() {
// - term 2.2
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j <= $i; $j++) {
$terms[$i][$j] = $term = entity_create('taxonomy_term', array(
$this->terms[$i][$j] = $term = Term::create([
'vid' => 'tags',
'name' => "Term $i.$j",
'parent' => isset($terms[$i][0]) ? $terms[$i][0]->id() : 0,
));
]);
$term->save();
}
}
ViewTestData::createTestViews(get_class($this), array('taxonomy_test_views'));
}
/**
* Tests the filter UI.
*/
public function testFilterUI() {
$this->drupalGet('admin/structure/views/nojs/handler/test_filter_taxonomy_index_tid/default/filter/tid');
$result = $this->xpath('//select[@id="edit-options-value"]/option');
......@@ -78,12 +85,12 @@ public function testFilterUI() {
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j <= $i; $j++) {
$option = $result[$counter++];
$prefix = $terms[$i][$j]->parent->target_id ? '-' : '';
$prefix = $this->terms[$i][$j]->parent->target_id ? '-' : '';
$attributes = $option->attributes();
$tid = (string) $attributes->value;
$this->assertEqual($prefix . $terms[$i][$j]->getName(), (string) $option);
$this->assertEqual($terms[$i][$j]->id(), $tid);
$this->assertEqual($prefix . $this->terms[$i][$j]->getName(), (string) $option);
$this->assertEqual($this->terms[$i][$j]->id(), $tid);
}
}
......@@ -96,6 +103,22 @@ public function testFilterUI() {
$this->drupalGet('admin/structure/views/nojs/handler/test_filter_taxonomy_index_tid/default/filter/tid');
$result = $this->xpath('//input[@id="edit-options-value"]/@data-autocomplete-path');
$this->assertEqual((string) $result[0], \Drupal::url('taxonomy.autocomplete_vid', ['taxonomy_vocabulary' => 'tags']));
// Tests \Drupal\taxonomy\Plugin\views\filter\TaxonomyIndexTid::calculateDependencies().
$expected = [
'config' => [
'taxonomy.vocabulary.tags',
],
'content' => [
'taxonomy_term:tags:' . Term::load(2)->uuid(),
],
'module' => [
'node',
'taxonomy',
'user',
],
];
$this->assertIdentical($expected, $view->calculateDependencies());
}
}
......@@ -136,7 +136,7 @@ display:
admin_label: ''
operator: or
value:
2: '2'
- '2'
group: 1
exposed: false
expose:
......
......@@ -61,16 +61,14 @@ display:
id: term_node_tid
admin_label: 'Term #1'
table: node
vids:
tags: ''
vids: {}
plugin_id: node_term_data
term_node_tid_1:
field: term_node_tid
id: term_node_tid_1
admin_label: 'Term #2'
table: node
vids:
tags: ''
vids: {}
plugin_id: node_term_data
sorts:
nid:
......
......@@ -9,7 +9,9 @@
use Drupal\Component\Utility\String;
use Drupal\Core\Form\FormStateInterface;
use Drupal\user\RoleStorageInterface;