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 393bfd7..a3ee208 100644 --- a/core/modules/comment/src/Tests/CommentFieldsTest.php +++ b/core/modules/comment/src/Tests/CommentFieldsTest.php @@ -63,6 +63,29 @@ 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', + )); + + // Delete the first comment field. + FieldStorageConfig::loadByName('node', 'comment')->delete(); + $this->drupalGet('node/' . $node->nid->value); + $this->assertResponse(200); + } + + /** * Tests that comment module works when installed after a content module. */ function testCommentInstallAfterContentModule() { diff --git a/core/modules/config/src/Tests/ConfigImportAllTest.php b/core/modules/config/src/Tests/ConfigImportAllTest.php index 7b73631..008c4b7 100644 --- a/core/modules/config/src/Tests/ConfigImportAllTest.php +++ b/core/modules/config/src/Tests/ConfigImportAllTest.php @@ -78,6 +78,12 @@ public function testInstallUninstall() { system_list_reset(); $all_modules = system_rebuild_module_data(); + // 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(); + } $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/forum/forum.install b/core/modules/forum/forum.install index 2ebc79f..0ab5db2 100644 --- a/core/modules/forum/forum.install +++ b/core/modules/forum/forum.install @@ -9,6 +9,7 @@ use Drupal\field\Entity\FieldInstanceConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\comment\CommentManagerInterface; +use Drupal\node\Entity\NodeType; /** * Implements hook_install(). @@ -126,10 +127,9 @@ function forum_uninstall() { // Purge field data now to allow taxonomy and options module to be uninstalled // if this is the only field remaining. field_purge_batch(10); - // Allow to delete a forum's node type. - $locked = \Drupal::state()->get('node.type.locked'); - unset($locked['forum']); - \Drupal::state()->set('node.type.locked', $locked); + + // Remove the forum node type. + NodeType::load('forum')->delete(); } /** diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index 039c305..cea0a6d 100644 --- a/core/modules/forum/forum.module +++ b/core/modules/forum/forum.module @@ -10,8 +10,10 @@ use Drupal\Component\Utility\Xss; use Drupal\Core\Entity\EntityInterface; 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; /** * Implements hook_help(). @@ -737,3 +739,52 @@ 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 to call entity_load_multiple_by_properties() during + // maintenance mode. + if ($type == 'module' && !defined('MAINTENANCE_MODE') && $file->getName() == 'forum') { + $nodes = entity_load_multiple_by_properties('node', ['type' => 'forum']); + 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 = entity_load_multiple_by_properties('taxonomy_term', ['vid' => $vid]); + if (!empty($terms)) { + $vocabulary = Vocabulary::load($vid); + $access = \Drupal::currentUser()->hasPermission('administer taxonomy'); + $info['required'] = TRUE; + if (!empty($info['explanation'])) { + if ($access) { + $info['explanation'] = t('To uninstall Forum first delete all Forum content and %vocabulary terms.', [ + '%vocabulary' => $vocabulary->label(), + '!url' => \Drupal::url('entity.taxonomy_vocabulary.overview_form', ['taxonomy_vocabulary' => $vid]), + ]); + } + else { + $info['explanation'] = t('To uninstall Forum first delete all Forum content and %vocabulary terms.', [ + '%vocabulary' => $vocabulary->label() + ]); + } + } + else { + if ($access) { + $info['explanation'] = t('To uninstall Forum first delete all %vocabulary terms.', [ + '%vocabulary' => $vocabulary->label(), + '!url' => \Drupal::url('entity.taxonomy_vocabulary.overview_form', ['taxonomy_vocabulary' => $vid]), + ]); + } + else { + $info['explanation'] = t('To uninstall Forum first delete all %vocabulary terms.', ['%vocabulary' => $vocabulary->label()]); + } + } + } + } +} diff --git a/core/modules/forum/src/Tests/ForumUninstallTest.php b/core/modules/forum/src/Tests/ForumUninstallTest.php index 388910c..5834a9d 100644 --- a/core/modules/forum/src/Tests/ForumUninstallTest.php +++ b/core/modules/forum/src/Tests/ForumUninstallTest.php @@ -9,6 +9,10 @@ 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\simpletest\WebTestBase; @@ -29,7 +33,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'])); // 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 +70,53 @@ 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(); + } + + // Force a module rebuild. + //system_rebuild_module_data(); + + // 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.'); } - /** * 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 index 62c2783..2d8a7bd 100644 --- a/core/modules/node/src/Tests/NodeTypePersistenceTest.php +++ b/core/modules/node/src/Tests/NodeTypePersistenceTest.php @@ -13,48 +13,48 @@ * @group node */ class NodeTypePersistenceTest extends NodeTestBase { - // Enable the prerequisite modules for forum - public static $modules = array('history', 'taxonomy', 'options', 'comment'); + // Enable the prerequisite modules for book + public static $modules = array('node'); /** * Tests that node type customizations persist through disable and uninstall. */ function testNodeTypeCustomizationPersistence() { $web_user = $this->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"); + $book_key = 'modules[Core][book][enable]'; + $book_enable = array($book_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'); + // Enable book and verify that the node type exists and is not disabled. + $this->drupalPostForm('admin/modules', $book_enable, t('Save configuration')); + $book = entity_load('node_type', 'book'); + $this->assertTrue($book->id(), 'book node type found.'); + $this->assertTrue($book->isLocked(), 'book node type is locked'); - // Check that forum node type (uncustomized) shows up. + // Check that book node type (uncustomized) shows up. $this->drupalGet('node/add'); - $this->assertText('forum', 'forum type is found on node/add'); + $this->assertText('book', 'book type is found on node/add'); - // Customize forum description. + // Customize book description. $description = $this->randomMachineName(); $edit = array('description' => $description); - $this->drupalPostForm('admin/structure/types/manage/forum', $edit, t('Save content type')); + $this->drupalPostForm('admin/structure/types/manage/book', $edit, t('Save content type')); - // Check that forum node type customization shows up. + // Check that book node type customization shows up. $this->drupalGet('node/add'); $this->assertText($description, 'Customized description found'); - // Uninstall forum. - $edit = array('uninstall[forum]' => 'forum'); + // Uninstall book. + $edit = array('uninstall[book]' => 'book'); $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'); + $book = entity_load('node_type', 'book'); + $this->assertFalse($book->isLocked(), 'book node type no longer exists.'); $this->drupalGet('node/add'); - $this->assertNoText('forum', 'forum type is no longer found on node/add'); + $this->assertNoText('book', 'book type is no longer found on node/add'); - // Reenable forum and check that the customization survived the module + // Reenable book and check that the customization survived the module // uninstall. - $this->drupalPostForm('admin/modules', $forum_enable, t('Save configuration')); + $this->drupalPostForm('admin/modules', $book_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 8c2d128..4c997ba 100644 --- a/core/modules/node/src/Tests/NodeTypeTest.php +++ b/core/modules/node/src/Tests/NodeTypeTest.php @@ -183,19 +183,19 @@ 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')); - // Call to flush all caches after installing the forum module in the same + // Test that book node type could not be deleted while book active. + $this->container->get('module_handler')->install(array('book')); + // Call to flush all caches after installing the book 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/book'); $this->assertNoLink(t('Delete')); - $this->drupalGet('admin/structure/types/manage/forum/delete'); + $this->drupalGet('admin/structure/types/manage/book/delete'); $this->assertResponse(403); - $this->container->get('module_handler')->uninstall(array('forum')); - $this->drupalGet('admin/structure/types/manage/forum'); + $this->container->get('module_handler')->uninstall(array('book')); + $this->drupalGet('admin/structure/types/manage/book'); $this->assertLink(t('Delete')); - $this->drupalGet('admin/structure/types/manage/forum/delete'); + $this->drupalGet('admin/structure/types/manage/book/delete'); $this->assertResponse(200); } diff --git a/core/modules/system/src/Tests/Module/DependencyTest.php b/core/modules/system/src/Tests/Module/DependencyTest.php index 6ef1fb9..993c889 100644 --- a/core/modules/system/src/Tests/Module/DependencyTest.php +++ b/core/modules/system/src/Tests/Module/DependencyTest.php @@ -154,6 +154,12 @@ 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'); + $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 c826d1c..4005ee1 100644 --- a/core/modules/system/src/Tests/Module/InstallUninstallTest.php +++ b/core/modules/system/src/Tests/Module/InstallUninstallTest.php @@ -14,7 +14,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. @@ -151,6 +151,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'));