function SmartDefaultSettings::computeSmartDefaultSettings

Same name and namespace in other branches
  1. 10 core/modules/ckeditor5/src/SmartDefaultSettings.php \Drupal\ckeditor5\SmartDefaultSettings::computeSmartDefaultSettings()
  2. 11.x core/modules/ckeditor5/src/SmartDefaultSettings.php \Drupal\ckeditor5\SmartDefaultSettings::computeSmartDefaultSettings()

Computes the closest possible equivalent settings for switching to CKEditor 5.

Parameters

\Drupal\editor\EditorInterface|null $text_editor: The editor being reconfigured for CKEditor 5 to match the CKEditor 4 settings as closely as possible (if it was using CKEditor 4).

\Drupal\filter\FilterFormatInterface $text_format: The text format for which to compute smart default settings.

Return value

array An array with two values: 1. The cloned config entity with settings modified for CKEditor 5 … or a completely new config entity if this text format did not yet have one. 2. Messages explaining what conclusions were reached.

Throws

\InvalidArgumentException Thrown when computing smart default settings for a new text format, or when the given text editor and format do not form a pair.

File

core/modules/ckeditor5/src/SmartDefaultSettings.php, line 110

Class

SmartDefaultSettings
Generates CKEditor 5 settings for existing text editors/formats.

Namespace

Drupal\ckeditor5

Code

public function computeSmartDefaultSettings(?EditorInterface $text_editor, FilterFormatInterface $text_format) : array {
    if ($text_format->isNew()) {
        throw new \InvalidArgumentException('Smart default settings can only be computed when there is a pre-existing text format.');
    }
    if ($text_editor && $text_editor->id() !== $text_format->id()) {
        throw new \InvalidArgumentException('The given text editor and text format must form a pair.');
    }
    $messages = [];
    // Ensure that unsaved changes to the text format object are also respected.
    if ($text_editor) {
        // Overwrite the Editor config entity object's $filterFormat property, to
        // prevent calls to Editor::hasAssociatedFilterFormat() and
        // Editor::getFilterFormat() from loading the FilterFormat from storage.
        // @todo Remove in https://www.drupal.org/project/drupal/issues/3231347.
        $reflector = new \ReflectionObject($text_editor);
        $property = $reflector->getProperty('filterFormat');
        $property->setAccessible(TRUE);
        $property->setValue($text_editor, $text_format);
    }
    // When there is a pre-existing text editor, pass that. Otherwise, generate
    // an empty shell of a text editor config entity — this will then
    // automatically get the default CKEditor 5 settings.
    // @todo Update after https://www.drupal.org/project/drupal/issues/3226673.
    
    /** @var \Drupal\editor\Entity\Editor $editor */
    $editor = $text_editor !== NULL ? clone $text_editor : Editor::create([
        'format' => $text_format->id(),
        // @see \Drupal\editor\Entity\Editor::__construct()
        // @see \Drupal\ckeditor5\Plugin\Editor\CKEditor5::getDefaultSettings()
'editor' => 'ckeditor5',
    ]);
    $editor->setEditor('ckeditor5');
    $source_editing_additions = HTMLRestrictions::emptySet();
    // Compute the appropriate settings based on the CKEditor 4 configuration
    // if it exists.
    $old_editor = $editor->id() ? Editor::load($editor->id()) : NULL;
    $old_editor_restrictions = $old_editor ? HTMLRestrictions::fromTextFormat($old_editor->getFilterFormat()) : HTMLRestrictions::emptySet();
    // @todo Remove in https://www.drupal.org/project/drupal/issues/3245351
    if ($old_editor) {
        $editor->setImageUploadSettings($old_editor->getImageUploadSettings());
    }
    if ($old_editor && $old_editor->getEditor() === 'ckeditor') {
        [
            $upgraded_settings,
            $messages,
        ] = $this->createSettingsFromCKEditor4($old_editor->getSettings(), HTMLRestrictions::fromTextFormat($old_editor->getFilterFormat()));
        $editor->setSettings($upgraded_settings);
        // *Before* determining which elements are still needed for this text
        // format, ensure that all already enabled plugins that are configurable
        // have valid settings.
        // For all already enabled plugins, find the ones that are configurable,
        // and add their default settings. For enabled plugins with element
        // subsets, compute the appropriate settings to achieve the subset that
        // matches the original text format restrictions.
        $this->addDefaultSettingsForEnabledConfigurablePlugins($editor);
        $this->computeSubsetSettingForEnabledPluginsWithSubsets($editor, $text_format);
    }
    // Add toolbar items based on HTML tags and attributes.
    // NOTE: Helper updates $editor->settings by reference and returns info for the message.
    $result = $this->addToolbarItemsToMatchHtmlElementsInFormat($text_format, $editor);
    if ($result !== NULL) {
        [
            $enabling_message_content,
            $enabled_for_attributes_message_content,
            $missing,
            $plugins_enabled,
        ] = $result;
        // Distinguish between unsupported elements covering only tags or not.
        $missing_attributes = new HTMLRestrictions(array_filter($missing->getAllowedElements()));
        $unsupported = $missing->diff($missing_attributes);
        if ($enabling_message_content) {
            $this->logger
                ->info('The CKEditor 5 migration enabled the following plugins to support tags that are allowed by the %text_format text format: %enabling_message_content. The text format must be saved to make these changes active.', [
                '%text_format' => $editor->getFilterFormat()
                    ->get('name'),
                '%enabling_message_content' => $enabling_message_content,
            ]);
        }
        // Warn user about unsupported tags.
        if (!$unsupported->allowsNothing()) {
            $this->addTagsToSourceEditing($editor, $unsupported);
            $source_editing_additions = $source_editing_additions->merge($unsupported);
            $this->logger
                ->info("The following tags were permitted by the %text_format text format's filter configuration, but no plugin was available that supports them. To ensure the tags remain supported by this text format, the following were added to the Source Editing plugin's <em>Manually editable HTML tags</em>: @unsupported_string. The text format must be saved to make these changes active.", [
                '%text_format' => $editor->getFilterFormat()
                    ->get('name'),
                '@unsupported_string' => $unsupported->toFilterHtmlAllowedTagsString(),
            ]);
        }
        if ($enabled_for_attributes_message_content) {
            $this->logger
                ->info('The CKEditor 5 migration process enabled the following plugins to support specific attributes that are allowed by the %text_format text format: %enabled_for_attributes_message_content.', [
                '%text_format' => $editor->getFilterFormat()
                    ->get('name'),
                '%enabled_for_attributes_message_content' => $enabled_for_attributes_message_content,
            ]);
        }
        // Warn user about supported tags but missing attributes.
        if (!$missing_attributes->allowsNothing()) {
            $this->addTagsToSourceEditing($editor, $missing_attributes);
            $source_editing_additions = $source_editing_additions->merge($missing_attributes);
            $this->logger
                ->info("As part of migrating to CKEditor 5, it was found that the %text_format text format's HTML filters includes plugins that support the following tags, but not some of their attributes. To ensure these attributes remain supported, the following were added to the Source Editing plugin's <em>Manually editable HTML tags</em>: @missing_attributes. The text format must be saved to make these changes active.", [
                '%text_format' => $editor->getFilterFormat()
                    ->get('name'),
                '@missing_attributes' => $missing_attributes->toFilterHtmlAllowedTagsString(),
            ]);
        }
    }
    $has_html_restrictions = $editor->getFilterFormat()
        ->filters('filter_html')->status;
    $missing_fundamental_tags = HTMLRestrictions::emptySet();
    if ($has_html_restrictions) {
        $fundamental = new HTMLRestrictions($this->pluginManager
            ->getProvidedElements([
            'ckeditor5_essentials',
            'ckeditor5_paragraph',
        ]));
        $filter_html_restrictions = HTMLRestrictions::fromTextFormat($editor->getFilterFormat());
        $missing_fundamental_tags = $fundamental->diff($filter_html_restrictions);
        if (!$missing_fundamental_tags->allowsNothing()) {
            $editor->getFilterFormat()
                ->setFilterConfig('filter_html', $filter_html_restrictions->merge($fundamental)
                ->getAllowedElements());
            $this->logger
                ->warning("As part of migrating the %text_format text format to CKEditor 5, the following tag(s) were added to <em>Limit allowed HTML tags and correct faulty HTML</em>, because they are needed to provide fundamental CKEditor 5 functionality : @missing_tags. The text format must be saved to make these changes active.", [
                '%text_format' => $editor->getFilterFormat()
                    ->get('name'),
                '@missing_tags' => $missing_fundamental_tags->toFilterHtmlAllowedTagsString(),
            ]);
        }
    }
    // Finally: for all enabled plugins, find the ones that are configurable,
    // and add their default settings. For enabled plugins with element subsets,
    // compute the appropriate settings to achieve the subset that matches the
    // original text format restrictions.
    // Note: if switching from CKEditor 4, this will already have happened for
    // plugins that were already enabled in CKEditor 4. It's harmless to compute
    // this again.
    $this->addDefaultSettingsForEnabledConfigurablePlugins($editor);
    $this->computeSubsetSettingForEnabledPluginsWithSubsets($editor, $text_format);
    // In CKEditor 4, it's possible for settings to exist for plugins that are
    // not actually enabled. During the upgrade path, these would then be mapped
    // to equivalent CKEditor 5 configuration. But CKEditor 5 does not allow
    // configuration to be stored for disabled plugins. Therefore determine
    // which plugins actually are enabled, and omit the (upgraded) plugin
    // configuration for disabled plugins.
    // @see \Drupal\ckeditor5\Plugin\CKEditor4To5UpgradePluginInterface::mapCKEditor4SettingsToCKEditor5Configuration()
    if ($old_editor && $old_editor->getEditor() === 'ckeditor') {
        $enabled_definitions = $this->pluginManager
            ->getEnabledDefinitions($editor);
        $enabled_configurable_definitions = array_filter($enabled_definitions, function (CKEditor5PluginDefinition $definition) : bool {
            return is_a($definition->getClass(), CKEditor5PluginConfigurableInterface::class, TRUE);
        });
        $settings = $editor->getSettings();
        $settings['plugins'] = array_intersect_key($settings['plugins'], $enabled_configurable_definitions);
        $editor->setSettings($settings);
    }
    if ($has_html_restrictions) {
        // Determine what tags/attributes are allowed in this text format that were
        // not allowed previous to the switch.
        $allowed_by_new_plugin_config = new HTMLRestrictions($this->pluginManager
            ->getProvidedElements(array_keys($this->pluginManager
            ->getEnabledDefinitions($editor)), $editor));
        $surplus_tags_attributes = $allowed_by_new_plugin_config->diff($old_editor_restrictions)
            ->diff($missing_fundamental_tags);
        $attributes_to_tag = [];
        $added_tags = [];
        if (!$surplus_tags_attributes->allowsNothing()) {
            $surplus_elements = $surplus_tags_attributes->getAllowedElements();
            $added_tags = array_diff_key($surplus_elements, $old_editor_restrictions->getAllowedElements());
            foreach ($surplus_elements as $tag => $attributes) {
                $the_attributes = is_array($attributes) ? $attributes : [];
                foreach ($the_attributes as $attribute_name => $enabled) {
                    if ($enabled) {
                        $attributes_to_tag[$attribute_name][] = $tag;
                    }
                }
            }
        }
        $help_enabled = $this->moduleHandler
            ->moduleExists('help');
        $can_access_dblog = $this->currentUser
            ->hasPermission('access site reports') && $this->moduleHandler
            ->moduleExists('dblog');
        if (!empty($plugins_enabled) || !$source_editing_additions->allowsNothing()) {
            $beginning = $help_enabled ? $this->t('To maintain the capabilities of this text format, <a target="_blank" href=":ck_migration_url">the CKEditor 5 migration</a> did the following:', [
                ':ck_migration_url' => Url::fromRoute('help.page', [
                    'name' => 'ckeditor5',
                ], [
                    'fragment' => 'migration-settings',
                ])->toString(),
            ]) : $this->t('To maintain the capabilities of this text format, the CKEditor 5 migration did the following:');
            $plugin_info = !empty($plugins_enabled) ? $this->t('Enabled these plugins: (%plugins).', [
                '%plugins' => implode(', ', $plugins_enabled),
            ]) : '';
            $source_editing_info = '';
            if (!$source_editing_additions->allowsNothing()) {
                $source_editing_info = $help_enabled ? $this->t('Added these tags/attributes to the Source Editing Plugin\'s <a target="_blank" href=":source_edit_url">Manually editable HTML tags</a> setting: @tag_list', [
                    '@tag_list' => $source_editing_additions->toFilterHtmlAllowedTagsString(),
                    ':source_edit_url' => Url::fromRoute('help.page', [
                        'name' => 'ckeditor5',
                    ], [
                        'fragment' => 'source-editing',
                    ])->toString(),
                ]) : $this->t("Added these tags/attributes to the Source Editing Plugin's Manually editable HTML tags setting: @tag_list", [
                    '@tag_list' => $source_editing_additions->toFilterHtmlAllowedTagsString(),
                ]);
            }
            $end = $can_access_dblog ? $this->t('Additional details are available <a target="_blank" href=":dblog_url">in your logs</a>.', [
                ':dblog_url' => Url::fromRoute('dblog.overview')->setOption('query', [
                    'type[]' => 'ckeditor5',
                ])
                    ->toString(),
            ]) : $this->t('Additional details are available in your logs.');
            $messages[MessengerInterface::TYPE_STATUS][] = new FormattableMarkup('@beginning @plugin_info @source_editing_info. @end', [
                '@beginning' => $beginning,
                '@plugin_info' => $plugin_info,
                '@source_editing_info' => $source_editing_info,
                '@end' => $end,
            ]);
        }
        // Generate warning for:
        // - The addition of <p>/<br> due to them being fundamental tags.
        // - The addition of other tags/attributes previously unsupported by the
        //   format.
        if (!$missing_fundamental_tags->allowsNothing() || !empty($attributes_to_tag) || !empty($added_tags)) {
            $beginning = $this->t('Updating to CKEditor 5 added support for some previously unsupported tags/attributes.');
            $fundamental_tags = '';
            if ($help_enabled && !$missing_fundamental_tags->allowsNothing()) {
                $fundamental_tags = $this->formatPlural(count($missing_fundamental_tags->toCKEditor5ElementsArray()), 'The @tag tag was added because it is <a target="_blank" href=":fundamental_tag_link">required by CKEditor 5</a>.', 'The @tag tags were added because they are <a target="_blank" href=":fundamental_tag_link">required by CKEditor 5</a>.', [
                    '@tag' => implode(', ', $missing_fundamental_tags->toCKEditor5ElementsArray()),
                    ':fundamental_tag_link' => URL::fromRoute('help.page', [
                        'name' => 'ckeditor5',
                    ], [
                        'fragment' => 'required-tags',
                    ])->toString(),
                ]);
            }
            elseif (!$missing_fundamental_tags->allowsNothing()) {
                $fundamental_tags = $this->formatPlural(count($missing_fundamental_tags->toCKEditor5ElementsArray()), 'The @tag tag was added because it is required by CKEditor 5.', 'The @tag tags were added because they are required by CKEditor 5.', [
                    '@tag' => implode(', ', $missing_fundamental_tags->toCKEditor5ElementsArray()),
                ]);
            }
            $added_elements_begin = !empty($attributes_to_tag) || !empty($added_tags) ? $this->t('A plugin introduced support for the following:') : '';
            $added_elements_tags = !empty($added_tags) ? $this->formatPlural(count($added_tags), 'The tag %tags;', 'The tags %tags;', [
                '%tags' => implode(', ', array_map(function ($tag_name) {
                    return "<{$tag_name}>";
                }, array_keys($added_tags))),
            ]) : '';
            $added_elements_attributes = !empty($attributes_to_tag) ? $this->formatPlural(count($attributes_to_tag), 'This attribute: %attributes;', 'These attributes: %attributes;', [
                '%attributes' => rtrim(array_reduce(array_keys($attributes_to_tag), function ($carry, $item) use ($attributes_to_tag) {
                    $for_tags = implode(', ', array_map(function ($item) {
                        return "<{$item}>";
                    }, $attributes_to_tag[$item]));
                    return "{$carry} {$item} ({$this->t('for', [], [
                        'context' => 'Ckeditor 5 tag list',
                    ])} {$for_tags}),";
                }, ''), " ,"),
            ]) : '';
            $end = $can_access_dblog ? $this->t('Additional details are available <a target="_blank" href=":dblog_url">in your logs</a>.', [
                ':dblog_url' => Url::fromRoute('dblog.overview')->setOption('query', [
                    'type[]' => 'ckeditor5',
                ])
                    ->toString(),
            ]) : $this->t('Additional details are available in your logs.');
            $messages[MessengerInterface::TYPE_WARNING][] = new FormattableMarkup('@beginning @added_elements_begin @fundamental_tags @added_elements_tags @added_elements_attributes @end', [
                '@beginning' => $beginning,
                '@added_elements_begin' => $added_elements_begin,
                '@fundamental_tags' => $fundamental_tags,
                '@added_elements_tags' => $added_elements_tags,
                '@added_elements_attributes' => $added_elements_attributes,
                '@end' => $end,
            ]);
        }
    }
    return [
        $editor,
        $messages,
    ];
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.