Issue #2224581 by alexpott, larowlan, jhodgdon, mgifford: Delete forum data on uninstall.

8.0.x
Nathaniel Catchpole 2014-10-22 10:52:29 +01:00
parent 69be11ebad
commit 0c11136455
19 changed files with 342 additions and 162 deletions

View File

@ -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:
enforced:
type: config_dependencies_base
label: 'Enforced configuration dependencies'
config_entity:
type: mapping
mapping:

View File

@ -19,42 +19,71 @@ use Drupal\Component\Utility\SortArray;
* handled in the correct order. For example, node types are created before
* their fields, and both are created before the view display configuration.
*
* 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
* configuration entity is provided by an installation profile and the view will
* not work without a certain module, the profile must declare a dependency on
* this module in its info.yml file. If you do not want your extension to always
* 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.
* The configuration dependency value is structured like this:
* <code>
* array(
* 'entity => array(
* // An array of configuration entity object names. Recalculated on save.
* ),
* 'module' => array(
* // An array of module names. Recalculated on save.
* ),
* 'theme' => array(
* // An array of theme names. Recalculated on save.
* ),
* 'enforced' => array(
* // An array of configuration dependencies that the config entity is
* // ensured to have regardless of the details of the configuration. These
* // dependencies are not recalculated on save.
* 'entity' => array(),
* 'module' => array(),
* 'theme' => array(),
* ),
* );
* </code>
*
* Classes for configuration entities with dependencies normally extend
* \Drupal\Core\Config\Entity\ConfigEntityBase or at least use
* \Drupal\Core\Plugin\PluginDependencyTrait to manage dependencies. The class
* must implement
* \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies(),
* which should calculate (and return) the dependencies. In this method, use
* Configuration entity dependencies are recalculated on save based on the
* current values of the configuration. For example, a filter format will depend
* on the modules that provide the filter plugins it configures. The filter
* format can be reconfigured to use a different filter plugin provided by
* another module. If this occurs, the dependencies will be recalculated on save
* and the old module will be removed from the list of dependencies and replaced
* with the new one.
*
* Configuration entity classes usually extend
* \Drupal\Core\Config\Entity\ConfigEntityBase. The base class provides a
* generic implementation of the calculateDependencies() method that can
* discover dependencies due to enforced dependencies, plugins, and third party
* settings. If the configuration entity has dependencies that cannot be
* discovered by the base class's implementation, then it needs to implement
* \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies() to
* calculate (and return) the dependencies. In this method, use
* \Drupal\Core\Config\Entity\ConfigEntityBase::addDependency() to add
* dependencies. Most implementations 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
* \Drupal\Core\Config\Entity\ConfigEntityInterface::getDependencies().
* dependencies. Implementations should call the base class implementation to
* inherit the generic functionality.
*
* Classes for configurable plugins are a special case. They can either declare
* their configuration dependencies using the calculateDependencies() method
* described in the paragraph above, or if they have only static dependencies,
* these can be declared using the 'config_dependencies' annotation key.
*
* If an extension author wants a configuration entity to depend on something
* that is not calculable then they can add these dependencies to the enforced
* dependencies 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
* an enforced dependency on the module. The dependency on the Forum module can
* not be calculated since there is nothing inherent in the state of the node
* type configuration entity that depends on functionality provided by the Forum
* module.
*
* Once declared properly, dependencies are saved to the configuration entity's
* configuration object so that they can be checked without the module that
* provides the configuration entity class being installed. This is important
* for configuration synchronization, which needs to be able to validate
* configuration in the staging directory before the synchronization has
* occurred.
* occurred. Also, if you have a configuration entity object and you want to
* get the current dependencies without recalculation, you can use
* \Drupal\Core\Config\Entity\ConfigEntityInterface::getDependencies().
*
* When uninstalling a module or a theme, configuration entities that are
* dependent will also be removed. This default behavior can lead to undesirable
@ -65,10 +94,21 @@ use Drupal\Component\Utility\SortArray;
* which allows the entity class to remove dependencies instead of being deleted
* themselves. Implementations should save the entity if dependencies have been
* successfully removed, in order to register the newly cleaned-out dependency
* list. So, in the above example, the node view mode configuration entity class
* list. So, for example, the node view mode configuration entity class
* should implement this method to remove references to formatters if the plugin
* that supplies them depends on a module that is being uninstalled.
*
* 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
* configuration entity is provided by an installation profile and the view will
* not work without a certain module, the profile must declare a dependency on
* this module in its info.yml file. If you do not want your extension to always
* 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.
*
* @see \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies()
* @see \Drupal\Core\Config\Entity\ConfigEntityInterface::getConfigDependencyName()
* @see \Drupal\Core\Config\Entity\ConfigEntityInterface::onDependencyRemoval()

View File

@ -316,7 +316,14 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
public function calculateDependencies() {
// Dependencies should be recalculated on every save. This ensures stale
// dependencies are never saved.
$this->dependencies = array();
if (isset($this->dependencies['enforced'])) {
$dependencies = $this->dependencies['enforced'];
$this->dependencies = $dependencies;
$this->dependencies['enforced'] = $dependencies;
}
else {
$this->dependencies = array();
}
if ($this instanceof EntityWithPluginCollectionInterface) {
// Configuration entities need to depend on the providers of any plugins
// that they store the configuration for.

View File

@ -36,6 +36,7 @@ class BlockConfigSchemaTest extends KernelTestBase {
'system',
'taxonomy',
'user',
'text',
);
/**
@ -61,6 +62,8 @@ class BlockConfigSchemaTest extends KernelTestBase {
$this->typedConfig = \Drupal::service('config.typed');
$this->blockManager = \Drupal::service('plugin.manager.block');
$this->installEntitySchema('block_content');
$this->installEntitySchema('taxonomy_term');
$this->installEntitySchema('node');
}
/**

View File

@ -62,6 +62,36 @@ class CommentFieldsTest extends CommentTestBase {
$this->assertEqual($field->default_value[0]['status'], CommentItemInterface::CLOSED);
}
/**
* 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.
*/

View File

@ -47,8 +47,10 @@ class ConfigDependencyTest extends DrupalUnitTestBase {
$entity1 = $storage->create(
array(
'id' => 'entity1',
'test_dependencies' => array(
'module' => array('node', 'config_test')
'dependencies' => array(
'enforced' => array(
'module' => array('node')
)
)
)
);
@ -63,15 +65,14 @@ class ConfigDependencyTest extends DrupalUnitTestBase {
// 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('enforced' => 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('enforced' => 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('enforced' => array('entity' => array($entity3->getConfigDependencyName())))));
$entity4->save();
// Test getting $entity1's dependencies as configuration dependency objects.
@ -100,10 +101,8 @@ class ConfigDependencyTest extends DrupalUnitTestBase {
// 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->setEnforcedDependencies([])->save();
$entity3->setEnforcedDependencies(['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 @@ class ConfigDependencyTest extends DrupalUnitTestBase {
// 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('enforced' => array('entity' => array($entity1->getConfigDependencyName())))))->save();
$alt_storage->create(array('id' => 'entity2', 'dependencies' => array('enforced' => 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 @@ class ConfigDependencyTest extends DrupalUnitTestBase {
$entity1 = $storage->create(
array(
'id' => 'entity1',
'test_dependencies' => array(
'module' => array('node', 'config_test')
'dependencies' => array(
'enforced' => array(
'module' => array('node', 'config_test')
),
),
)
);
@ -166,8 +167,10 @@ class ConfigDependencyTest extends DrupalUnitTestBase {
$entity2 = $storage->create(
array(
'id' => 'entity2',
'test_dependencies' => array(
'entity' => array($entity1->getConfigDependencyName()),
'dependencies' => array(
'enforced' => array(
'entity' => array($entity1->getConfigDependencyName()),
),
),
)
);
@ -181,8 +184,10 @@ class ConfigDependencyTest extends DrupalUnitTestBase {
$entity1 = $storage->create(
array(
'id' => 'entity1',
'test_dependencies' => array(
'module' => array('node', 'config_test')
'dependencies' => array(
'enforced' => array(
'module' => array('node', 'config_test')
),
),
)
);
@ -190,8 +195,10 @@ class ConfigDependencyTest extends DrupalUnitTestBase {
$entity2 = $storage->create(
array(
'id' => 'entity2',
'test_dependencies' => array(
'entity' => array($entity1->getConfigDependencyName()),
'dependencies' => array(
'enforced' => array(
'entity' => array($entity1->getConfigDependencyName()),
),
),
)
);

View File

@ -76,8 +76,16 @@ class ConfigImportAllTest extends ModuleTestBase {
// 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) {

View File

@ -58,7 +58,6 @@ class ConfigImportUITest extends WebTestBase {
'label' => 'New',
'weight' => 0,
'style' => '',
'test_dependencies' => array(),
'protected_property' => '',
);
$staging->write($dynamic_name, $original_dynamic_data);
@ -367,7 +366,6 @@ class ConfigImportUITest extends WebTestBase {
'label' => 'Primary',
'weight' => 0,
'style' => NULL,
'test_dependencies' => array(),
'protected_property' => null,
);
$staging->write($name_primary, $values_primary);
@ -383,7 +381,6 @@ class ConfigImportUITest extends WebTestBase {
'label' => 'Secondary Sync',
'weight' => 0,
'style' => NULL,
'test_dependencies' => array(),
'protected_property' => null,
);
$staging->write($name_secondary, $values_secondary);

View File

@ -172,7 +172,6 @@ class ConfigImporterTest extends DrupalUnitTestBase {
'label' => 'New',
'weight' => 0,
'style' => '',
'test_dependencies' => array(),
'protected_property' => '',
);
$staging->write($dynamic_name, $original_dynamic_data);

View File

@ -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'

View File

@ -71,13 +71,6 @@ class ConfigTest extends ConfigEntityBase implements ConfigTestInterface {
*/
public $style;
/**
* Test dependencies.
*
* @var array;
*/
public $test_dependencies = array();
/**
* A protected property of the configuration entity.
*
@ -124,18 +117,6 @@ class ConfigTest extends ConfigEntityBase implements ConfigTestInterface {
}
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
parent::calculateDependencies();
foreach ($this->test_dependencies as $type => $deps) {
foreach ($deps as $dep) {
$this->addDependency($type, $dep);
}
}
}
/**
* {@inheritdoc}
*/
@ -144,10 +125,10 @@ class ConfigTest extends ConfigEntityBase implements ConfigTestInterface {
$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['enforced']['entity']);
if ($key !== FALSE) {
$changed = TRUE;
unset($this->test_dependencies['entity'][$key]);
unset($this->dependencies['enforced']['entity'][$key]);
}
}
}
@ -156,4 +137,19 @@ class ConfigTest extends ConfigEntityBase implements ConfigTestInterface {
}
}
/**
* Sets the enforced dependencies.
*
* @param array $dependencies
* A config dependency array.
*
* @return $this
*
* @see \Drupal\Core\Config\Entity\ConfigDependencyManager
*/
public function setEnforcedDependencies(array $dependencies) {
$this->dependencies['enforced'] = $dependencies;
return $this;
}
}

View File

@ -1,9 +1,13 @@
type: forum
langcode: en
status: true
dependencies:
enforced:
module:
- forum
name: 'Forum topic'
type: forum
description: 'A <em>forum topic</em> 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

View File

@ -11,8 +11,11 @@ use Drupal\Component\Utility\Xss;
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 <em>Forum</em> 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 <em>Forum</em> content and <a href="!url">%vocabulary</a> terms.', [
'%vocabulary' => $vocabulary->label(),
'!url' => $vocabulary->url('overview-form'),
]);
}
else {
$info['explanation'] = t('To uninstall Forum first delete all <em>Forum</em> content and %vocabulary terms.', [
'%vocabulary' => $vocabulary->label()
]);
}
}
else {
$info['explanation'] = t('To uninstall Forum first delete all <a href="!url">%vocabulary</a> 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 <em>Forum</em> content and %vocabulary terms.', [
'%vocabulary' => $vocabulary->label()
]);
}
}
}
}

View File

@ -9,7 +9,12 @@ namespace Drupal\forum\Tests;
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 @@ class ForumUninstallTest extends WebTestBase {
));
$comment->save();
// Uninstall the forum module which should trigger field deletion.
$this->container->get('module_handler')->uninstall(array('forum'));
// 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');
// 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');
// Delete the node.
$this->drupalPostForm('node/' . $node->id() . '/delete', array(), t('Delete'));
$this->drupalGet('node/' . $node->nid->value);
$this->assertResponse(200);
// 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.');

View File

@ -1,62 +0,0 @@
<?php
/**
* @file
* Definition of Drupal\node\Tests\NodeTypePersistenceTest.
*/
namespace Drupal\node\Tests;
/**
* Ensures that node type customization survives module enabling and disabling.
*
* @group node
*/
class NodeTypePersistenceTest extends NodeTestBase {
// Enable the prerequisite modules for forum
public static $modules = array('history', 'taxonomy', 'options', 'comment');
/**
* 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");
// 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.');
}
}

View File

@ -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 @@ class NodeTypeTest extends NodeTestBase {
'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.');
}
/**

View File

@ -154,6 +154,15 @@ class DependencyTest extends ModuleTestBase {
$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');

View File

@ -16,7 +16,7 @@ use Drupal\Core\Logger\RfcLogLevel;
*/
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 @@ class InstallUninstallTest extends ModuleTestBase {
*/
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'));

View File

@ -144,6 +144,14 @@ class ConfigEntityBaseUnitTest extends UnitTestCase {
// 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 enforced
// dependencies.
$this->entity->set('dependencies', array('module' => array('node'), 'enforced' => array('module' => 'views')));
$dependencies = $this->entity->calculateDependencies();
$this->assertContains('views', $dependencies['module']);
$this->assertNotContains('node', $dependencies['module']);
$this->assertContains('views', $dependencies['enforced']['module']);
}
/**