diff --git a/core/modules/filter/filter.install b/core/modules/filter/filter.install new file mode 100644 index 0000000..b0a9df9 --- /dev/null +++ b/core/modules/filter/filter.install @@ -0,0 +1,19 @@ +listAll('filter.format.') as $key) { + list(,, $format) = explode('.', $key); + $roles = array_keys(user_roles(FALSE, "use text format $format")); + $factory->getEditable($key)->set('roles', $roles)->save(TRUE); + } +} diff --git a/core/modules/filter/src/Entity/FilterFormat.php b/core/modules/filter/src/Entity/FilterFormat.php index 7419a15..b477d65 100644 --- a/core/modules/filter/src/Entity/FilterFormat.php +++ b/core/modules/filter/src/Entity/FilterFormat.php @@ -14,6 +14,7 @@ use Drupal\filter\FilterFormatInterface; use Drupal\filter\FilterPluginCollection; use Drupal\filter\Plugin\FilterInterface; +use Drupal\user\Entity\Role; /** * Represents a text format. @@ -86,19 +87,15 @@ class FilterFormat extends ConfigEntityBase implements FilterFormatInterface, En protected $weight = 0; /** - * List of user role IDs to grant access to use this format on initial creation. + * List of user role IDs granted with "use text format {$this->id()}" + * permission. * - * This property is always empty and unused for existing text formats. + * If this text format is the fallback format, this property is an empty + * array. * - * Default configuration objects of modules and installation profiles are - * allowed to specify a list of user role IDs to grant access to. - * - * This property only has an effect when a new text format is created and the - * list is not empty. By default, no user role is allowed to use a new format. - * - * @var array + * @var string[] */ - protected $roles; + protected $roles = []; /** * Configured filters for this text format. @@ -168,17 +165,6 @@ public function setFilterConfig($instance_id, array $configuration) { /** * {@inheritdoc} */ - public function toArray() { - $properties = parent::toArray(); - // The 'roles' property is only used during install and should never - // actually be saved. - unset($properties['roles']); - return $properties; - } - - /** - * {@inheritdoc} - */ public function disable() { if ($this->isFallbackFormat()) { throw new \LogicException("The fallback text format '{$this->id()}' cannot be disabled."); @@ -216,17 +202,14 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { // Clear the static caches of filter_formats() and others. filter_formats_reset(); - if (!$update && !$this->isSyncing()) { - // Default configuration of modules and installation profiles is allowed - // to specify a list of user roles to grant access to for the new format; - // apply the defined user role permissions when a new format is inserted - // and has a non-empty $roles property. + if (!$this->isSyncing()) { + // Synchronize user roles permissions on this text format. // Note: user_role_change_permissions() triggers a call chain back into // \Drupal\filter\FilterPermissions::permissions() and lastly // filter_formats(), so its cache must be reset upfront. - if (($roles = $this->get('roles')) && $permission = $this->getPermissionName()) { + if ($permission = $this->getPermissionName()) { foreach (user_roles() as $rid => $name) { - $enabled = in_array($rid, $roles, TRUE); + $enabled = in_array($rid, $this->roles, TRUE); user_role_change_permissions($rid, array($permission => $enabled)); } } @@ -421,12 +404,36 @@ public function onDependencyRemoval(array $dependencies) { $changed = TRUE; } } + + // Remove the dependencies of deleted user roles. + foreach ($dependencies['config'] as $key => $config) { + /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $config */ + if ($config->getEntityTypeId() == 'user_role') { + if (($delta = array_search($config->id(), $this->roles)) !== FALSE) { + unset($this->roles[$delta]); + $changed = TRUE; + } + } + } + return $changed; } /** * {@inheritdoc} */ + public function calculateDependencies() { + parent::calculateDependencies(); + // Add user role config entities as dependencies. + foreach (Role::loadMultiple($this->get('roles')) as $role) { + $this->addDependency($role->getConfigDependencyKey(), $role->getConfigDependencyName()); + } + return $this->dependencies; + } + + /** + * {@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 diff --git a/core/modules/filter/src/FilterFormatFormBase.php b/core/modules/filter/src/FilterFormatFormBase.php index e1222b4..74c64a9 100644 --- a/core/modules/filter/src/FilterFormatFormBase.php +++ b/core/modules/filter/src/FilterFormatFormBase.php @@ -7,7 +7,6 @@ namespace Drupal\filter; -use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityForm; use Drupal\Core\Entity\Query\QueryFactory; use Drupal\Core\Form\FormStateInterface; @@ -215,6 +214,11 @@ public function validateForm(array &$form, FormStateInterface $form_state) { $form_state->setValueForElement($form['format'], $format_format); $form_state->setValueForElement($form['name'], $format_name); + // Normalize roles by converting the list from a 'checkboxes' values format + // into a simple indexed list, having the unchecked values filtered out. + $roles = array_values(array_filter($form_state->getValue('roles'))); + $form_state->setValueForElement($form['roles'], $roles); + $format_exists = $this->queryFactory ->get('filter_format') ->condition('format', $format_format, '<>') @@ -245,13 +249,6 @@ public function submitForm(array &$form, FormStateInterface $form_state) { } $format->save(); - // Save user permissions. - if ($permission = $format->getPermissionName()) { - foreach ($form_state->getValue('roles') as $rid => $enabled) { - user_role_change_permissions($rid, array($permission => $enabled)); - } - } - $form_state->setRedirect('filter.admin_overview'); return $this->entity; diff --git a/core/modules/filter/src/Tests/FilterDefaultConfigTest.php b/core/modules/filter/src/Tests/FilterDefaultConfigTest.php index daef17b..a9e60cb 100644 --- a/core/modules/filter/src/Tests/FilterDefaultConfigTest.php +++ b/core/modules/filter/src/Tests/FilterDefaultConfigTest.php @@ -46,8 +46,8 @@ function testInstallation() { // Verify that format default property values have been added/injected. $this->assertTrue($format->uuid()); - // Verify that the loaded format does not contain any roles. - $this->assertEqual($format->get('roles'), NULL); + // The loaded format contains the roles supplied in the default config. + $this->assertEqual($format->get('roles'), array(RoleInterface::ANONYMOUS_ID, RoleInterface::AUTHENTICATED_ID)); // Verify that the defined roles in the default config have been processed. $this->assertEqual(array_keys(filter_get_roles_by_format($format)), array( RoleInterface::ANONYMOUS_ID, @@ -89,10 +89,9 @@ function testUpdateRoles() { )); $format->save(); - // Verify that roles have not been updated. + // Verify if the roles have been updated. $format = entity_load('filter_format', 'filter_test'); $this->assertEqual(array_keys(filter_get_roles_by_format($format)), array( - RoleInterface::ANONYMOUS_ID, RoleInterface::AUTHENTICATED_ID, )); } diff --git a/core/modules/filter/src/Tests/FilterSettingsTest.php b/core/modules/filter/src/Tests/FilterSettingsTest.php index 9b95336..4e53db9 100644 --- a/core/modules/filter/src/Tests/FilterSettingsTest.php +++ b/core/modules/filter/src/Tests/FilterSettingsTest.php @@ -7,7 +7,11 @@ namespace Drupal\filter\Tests; +use Drupal\Component\Utility\Unicode; +use Drupal\Core\Form\FormState; +use Drupal\filter\Entity\FilterFormat; use Drupal\simpletest\KernelTestBase; +use Drupal\user\Entity\Role; /** * Tests filter settings. @@ -17,11 +21,9 @@ class FilterSettingsTest extends KernelTestBase { /** - * Modules to enable. - * - * @var array + * {@inheritdoc} */ - public static $modules = array('filter'); + public static $modules = ['filter', 'user', 'system']; /** * Tests explicit and implicit default settings for filters. @@ -62,4 +64,60 @@ function testFilterDefaults() { ))); } } + + /** + * Tests dependencies. + */ + public function testDependencies() { + /** @var \Drupal\Core\Config\ConfigFactoryInterface $factory */ + $factory = $this->container->get('config.factory'); + $this->installEntitySchema('user'); + + /** @var \Drupal\user\RoleInterface[] $roles */ + $roles = []; + // Create two arbitrary roles. + for ($i = 0; $i < 2; $i++) { + $roles[$i] = Role::create([ + 'id' => Unicode::strtolower($this->randomMachineName()), + 'label' => $this->randomString(), + ]); + $roles[$i]->save(); + } + + // Create a text format and grant the two roles to use it. + /** @var \Drupal\filter\FilterFormatInterface $format */ + $format = FilterFormat::create([ + 'format' => Unicode::strtolower($this->randomMachineName()), + 'name' => $this->randomString(), + 'roles' => [$roles[0]->id(), $roles[1]->id()], + ]); + + // Save the format. + $format->save(); + + // The format should be dependent on both role config entities. + $dependencies = $format->getDependencies()['config']; + $this->assertIdentical(count($dependencies), count($format->get('roles'))); + $this->assertTrue(in_array($roles[0]->getConfigDependencyName(), $dependencies)); + $this->assertTrue(in_array($roles[1]->getConfigDependencyName(), $dependencies)); + $this->assertIdentical($roles[0]->getConfigDependencyName(), "user.role.{$format->get('roles')[0]}"); + $this->assertIdentical($roles[1]->getConfigDependencyName(), "user.role.{$format->get('roles')[1]}"); + + // The text format exists before deleting the role. + $this->assertNotNull($format = FilterFormat::load($format->id())); + + // Delete the second role. + $roles[1]->delete(); + + // Deleting a role should not delete the filter format. + $this->assertNotNull($format = FilterFormat::load($format->id())); + + // The format should be dependent now only on the first role. + $dependencies = $format->getDependencies()['config']; + $this->assertIdentical(count($dependencies), count($format->get('roles'))); + $this->assertTrue(in_array($roles[0]->getConfigDependencyName(), $dependencies)); + $this->assertFalse(in_array($roles[1]->getConfigDependencyName(), $dependencies)); + $this->assertIdentical($roles[0]->getConfigDependencyName(), "user.role.{$format->get('roles')[0]}"); + } + } diff --git a/core/modules/filter/src/Tests/Update/FilterUpdateTest.php b/core/modules/filter/src/Tests/Update/FilterUpdateTest.php new file mode 100644 index 0000000..854e0a3 --- /dev/null +++ b/core/modules/filter/src/Tests/Update/FilterUpdateTest.php @@ -0,0 +1,55 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz', + ]; + } + + /** + * Tests filter_update_8001(). + * + * @see filter_update_8001(). + */ + public function testFilterUpdate8001() { + /** @var \Drupal\Core\Config\ConfigFactoryInterface $factory */ + $factory = $this->container->get('config.factory'); + + // Load a common text format. + $full_html = $factory->get('filter.format.full_html'); + $roles = array_keys(user_roles(FALSE, "use text format full_html")); + + // Before the update the text format misses the 'roles' property. + $this->assertNull($full_html->get('roles')); + + // Run updates. + $this->runUpdates(); + + // Reload text formats. + $full_html = $factory->get('filter.format.full_html'); + + // The roles allowed to use 'full_html' text format were copied into the + // config entity, in 'roles' property. + $this->assertIdentical($full_html->get('roles'), $roles); + } + +}