diff --git a/core/modules/filter/filter.install b/core/modules/filter/filter.install new file mode 100644 index 0000000..0d670dc --- /dev/null +++ b/core/modules/filter/filter.install @@ -0,0 +1,26 @@ +get('filter.settings')->get('fallback_format'); + + foreach ($factory->listAll('filter.format.') as $key) { + list(,, $format) = explode('.', $key); + if ($format != $fallback_format) { + $roles = array_keys(user_roles(FALSE, "use text format $format")); + } + else { + $roles = []; + } + $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 7926b58..1df794e 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,6 +404,18 @@ 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; } @@ -430,10 +425,10 @@ public function onDependencyRemoval(array $dependencies) { public function calculateDependencies() { parent::calculateDependencies(); // Add user role config entities as dependencies. The permission name is - // FALSE when this is a fallback text format and that doesn't depend on any - // user role. - if ($permission = $this->getPermissionName()) { - foreach (user_roles(FALSE, $permission) as $rid => $role) { + // FALSE when this is a fallback text format and that doesn't create any + // dependency on user roles. + if (($rids = $this->get('roles')) && $permission = $this->getPermissionName()) { + foreach (Role::loadMultiple($rids) as $role) { $this->addDependency($role->getConfigDependencyKey(), $role->getConfigDependencyName()); } } diff --git a/core/modules/filter/src/FilterFormatFormBase.php b/core/modules/filter/src/FilterFormatFormBase.php index e1222b4..0d64c8a 100644 --- a/core/modules/filter/src/FilterFormatFormBase.php +++ b/core/modules/filter/src/FilterFormatFormBase.php @@ -215,6 +215,12 @@ 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 'checkboxes' values format to + // a simple indexed list, having unchecked values filtered out. + $is_fallback = $format_format == $this->config('filter.settings')->get('fallback_format'); + $roles = $is_fallback ? [] : 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 +251,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/FilterSettingsTest.php b/core/modules/filter/src/Tests/FilterSettingsTest.php index 6511989..37b20a4 100644 --- a/core/modules/filter/src/Tests/FilterSettingsTest.php +++ b/core/modules/filter/src/Tests/FilterSettingsTest.php @@ -72,13 +72,6 @@ public function testDependencies() { $factory = $this->container->get('config.factory'); $this->installEntitySchema('user'); - // Create a text formats. - /** @var \Drupal\filter\FilterFormatInterface $format */ - $format = FilterFormat::create([ - 'format' => Unicode::strtolower($this->randomMachineName()), - 'name' => $this->randomString(), - ]); - // Create 2 arbitrary roles allowed to use the text format $format. /** @var \Drupal\user\RoleInterface[] $roles */ $roles = []; @@ -86,11 +79,18 @@ public function testDependencies() { $roles[$i] = Role::create([ 'id' => Unicode::strtolower($this->randomMachineName()), 'label' => $this->randomString(), - 'permissions' => [$format->getPermissionName()], ]); $roles[$i]->save(); } + // Create a text formats. + /** @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(); @@ -123,21 +123,20 @@ public function testDependencies() { $this->assertTrue(in_array($roles[0]->getConfigDependencyName(), $dependencies)); $this->assertTrue(in_array($roles[1]->getConfigDependencyName(), $dependencies)); - $this->assertTrue(FilterFormat::load($format->id()), 'Filter format exists before deleting the role'); + // The text format exists before deleting the role. + $this->assertTrue($format = FilterFormat::load($format->id())); + // Delete the second role. $roles[1]->delete(); + // Deleting a role should not delete the filter format. - $this->assertTrue(FilterFormat::load($format->id()), 'Filter format exists after deleting the role'); - $format->save(); + $this->assertTrue($format = FilterFormat::load($format->id())); // The format should be dependent only on first role. $dependencies = $format->getDependencies()['config']; $this->assertIdentical(count($dependencies), 1); $this->assertTrue(in_array($roles[0]->getConfigDependencyName(), $dependencies)); $this->assertFalse(in_array($roles[1]->getConfigDependencyName(), $dependencies)); - - // The text format was not deleted on removal of dependencies. - $this->assertFalse(empty($format)); } } 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..6eaec35 --- /dev/null +++ b/core/modules/filter/src/Tests/Update/FilterUpdateTest.php @@ -0,0 +1,62 @@ +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'); + $full_html_roles = array_keys(user_roles(FALSE, "use text format {$full_html->get('format')}")); + + // Load the fallback text format. + $plain_text = $factory->get('filter.format.plain_text'); + + // The text formats are missing the 'roles' property. + $this->assertNull($full_html->get('roles')); + $this->assertNull($plain_text->get('roles')); + + // Run updates. + $this->runUpdates(); + + // Reload text formats. + $full_html = $factory->get('filter.format.full_html'); + $plain_text = $factory->get('filter.format.plain_text'); + + // The roles allowed to use 'full_html' text format were copied in the + // config entity. + $this->assertIdentical($full_html->get('roles'), $full_html_roles); + // The fallback text format has no roles. + $this->assertIdentical($plain_text->get('roles'), []); + } + +}