Revert "Issue #1833716 by quicksketch, Wim Leers, effulgentsia: added WYSIWYG: Introduce 'Text editors' as part of filter format configuration."

Fixing commit credit.

This reverts commit ee1b6de693.
8.0.x
webchick 2013-01-16 09:06:28 -08:00
parent d2bfb66949
commit b6ac1b819d
16 changed files with 0 additions and 1421 deletions

View File

@ -1,108 +0,0 @@
<?php
/**
* @file
* Documentation for Text Editor API.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Performs alterations on text editor definitions.
*
* @param array $editors
* An array of metadata of text editors, as collected by the plugin annotation
* discovery mechanism.
*
* @see \Drupal\editor\Plugin\EditorBase
*/
function hook_editor_info_alter(array &$editors) {
$editors['some_other_editor']['label'] = t('A different name');
$editors['some_other_editor']['library']['module'] = 'myeditoroverride';
}
/**
* Provides defaults for editor instances.
*
* Modules that extend the list of settings for a particular text editor library
* should specify defaults for those settings using this hook. These settings
* will be used for any new editors, as well as merged into any existing editor
* configuration that has not yet been provided with a specific value for a
* setting (as may happen when a module providing a new setting is enabled after
* the text editor has been configured).
*
* Note that only the top-level of this array is merged into the defaults. If
* multiple modules provide nested settings with the same top-level key, only
* the first will be used. Modules should avoid deep nesting of settings to
* avoid defaults being undefined.
*
* The return value of this hook is not cached. If retrieving defaults in a
* complex manner, the implementing module should provide its own caching inside
* the hook.
*
* @param $editor
* A string indicating the name of the editor library whose default settings
* are being provided.
*
* @return array
* An array of default settings that will be merged into the editor defaults.
*/
function hook_editor_default_settings($editor) {
return array(
'mymodule_new_setting1' => TRUE,
'mymodule_new_setting2' => array(
'foo' => 'baz',
'bar' => 'qux',
),
);
}
/**
* Modifies default settings for editor instances.
*
* Modules that extend the behavior of other modules may use this hook to change
* the default settings provided to new and existing editors. This hook should
* be used when changing an existing setting to a new value. To add a new
* default setting, hook_editor_default_settings() should be used.
*
* The return value of this hook is not cached. If retrieving defaults in a
* complex manner, the implementing module should provide its own caching inside
* the hook.
*
* @param $default_settings
* The array of default settings which may be modified, passed by reference.
* @param $editor
* A string indicating the name of the editor library whose default settings
* are being provided.
*
* @return array
* An array of default settings that will be merged into the editor defaults.
*
* @see hook_editor_default_settings()
*/
function hook_editor_default_settings_alter(&$default_settings, $editor) {
$default_settings['toolbar'] = array('Bold', 'Italics', 'Underline');
}
/**
* Modifies JavaScript settings that are added for text editors.
*
* @param array $settings
* All the settings that will be added to the page via drupal_add_js() for
* the text formats to which a user has access.
* @param array $formats
* The list of format objects for which settings are being added.
*/
function hook_editor_js_settings_alter(array &$settings, array $formats) {
if (isset($formats['filtered_html'])) {
$settings['filtered_html']['editor'][] = 'MyDifferentEditor';
$settings['filtered_html']['editorSettings']['buttons'] = array('strong', 'italic', 'underline');
}
}
/**
* @} End of "addtogroup hooks".
*/

View File

@ -1,7 +0,0 @@
name = Text Editor
description = "Allows to associate text formats with text editor libraries such as WYSIWYGs or toolbars."
package = Core
version = VERSION
core = 8.x
dependencies[] = filter
configure = admin/config/content/formats

View File

@ -1,288 +0,0 @@
<?php
/**
* @file
* Adds bindings for client-side "text editors" to text formats.
*/
use Drupal\file\Plugin\Core\Entity\File;
use Drupal\editor\Plugin\Core\Entity\Editor;
use Drupal\Component\Utility\NestedArray;
/**
* Implements hook_help().
*/
function editor_help($path, $arg) {
switch ($path) {
case 'admin/help#editor':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Text Editor module provides a framework to extend the user interface on text fields that allow HTML input. Without Text Editor module, fields accept only text where formatting must be typed manually, such as entering a <code>&lt;strong&gt;</code> tag to make text bold or an <code>&lt;em&gt;</code> tag to italicize text. The Text Editor module allows these fields to be enhanced with rich text editors (WYSIWYGs) or toolbars, which make entering and formatting content easier. For more information, see the online handbook entry for <a href="@editor">Editor module</a>.', array('@editor' => 'http://drupal.org/documentation/modules/editor/')) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Enabling or configuring a text editor') . '</dt>';
$output .= '<dd>' . t('The Text Editor module does not have its own configuration page. Instead it enhances existing configuration pages with additional options. Text editors are attached to individual text formats, which can be configured on the <a href="@formats">Text formats page</a>. Each text format may be associated with a single text editor. When entering content with that text format, the associated text editor will automatically be enabled.', array('@formats' => url('admin/config/content/formats'))) . '</dd>';
$output .= '<dt>' . t('Allowing a user to choose a text editor') . '</dt>';
$output .= '<dd>' . t('Because text editor configurations are bound to a text format, users with access to more than one text format may switch between available text editors by changing the text format for a field. For more information about text formats, see the <a href="@filter">Filter module help page</a>, which describes text formats in more detail.', array('@filter' => url('admin/help/filter'))) . '</dd>';
// @todo: Mention the availability of the built-in core WYSIWYG (CKEditor)
// when it becomes available. See http://drupal.org/node/1878344.
$output .= '<dt>' . t('Installing additional text editor libraries') . '</dt>';
$output .= '<dd>' . t('The Text Editor module does not provide any text editor libraries itself. Most installations of Drupal include a module that provides a text editor library which may be enabled on the <a href="@modules">Modules page</a>. Additional modules that provide text editor libraries may be <a href="@download">downloaded from Drupal.org</a>.', array('@modules' => url('admin/modules'), '@download' => 'http://drupal.org/search/site/wysiwyg%20module')) . '</dd>';
$output .= '</dl>';
return $output;
}
}
/**
* Implements hook_menu_alter().
*
* Rewrites the menu entries for filter module that relate to the configuration
* of text editors.
*/
function editor_menu_alter(&$items) {
$items['admin/config/content/formats']['title'] = 'Text formats and editors';
$items['admin/config/content/formats']['description'] = 'Configure how user-contributed content is filtered and formatted, as well as the text editor user interface (WYSIWYGs or toolbars).';
}
/**
* Implements hook_element_info().
*
* Extends the functionality of text_format elements (provided by Filter
* module), so that selecting a text format notifies a client-side text editor
* when it should be enabled or disabled.
*
* @see filter_element_info()
*/
function editor_element_info() {
$type['text_format'] = array(
'#pre_render' => array('editor_pre_render_format'),
);
return $type;
}
/**
* Implements hook_library_info().
*/
function editor_library_info() {
$path = drupal_get_path('module', 'editor');
$libraries['drupal.editor'] = array(
'title' => 'Text Editor',
'version' => VERSION,
'js' => array(
$path . '/js/editor.js' => array(),
),
'dependencies' => array(
array('system', 'jquery'),
array('system', 'drupal'),
array('system', 'drupalSettings'),
array('system', 'jquery.once'),
),
);
return $libraries;
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function editor_form_filter_admin_overview_alter(&$form, $form_state) {
// @todo Cleanup column injection: http://drupal.org/node/1876718
// Splice in the column for "Text editor" into the header.
$position = array_search('name', $form['formats']['#header']) + 1;
$start = array_splice($form['formats']['#header'], 0, $position, array('editor' => t('Text editor')));
$form['formats']['#header'] = array_merge($start, $form['formats']['#header']);
// Then splice in the name of each text editor for each text format.
$editors = drupal_container()->get('plugin.manager.editor')->getDefinitions();
foreach (element_children($form['formats']) as $format_id) {
$editor = editor_load($format_id);
$editor_name = ($editor && isset($editors[$editor->editor])) ? $editors[$editor->editor]['label'] : drupal_placeholder('—');
$editor_column['editor'] = array('#markup' => $editor_name);
$position = array_search('name', array_keys($form['formats'][$format_id])) + 1;
$start = array_splice($form['formats'][$format_id], 0, $position, $editor_column);
$form['formats'][$format_id] = array_merge($start, $form['formats'][$format_id]);
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function editor_form_filter_admin_format_form_alter(&$form, &$form_state) {
if (!isset($form_state['editor'])) {
$format_id = $form['#format']->format;
$form_state['editor'] = editor_load($format_id);
$form_state['editor_manager'] = drupal_container()->get('plugin.manager.editor');
}
$editor = $form_state['editor'];
$manager = $form_state['editor_manager'];
// Associate a text editor with this text format.
$editor_options = $manager->listOptions();
$form['editor'] = array(
'#type' => 'select',
'#title' => t('Text editor'),
'#options' => $editor_options,
'#empty_option' => t('None'),
'#default_value' => $editor ? $editor->editor : '',
// Position the editor selection before the filter settings (weight of 0),
// but after the filter label and name (weight of -20).
'#weight' => -9,
'#ajax' => array(
'trigger_as' => array('name' => 'editor_configure'),
'callback' => 'editor_form_filter_admin_form_ajax',
'wrapper' => 'editor-settings-wrapper',
),
);
$form['editor_configure'] = array(
'#type' => 'submit',
'#name' => 'editor_configure',
'#value' => t('Configure'),
'#limit_validation_errors' => array(array('editor')),
'#submit' => array('editor_form_filter_admin_format_editor_configure'),
'#ajax' => array(
'callback' => 'editor_form_filter_admin_form_ajax',
'wrapper' => 'editor-settings-wrapper',
),
'#weight' => -9,
'#attributes' => array('class' => array('js-hide')),
);
// If there aren't any options (other than "None"), disable the select list.
if (empty($editor_options)) {
$form['editor']['#disabled'] = TRUE;
$form['editor']['#description'] = t('This option is disabled because no modules that provide a text editor are currently enabled.');
}
$form['editor_settings'] = array(
'#tree' => TRUE,
'#weight' => -8,
'#type' => 'container',
'#id' => 'editor-settings-wrapper',
);
// Add editor-specific validation and submit handlers.
if ($editor) {
$plugin = $manager->createInstance($editor->editor);
$settings_form = array();
$settings_form['#element_validate'][] = array($plugin, 'settingsFormValidate');
$form['editor_settings']['settings'] = $plugin->settingsForm($settings_form, $form_state, $editor);
$form['editor_settings']['settings']['#parents'] = array('editor_settings');
$form['#submit'][] = array($plugin, 'settingsFormSubmit');
}
$form['#submit'][] = 'editor_form_filter_admin_format_submit';
}
/**
* Button submit handler for filter_admin_format_form()'s 'editor_configure' button.
*/
function editor_form_filter_admin_format_editor_configure($form, &$form_state) {
$editor = $form_state['editor'];
if (isset($form_state['values']['editor'])) {
if ($form_state['values']['editor'] === '') {
$form_state['editor'] = FALSE;
}
elseif (empty($editor) || $form_state['values']['editor'] !== $editor->editor) {
$editor = entity_create('editor', array(
'format' => $form['#format']->format,
'editor' => $form_state['values']['editor'],
));
$form_state['editor'] = $editor;
}
}
$form_state['rebuild'] = TRUE;
}
/**
* AJAX callback handler for filter_admin_format_form().
*/
function editor_form_filter_admin_form_ajax($form, &$form_state) {
return $form['editor_settings'];
}
/**
* Additional submit handler for filter_admin_format_form().
*/
function editor_form_filter_admin_format_submit($form, &$form_state) {
// Delete the existing editor if disabling or switching between editors.
$format_id = $form['#format']->format;
$original_editor = editor_load($format_id);
if ($original_editor && $original_editor->editor != $form_state['values']['editor']) {
$original_editor->delete();
}
// Create a new editor or update the existing editor.
if ($form_state['values']['editor'] !== '') {
$form_state['editor']->settings = $form_state['values']['editor_settings'];
$form_state['editor']->save();
}
}
/**
* Loads an individual configured text editor based on text format ID.
*
* @return \Drupal\editor\Plugin\Core\Entity\Editor|FALSE
* A text editor object, or FALSE.
*/
function editor_load($format_id) {
// Load all the editors at once here, assuming that either no editors or more
// than one editor will be needed on a page (such as having multiple text
// formats for administrators). Loading a small number of editors all at once
// is more efficient than loading multiple editors individually.
$editors = entity_load_multiple('editor');
return isset($editors[$format_id]) ? $editors[$format_id] : FALSE;
}
/**
* Additional #pre_render callback for 'text_format' elements.
*/
function editor_pre_render_format($element) {
// Allow modules to programmatically enforce no client-side editor by setting
// the #editor property to FALSE.
if (isset($element['#editor']) && !$element['#editor']) {
return $element;
}
// filter_process_format() copies properties to the expanded 'value' child
// element. Skip this text format widget, if it contains no 'format' or when
// the current user does not have access to edit the value.
if (!isset($element['format']) || !empty($element['value']['#disabled'])) {
return $element;
}
$format_ids = array_keys($element['format']['format']['#options']);
// Early-return if no text editor is associated with any of the text formats.
if (count(entity_load_multiple('editor', $format_ids)) === 0) {
return $element;
}
// Use a hidden element for a single text format.
$field_id = $element['value']['#id'];
if (!$element['format']['format']['#access']) {
// Use the first (and only) available text format.
$format_id = $format_ids[0];
$element['format']['editor'] = array(
'#type' => 'hidden',
'#name' => $element['format']['format']['#name'],
'#value' => $format_id,
'#attributes' => array(
'class' => array('editor'),
'data-editor-for' => $field_id,
),
);
}
// Otherwise, attach to text format selector.
else {
$element['format']['format']['#attributes']['class'][] = 'editor';
$element['format']['format']['#attributes']['data-editor-for'] = $field_id;
}
// Attach Text Editor module's (this module) library.
$element['#attached']['library'][] = array('editor', 'drupal.editor');
// Attach attachments for all available editors.
$manager = drupal_container()->get('plugin.manager.editor');
$element['#attached'] = NestedArray::mergeDeep($element['#attached'], $manager->getAttachments($format_ids));
return $element;
}

View File

@ -1,109 +0,0 @@
/**
* @file
* Attaches behavior for the Editor module.
*/
(function ($, Drupal) {
"use strict";
/**
* Initialize an empty object for editors to place their attachment code.
*/
Drupal.editors = {};
/**
* Enables editors on text_format elements.
*/
Drupal.behaviors.editor = {
attach: function (context, settings) {
// If there are no editor settings, there are no editors to enable.
if (!settings.editor) {
return;
}
var $context = $(context);
var behavior = this;
$context.find('.editor').once('editor', function () {
var $this = $(this);
var activeFormatID = $this.val();
var field = behavior.findFieldForFormatSelector($this);
// Directly attach this editor, if the text format is enabled.
if (settings.editor.formats[activeFormatID]) {
Drupal.editorAttach(field, settings.editor.formats[activeFormatID]);
}
// Attach onChange handler to text format selector element.
if ($this.is('select')) {
$this.on('change.editorAttach', function () {
var newFormatID = $this.val();
// Prevent double-attaching if the change event is triggered manually.
if (newFormatID === activeFormatID) {
return;
}
// Detach the current editor (if any) and attach a new editor.
if (settings.editor.formats[activeFormatID]) {
Drupal.editorDetach(field, settings.editor.formats[activeFormatID]);
}
activeFormatID = newFormatID;
if (settings.editor.formats[activeFormatID]) {
Drupal.editorAttach(field, settings.editor.formats[activeFormatID]);
}
});
}
// Detach any editor when the containing form is submitted.
$this.parents('form').submit(function (event) {
// Do not detach if the event was canceled.
if (event.isDefaultPrevented()) {
return;
}
Drupal.editorDetach(field, settings.editor.formats[activeFormatID]);
});
});
},
detach: function (context, settings, trigger) {
var editors;
// The 'serialize' trigger indicates that we should simply update the
// underlying element with the new text, without destroying the editor.
if (trigger == 'serialize') {
// Removing the editor-processed class guarantees that the editor will
// be reattached. Only do this if we're planning to destroy the editor.
editors = $(context).find('.editor-processed');
}
else {
editors = $(context).find('.editor').removeOnce('editor');
}
var behavior = this;
editors.each(function () {
var $this = $(this);
var activeFormatID = $this.val();
var field = behavior.findFieldForFormatSelector($this);
Drupal.editorDetach(field, settings.editor.formats[activeFormatID], trigger);
});
},
findFieldForFormatSelector: function ($formatSelector) {
var field_id = $formatSelector.attr('data-editor-for');
return $('#' + field_id).get(0);
}
};
Drupal.editorAttach = function (field, format) {
if (format.editor) {
Drupal.editors[format.editor].attach(field, format);
}
};
Drupal.editorDetach = function (field, format, trigger) {
if (format.editor) {
Drupal.editors[format.editor].detach(field, format, trigger);
}
};
})(jQuery, Drupal);

View File

@ -1,27 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\editor\EditorBundle.
*/
namespace Drupal\editor;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* Editor dependency injection container.
*/
class EditorBundle extends Bundle {
/**
* Overrides Symfony\Component\HttpKernel\Bundle\Bundle::build().
*/
public function build(ContainerBuilder $container) {
// Register the plugin manager for our plugin type with the dependency
// injection container.
$container->register('plugin.manager.editor', 'Drupal\editor\Plugin\EditorManager');
}
}

View File

@ -1,84 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\editor\Plugin\Core\Entity\Editor.
*/
namespace Drupal\editor\Plugin\Core\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Annotation\Plugin;
use Drupal\Core\Annotation\Translation;
/**
* Defines the configured text editor entity.
*
* @Plugin(
* id = "editor",
* label = @Translation("Editor"),
* module = "editor",
* controller_class = "Drupal\Core\Config\Entity\ConfigStorageController",
* config_prefix = "editor.editor",
* entity_keys = {
* "id" = "format",
* "uuid" = "uuid"
* }
* )
*/
class Editor extends ConfigEntityBase {
/**
* The machine name of the text format with which this configured text editor
* is associated.
*
* @var string
*/
public $format;
/**
* The name (plugin ID) of the text editor.
*
* @var string
*/
public $editor;
/**
* The array of settings for the text editor.
*
* @var array
*/
public $settings = array();
/**
* Overrides Drupal\Core\Entity\Entity::id().
*/
public function id() {
return $this->format;
}
/**
* Overrides Drupal\Core\Entity\Entity::label().
*/
public function label($langcode = NULL) {
$format = filter_format_load($this->format);
return $format->name;
}
/**
* Overrides Drupal\Core\Entity\Entity::__construct()
*/
public function __construct(array $values, $entity_type) {
parent::__construct($values, $entity_type);
$manager = drupal_container()->get('plugin.manager.editor');
$plugin = $manager->createInstance($this->editor);
// Initialize settings, merging module-provided defaults.
$default_settings = $plugin->getDefaultSettings();
$default_settings += module_invoke_all('editor_default_settings', $this->editor);
drupal_alter('editor_default_settings', $default_settings, $this->editor);
$this->settings += $default_settings;
}
}

View File

@ -1,67 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\editor\Plugin\EditorBase.
*/
namespace Drupal\editor\Plugin;
use Drupal\Component\Plugin\PluginBase;
use Drupal\editor\Plugin\Core\Entity\Editor;
use Drupal\editor\Plugin\EditorInterface;
/**
* Defines a base class from which other modules providing editors may extend.
*
* This class provides default implementations of the EditorInterface so that
* classes extending this one do not need to implement every method.
*
* Plugins extending this class need to define a plugin definition array through
* annotation. These definition arrays may be altered through
* hook_editor_info_alter(). The definition includes the following keys:
*
* - id: The unique, system-wide identifier of the text editor. Typically named
* the same as the editor library.
* - label: The human-readable name of the text editor, translated.
* - module: The name of the module providing the plugin.
*
* A complete sample plugin definition should be defined as in this example:
*
* @code
* @Plugin(
* id = "myeditor",
* label = @Translation("My Editor"),
* module = "mymodule"
* )
* @endcode
*/
abstract class EditorBase extends PluginBase implements EditorInterface {
/**
* Implements \Drupal\editor\Plugin\EditorInterface::getDefaultSettings().
*/
public function getDefaultSettings() {
return array();
}
/**
* Implements \Drupal\editor\Plugin\EditorInterface::settingsForm().
*/
public function settingsForm(array $form, array &$form_state, Editor $editor) {
return $form;
}
/**
* Implements \Drupal\editor\Plugin\EditorInterface::settingsFormValidate().
*/
public function settingsFormValidate(array $form, array &$form_state) {
}
/**
* Implements \Drupal\editor\Plugin\EditorInterface::settingsFormSubmit().
*/
public function settingsFormSubmit(array $form, array &$form_state) {
}
}

View File

@ -1,116 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\editor\Plugin\EditorInterface.
*/
namespace Drupal\editor\Plugin;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\editor\Plugin\Core\Entity\Editor;
/**
* Defines an interface for configurable text editors.
*
* Modules implementing this interface may want to extend the EditorBase
* class, which provides default implementations of each method where
* appropriate.
*/
interface EditorInterface extends PluginInspectionInterface {
/**
* Returns the default settings for this configurable text editor.
*
* @return array
* An array of settings as they would be stored by a configured text editor
* entity (\Drupal\editor\Plugin\Core\Entity\Editor).
*/
function getDefaultSettings();
/**
* Returns a settings form to configure this text editor.
*
* If the editor's behavior depends on extensive options and/or external data,
* then the implementing module can choose to provide a separate, global
* configuration page rather than per-text-format settings. In that case, this
* form should provide a link to the separate settings page.
*
* @param array $form
* An empty form array to be populated with a configuration form, if any.
* @param array $form_state
* The state of the entire filter administration form.
* @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
* A configured text editor object.
*
* @return array
* A render array for the settings form.
*/
function settingsForm(array $form, array &$form_state, Editor $editor);
/**
* Validates the settings form for an editor.
*
* The contents of the editor settings are located in
* $form_state['values']['editor_settings']. Calls to form_error() should
* reflect this location in the settings form.
*
* @param array $form
* An associative array containing the structure of the form.
* @param array $form_state
* A reference to a keyed array containing the current state of the form.
*/
function settingsFormValidate(array $form, array &$form_state);
/**
* Modifies any values in the form state to prepare them for saving.
*
* Values in $form_state['values']['editor_settings'] are saved by Editor
* module in editor_form_filter_admin_format_submit().
*
* @param array $form
* An associative array containing the structure of the form.
* @param array $form_state
* A reference to a keyed array containing the current state of the form.
*/
function settingsFormSubmit(array $form, array &$form_state);
/**
* Returns JavaScript settings to be attached.
*
* Most text editors use JavaScript to provide a WYSIWYG or toolbar on the
* client-side interface. This method can be used to convert internal settings
* of the text editor into JavaScript variables that will be accessible when
* the text editor is loaded.
*
* @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
* A configured text editor object.
*
* @return array
* An array of settings that will be added to the page for use by this text
* editor's JavaScript integration.
*
* @see drupal_process_attached()
* @see EditorManager::getAttachments()
*/
function getJSSettings(Editor $editor);
/**
* Returns libraries to be attached.
*
* Because this is a method, plugins can dynamically choose to attach a
* different library for different configurations, instead of being forced to
* always use the same method.
*
* @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
* A configured text editor object.
*
* @return array
* An array of libraries that will be added to the page for use by this
* text editor.
*
* @see drupal_process_attached()
* @see EditorManager::getAttachments()
*/
function getLibraries(Editor $editor);
}

View File

@ -1,108 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\editor\Plugin\EditorManager.
*/
namespace Drupal\editor\Plugin;
use Drupal\Component\Plugin\PluginManagerBase;
use Drupal\Component\Plugin\Factory\DefaultFactory;
use Drupal\Component\Plugin\Discovery\ProcessDecorator;
use Drupal\Core\Plugin\Discovery\AlterDecorator;
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
use Drupal\Core\Plugin\Discovery\CacheDecorator;
/**
* Configurable text editor manager.
*/
class EditorManager extends PluginManagerBase {
/**
* Overrides \Drupal\Component\Plugin\PluginManagerBase::__construct().
*/
public function __construct() {
$this->discovery = new AnnotatedClassDiscovery('editor', 'editor');
$this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition'));
$this->discovery = new AlterDecorator($this->discovery, 'editor_info');
$this->discovery = new CacheDecorator($this->discovery, 'editor');
$this->factory = new DefaultFactory($this->discovery);
}
/**
* Populates a key-value pair of available text editors.
*
* @return array
* An array of translated text editor labels, keyed by ID.
*/
public function listOptions() {
$options = array();
foreach ($this->getDefinitions() as $key => $definition) {
$options[$key] = $definition['label'];
}
return $options;
}
/**
* Retrieves text editor libraries and JavaScript settings.
*
* @param array $format_ids
* An array of format IDs as returned by array_keys(filter_formats()).
*
* @return array
* An array of attachments, for use with #attached.
*
* @see drupal_process_attached()
*/
public function getAttachments(array $format_ids) {
$attachments = array('library' => array());
$settings = array();
foreach ($format_ids as $format_id) {
$editor = editor_load($format_id);
if (!$editor) {
continue;
}
$plugin = $this->createInstance($editor->editor);
// Libraries.
$attachments['library'] = array_merge($attachments['library'], $plugin->getLibraries($editor));
// JavaScript settings.
$settings[$format_id] = array(
'editor' => $editor->editor,
'editorSettings' => $plugin->getJSSettings($editor),
);
}
// We have all JavaScript settings, allow other modules to alter them.
drupal_alter('editor_js_settings', $settings, $formats);
if (empty($attachments['library']) && empty($settings)) {
return array();
}
$attachments['js'][] = array(
'type' => 'setting',
'data' => array('editor' => array('formats' => $settings)),
);
return $attachments;
}
/**
* Overrides Drupal\Component\Plugin\PluginManagerBase::processDefinition().
*/
public function processDefinition(&$definition, $plugin_id) {
parent::processDefinition($definition, $plugin_id);
// @todo Remove this check once http://drupal.org/node/1780396 is resolved.
if (!module_exists($definition['module'])) {
$definition = NULL;
return;
}
}
}

View File

@ -1,117 +0,0 @@
<?php
/**
* @file
* Definition of \Drupal\editor\Tests\EditorAdminTest.
*/
namespace Drupal\editor\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests administration of text editors.
*/
class EditorAdminTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('filter', 'editor');
public static function getInfo() {
return array(
'name' => 'Text editor administration',
'description' => 'Tests administration of text editors.',
'group' => 'Text Editor',
);
}
function setUp() {
parent::setUp();
// Add text format.
$filtered_html_format = array(
'format' => 'filtered_html',
'name' => 'Filtered HTML',
'weight' => 0,
'filters' => array(),
);
$filtered_html_format = (object) $filtered_html_format;
filter_format_save($filtered_html_format);
// Create admin user.
$this->admin_user = $this->drupalCreateUser(array('administer filters'));
}
function testWithoutEditorAvailable() {
$this->drupalLogin($this->admin_user);
$this->drupalGet('admin/config/content/formats/filtered_html');
// Ensure the form field order is correct.
$roles_pos = strpos($this->drupalGetContent(), 'Roles');
$editor_pos = strpos($this->drupalGetContent(), 'Text editor');
$filters_pos = strpos($this->drupalGetContent(), 'Enabled filters');
$this->assertTrue($roles_pos < $editor_pos && $editor_pos < $filters_pos, '"Text Editor" select appears in the correct location of the text format configuration UI.');
// Verify the <select>.
$select = $this->xpath('//select[@name="editor"]');
$select_is_disabled = $this->xpath('//select[@name="editor" and @disabled="disabled"]');
$options = $this->xpath('//select[@name="editor"]/option');
$this->assertTrue(count($select) === 1, 'The Text Editor select exists.');
$this->assertTrue(count($select_is_disabled) === 1, 'The Text Editor select is disabled.');
$this->assertTrue(count($options) === 1, 'The Text Editor select has only one option.');
$this->assertTrue(((string) $options[0]) === 'None', 'Option 1 in the he Text Editor select is "None".');
$this->assertRaw(t('This option is disabled because no modules that provide a text editor are currently enabled.'), 'Description for select present that tells users to install a text editor module.');
// Make a text editor available.
module_enable(array('editor_test'));
$this->rebuildContainer();
$this->resetAll();
$this->drupalGet('admin/config/content/formats/filtered_html');
// Verify the <select> when a text editor is available.
$select = $this->xpath('//select[@name="editor"]');
$select_is_disabled = $this->xpath('//select[@name="editor" and @disabled="disabled"]');
$options = $this->xpath('//select[@name="editor"]/option');
$this->assertTrue(count($select) === 1, 'The Text Editor select exists.');
$this->assertTrue(count($select_is_disabled) === 0, 'The Text Editor select is not disabled.');
$this->assertTrue(count($options) === 2, 'The Text Editor select has two options.');
$this->assertTrue(((string) $options[0]) === 'None', 'Option 1 in the he Text Editor select is "None".');
$this->assertTrue(((string) $options[1]) === 'Unicorn Editor', 'Option 2 in the he Text Editor select is "Unicorn Editor".');
$this->assertTrue(((string) $options[0]['selected']) === 'selected', 'Option 1 ("None") is selected.');
// Ensure the none option is selected
$this->assertNoRaw(t('This option is disabled because no modules that provide a text editor are currently enabled.'), 'Description for select absent that tells users to install a text editor module.');
// Select the "Unicorn Editor" editor and click the "Configure" button.
$edit = array(
'editor' => 'unicorn',
);
$this->drupalPostAjax(NULL, $edit, 'editor_configure');
$unicorn_setting_foo = $this->xpath('//input[@name="editor_settings[foo]" and @type="text" and @value="bar"]');
$this->assertTrue(count($unicorn_setting_foo), "Unicorn Editor's settings form is present.");
$options = $this->xpath('//select[@name="editor"]/option');
// Now configure the setting to another value.
$edit['editor_settings[foo]'] = 'baz';
$this->drupalPost(NULL, $edit, t('Save configuration'));
// Verify the editor configuration is saved correctly.
$editor = editor_load('filtered_html');
$this->assertIdentical($editor->editor, 'unicorn', 'The text editor is configured correctly.');
$this->assertIdentical($editor->settings['foo'], 'baz', 'The text editor settings are stored correctly.');
$this->assertIdentical($editor->settings['ponies too'], true, 'The text editor defaults are retrieved correctly.');
$this->assertIdentical($editor->settings['rainbows'], true, 'The text editor defaults added by hook_editor_settings_defaults() are retrieved correctly.');
$this->assertIdentical($editor->settings['sparkles'], false, 'The text editor defaults modified by hook_editor_settings_defaults_alter() are retrieved correctly.');
$this->drupalGet('admin/config/content/formats/filtered_html');
$select = $this->xpath('//select[@name="editor"]');
$select_is_disabled = $this->xpath('//select[@name="editor" and @disabled="disabled"]');
$options = $this->xpath('//select[@name="editor"]/option');
$this->assertTrue(count($select) === 1, 'The Text Editor select exists.');
$this->assertTrue(count($select_is_disabled) === 0, 'The Text Editor select is not disabled.');
$this->assertTrue(count($options) === 2, 'The Text Editor select has two options.');
$this->assertTrue(((string) $options[1]['selected']) === 'selected', 'Option 2 ("Unicorn Editor") is selected.');
}
}

View File

@ -1,175 +0,0 @@
<?php
/**
* @file
* Definition of \Drupal\editor\Tests\EditorLoadingTest.
*/
namespace Drupal\editor\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests loading of text editors.
*/
class EditorLoadingTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('filter', 'editor', 'editor_test', 'node');
public static function getInfo() {
return array(
'name' => 'Text editor loading',
'description' => 'Tests loading of text editors.',
'group' => 'Text Editor',
);
}
function setUp() {
parent::setUp();
// Add text formats.
$filtered_html_format = array(
'format' => 'filtered_html',
'name' => 'Filtered HTML',
'weight' => 0,
'filters' => array(),
);
$filtered_html_format = (object) $filtered_html_format;
filter_format_save($filtered_html_format);
$full_html_format = array(
'format' => 'full_html',
'name' => 'Full HTML',
'weight' => 1,
'filters' => array(),
);
$full_html_format = (object) $full_html_format;
filter_format_save($full_html_format);
// Create node type.
$this->drupalCreateContentType(array(
'type' => 'article',
'name' => 'Article',
));
// Create 3 users, each with access to different text formats:
// - "untrusted": plain_text
// - "normal": plain_text, filtered_html
// - "privileged": plain_text, filtered_html, full_html
$this->untrusted_user = $this->drupalCreateUser(array('create article content', 'edit any article content'));
$this->normal_user = $this->drupalCreateUser(array('create article content', 'edit any article content', 'use text format filtered_html'));
$this->privileged_user = $this->drupalCreateUser(array('create article content', 'edit any article content', 'use text format filtered_html', 'use text format full_html'));
}
/**
* Tests loading of text editors.
*/
function testLoading() {
// Only associate a text editor with the "Full HTML" text format.
$editor = entity_create('editor', array(
'format' => 'full_html',
'editor' => 'unicorn',
));
$editor->save();
// The normal user:
// - has access to 2 text formats;
// - doesn't have access to the full_html text format, so: no text editor.
$this->drupalLogin($this->normal_user);
$this->drupalGet('node/add/article');
list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck();
$this->assertFalse($editor_settings_present, 'No Text Editor module settings.');
$this->assertFalse($editor_js_present, 'No Text Editor JavaScript.');
$this->assertTrue(count($body) === 1, 'A body field exists.');
$this->assertTrue(count($format_selector) === 1, 'A single text format selector exists on the page.');
$specific_format_selector = $this->xpath('//select[contains(@class, "filter-list") and not(contains(@class, "editor")) and not(@data-editor-for="edit-body-und-0-value")]');
$this->assertTrue(count($specific_format_selector) === 1, 'A single text format selector exists on the page and does not have the "editor" class nor a "data-editor-for" attribute.');
$this->drupalLogout($this->normal_user);
// The normal user:
// - has access to 3 text formats;
// - does have access to the full_html text format, so: Unicorn text editor.
$this->drupalLogin($this->privileged_user);
$this->drupalGet('node/add/article');
list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck();
$expected = array('formats' => array('full_html' => array(
'editor' => 'unicorn',
'editorSettings' => array('ponyModeEnabled' => TRUE),
)));
$this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page.");
$this->assertIdentical($expected, $settings['editor'], "Text Editor module's JavaScript settings on the page are correct.");
$this->assertTrue($editor_js_present, 'Text Editor JavaScript is present.');
$this->assertTrue(count($body) === 1, 'A body field exists.');
$this->assertTrue(count($format_selector) === 1, 'A single text format selector exists on the page.');
$specific_format_selector = $this->xpath('//select[contains(@class, "filter-list") and contains(@class, "editor") and @data-editor-for="edit-body-und-0-value"]');
$this->assertTrue(count($specific_format_selector) === 1, 'A single text format selector exists on the page and has the "editor" class and a "data-editor-for" attribute with the correct value.');
$this->drupalLogout($this->privileged_user);
// Also associate a text editor with the "Plain Text" text format.
$editor = entity_create('editor', array(
'format' => 'plain_text',
'editor' => 'unicorn',
));
$editor->save();
// The untrusted user:
// - has access to 1 text format (plain_text);
// - has access to the plain_text text format, so: Unicorn text editor.
$this->drupalLogin($this->untrusted_user);
$this->drupalGet('node/add/article');
list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck();
$expected = array('formats' => array('plain_text' => array(
'editor' => 'unicorn',
'editorSettings' => array('ponyModeEnabled' => TRUE),
)));
$this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page.");
$this->assertIdentical($expected, $settings['editor'], "Text Editor module's JavaScript settings on the page are correct.");
$this->assertTrue($editor_js_present, 'Text Editor JavaScript is present.');
$this->assertTrue(count($body) === 1, 'A body field exists.');
$this->assertTrue(count($format_selector) === 0, 'No text format selector exists on the page.');
$hidden_input = $this->xpath('//input[@type="hidden" and @value="plain_text" and contains(@class, "editor") and @data-editor-for="edit-body-und-0-value"]');
$this->assertTrue(count($hidden_input) === 1, 'A single text format hidden input exists on the page and has the "editor" class and a "data-editor-for" attribute with the correct value.');
// Create an "article" node that users the full_html text format, then try
// to let the untrusted user edit it.
$this->drupalCreateNode(array(
'type' => 'article',
'body' => array(LANGUAGE_NOT_SPECIFIED => array(
0 => array('value' => $this->randomName(32), 'format' => 'full_html')
)),
));
// The untrusted user tries to edit content that is written in a text format
// that (s)he is not allowed to use.
$this->drupalGet('node/1/edit');
list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck();
$this->assertFalse($editor_settings_present, 'No Text Editor module settings.');
$this->assertFalse($editor_js_present, 'No Text Editor JavaScript.');
$this->assertTrue(count($body) === 1, 'A body field exists.');
$this->assertFieldByXPath('//textarea[@id="edit-body-und-0-value" and @disabled="disabled"]', t('This field has been disabled because you do not have sufficient permissions to edit it.'), 'Text format access denied message found.');
$this->assertTrue(count($format_selector) === 0, 'No text format selector exists on the page.');
$hidden_input = $this->xpath('//input[@type="hidden" and contains(@class, "editor")]');
$this->assertTrue(count($hidden_input) === 0, 'A single text format hidden input does not exist on the page.');
}
protected function getThingsToCheck() {
$settings = $this->drupalGetSettings();
return array(
// JavaScript settings.
$settings,
// Editor.module's JS settings present.
isset($settings['editor']),
// Editor.module's JS present.
isset($settings['ajaxPageState']['js']['core/modules/editor/js/editor.js']),
// Body field.
$this->xpath('//textarea[@id="edit-body-und-0-value"]'),
// Format selector.
$this->xpath('//select[contains(@class, "filter-list")]'),
);
}
}

View File

@ -1,118 +0,0 @@
<?php
/**
* @file
* Definition of \Drupal\editor\Tests\EditorManagerTest.
*/
namespace Drupal\editor\Tests;
use Drupal\simpletest\DrupalUnitTestBase;
use Drupal\editor\Plugin\EditorManager;
/**
* Unit tests for the configurable text editor manager.
*/
class EditorManagerTest extends DrupalUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('filter', 'editor');
/**
* The manager for text editor plugins.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
protected $editorManager;
public static function getInfo() {
return array(
'name' => 'Text editor manager',
'description' => 'Tests detection of text editors and correct generation of attachments.',
'group' => 'Text Editor',
);
}
function setUp() {
parent::setUp();
// Install the Filter module.
$this->enableModules(array('filter'));
// Add text formats.
$filtered_html_format = array(
'format' => 'filtered_html',
'name' => 'Filtered HTML',
'weight' => 0,
'filters' => array(),
);
$filtered_html_format = (object) $filtered_html_format;
filter_format_save($filtered_html_format);
$full_html_format = array(
'format' => 'full_html',
'name' => 'Full HTML',
'weight' => 1,
'filters' => array(),
);
$full_html_format = (object) $full_html_format;
filter_format_save($full_html_format);
}
/**
* Tests the configurable text editor manager.
*/
function testManager() {
$this->editorManager = new EditorManager();
// Case 1: no text editor available:
// - listOptions() should return an empty list of options
// - getAttachments() should return an empty #attachments array (and not
// a JS settings structure that is empty)
$this->assertIdentical(array(), $this->editorManager->listOptions(), 'When no text editor is enabled, the manager works correctly.');
$this->assertIdentical(array(), $this->editorManager->getAttachments(array()), 'No attachments when no text editor is enabled and retrieving attachments for zero text formats.');
$this->assertIdentical(array(), $this->editorManager->getAttachments(array('filtered_html', 'full_html')), 'No attachments when no text editor is enabled and retrieving attachments for multiple text formats.');
// Enable the Text Editor Test module, which has the Unicorn Editor and
// clear the editor manager's cache so it is picked up.
$this->enableModules(array('editor_test'));
$this->editorManager->clearCachedDefinitions();
// Case 2: a text editor available.
$this->assertIdentical(array('unicorn' => 'Unicorn Editor'), $this->editorManager->listOptions(), 'When some text editor is enabled, the manager works correctly.');
// Case 3: a text editor available & associated (but associated only with
// the 'Full HTML' text format).
$unicorn_plugin = $this->editorManager->createInstance('unicorn');
$default_editor_settings = $unicorn_plugin->getDefaultSettings();
$editor = entity_create('editor', array(
'name' => 'Full HTML',
'format' => 'full_html',
'editor' => 'unicorn',
));
$editor->save();
$this->assertIdentical(array(), $this->editorManager->getAttachments(array()), 'No attachments when one text editor is enabled and retrieving attachments for zero text formats.');
$expected = array(
'library' => array(
0 => array('edit_test', 'unicorn'),
),
'js' => array(
0 => array(
'type' => 'setting',
'data' => array('editor' => array('formats' => array(
'full_html' => array(
'editor' => 'unicorn',
'editorSettings' => $unicorn_plugin->getJSSettings($editor),
)
)))
)
),
);
$this->assertIdentical($expected, $this->editorManager->getAttachments(array('filtered_html', 'full_html')), 'Correct attachments when one text editor is enabled and retrieving attachments for multiple text formats.');
}
}

View File

@ -1,6 +0,0 @@
name = Text Editor test
description = Support module for the Text Editor module tests.
core = 8.x
package = Testing
version = VERSION
hidden = TRUE

View File

@ -1,27 +0,0 @@
<?php
/**
* @file
* Helper module for the Text Editor tests.
*/
/**
* Implements hook_editor_default_settings().
*/
function editor_test_editor_default_settings($editor) {
if ($editor === 'unicorn') {
return array(
'rainbows' => TRUE,
'sparkles' => TRUE,
);
}
}
/**
* Implements hook_editor_default_settings_alter().
*/
function editor_test_editor_default_settings_alter(&$settings, $editor) {
if ($editor === 'unicorn' && isset($settings['sparkles'])) {
$settings['sparkles'] = FALSE;
}
}

View File

@ -1,61 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\editor_test\Plugin\editor\editor\UnicornEditor.
*/
namespace Drupal\editor_test\Plugin\editor\editor;
use Drupal\editor\Plugin\EditorBase;
use Drupal\Core\Annotation\Plugin;
use Drupal\Core\Annotation\Translation;
use Drupal\editor\Plugin\Core\Entity\Editor;
/**
* Defines a Unicorn-powered text editor for Drupal.
*
* @Plugin(
* id = "unicorn",
* label = @Translation("Unicorn Editor"),
* module = "editor_test"
* )
*/
class UnicornEditor extends EditorBase {
/**
* Implements \Drupal\editor\Plugin\EditorInterface::getDefaultSettings().
*/
function getDefaultSettings() {
return array('ponies too' => TRUE);
}
/**
* Implements \Drupal\editor\Plugin\EditorInterface::settingsForm().
*/
function settingsForm(array $form, array &$form_state, Editor $editor) {
$form['foo'] = array('#type' => 'textfield', '#default_value' => 'bar');
return $form;
}
/**
* Implements \Drupal\editor\Plugin\EditorInterface::getJSSettings().
*/
function getJSSettings(Editor $editor) {
$settings = array();
if ($editor->settings['ponies too']) {
$settings['ponyModeEnabled'] = TRUE;
}
return $settings;
}
/**
* Implements \Drupal\editor\Plugin\EditorInterface::getLibraries().
*/
public function getLibraries(Editor $editor) {
return array(
array('edit_test', 'unicorn'),
);
}
}

View File

@ -153,7 +153,6 @@ function filter_admin_format_form($form, &$form_state, $format) {
'#title' => t('Name'),
'#default_value' => $format->name,
'#required' => TRUE,
'#weight' => -30,
);
$form['format'] = array(
'#type' => 'machine_name',
@ -165,7 +164,6 @@ function filter_admin_format_form($form, &$form_state, $format) {
'source' => array('name'),
),
'#disabled' => !empty($format->format),
'#weight' => -20,
);
// Add user role access selection.
@ -174,7 +172,6 @@ function filter_admin_format_form($form, &$form_state, $format) {
'#title' => t('Roles'),
'#options' => array_map('check_plain', user_roles()),
'#disabled' => $is_fallback,
'#weight' => -10,
);
if ($is_fallback) {
$form['roles']['#description'] = t('All roles for this text format must be enabled and cannot be changed.');