Commit 8b6c3435 authored by catch's avatar catch

Issue #2348925 by alexpott, cilefen, Wim Leers: Uninstalling a filter plugin removes text formats

parent 3c6f8b8d
......@@ -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;
......@@ -82,6 +83,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.
*
......
......@@ -7,6 +7,7 @@
namespace Drupal\filter\Entity;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
use Drupal\Core\Entity\EntityStorageInterface;
......@@ -387,4 +388,44 @@ public function getHtmlRestrictions() {
}
}
/**
* {@inheritdoc}
*/
public function removeFilter($instance_id) {
unset($this->filters[$instance_id]);
$this->filterCollection->removeInstanceId($instance_id);
}
/**
* {@inheritdoc}
*/
public function onDependencyRemoval(array $dependencies) {
$changed = FALSE;
$filters = $this->filters();
foreach ($filters as $filter) {
// Remove disabled filters, so that this FilterFormat config entity can
// continue to exist.
if (!$filter->status && in_array($filter->provider, $dependencies['module'])) {
$this->removeFilter($filter->getPluginId());
$changed = TRUE;
}
}
if ($changed) {
$this->save();
}
}
/**
* {@inheritdoc}
*/
protected function calculatePluginDependencies(PluginInspectionInterface $instance) {
// Only add dependencies for plugins that are actually configured. This is
// necessary because the filter plugin collection will return all available
// filter plugins.
// @see \Drupal\filter\FilterPluginCollection::getConfiguration()
if (isset($this->filters[$instance->getPluginId()])) {
parent::calculatePluginDependencies($instance);
}
}
}
......@@ -84,4 +84,12 @@ public function getFilterTypes();
*/
public function getHtmlRestrictions();
/**
* Removes a filter.
*
* @param string $instance_id
* The ID of a filter plugin to be removed.
*/
public function removeFilter($instance_id);
}
......@@ -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;
......@@ -386,4 +387,68 @@ public function assertFilterFormatViolation(ConstraintViolationListInterface $vi
}
$this->assertTrue($filter_format_violation_found, format_string('Validation violation for invalid value "%invalid_value" found', array('%invalid_value' => $invalid_value)));
}
/**
* Tests that filter format dependency removal works.
*
* Ensure that modules providing filter plugins are required when the plugin
* is in use, and that only disabled plugins are removed from format
* configuration entities rather than the configuration entities being
* deleted.
*
* @see \Drupal\filter\Entity\FilterFormat::onDependencyRemoval()
* @see filter_system_info_alter()
*/
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 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();
$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::service('module_installer')->uninstall(array('filter_test'));
// 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.');
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment