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..017f3fb 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..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/forum/config/install/node.type.forum.yml b/core/modules/forum/config/install/node.type.forum.yml
index f7833f7..796ff70 100644
--- a/core/modules/forum/config/install/node.type.forum.yml
+++ b/core/modules/forum/config/install/node.type.forum.yml
@@ -1,9 +1,15 @@
-type: forum
+langcode: en
+status: true
+dependencies:
+ 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
+third_party_settings:
+# Make the node type dependent on the forum module.
+ forum: { }
diff --git a/core/modules/forum/config/schema/forum.schema.yml b/core/modules/forum/config/schema/forum.schema.yml
index f74587c..8f6f903 100644
--- a/core/modules/forum/config/schema/forum.schema.yml
+++ b/core/modules/forum/config/schema/forum.schema.yml
@@ -64,3 +64,8 @@ block.settings.forum_new_block:
block_count:
type: integer
label: 'Block count'
+
+# Forum's third party settings only exist to ensure that the node type is
+# dependent on the forum module.
+node_type.third_party.forum:
+ type: ignore
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 0e7087d..4c14535 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().
@@ -725,3 +727,56 @@ 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')
+ ->count()
+ ->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)
+ ->count()
+ ->accessCheck(FALSE)
+ ->range(0, 1)
+ ->execute();
+ if (!empty($terms)) {
+ $vocabulary = Vocabulary::load($vid);
+ $info['required'] = TRUE;
+ 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'),
+ ]);
+ }
+ }
+ }
+}
diff --git a/core/modules/forum/src/Tests/ForumUninstallTest.php b/core/modules/forum/src/Tests/ForumUninstallTest.php
index 388910c..2c2e89e 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 3125928..7a7e8f2 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,28 @@ 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'));
+ 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 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'));