diff -u b/core/modules/filter/src/Entity/FilterFormat.php b/core/modules/filter/src/Entity/FilterFormat.php --- b/core/modules/filter/src/Entity/FilterFormat.php +++ b/core/modules/filter/src/Entity/FilterFormat.php @@ -403,7 +403,8 @@ $changed = FALSE; $filters = $this->filters(); foreach ($filters as $filter) { - if (in_array($filter->provider, $dependencies['module'])) { + // Only remove disabled filters. + if (!$filter->status && in_array($filter->provider, $dependencies['module'])) { $this->removeFilter($filter->getPluginId()); $changed = TRUE; } diff -u b/core/modules/filter/src/Tests/FilterAPITest.php b/core/modules/filter/src/Tests/FilterAPITest.php --- b/core/modules/filter/src/Tests/FilterAPITest.php +++ b/core/modules/filter/src/Tests/FilterAPITest.php @@ -7,6 +7,7 @@ namespace Drupal\filter\Tests; +use Drupal\Component\Utility\String; use Drupal\Core\Session\AnonymousUserSession; use Drupal\Core\TypedData\OptionsProviderInterface; use Drupal\Core\TypedData\DataDefinition; @@ -390,30 +391,60 @@ /** * Tests that dependency removal works. * - * Uninstalling a module that provides a filter should not remove a text - * format that uses the filter. + * Uninstalling a module that provides a filter plugin should not remove a + * filter format configuration entity that does not have the filter plugin + * enabled. */ public function testDependencyRemoval() { $this->installSchema('user', array('users_data')); $filter_format = \Drupal\filter\Entity\FilterFormat::load('filtered_html'); - // Enable the filter_test_restrict_tags_and_attributes filter. + // Enable the filter_test_restrict_tags_and_attributes filter plugin on the + // filtered_html filter format. $filter_config = [ 'weight' => 10, 'status' => 1, ]; $filter_format->setFilterConfig('filter_test_restrict_tags_and_attributes', $filter_config)->save(); - // Verify that a dependency exists on the module that provides the filter. + $module_data = _system_rebuild_module_data(); + $this->assertTrue($module_data['filter_test']->info['required'], 'The filter_test module is required.'); + $this->assertEqual($module_data['filter_test']->info['explanation'], String::format('Provides a filter plugin that is in use in the following filter formats: %formats', array('%formats' => $filter_format->label()))); + + // Disable the filter_test_restrict_tags_and_attributes filter plugin but + // have custom configuration so that the filter plugin is still configured + // in filtered_html the filter format. + $filter_config = [ + 'weight' => 20, + 'status' => 0, + ]; + $filter_format->setFilterConfig('filter_test_restrict_tags_and_attributes', $filter_config)->save(); + // Use the get method to match the assert after the module has been + // uninstalled. + $filters = $filter_format->get('filters'); + $this->assertTrue(isset($filters['filter_test_restrict_tags_and_attributes']), 'The filter plugin filter_test_restrict_tags_and_attributes is configured by the filtered_html filter format.'); + + drupal_static_reset('filter_formats'); + \Drupal::entityManager()->getStorage('filter_format')->resetCache(); + $module_data = _system_rebuild_module_data(); + $this->assertFalse(isset($module_data['filter_test']->info['required']), 'The filter_test module is required.'); + + // Verify that a dependency exists on the module that provides the filter + // plugin since it has configuration for the disabled plugin. $this->assertEqual(['module' => ['filter_test']], $filter_format->getDependencies()); // Uninstall the module. \Drupal::moduleHandler()->uninstall(array('filter_test')); - \Drupal::entityManager()->getStorage('filter_format')->resetCache(); - // Verify the text format still exists but the dependency and filter is gone. + // Verify the filter format still exists but the dependency and filter is + // gone. + \Drupal::entityManager()->getStorage('filter_format')->resetCache(); $filter_format = \Drupal\filter\Entity\FilterFormat::load('filtered_html'); $this->assertEqual([], $filter_format->getDependencies()); + // Use the get method since the FilterFormat::filters() method only returns + // existing plugins. + $filters = $filter_format->get('filters'); + $this->assertFalse(isset($filters['filter_test_restrict_tags_and_attributes']), 'The filter plugin filter_test_restrict_tags_and_attributes is not configured by the filtered_html filter format.'); } } only in patch2: unchanged: --- a/core/modules/filter/filter.module +++ b/core/modules/filter/filter.module @@ -10,6 +10,7 @@ use Drupal\Component\Utility\String; use Drupal\Component\Utility\Xss; use Drupal\Core\Cache\Cache; +use Drupal\Core\Extension\Extension; use Drupal\Core\Render\Element; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; @@ -83,6 +84,38 @@ function filter_theme() { } /** + * Implements hook_system_info_alter(). + * + * Prevents uninstallation of modules that provide filter plugins that are being + * used in a filter format. + */ +function filter_system_info_alter(&$info, Extension $file, $type) { + // It is not safe to call filter_formats() during maintenance mode. + if ($type == 'module' && !defined('MAINTENANCE_MODE')) { + // Get filter plugins supplied by this module. + $filter_plugins = array_filter(\Drupal::service('plugin.manager.filter')->getDefinitions(), function ($definition) use ($file) { + return $definition['provider'] == $file->getName(); + }); + if (!empty($filter_plugins)) { + $used_in = []; + // Find out if any filter formats have the plugin enabled. + foreach(filter_formats() as $filter_format) { + foreach($filter_plugins as $filter_plugin) { + if ($filter_format->filters($filter_plugin['id'])->status) { + $used_in[] = $filter_format->label(); + $info['required'] = TRUE; + break; + } + } + } + if (!empty($used_in)) { + $info['explanation'] = t('Provides a filter plugin that is in use in the following filter formats: %formats', array('%formats' => implode(', ', $used_in))); + } + } + } +} + +/** * Retrieves a list of enabled text formats, ordered by weight. * * @param \Drupal\Core\Session\AccountInterface|null $account