diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index c695934..4035c86 100644 --- a/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -208,9 +208,8 @@ route: label: 'Param' # Config dependencies. -config_dependencies: +config_dependencies_base: type: mapping - label: 'Configuration dependencies' mapping: entity: type: sequence @@ -228,6 +227,14 @@ config_dependencies: sequence: - type: string +config_dependencies: + type: config_dependencies_base + label: 'Configuration dependencies' + mapping: + fixed: + type: config_dependencies_base + label: 'Fixed configuration dependencies' + config_entity: type: mapping mapping: diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php b/core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php index 84a0462..342c055 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php @@ -19,6 +19,27 @@ * handled in the correct order. For example, node types are created before * their fields, and both are created before the view display configuration. * + * The configuration dependency value is structured like so: + * + * array( + * 'entity => array( + * // An array of configuration entity object names. + * ), + * 'module' => array( + * // An array of module names. + * ), + * 'theme' => array( + * // An array of theme names. + * ), + * 'fixed' => array( + * // An array of fixed configuration dependencies. + * 'entity' => array(), + * 'module' => array(), + * 'theme' => array(), + * ), + * ); + * + * * If a configuration entity is provided as default configuration by an * extension (module, theme, or profile), the extension has to depend on any * modules or themes that the configuration depends on. For example, if a view @@ -28,7 +49,11 @@ * depend on a particular module that one of its default configuration entities * depends on, you can use a sub-module: move the configuration entity to the * sub-module instead of including it in the main extension, and declare the - * module dependency in the sub-module only. + * module dependency in the sub-module only. If the extension author wants the + * configuration to depend on something that is not calculable then they can add + * these dependencies to the fixed dependency key. For example, the forum module + * provides the forum node type and in order for it to be deleted when the forum + * module is uninstalled it has a fixed dependency on the module. * * Classes for configuration entities with dependencies normally extend * \Drupal\Core\Config\Entity\ConfigEntityBase or at least use @@ -37,11 +62,11 @@ * \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies(), * which should calculate (and return) the dependencies. In this method, use * \Drupal\Core\Config\Entity\ConfigEntityBase::addDependency() to add - * dependencies. Most implementations call + * dependencies. Implementations should call * \Drupal\Core\Config\Entity\ConfigEntityBase::calculateDependencies() since it * provides a generic implementation that will discover dependencies due to - * plugins and third party settings. To get a configuration entity's current - * dependencies without forcing a recalculation use + * fixed dependencies, plugins, and third party settings. To get a configuration + * entity's current dependencies without forcing a recalculation use * \Drupal\Core\Config\Entity\ConfigEntityInterface::getDependencies(). * * Classes for configurable plugins are a special case. They can either declare diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php index 96e877b..ac00fd7 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php @@ -316,7 +316,14 @@ public function preSave(EntityStorageInterface $storage) { public function calculateDependencies() { // Dependencies should be recalculated on every save. This ensures stale // dependencies are never saved. - $this->dependencies = array(); + if (isset($this->dependencies['fixed'])) { + $dependencies = $this->dependencies['fixed']; + $this->dependencies = $dependencies; + $this->dependencies['fixed'] = $dependencies; + } + else { + $this->dependencies = array(); + } if ($this instanceof EntityWithPluginBagsInterface) { // Configuration entities need to depend on the providers of any plugins // that they store the configuration for. diff --git a/core/modules/block/src/Tests/BlockConfigSchemaTest.php b/core/modules/block/src/Tests/BlockConfigSchemaTest.php index 47e2421..ccd0a73 100644 --- a/core/modules/block/src/Tests/BlockConfigSchemaTest.php +++ b/core/modules/block/src/Tests/BlockConfigSchemaTest.php @@ -36,6 +36,7 @@ class BlockConfigSchemaTest extends KernelTestBase { 'system', 'taxonomy', 'user', + 'text', ); /** @@ -61,6 +62,8 @@ protected function setUp() { $this->typedConfig = \Drupal::service('config.typed'); $this->blockManager = \Drupal::service('plugin.manager.block'); $this->installEntitySchema('block_content'); + $this->installEntitySchema('taxonomy_term'); + $this->installEntitySchema('node'); } /** diff --git a/core/modules/comment/src/Tests/CommentFieldsTest.php b/core/modules/comment/src/Tests/CommentFieldsTest.php index a0cb95d..bec1f5a 100644 --- a/core/modules/comment/src/Tests/CommentFieldsTest.php +++ b/core/modules/comment/src/Tests/CommentFieldsTest.php @@ -63,6 +63,36 @@ function testCommentDefaultFields() { } /** + * Tests that you can remove a comment field. + */ + public function testCommentFieldDelete() { + $this->drupalCreateContentType(array('type' => 'test_node_type')); + $this->container->get('comment.manager')->addDefaultField('node', 'test_node_type'); + // We want to test the handling of removing the primary comment field, so we + // ensure there is at least one other comment field attached to a node type + // so that comment_entity_load() runs for nodes. + $this->container->get('comment.manager')->addDefaultField('node', 'test_node_type', 'comment2'); + + // Create a sample node. + $node = $this->drupalCreateNode(array( + 'title' => 'Baloney', + 'type' => 'test_node_type', + )); + + $this->drupalLogin($this->web_user); + + $this->drupalGet('node/' . $node->nid->value); + $elements = $this->cssSelect('.field-type-comment'); + $this->assertEqual(2, count($elements), 'There are two comment fields on the node.'); + + // Delete the first comment field. + FieldStorageConfig::loadByName('node', 'comment')->delete(); + $this->drupalGet('node/' . $node->nid->value); + $elements = $this->cssSelect('.field-type-comment'); + $this->assertEqual(1, count($elements), 'There is one comment field on the node.'); + } + + /** * Tests that comment module works when installed after a content module. */ function testCommentInstallAfterContentModule() { diff --git a/core/modules/config/src/Tests/ConfigDependencyTest.php b/core/modules/config/src/Tests/ConfigDependencyTest.php index 8da5b24..68d60b0 100644 --- a/core/modules/config/src/Tests/ConfigDependencyTest.php +++ b/core/modules/config/src/Tests/ConfigDependencyTest.php @@ -47,8 +47,10 @@ public function testDependencyMangement() { $entity1 = $storage->create( array( 'id' => 'entity1', - 'test_dependencies' => array( - 'module' => array('node', 'config_test') + 'dependencies' => array( + 'fixed' => array( + 'module' => array('node') + ) ) ) ); @@ -63,15 +65,14 @@ public function testDependencyMangement() { // Ensure that the provider of the config entity is not actually written to // the dependencies array. $raw_config = \Drupal::config('config_test.dynamic.entity1'); - $this->assertTrue(array_search('config_test', $raw_config->get('dependencies.module')) === FALSE, 'Module that the provides the configuration entity is not written to the dependencies array as this is implicit.'); $this->assertTrue(array_search('node', $raw_config->get('dependencies.module')) !== FALSE, 'Node module is written to the dependencies array as this has to be explicit.'); // Create additional entities to test dependencies on config entities. - $entity2 = $storage->create(array('id' => 'entity2', 'test_dependencies' => array('entity' => array($entity1->getConfigDependencyName())))); + $entity2 = $storage->create(array('id' => 'entity2', 'dependencies' => array('fixed' => array('entity' => array($entity1->getConfigDependencyName()))))); $entity2->save(); - $entity3 = $storage->create(array('id' => 'entity3', 'test_dependencies' => array('entity' => array($entity2->getConfigDependencyName())))); + $entity3 = $storage->create(array('id' => 'entity3', 'dependencies' => array('fixed' => array('entity' => array($entity2->getConfigDependencyName()))))); $entity3->save(); - $entity4 = $storage->create(array('id' => 'entity4', 'test_dependencies' => array('entity' => array($entity3->getConfigDependencyName())))); + $entity4 = $storage->create(array('id' => 'entity4', 'dependencies' => array('fixed' => array('entity' => array($entity3->getConfigDependencyName()))))); $entity4->save(); // Test getting $entity1's dependencies as configuration dependency objects. @@ -100,10 +101,8 @@ public function testDependencyMangement() { // Test getting node module's dependencies as configuration dependency // objects after making $entity3 also dependent on node module but $entity1 // no longer depend on node module. - $entity1->test_dependencies = array(); - $entity1->save(); - $entity3->test_dependencies['module'] = array('node'); - $entity3->save(); + $entity1->setFixedDependencies([])->save(); + $entity3->setFixedDependencies(['module' => ['node'], 'entity' => [$entity2->getConfigDependencyName()]])->save(); $dependents = $config_manager->findConfigEntityDependents('module', array('node')); $this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on the Node module.'); $this->assertFalse(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 does not have a dependency on the Node module.'); @@ -113,15 +112,15 @@ public function testDependencyMangement() { // Create a configuration entity of a different type with the same ID as one // of the entities already created. $alt_storage = $this->container->get('entity.manager')->getStorage('config_query_test'); - $alt_storage->create(array('id' => 'entity1', 'test_dependencies' => array('entity' => array($entity1->getConfigDependencyName()))))->save(); - $alt_storage->create(array('id' => 'entity2', 'test_dependencies' => array('module' => array('views'))))->save(); + $alt_storage->create(array('id' => 'entity1', 'dependencies' => array('fixed' => array('entity' => array($entity1->getConfigDependencyName())))))->save(); + $alt_storage->create(array('id' => 'entity2', 'dependencies' => array('fixed' => array('module' => array('views')))))->save(); $dependents = $config_manager->findConfigEntityDependentsAsEntities('entity', array($entity1->getConfigDependencyName())); $dependent_ids = $this->getDependentIds($dependents); $this->assertFalse(in_array('config_test:entity1', $dependent_ids), 'config_test.dynamic.entity1 does not have a dependency on itself.'); $this->assertTrue(in_array('config_test:entity2', $dependent_ids), 'config_test.dynamic.entity2 has a dependency on config_test.dynamic.entity1.'); $this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity3 has a dependency on config_test.dynamic.entity1.'); - $this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on config_test.dynamic.entity1.'); + $this->assertTrue(in_array('config_test:entity4', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on config_test.dynamic.entity1.'); $this->assertTrue(in_array('config_query_test:entity1', $dependent_ids), 'config_query_test.dynamic.entity1 has a dependency on config_test.dynamic.entity1.'); $this->assertFalse(in_array('config_query_test:entity2', $dependent_ids), 'config_query_test.dynamic.entity2 does not have a dependency on config_test.dynamic.entity1.'); @@ -157,8 +156,10 @@ public function testConfigEntityUninstall() { $entity1 = $storage->create( array( 'id' => 'entity1', - 'test_dependencies' => array( - 'module' => array('node', 'config_test') + 'dependencies' => array( + 'fixed' => array( + 'module' => array('node', 'config_test') + ), ), ) ); @@ -166,8 +167,10 @@ public function testConfigEntityUninstall() { $entity2 = $storage->create( array( 'id' => 'entity2', - 'test_dependencies' => array( - 'entity' => array($entity1->getConfigDependencyName()), + 'dependencies' => array( + 'fixed' => array( + 'entity' => array($entity1->getConfigDependencyName()), + ), ), ) ); @@ -181,8 +184,10 @@ public function testConfigEntityUninstall() { $entity1 = $storage->create( array( 'id' => 'entity1', - 'test_dependencies' => array( - 'module' => array('node', 'config_test') + 'dependencies' => array( + 'fixed' => array( + 'module' => array('node', 'config_test') + ), ), ) ); @@ -190,8 +195,10 @@ public function testConfigEntityUninstall() { $entity2 = $storage->create( array( 'id' => 'entity2', - 'test_dependencies' => array( - 'entity' => array($entity1->getConfigDependencyName()), + 'dependencies' => array( + 'fixed' => array( + 'entity' => array($entity1->getConfigDependencyName()), + ), ), ) ); diff --git a/core/modules/config/src/Tests/ConfigImportAllTest.php b/core/modules/config/src/Tests/ConfigImportAllTest.php index 7b73631..bc801c3 100644 --- a/core/modules/config/src/Tests/ConfigImportAllTest.php +++ b/core/modules/config/src/Tests/ConfigImportAllTest.php @@ -76,8 +76,16 @@ public function testInstallUninstall() { // Purge the data. field_purge_batch(1000); + // Delete any forum terms so it can be uninstalled. + $vid = \Drupal::config('forum.settings')->get('vocabulary'); + $terms = entity_load_multiple_by_properties('taxonomy_term', ['vid' => $vid]); + foreach ($terms as $term) { + $term->delete(); + } + system_list_reset(); $all_modules = system_rebuild_module_data(); + $modules_to_uninstall = array_filter($all_modules, function ($module) { // Filter required and not enabled modules. if (!empty($module->info['required']) || $module->status == FALSE) { diff --git a/core/modules/config/src/Tests/ConfigImportUITest.php b/core/modules/config/src/Tests/ConfigImportUITest.php index f3a7f39..8285979 100644 --- a/core/modules/config/src/Tests/ConfigImportUITest.php +++ b/core/modules/config/src/Tests/ConfigImportUITest.php @@ -58,7 +58,6 @@ function testImport() { 'label' => 'New', 'weight' => 0, 'style' => '', - 'test_dependencies' => array(), 'protected_property' => '', ); $staging->write($dynamic_name, $original_dynamic_data); @@ -367,7 +366,6 @@ function testImportErrorLog() { 'label' => 'Primary', 'weight' => 0, 'style' => NULL, - 'test_dependencies' => array(), 'protected_property' => null, ); $staging->write($name_primary, $values_primary); @@ -383,7 +381,6 @@ function testImportErrorLog() { 'label' => 'Secondary Sync', 'weight' => 0, 'style' => NULL, - 'test_dependencies' => array(), 'protected_property' => null, ); $staging->write($name_secondary, $values_secondary); diff --git a/core/modules/config/src/Tests/ConfigImporterTest.php b/core/modules/config/src/Tests/ConfigImporterTest.php index 6e00397..d8179d5 100644 --- a/core/modules/config/src/Tests/ConfigImporterTest.php +++ b/core/modules/config/src/Tests/ConfigImporterTest.php @@ -172,7 +172,6 @@ function testNew() { 'label' => 'New', 'weight' => 0, 'style' => '', - 'test_dependencies' => array(), 'protected_property' => '', ); $staging->write($dynamic_name, $original_dynamic_data); diff --git a/core/modules/config/tests/config_test/config/schema/config_test.schema.yml b/core/modules/config/tests/config_test/config/schema/config_test.schema.yml index a557789..6b375af 100644 --- a/core/modules/config/tests/config_test/config/schema/config_test.schema.yml +++ b/core/modules/config/tests/config_test/config/schema/config_test.schema.yml @@ -15,9 +15,6 @@ config_test_dynamic: style: type: string label: 'style' - test_dependencies: - type: config_dependencies - label: 'Configuration dependencies' protected_property: type: string label: 'Protected property' diff --git a/core/modules/config/tests/config_test/src/Entity/ConfigTest.php b/core/modules/config/tests/config_test/src/Entity/ConfigTest.php index b022cc6..e61e672 100644 --- a/core/modules/config/tests/config_test/src/Entity/ConfigTest.php +++ b/core/modules/config/tests/config_test/src/Entity/ConfigTest.php @@ -72,13 +72,6 @@ class ConfigTest extends ConfigEntityBase implements ConfigTestInterface { public $style; /** - * Test dependencies. - * - * @var array; - */ - public $test_dependencies = array(); - - /** * A protected property of the configuration entity. * * @var string @@ -127,27 +120,15 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti /** * {@inheritdoc} */ - public function calculateDependencies() { - parent::calculateDependencies(); - foreach ($this->test_dependencies as $type => $deps) { - foreach ($deps as $dep) { - $this->addDependency($type, $dep); - } - } - } - - /** - * {@inheritdoc} - */ public function onDependencyRemoval(array $dependencies) { $changed = FALSE; $fix_deps = \Drupal::state()->get('config_test.fix_dependencies', array()); foreach ($dependencies['entity'] as $entity) { if (in_array($entity->getConfigDependencyName(), $fix_deps)) { - $key = array_search($entity->getConfigDependencyName(), $this->test_dependencies['entity']); + $key = array_search($entity->getConfigDependencyName(), $this->dependencies['fixed']['entity']); if ($key !== FALSE) { $changed = TRUE; - unset($this->test_dependencies['entity'][$key]); + unset($this->dependencies['fixed']['entity'][$key]); } } } @@ -156,4 +137,19 @@ public function onDependencyRemoval(array $dependencies) { } } + /** + * Sets the fixed dependencies. + * + * @param array $dependencies + * A config dependency array. + * + * @return $this + * + * @see \Drupal\Core\Config\Entity\ConfigDependencyManager + */ + public function setFixedDependencies(array $dependencies) { + $this->dependencies['fixed'] = $dependencies; + return $this; + } + } diff --git a/core/modules/forum/config/install/node.type.forum.yml b/core/modules/forum/config/install/node.type.forum.yml index f7833f7..a3dccc4 100644 --- a/core/modules/forum/config/install/node.type.forum.yml +++ b/core/modules/forum/config/install/node.type.forum.yml @@ -1,9 +1,13 @@ -type: forum +langcode: en +status: true +dependencies: + fixed: + module: + - forum name: 'Forum topic' +type: forum description: 'A forum topic starts a new discussion thread within a forum.' help: '' new_revision: false -display_submitted: true preview_mode: 1 -status: true -langcode: en +display_submitted: true diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index 0d36992..6b99955 100644 --- a/core/modules/forum/forum.module +++ b/core/modules/forum/forum.module @@ -11,8 +11,11 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Url; use Drupal\Component\Utility\String; +use Drupal\Core\Extension\Extension; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\taxonomy\Entity\Vocabulary; +use Symfony\Component\Routing\Exception\RouteNotFoundException; /** * Implements hook_help(). @@ -691,3 +694,63 @@ function template_preprocess_forum_submitted(&$variables) { } $variables['time'] = isset($variables['topic']->created) ? \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $variables['topic']->created) : ''; } + +/** + * Implements hook_system_info_alter(). + * + * Prevents forum module from being uninstalled whilst any forum nodes exist + * or there are any terms in the forum vocabulary. + */ +function forum_system_info_alter(&$info, Extension $file, $type) { + // It is not safe use the entity query service during maintenance mode. + if ($type == 'module' && !defined('MAINTENANCE_MODE') && $file->getName() == 'forum') { + $factory = \Drupal::service('entity.query'); + $nodes = $factory->get('node') + ->condition('type', 'forum') + ->accessCheck(FALSE) + ->range(0, 1) + ->execute(); + if (!empty($nodes)) { + $info['required'] = TRUE; + $info['explanation'] = t('To uninstall Forum first delete all Forum content.'); + } + $vid = \Drupal::config('forum.settings')->get('vocabulary'); + $terms = $factory->get('taxonomy_term') + ->condition('vid', $vid) + ->accessCheck(FALSE) + ->range(0, 1) + ->execute(); + if (!empty($terms)) { + $vocabulary = Vocabulary::load($vid); + $info['required'] = TRUE; + try { + if (!empty($info['explanation'])) { + if ($vocabulary->access('view')) { + $info['explanation'] = t('To uninstall Forum first delete all Forum content and %vocabulary terms.', [ + '%vocabulary' => $vocabulary->label(), + '!url' => $vocabulary->url('overview-form'), + ]); + } + else { + $info['explanation'] = t('To uninstall Forum first delete all Forum content and %vocabulary terms.', [ + '%vocabulary' => $vocabulary->label() + ]); + } + } + else { + $info['explanation'] = t('To uninstall Forum first delete all %vocabulary terms.', [ + '%vocabulary' => $vocabulary->label(), + '!url' => $vocabulary->url('overview-form'), + ]); + } + } + catch (RouteNotFoundException $e) { + // Route rebuilding might not have occurred before this hook is fired. + // Just use an explanation without a link for the time being. + $info['explanation'] = t('To uninstall Forum first delete all Forum content and %vocabulary terms.', [ + '%vocabulary' => $vocabulary->label() + ]); + } + } + } +} diff --git a/core/modules/forum/src/Tests/ForumUninstallTest.php b/core/modules/forum/src/Tests/ForumUninstallTest.php index eb57a5d..70983fd 100644 --- a/core/modules/forum/src/Tests/ForumUninstallTest.php +++ b/core/modules/forum/src/Tests/ForumUninstallTest.php @@ -9,7 +9,12 @@ use Drupal\comment\CommentInterface; use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface; +use Drupal\Component\Utility\String; +use Drupal\Core\DrupalKernel; +use Drupal\Core\Session\UserSession; +use Drupal\Core\Site\Settings; use Drupal\field\Entity\FieldStorageConfig; +use Drupal\node\Entity\NodeType; use Drupal\simpletest\WebTestBase; /** @@ -29,7 +34,8 @@ class ForumUninstallTest extends WebTestBase { /** * Tests if forum module uninstallation properly deletes the field. */ - function testForumUninstallWithField() { + public function testForumUninstallWithField() { + $this->drupalLogin($this->drupalCreateUser(['administer taxonomy', 'administer nodes', 'administer modules', 'delete any forum content', 'administer content types'])); // Ensure that the field exists before uninstallation. $field_storage = FieldStorageConfig::loadByName('node', 'taxonomy_forums'); $this->assertNotNull($field_storage, 'The taxonomy_forums field storage exists.'); @@ -65,27 +71,69 @@ function testForumUninstallWithField() { )); $comment->save(); - // Uninstall the forum module which should trigger field deletion. - $this->container->get('module_handler')->uninstall(array('forum')); - - // We want to test the handling of removing the forum comment field, so we - // ensure there is at least one other comment field attached to a node type - // so that comment_entity_load() runs for nodes. - \Drupal::service('comment.manager')->addDefaultField('node', 'forum', 'another_comment_field', CommentItemInterface::OPEN, 'another_comment_field'); - - $this->drupalGet('node/' . $node->nid->value); - $this->assertResponse(200); + // Attempt to uninstall forum. + $this->drupalGet('admin/modules/uninstall'); + // Assert forum is required. + $this->assertNoFieldByName('uninstall[forum]'); + $this->drupalGet('admin/modules'); + $this->assertText('To uninstall Forum first delete all Forum content'); + + // Delete the node. + $this->drupalPostForm('node/' . $node->id() . '/delete', array(), t('Delete')); + + // Attempt to uninstall forum. + $this->drupalGet('admin/modules/uninstall'); + // Assert forum is still required. + $this->assertNoFieldByName('uninstall[forum]'); + $this->drupalGet('admin/modules'); + $this->assertText('To uninstall Forum first delete all Forums terms'); + + // Delete any forum terms. + $vid = \Drupal::config('forum.settings')->get('vocabulary'); + $terms = entity_load_multiple_by_properties('taxonomy_term', ['vid' => $vid]); + foreach ($terms as $term) { + $term->delete(); + } + + // Ensure that the forum node type can not be deleted. + $this->drupalGet('admin/structure/types/manage/forum'); + $this->assertNoLink(t('Delete')); + + // Now attempt to uninstall forum. + $this->drupalGet('admin/modules/uninstall'); + // Assert forum is no longer required. + $this->assertFieldByName('uninstall[forum]'); + $this->drupalGet('admin/modules'); + $this->assertNoText('To uninstall Forum first delete all Forum content'); + $this->drupalPostForm('admin/modules/uninstall', array( + 'uninstall[forum]' => 1, + ), t('Uninstall')); + $this->drupalPostForm(NULL, [], t('Uninstall')); // Check that the field is now deleted. $field_storage = FieldStorageConfig::loadByName('node', 'taxonomy_forums'); $this->assertNull($field_storage, 'The taxonomy_forums field storage has been deleted.'); - } + // Check that a node type with a machine name of forum can be created after + // uninstalling the forum module and the node type is not locked. + $edit = array( + 'name' => 'Forum', + 'title_label' => 'title for forum', + 'type' => 'forum', + ); + $this->drupalPostForm('admin/structure/types/add', $edit, t('Save content type')); + $this->assertTrue((bool) NodeType::load('forum'), 'Node type with machine forum created.'); + $this->drupalGet('admin/structure/types/manage/forum'); + $this->clickLink(t('Delete')); + $this->drupalPostForm(NULL, array(), t('Delete')); + $this->assertResponse(200); + $this->assertFalse((bool) NodeType::load('forum'), 'Node type with machine forum deleted.'); + } /** * Tests uninstallation if the field storage has been deleted beforehand. */ - function testForumUninstallWithoutFieldStorage() { + public function testForumUninstallWithoutFieldStorage() { // Manually delete the taxonomy_forums field before module uninstallation. $field_storage = FieldStorageConfig::loadByName('node', 'taxonomy_forums'); $this->assertNotNull($field_storage, 'The taxonomy_forums field storage exists.'); diff --git a/core/modules/node/src/Tests/NodeTypePersistenceTest.php b/core/modules/node/src/Tests/NodeTypePersistenceTest.php deleted file mode 100644 index 62c2783..0000000 --- a/core/modules/node/src/Tests/NodeTypePersistenceTest.php +++ /dev/null @@ -1,62 +0,0 @@ -drupalCreateUser(array('bypass node access', 'administer content types', 'administer modules')); - $this->drupalLogin($web_user); - $forum_key = 'modules[Core][forum][enable]'; - $forum_enable = array($forum_key => "1"); - - // Enable forum and verify that the node type exists and is not disabled. - $this->drupalPostForm('admin/modules', $forum_enable, t('Save configuration')); - $forum = entity_load('node_type', 'forum'); - $this->assertTrue($forum->id(), 'Forum node type found.'); - $this->assertTrue($forum->isLocked(), 'Forum node type is locked'); - - // Check that forum node type (uncustomized) shows up. - $this->drupalGet('node/add'); - $this->assertText('forum', 'forum type is found on node/add'); - - // Customize forum description. - $description = $this->randomMachineName(); - $edit = array('description' => $description); - $this->drupalPostForm('admin/structure/types/manage/forum', $edit, t('Save content type')); - - // Check that forum node type customization shows up. - $this->drupalGet('node/add'); - $this->assertText($description, 'Customized description found'); - - // Uninstall forum. - $edit = array('uninstall[forum]' => 'forum'); - $this->drupalPostForm('admin/modules/uninstall', $edit, t('Uninstall')); - $this->drupalPostForm(NULL, array(), t('Uninstall')); - $forum = entity_load('node_type', 'forum'); - $this->assertFalse($forum->isLocked(), 'Forum node type is not locked'); - $this->drupalGet('node/add'); - $this->assertNoText('forum', 'forum type is no longer found on node/add'); - - // Reenable forum and check that the customization survived the module - // uninstall. - $this->drupalPostForm('admin/modules', $forum_enable, t('Save configuration')); - $this->drupalGet('node/add'); - $this->assertText($description, 'Customized description is found even after uninstall and reenable.'); - } - -} diff --git a/core/modules/node/src/Tests/NodeTypeTest.php b/core/modules/node/src/Tests/NodeTypeTest.php index b0bb658..23b692d 100644 --- a/core/modules/node/src/Tests/NodeTypeTest.php +++ b/core/modules/node/src/Tests/NodeTypeTest.php @@ -7,6 +7,7 @@ namespace Drupal\node\Tests; use Drupal\field\Entity\FieldConfig; +use Drupal\node\Entity\NodeType; /** * Ensures that node type functions work correctly. @@ -183,20 +184,29 @@ function testNodeTypeDeletion() { 'The content type is available for deletion.' ); $this->assertText(t('This action cannot be undone.'), 'The node type deletion confirmation form is available.'); - // Test that forum node type could not be deleted while forum active. - $this->container->get('module_handler')->install(array('forum')); + + // Test that a locked node type could not be deleted. + $this->container->get('module_handler')->install(array('node_test_config')); + // Lock the default node type. + $locked = \Drupal::state()->get('node.type.locked'); + $locked['default'] = 'default'; + \Drupal::state()->set('node.type.locked', $locked); // Call to flush all caches after installing the forum module in the same // way installing a module through the UI does. $this->resetAll(); - $this->drupalGet('admin/structure/types/manage/forum'); + $this->drupalGet('admin/structure/types/manage/default'); $this->assertNoLink(t('Delete')); - $this->drupalGet('admin/structure/types/manage/forum/delete'); + $this->drupalGet('admin/structure/types/manage/default/delete'); $this->assertResponse(403); - $this->container->get('module_handler')->uninstall(array('forum')); - $this->drupalGet('admin/structure/types/manage/forum'); - $this->assertLink(t('Delete')); - $this->drupalGet('admin/structure/types/manage/forum/delete'); + $this->container->get('module_handler')->uninstall(array('node_test_config')); + $this->container = \Drupal::getContainer(); + unset($locked['default']); + \Drupal::state()->set('node.type.locked', $locked); + $this->drupalGet('admin/structure/types/manage/default'); + $this->clickLink(t('Delete')); $this->assertResponse(200); + $this->drupalPostForm(NULL, array(), t('Delete')); + $this->assertFalse((bool) NodeType::load('default'), 'Node type with machine default deleted.'); } /** diff --git a/core/modules/system/src/Tests/Module/DependencyTest.php b/core/modules/system/src/Tests/Module/DependencyTest.php index 6ef1fb9..710341c 100644 --- a/core/modules/system/src/Tests/Module/DependencyTest.php +++ b/core/modules/system/src/Tests/Module/DependencyTest.php @@ -154,6 +154,15 @@ function testUninstallDependents() { $checkbox = $this->xpath('//input[@type="checkbox" and @name="uninstall[comment]"]'); $this->assert(count($checkbox) == 0, 'Checkbox for uninstalling the comment module not found.'); + // Delete any forum terms. + $vid = \Drupal::config('forum.settings')->get('vocabulary'); + // Ensure taxonomy has been loaded into the test-runner after forum was + // enabled. + \Drupal::moduleHandler()->load('taxonomy'); + $terms = entity_load_multiple_by_properties('taxonomy_term', ['vid' => $vid]); + foreach ($terms as $term) { + $term->delete(); + } // Uninstall the forum module, and check that taxonomy now can also be // uninstalled. $edit = array('uninstall[forum]' => 'forum'); diff --git a/core/modules/system/src/Tests/Module/InstallUninstallTest.php b/core/modules/system/src/Tests/Module/InstallUninstallTest.php index a57412c..43e16f4 100644 --- a/core/modules/system/src/Tests/Module/InstallUninstallTest.php +++ b/core/modules/system/src/Tests/Module/InstallUninstallTest.php @@ -16,7 +16,7 @@ */ class InstallUninstallTest extends ModuleTestBase { - public static $modules = array('system_test', 'dblog'); + public static $modules = array('system_test', 'dblog', 'taxonomy'); /** * Tests that a fixed set of modules can be installed and uninstalled. @@ -153,6 +153,15 @@ public function testInstallUninstall() { */ protected function assertSuccessfullUninstall($module, $package = 'Core') { $edit = array(); + if ($module == 'forum') { + // Forum cannot be uninstalled until all of the content entities related + // to it have been deleted. + $vid = \Drupal::config('forum.settings')->get('vocabulary'); + $terms = entity_load_multiple_by_properties('taxonomy_term', ['vid' => $vid]); + foreach ($terms as $term) { + $term->delete(); + } + } $edit['uninstall[' . $module . ']'] = TRUE; $this->drupalPostForm('admin/modules/uninstall', $edit, t('Uninstall')); $this->drupalPostForm(NULL, NULL, t('Uninstall')); diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php index bdf3c07..0622a5d 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php @@ -144,6 +144,14 @@ public function testCalculateDependencies() { // Calculating dependencies will reset the dependencies array. $this->entity->set('dependencies', array('module' => array('node'))); $this->assertEmpty($this->entity->calculateDependencies()); + + // Calculating dependencies will reset the dependencies array using fixed + // dependencies. + $this->entity->set('dependencies', array('module' => array('node'), 'fixed' => array('module' => 'views'))); + $dependencies = $this->entity->calculateDependencies(); + $this->assertContains('views', $dependencies['module']); + $this->assertNotContains('node', $dependencies['module']); + $this->assertContains('views', $dependencies['fixed']['module']); } /**