Issue #2468045 by Lendude, vaplas, geertvd, tim.plunkett, dawehner, xjm, cilefen, catch, Berdir, alexpott: When deleting a content type field, users do not realize the related View also is deleted
parent
e4e32ed659
commit
f7520a2969
|
|
@ -5,6 +5,7 @@ namespace Drupal\field_ui\Tests;
|
|||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\views\Entity\View;
|
||||
use Drupal\views\Tests\ViewTestData;
|
||||
|
||||
/**
|
||||
|
|
@ -74,6 +75,13 @@ class FieldUIDeleteTest extends WebTestBase {
|
|||
\Drupal::service('module_installer')->install(['views']);
|
||||
ViewTestData::createTestViews(get_class($this), ['field_test_views']);
|
||||
|
||||
$view = View::load('test_view_field_delete');
|
||||
$this->assertNotNull($view);
|
||||
$this->assertTrue($view->status());
|
||||
// Test that the View depends on the field.
|
||||
$dependencies = $view->getDependencies() + ['config' => []];
|
||||
$this->assertTrue(in_array("field.storage.node.$field_name", $dependencies['config']));
|
||||
|
||||
// Check the config dependencies of the first field, the field storage must
|
||||
// not be shown as being deleted yet.
|
||||
$this->drupalGet("$bundle_path1/fields/node.$type_name1.$field_name/delete");
|
||||
|
|
@ -91,13 +99,13 @@ class FieldUIDeleteTest extends WebTestBase {
|
|||
|
||||
// Check the config dependencies of the first field.
|
||||
$this->drupalGet("$bundle_path2/fields/node.$type_name2.$field_name/delete");
|
||||
$this->assertText(t('The listed configuration will be deleted.'));
|
||||
$this->assertText(t('The listed configuration will be updated.'));
|
||||
$this->assertText(t('View'));
|
||||
$this->assertText('test_view_field_delete');
|
||||
|
||||
$xml = $this->cssSelect('#edit-entity-deletes');
|
||||
// Remove the wrapping HTML.
|
||||
$this->assertIdentical(FALSE, strpos($xml[0]->asXml(), $field_label), 'The currently being deleted field is not shown in the entity deletions.');
|
||||
// Test that nothing is scheduled for deletion.
|
||||
$this->assertFalse(isset($xml[0]), 'The field currently being deleted is not shown in the entity deletions.');
|
||||
|
||||
// Delete the second field.
|
||||
$this->fieldUIDeleteField($bundle_path2, "node.$type_name2.$field_name", $field_label, $type_name2);
|
||||
|
|
@ -106,6 +114,14 @@ class FieldUIDeleteTest extends WebTestBase {
|
|||
$this->assertNull(FieldConfig::loadByName('node', $type_name2, $field_name), 'Field was deleted.');
|
||||
// Check that the field storage was deleted too.
|
||||
$this->assertNull(FieldStorageConfig::loadByName('node', $field_name), 'Field storage was deleted.');
|
||||
|
||||
// Test that the View isn't deleted and has been disabled.
|
||||
$view = View::load('test_view_field_delete');
|
||||
$this->assertNotNull($view);
|
||||
$this->assertFalse($view->status());
|
||||
// Test that the View no longer depends on the deleted field.
|
||||
$dependencies = $view->getDependencies() + ['config' => []];
|
||||
$this->assertFalse(in_array("field.storage.node.$field_name", $dependencies['config']));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use Drupal\Core\Entity\ContentEntityTypeInterface;
|
|||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\views\Plugin\DependentWithRemovalPluginInterface;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\views\ViewEntityInterface;
|
||||
|
||||
|
|
@ -512,4 +513,61 @@ class View extends ConfigEntityBase implements ViewEntityInterface {
|
|||
\Drupal::service('cache_tags.invalidator')->invalidateTags($tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onDependencyRemoval(array $dependencies) {
|
||||
$changed = FALSE;
|
||||
|
||||
// Don't intervene if the views module is removed.
|
||||
if (isset($dependencies['module']) && in_array('views', $dependencies['module'])) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// If the base table for the View is provided by a module being removed, we
|
||||
// delete the View because this is not something that can be fixed manually.
|
||||
$views_data = Views::viewsData();
|
||||
$base_table = $this->get('base_table');
|
||||
$base_table_data = $views_data->get($base_table);
|
||||
if (!empty($base_table_data['table']['provider']) && in_array($base_table_data['table']['provider'], $dependencies['module'])) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$current_display = $this->getExecutable()->current_display;
|
||||
$handler_types = Views::getHandlerTypes();
|
||||
|
||||
// Find all the handlers and check whether they want to do something on
|
||||
// dependency removal.
|
||||
foreach ($this->display as $display_id => $display_plugin_base) {
|
||||
$this->getExecutable()->setDisplay($display_id);
|
||||
$display = $this->getExecutable()->getDisplay();
|
||||
|
||||
foreach (array_keys($handler_types) as $handler_type) {
|
||||
$handlers = $display->getHandlers($handler_type);
|
||||
foreach ($handlers as $handler_id => $handler) {
|
||||
if ($handler instanceof DependentWithRemovalPluginInterface) {
|
||||
if ($handler->onDependencyRemoval($dependencies)) {
|
||||
// Remove the handler and indicate we made changes.
|
||||
unset($this->display[$display_id]['display_options'][$handler_types[$handler_type]['plural']][$handler_id]);
|
||||
$changed = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disable the View if we made changes.
|
||||
// @todo https://www.drupal.org/node/2832558 Give better feedback for
|
||||
// disabled config.
|
||||
if ($changed) {
|
||||
// Force a recalculation of the dependencies if we made changes.
|
||||
$this->getExecutable()->current_display = NULL;
|
||||
$this->calculateDependencies();
|
||||
$this->disable();
|
||||
}
|
||||
|
||||
$this->getExecutable()->setDisplay($current_display);
|
||||
return $changed;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\views\Plugin;
|
||||
|
||||
/**
|
||||
* Provides an interface for a plugin that has dependencies that can be removed.
|
||||
*
|
||||
* @ingroup views_plugins
|
||||
*/
|
||||
interface DependentWithRemovalPluginInterface {
|
||||
|
||||
/**
|
||||
* Allows a plugin to define whether it should be removed.
|
||||
*
|
||||
* If this method returns TRUE then the plugin should be removed.
|
||||
*
|
||||
* @param array $dependencies
|
||||
* An array of dependencies that will be deleted keyed by dependency type.
|
||||
* Dependency types are, for example, entity, module and theme.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the plugin instance should be removed.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\ConfigDependencyManager
|
||||
* @see \Drupal\Core\Config\ConfigEntityBase::preDelete()
|
||||
* @see \Drupal\Core\Config\ConfigManager::uninstall()
|
||||
* @see \Drupal\Core\Entity\EntityDisplayBase::onDependencyRemoval()
|
||||
*/
|
||||
public function onDependencyRemoval(array $dependencies);
|
||||
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ use Drupal\Core\TypedData\TypedDataInterface;
|
|||
use Drupal\views\FieldAPIHandlerTrait;
|
||||
use Drupal\views\Entity\Render\EntityFieldRenderer;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\Plugin\DependentWithRemovalPluginInterface;
|
||||
use Drupal\views\ResultRow;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
|
@ -34,7 +35,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||
*
|
||||
* @ViewsField("field")
|
||||
*/
|
||||
class EntityField extends FieldPluginBase implements CacheableDependencyInterface, MultiItemsFieldHandlerInterface {
|
||||
class EntityField extends FieldPluginBase implements CacheableDependencyInterface, MultiItemsFieldHandlerInterface, DependentWithRemovalPluginInterface {
|
||||
|
||||
use FieldAPIHandlerTrait;
|
||||
use PluginDependencyTrait;
|
||||
|
|
@ -1077,4 +1078,29 @@ class EntityField extends FieldPluginBase implements CacheableDependencyInterfac
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onDependencyRemoval(array $dependencies) {
|
||||
// See if this handler is responsible for any of the dependencies being
|
||||
// removed. If this is the case, indicate that this handler needs to be
|
||||
// removed from the View.
|
||||
$remove = FALSE;
|
||||
// Get all the current dependencies for this handler.
|
||||
$current_dependencies = $this->calculateDependencies();
|
||||
foreach ($current_dependencies as $group => $dependency_list) {
|
||||
// Check if any of the handler dependencies match the dependencies being
|
||||
// removed.
|
||||
foreach ($dependency_list as $config_key) {
|
||||
if (isset($dependencies[$group]) && array_key_exists($config_key, $dependencies[$group])) {
|
||||
// This handlers dependency matches a dependency being removed,
|
||||
// indicate that this handler needs to be removed.
|
||||
$remove = TRUE;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $remove;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace Drupal\Tests\views\Kernel;
|
|||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\image\Entity\ImageStyle;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\views\Entity\View;
|
||||
|
||||
/**
|
||||
|
|
@ -17,13 +18,23 @@ class ViewsConfigDependenciesIntegrationTest extends ViewsKernelTestBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['field', 'file', 'image', 'entity_test'];
|
||||
public static $modules = ['field', 'file', 'image', 'entity_test', 'user', 'text'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $testViews = ['entity_test_fields'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE) {
|
||||
parent::setUp($import_test_views);
|
||||
|
||||
$this->installEntitySchema('user');
|
||||
$this->installSchema('user', ['users_data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests integration with image module.
|
||||
*/
|
||||
|
|
@ -69,7 +80,81 @@ class ViewsConfigDependenciesIntegrationTest extends ViewsKernelTestBase {
|
|||
// Delete the 'foo' image style.
|
||||
$style->delete();
|
||||
|
||||
$view = View::load('entity_test_fields');
|
||||
|
||||
// Checks that the view has not been deleted too.
|
||||
$this->assertNotNull(View::load('entity_test_fields'));
|
||||
|
||||
// Checks that the image field was removed from the View.
|
||||
$display = $view->getDisplay('default');
|
||||
$this->assertFalse(isset($display['display_options']['fields']['bar']));
|
||||
|
||||
// Checks that the view has been disabled.
|
||||
$this->assertFalse($view->status());
|
||||
|
||||
$dependencies = $view->getDependencies() + ['config' => []];
|
||||
// Checks that the dependency on style 'foo' has been removed.
|
||||
$this->assertFalse(in_array('image.style.foo', $dependencies['config']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests removing a config dependency that deletes the View.
|
||||
*/
|
||||
public function testConfigRemovalRole() {
|
||||
// Create a role we can add to the View and delete.
|
||||
$role = Role::create([
|
||||
'id' => 'dummy',
|
||||
'label' => 'dummy',
|
||||
]);
|
||||
|
||||
$role->save();
|
||||
|
||||
/** @var \Drupal\views\ViewEntityInterface $view */
|
||||
$view = View::load('entity_test_fields');
|
||||
$display = &$view->getDisplay('default');
|
||||
|
||||
// Set the access to be restricted by the dummy role.
|
||||
$display['display_options']['access'] = [
|
||||
'type' => 'role',
|
||||
'options' => [
|
||||
'role' => [
|
||||
$role->id() => $role->id(),
|
||||
],
|
||||
],
|
||||
];
|
||||
$view->save();
|
||||
|
||||
// Check that the View now has a dependency on the Role.
|
||||
$dependencies = $view->getDependencies() + ['config' => []];
|
||||
$this->assertTrue(in_array('user.role.dummy', $dependencies['config']));
|
||||
|
||||
// Delete the role.
|
||||
$role->delete();
|
||||
|
||||
$view = View::load('entity_test_fields');
|
||||
|
||||
// Checks that the view has been deleted too.
|
||||
$this->assertNull($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests uninstalling a module that provides a base table for a View.
|
||||
*/
|
||||
public function testConfigRemovalBaseTable() {
|
||||
// Find all the entity types provided by the entity_test module and install
|
||||
// the schema for them so we can uninstall them.
|
||||
$entities = \Drupal::entityTypeManager()->getDefinitions();
|
||||
foreach ($entities as $entity_type_id => $definition) {
|
||||
if ($definition->getProvider() == 'entity_test') {
|
||||
$this->installEntitySchema($entity_type_id);
|
||||
};
|
||||
}
|
||||
|
||||
// Check that removing the module that provides the base table for a View,
|
||||
// deletes the View.
|
||||
$this->assertNotNull(View::load('entity_test_fields'));
|
||||
$this->container->get('module_installer')->uninstall(['entity_test']);
|
||||
// Check that the View has been deleted.
|
||||
$this->assertNull(View::load('entity_test_fields'));
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue