diff --git a/core/modules/system/tests/fixtures/update/drupal8.views-image-style-dependency-2649914.php b/core/modules/system/tests/fixtures/update/drupal8.views-image-style-dependency-2649914.php new file mode 100644 index 00000000000..fcb90ba4e21 --- /dev/null +++ b/core/modules/system/tests/fixtures/update/drupal8.views-image-style-dependency-2649914.php @@ -0,0 +1,22 @@ +insert('config') + ->fields(['collection', 'name', 'data']) + ->values([ + 'collection' => '', + 'name' => 'views.view.' . $views_config['id'], + 'data' => serialize($views_config), + ])->execute(); diff --git a/core/modules/system/tests/fixtures/update/drupal8.views-image-style-dependency-2649914.yml b/core/modules/system/tests/fixtures/update/drupal8.views-image-style-dependency-2649914.yml new file mode 100644 index 00000000000..585da34844e --- /dev/null +++ b/core/modules/system/tests/fixtures/update/drupal8.views-image-style-dependency-2649914.yml @@ -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 diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php index c199d248d0d..2348408bca9 100644 --- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php @@ -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)); diff --git a/core/modules/views/src/Plugin/views/field/Field.php b/core/modules/views/src/Plugin/views/field/Field.php index af3dad1269c..52288a69575 100644 --- a/core/modules/views/src/Plugin/views/field/Field.php +++ b/core/modules/views/src/Plugin/views/field/Field.php @@ -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; } /** diff --git a/core/modules/views/src/Tests/Update/ImageStyleDependencyUpdateTest.php b/core/modules/views/src/Tests/Update/ImageStyleDependencyUpdateTest.php new file mode 100644 index 00000000000..be2521c3d1e --- /dev/null +++ b/core/modules/views/src/Tests/Update/ImageStyleDependencyUpdateTest.php @@ -0,0 +1,56 @@ +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)); + } + +} diff --git a/core/modules/views/src/Tests/ViewKernelTestBase.php b/core/modules/views/src/Tests/ViewKernelTestBase.php index 6060d04a5fb..bc9d36a41cf 100644 --- a/core/modules/views/src/Tests/ViewKernelTestBase.php +++ b/core/modules/views/src/Tests/ViewKernelTestBase.php @@ -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 { diff --git a/core/modules/views/tests/src/Kernel/ViewsConfigDependenciesIntegrationTest.php b/core/modules/views/tests/src/Kernel/ViewsConfigDependenciesIntegrationTest.php new file mode 100644 index 00000000000..25db7d91e8c --- /dev/null +++ b/core/modules/views/tests/src/Kernel/ViewsConfigDependenciesIntegrationTest.php @@ -0,0 +1,81 @@ + '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')); + } + +} diff --git a/core/modules/views/tests/src/Kernel/ViewsKernelTestBase.php b/core/modules/views/tests/src/Kernel/ViewsKernelTestBase.php new file mode 100644 index 00000000000..9d6e47a9a3c --- /dev/null +++ b/core/modules/views/tests/src/Kernel/ViewsKernelTestBase.php @@ -0,0 +1,156 @@ +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 = '
Executed view: ' . ((string) $view->build_info['query']). '
'; + if ($view->build_info['query'] instanceof SelectInterface) { + $verbose_message .= '
Arguments: ' . print_r($view->build_info['query']->getArguments(), TRUE) . '
'; + } + $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(); + } + +} diff --git a/core/modules/views/views.post_update.php b/core/modules/views/views.post_update.php index ecbf851b91b..6dc52eb6568 100644 --- a/core/modules/views/views.post_update.php +++ b/core/modules/views/views.post_update.php @@ -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 * @{