Issue #2649914 by claudiu.cristea, lokapujya, dawehner: Enforce formatter dependencies on views fields

8.1.x
Nathaniel Catchpole 2016-02-29 17:20:41 +09:00
parent d0ccb7457e
commit bf28e3d006
9 changed files with 421 additions and 24 deletions

View File

@ -0,0 +1,22 @@
<?php
/**
* @file
* Contains database additions to drupal-8-rc1.bare.standard.php.gz for testing
* the upgrade path of https://www.drupal.org/node/2649914.
*/
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Database\Database;
$connection = Database::getConnection();
$views_config = Yaml::decode(file_get_contents(__DIR__ . '/drupal8.views-image-style-dependency-2649914.yml'));
$connection->insert('config')
->fields(['collection', 'name', 'data'])
->values([
'collection' => '',
'name' => 'views.view.' . $views_config['id'],
'data' => serialize($views_config),
])->execute();

View File

@ -0,0 +1,43 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_image
module:
- image
- node
id: foo
label: Foo
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
fields:
field_image:
id: field_image
table: node__field_image
field: field_image
type: image
settings:
image_style: thumbnail
image_link: ''
plugin_id: field
field_image_1:
id: field_image_1
table: node__field_image
field: field_image
type: image
settings:
# This field's formatter is using a non-existent image style.
image_style: nonexistent
image_link: ''
plugin_id: field

View File

@ -951,7 +951,7 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
* {@inheritdoc}
*/
public function calculateDependencies() {
$this->addDependencies(parent::calculateDependencies());
$this->dependencies = parent::calculateDependencies();
// Collect all the dependencies of handlers and plugins. Only calculate
// their dependencies if they are configured by this display.
$plugins = array_merge($this->getAllHandlers(TRUE), $this->getAllPlugins(TRUE));

View File

@ -7,6 +7,7 @@
namespace Drupal\views\Plugin\views\field;
use Drupal\Component\Plugin\DependentPluginInterface;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
@ -18,6 +19,7 @@ use Drupal\Core\Field\FormatterPluginManager;
use Drupal\Core\Form\FormHelper;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\PluginDependencyTrait;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\RendererInterface;
@ -40,7 +42,9 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* @ViewsField("field")
*/
class Field extends FieldPluginBase implements CacheableDependencyInterface, MultiItemsFieldHandlerInterface {
use FieldAPIHandlerTrait;
use PluginDependencyTrait;
/**
* An array to store field renderable arrays for use by renderItems().
@ -471,25 +475,9 @@ class Field extends FieldPluginBase implements CacheableDependencyInterface, Mul
$form['field_api_classes']['#description'] .= ' ' . $this->t('Checking this option will cause the group Display Type and Separator values to be ignored.');
}
// Get the currently selected formatter.
$format = $this->options['type'];
$settings = $this->options['settings'] + $this->formatterPluginManager->getDefaultSettings($format);
$options = array(
'field_definition' => $field,
'configuration' => array(
'type' => $format,
'settings' => $settings,
'label' => '',
'weight' => 0,
),
'view_mode' => '_custom',
);
// Get the settings form.
$settings_form = array('#value' => array());
if ($formatter = $this->formatterPluginManager->getInstance($options)) {
if ($formatter = $this->getFormatterInstance()) {
$settings_form = $formatter->settingsForm($form, $form_state);
// Convert field UI selector states to work in the Views field form.
FormHelper::rewriteStatesSelector($settings_form, "fields[{$field->getName()}][settings_edit_form]", 'options');
@ -948,22 +936,50 @@ class Field extends FieldPluginBase implements CacheableDependencyInterface, Mul
}
}
/**
* Returns the field formatter instance.
*
* @return \Drupal\Core\Field\FormatterInterface|null
* The field formatter instance.
*/
protected function getFormatterInstance() {
$settings = $this->options['settings'] + $this->formatterPluginManager->getDefaultSettings($this->options['type']);
$options = [
'field_definition' => $this->getFieldDefinition(),
'configuration' => [
'type' => $this->options['type'],
'settings' => $settings,
'label' => '',
'weight' => 0,
],
'view_mode' => '_custom',
];
return $this->formatterPluginManager->getInstance($options);
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
$this->dependencies = parent::calculateDependencies();
// Add the module providing the configured field storage as a dependency.
if (($field_storage_definition = $this->getFieldStorageDefinition()) && $field_storage_definition instanceof EntityInterface) {
$dependencies['config'][] = $field_storage_definition->getConfigDependencyName();
$this->dependencies['config'][] = $field_storage_definition->getConfigDependencyName();
}
// Add the module providing the formatter.
if (!empty($this->options['type'])) {
$dependencies['module'][] = $this->formatterPluginManager->getDefinition($this->options['type'])['provider'];
// Add the module providing the formatter.
$this->dependencies['module'][] = $this->formatterPluginManager->getDefinition($this->options['type'])['provider'];
// Add the formatter's dependencies.
if (($formatter = $this->getFormatterInstance()) && $formatter instanceof DependentPluginInterface) {
$this->calculatePluginDependencies($formatter);
}
}
return $dependencies;
return $this->dependencies;
}
/**

View File

@ -0,0 +1,56 @@
<?php
/**
* @file
* Contains \Drupal\views\Tests\Update\ImageStyleDependencyUpdateTest.
*/
namespace Drupal\views\Tests\Update;
use Drupal\system\Tests\Update\UpdatePathTestBase;
use Drupal\views\Entity\View;
/**
* Tests Views image style dependencies update.
*
* @group views
*/
class ImageStyleDependencyUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8-rc1.bare.standard.php.gz',
__DIR__ . '/../../../../system/tests/fixtures/update/drupal8.views-image-style-dependency-2649914.php',
];
}
/**
* Tests the updating of views dependencies to image styles.
*/
public function testUpdateImageStyleDependencies() {
$config_dependencies = View::load('foo')->getDependencies()['config'];
// Checks that 'thumbnail' image style is not a dependency of view 'foo'.
$this->assertFalse(in_array('image.style.thumbnail', $config_dependencies));
// We test the case the the field formatter image style doesn't exist.
// Checks that 'nonexistent' image style is not a dependency of view 'foo'.
$this->assertFalse(in_array('image.style.nonexistent', $config_dependencies));
// Run updates.
$this->runUpdates();
$config_dependencies = View::load('foo')->getDependencies()['config'];
// Checks that 'thumbnail' image style is a dependency of view 'foo'.
$this->assertTrue(in_array('image.style.thumbnail', $config_dependencies));
// The 'nonexistent' style doesn't exist, thus is not a dependency. Checks
// that 'nonexistent' image style is a not dependency of view 'foo'.
$this->assertFalse(in_array('image.style.nonexistent', $config_dependencies));
}
}

View File

@ -18,7 +18,10 @@ use Drupal\simpletest\KernelTestBase;
* requires the full web test environment provided by WebTestBase, extend
* ViewTestBase instead.
*
* @see \Drupal\views\Tests\ViewTestBase
* @deprecated in Drupal 8.0.x, will be removed in Drupal 8.2.x. Use
* \Drupal\Tests\views\Kernel\ViewsKernelTestBase instead.
*
* @see \Drupal\Tests\views\Kernel\ViewsKernelTestBase
*/
abstract class ViewKernelTestBase extends KernelTestBase {

View File

@ -0,0 +1,81 @@
<?php
/**
* @file
* Contains \Drupal\Tests\views\Kernel\ViewsConfigDependenciesIntegrationTest.
*/
namespace Drupal\Tests\views\Kernel;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\image\Entity\ImageStyle;
use Drupal\views\Entity\View;
/**
* Tests integration of views with other modules.
*
* @group views
*/
class ViewsConfigDependenciesIntegrationTest extends ViewsKernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['field', 'file', 'image', 'entity_test'];
/**
* {@inheritdoc}
*/
public static $testViews = ['entity_test_fields'];
/**
* Tests integration with image module.
*/
public function testImage() {
/** @var \Drupal\image\ImageStyleInterface $style */
$style = ImageStyle::create(['name' => 'foo']);
$style->save();
// Create a new image field 'bar' to be used in 'entity_test_fields' view.
FieldStorageConfig::create([
'entity_type' => 'entity_test',
'field_name' => 'bar',
'type' => 'image',
])->save();
FieldConfig::create([
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
'field_name' => 'bar',
])->save();
/** @var \Drupal\views\ViewEntityInterface $view */
$view = View::load('entity_test_fields');
$display =& $view->getDisplay('default');
// Add the 'bar' image field to 'entity_test_fields' view.
$display['display_options']['fields']['bar'] = [
'id' => 'bar',
'field' => 'bar',
'plugin_id' => 'field',
'table' => 'entity_test__bar',
'entity_type' => 'entity_test',
'entity_field' => 'bar',
'type' => 'image',
'settings' => ['image_style' => 'foo', 'image_link' => ''],
];
$view->save();
$dependencies = $view->getDependencies() + ['config' => []];
// Checks that style 'foo' is a dependency of view 'entity_test_fields'.
$this->assertTrue(in_array('image.style.foo', $dependencies['config']));
// Delete the 'foo' image style.
$style->delete();
// Checks that the view has been deleted too.
$this->assertNull(View::load('entity_test_fields'));
}
}

View File

@ -0,0 +1,156 @@
<?php
/**
* @file
* Contains \Drupal\Tests\views\Kernel\ViewsKernelTestBase.
*/
namespace Drupal\Tests\views\Kernel;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\views\Tests\ViewResultAssertionTrait;
use Drupal\views\Tests\ViewTestData;
/**
* Defines a base class for Views kernel testing.
*/
class ViewsKernelTestBase extends KernelTestBase {
use ViewResultAssertionTrait;
/**
* Views to be enabled.
*
* Test classes should override this property and provide the list of testing
* views.
*
* @var array
*/
public static $testViews = [];
/**
* {@inheritdoc}
*/
public static $modules = ['system', 'views', 'views_test_config', 'views_test_data', 'user'];
/**
* {@inheritdoc}
*
* @param bool $import_test_views
* Should the views specified on the test class be imported. If you need
* to setup some additional stuff, like fields, you need to call false and
* then call createTestViews for your own.
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp();
$this->installSchema('system', ['router', 'sequences', 'key_value_expire']);
$this->setUpFixtures();
if ($import_test_views) {
ViewTestData::createTestViews(get_class($this), ['views_test_config']);
}
}
/**
* Sets up the configuration and schema of views and views_test_data modules.
*
* Because the schema of views_test_data.module is dependent on the test
* using it, it cannot be enabled normally.
*/
protected function setUpFixtures() {
// First install the system module. Many Views have Page displays have menu
// links, and for those to work, the system menus must already be present.
$this->installConfig(['system']);
/** @var \Drupal\Core\State\StateInterface $state */
$state = $this->container->get('state');
// Define the schema and views data variable before enabling the test module.
$state->set('views_test_data_schema', $this->schemaDefinition());
$state->set('views_test_data_views_data', $this->viewsData());
$this->installConfig(['views', 'views_test_config', 'views_test_data']);
foreach ($this->schemaDefinition() as $table => $schema) {
$this->installSchema('views_test_data', $table);
}
$this->container->get('router.builder')->rebuild();
// Load the test dataset.
$data_set = $this->dataSet();
$query = Database::getConnection()->insert('views_test_data')
->fields(array_keys($data_set[0]));
foreach ($data_set as $record) {
$query->values($record);
}
$query->execute();
}
/**
* Orders a nested array containing a result set based on a given column.
*
* @param array $result_set
* An array of rows from a result set, with each row as an associative
* array keyed by column name.
* @param string $column
* The column name by which to sort the result set.
* @param bool $reverse
* (optional) Boolean indicating whether to sort the result set in reverse
* order. Defaults to FALSE.
*
* @return array
* The sorted result set.
*/
protected function orderResultSet($result_set, $column, $reverse = FALSE) {
$order = $reverse ? -1 : 1;
usort($result_set, function ($a, $b) use ($column, $order) {
if ($a[$column] == $b[$column]) {
return 0;
}
return $order * (($a[$column] < $b[$column]) ? -1 : 1);
});
return $result_set;
}
/**
* Executes a view with debugging.
*
* @param \Drupal\views\ViewExecutable $view
* The view object.
* @param array $args
* (optional) An array of the view arguments to use for the view.
*/
protected function executeView($view, array $args = []) {
$view->setDisplay();
$view->preExecute($args);
$view->execute();
$verbose_message = '<pre>Executed view: ' . ((string) $view->build_info['query']). '</pre>';
if ($view->build_info['query'] instanceof SelectInterface) {
$verbose_message .= '<pre>Arguments: ' . print_r($view->build_info['query']->getArguments(), TRUE) . '</pre>';
}
$this->verbose($verbose_message);
}
/**
* Returns the schema definition.
*/
protected function schemaDefinition() {
return ViewTestData::schemaDefinition();
}
/**
* Returns the views data definition.
*/
protected function viewsData() {
return ViewTestData::viewsData();
}
/**
* Returns a very simple test dataset.
*/
protected function dataSet() {
return ViewTestData::dataSet();
}
}

View File

@ -6,8 +6,28 @@
*/
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\views\Entity\View;
use Drupal\views\Views;
/**
* @addtogroup updates-8.0.x
* @{
*/
/**
* Update dependencies to image style.
*/
function views_post_update_image_style_dependencies() {
$views = View::loadMultiple();
array_walk($views, function(View $view) {
$view->save();
});
}
/**
* @} End of "addtogroup updates-8.0.x".
*/
/**
* @addtogroup updates-8.0.0-beta
* @{