diff --git a/core/includes/entity.api.php b/core/includes/entity.api.php index da9c1b8255e..8e60f00c3be 100644 --- a/core/includes/entity.api.php +++ b/core/includes/entity.api.php @@ -314,6 +314,18 @@ function hook_entity_delete(Drupal\Core\Entity\EntityInterface $entity) { ->execute(); } +/** + * Respond to entity revision deletion. + * + * This hook runs after the entity type-specific revision delete hook. + * + * @param Drupal\Core\Entity\EntityInterface $entity + * The entity object for the entity revision that has been deleted. + */ +function hook_entity_revision_delete(Drupal\Core\Entity\EntityInterface $entity) { + // @todo: code example +} + /** * Alter or execute an Drupal\Core\Entity\Query\EntityQueryInterface. * diff --git a/core/modules/ckeditor/ckeditor.module b/core/modules/ckeditor/ckeditor.module index 66a81f20d7e..78135813fbb 100755 --- a/core/modules/ckeditor/ckeditor.module +++ b/core/modules/ckeditor/ckeditor.module @@ -79,6 +79,20 @@ function ckeditor_library_info() { array('system', 'underscore') ), ); + $libraries['drupal.ckeditor.drupalimage.admin'] = array( + 'title' => 'Only show the "drupalimage" plugin settings when its button is enabled.', + 'version' => VERSION, + 'js' => array( + $module_path . '/js/ckeditor.drupalimage.admin.js' => array(), + ), + 'dependencies' => array( + array('system', 'jquery'), + array('system', 'drupal'), + array('system', 'jquery.once'), + array('system', 'drupal.vertical-tabs'), + array('system', 'drupalSettings'), + ), + ); $libraries['drupal.ckeditor.stylescombo.admin'] = array( 'title' => 'Only show the "stylescombo" plugin settings when its button is enabled.', 'version' => VERSION, @@ -91,8 +105,6 @@ function ckeditor_library_info() { array('system', 'jquery.once'), array('system', 'drupal.vertical-tabs'), array('system', 'drupalSettings'), - // @todo D8 formUpdated event should be debounced already. - array('system', 'drupal.debounce'), ), ); $libraries['ckeditor'] = array( diff --git a/core/modules/ckeditor/js/ckeditor.drupalimage.admin.js b/core/modules/ckeditor/js/ckeditor.drupalimage.admin.js new file mode 100644 index 00000000000..ec093abc5fc --- /dev/null +++ b/core/modules/ckeditor/js/ckeditor.drupalimage.admin.js @@ -0,0 +1,65 @@ +(function ($, Drupal, drupalSettings) { + +"use strict"; + +/** + * Shows the "drupalimage" plugin settings only when the button is enabled. + */ +Drupal.behaviors.ckeditorDrupalImageSettings = { + attach: function (context) { + var $context = $(context); + var $drupalImageVerticalTab = $('#edit-editor-settings-plugins-drupalimage').data('verticalTab'); + + // Hide if the "DrupalImage" button is disabled. + if ($('.ckeditor-toolbar-disabled li[data-button-name="DrupalImage"]').length === 1) { + $drupalImageVerticalTab.tabHide(); + } + + // React to added/removed toolbar buttons. + $context + .find('.ckeditor-toolbar-active') + .on('CKEditorToolbarChanged.ckeditorDrupalImageSettings', function (e, action, button) { + if (button === 'DrupalImage') { + if (action === 'added') { + $drupalImageVerticalTab.tabShow(); + } + else { + $drupalImageVerticalTab.tabHide(); + } + } + }); + } + +}; + +/** + * Provides the summary for the "drupalimage" plugin settings vertical tab. + */ +Drupal.behaviors.ckeditorDrupalImageSettingsSummary = { + attach: function () { + $('#edit-editor-settings-plugins-drupalimage').drupalSetSummary(function (context) { + var root = 'input[name="editor[settings][plugins][drupalimage][image_upload]'; + var $status = $(root + '[status]"]'); + var $maxFileSize = $(root + '[max_size]"]'); + var $maxWidth = $(root + '[max_dimensions][width]"]'); + var $maxHeight = $(root + '[max_dimensions][height]"]'); + var $scheme = $(root + '[scheme]"]:checked'); + + var maxFileSize = $maxFileSize.val() ? $maxFileSize.val() : $maxFileSize.attr('placeholder'); + var maxDimensions = ($maxWidth.val() && $maxHeight.val()) ? '(' + $maxWidth.val() + 'x' + $maxHeight.val() + ')' : ''; + + if (!$status.is(':checked')) { + return Drupal.t('Uploads disabled'); + } + + var output = ''; + output += Drupal.t('Uploads enabled, max size: @size @dimensions', { '@size': maxFileSize, '@dimensions': maxDimensions }); + if ($scheme.length) { + output += '
' + $scheme.attr('data-label'); + } + return output; + }); + } +}; + +})(jQuery, Drupal, drupalSettings); diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/CKEditorPlugin/DrupalImage.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/CKEditorPlugin/DrupalImage.php index c1e2f94493b..3c1cde8882b 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/CKEditorPlugin/DrupalImage.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/CKEditorPlugin/DrupalImage.php @@ -8,6 +8,7 @@ namespace Drupal\ckeditor\Plugin\CKEditorPlugin; use Drupal\ckeditor\CKEditorPluginBase; +use Drupal\ckeditor\CKEditorPluginConfigurableInterface; use Drupal\ckeditor\Annotation\CKEditorPlugin; use Drupal\Core\Annotation\Translation; use Drupal\editor\Entity\Editor; @@ -17,11 +18,11 @@ use Drupal\editor\Entity\Editor; * * @CKEditorPlugin( * id = "drupalimage", - * label = @Translation("Drupal image"), + * label = @Translation("Image"), * module = "ckeditor" * ) */ -class DrupalImage extends CKEditorPluginBase { +class DrupalImage extends CKEditorPluginBase implements CKEditorPluginConfigurableInterface { /** * {@inheritdoc} @@ -61,4 +62,36 @@ class DrupalImage extends CKEditorPluginBase { ); } + /** + * {@inheritdoc} + * + * @see \Drupal\editor\Form\EditorImageDialog + * @see editor_image_upload_settings_form() + */ + public function settingsForm(array $form, array &$form_state, Editor $editor) { + form_load_include($form_state, 'inc', 'editor', 'editor.admin'); + $form['image_upload'] = editor_image_upload_settings_form($editor); + $form['image_upload']['#attached']['library'][] = array('ckeditor', 'drupal.ckeditor.drupalimage.admin'); + $form['image_upload']['#element_validate'] = array( + array($this, 'validateImageUploadSettings'), + ); + + return $form; + } + + /** + * #element_validate handler for the "image_upload" element in settingsForm(). + * + * Moves the text editor's image upload settings from the DrupalImage plugin's + * own settings into $editor->image_upload. + * + * @see \Drupal\editor\Form\EditorImageDialog + * @see editor_image_upload_settings_form() + */ + function validateImageUploadSettings(array $element, array &$form_state) { + $settings = &$form_state['values']['editor']['settings']['plugins']['drupalimage']['image_upload']; + $form_state['editor']->image_upload = $settings; + unset($form_state['values']['editor']['settings']['plugins']['drupalimage']); + } + } diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/Editor/CKEditor.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/Editor/CKEditor.php index 1faaa0506fa..fb8181ab555 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/Editor/CKEditor.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/Editor/CKEditor.php @@ -112,6 +112,7 @@ class CKEditor extends EditorBase implements ContainerFactoryPluginInterface { // CKEditor plugin settings, if any. $form['plugin_settings'] = array( '#type' => 'vertical_tabs', + '#title' => t('CKEditor plugin settings'), ); $this->ckeditorPluginManager->injectPluginSettingsForm($form, $form_state, $editor); if (count(element_children($form['plugins'])) === 0) { diff --git a/core/modules/editor/config/schema/editor.schema.yml b/core/modules/editor/config/schema/editor.schema.yml index 6654b88bd7d..4e2c98533f8 100644 --- a/core/modules/editor/config/schema/editor.schema.yml +++ b/core/modules/editor/config/schema/editor.schema.yml @@ -12,6 +12,32 @@ editor.editor.*: label: 'Text editor' settings: type: editor.settings.[%parent.editor] + image_upload: + type: mapping + label: 'Image upload settings' + mapping: + status: + type: boolean + label: 'Status' + scheme: + type: string + label: 'File storage' + directory: + type: string + label: 'Upload directory' + max_size: + type: string + label: 'Maximum file size' + max_dimensions: + type: mapping + label: 'Maximum dimensions' + mapping: + width: + type: integer + label: 'Maximum width' + height: + type: integer + label: 'Maximum height' status: type: boolean label: 'Status' diff --git a/core/modules/editor/editor.admin.inc b/core/modules/editor/editor.admin.inc new file mode 100644 index 00000000000..9dc6a304645 --- /dev/null +++ b/core/modules/editor/editor.admin.inc @@ -0,0 +1,131 @@ +image_upload = isset($editor->image_upload) ? $editor->image_upload : array(); + $editor->image_upload += array( + 'status' => FALSE, + 'scheme' => file_default_scheme(), + 'directory' => 'inline-images', + 'max_size' => '', + 'max_dimensions' => array('width' => '', 'height' => ''), + ); + + $form['status'] = array( + '#type' => 'checkbox', + '#title' => t('Enable image uploads'), + '#default_value' => $editor->image_upload['status'], + '#attributes' => array( + 'data-editor-image-upload' => 'status', + ), + ); + $show_if_image_uploads_enabled = array( + 'visible' => array( + ':input[data-editor-image-upload="status"]' => array('checked' => TRUE), + ), + ); + + // Any visible, writable wrapper can potentially be used for uploads, + // including a remote file system that integrates with a CDN. + $stream_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE); + foreach ($stream_wrappers as $scheme => $info) { + $options[$scheme] = $info['description']; + } + if (!empty($options)) { + $form['scheme'] = array( + '#type' => 'radios', + '#title' => t('File storage'), + '#default_value' => $editor->image_upload['scheme'], + '#options' => $options, + '#states' => $show_if_image_uploads_enabled, + '#access' => count($options) > 1, + ); + } + // Set data- attributes with human-readable names for all possible stream + // wrappers, so that drupal.ckeditor.drupalimage.admin's summary rendering + // can use that. + foreach ($stream_wrappers as $scheme => $info) { + $form['scheme'][$scheme]['#attributes']['data-label'] = t('Storage: @name', array('@name' => $info['name'])); + } + + $form['directory'] = array( + '#type' => 'textfield', + '#default_value' => $editor->image_upload['directory'], + '#title' => t('Upload directory'), + '#description' => t("A directory relative to Drupal's files directory where uploaded images will be stored."), + '#states' => $show_if_image_uploads_enabled, + ); + + $default_max_size = format_size(file_upload_max_size()); + $form['max_size'] = array( + '#type' => 'textfield', + '#default_value' => $editor->image_upload['max_size'], + '#title' => t('Maximum file size'), + '#description' => t('If this is left empty, then the file size will be limited by the PHP maximum upload size of @size.', array('@size' => $default_max_size)), + '#maxlength' => 20, + '#size' => 10, + '#placeholder' => $default_max_size, + '#states' => $show_if_image_uploads_enabled, + ); + + $form['max_dimensions'] = array( + '#type' => 'item', + '#title' => t('Maximum dimensions'), + '#field_prefix' => '
', + '#field_suffix' => '
', + '#description' => t('Images larger than these dimensions will be scaled down.'), + '#states' => $show_if_image_uploads_enabled, + ); + $form['max_dimensions']['width'] = array( + '#title' => t('Width'), + '#title_display' => 'invisible', + '#type' => 'number', + '#default_value' => $editor->image_upload['max_dimensions']['width'], + '#size' => 8, + '#maxlength' => 8, + '#min' => 1, + '#max' => 99999, + '#placeholder' => 'width', + '#field_suffix' => ' x ', + '#states' => $show_if_image_uploads_enabled, + ); + $form['max_dimensions']['height'] = array( + '#title' => t('Height'), + '#title_display' => 'invisible', + '#type' => 'number', + '#default_value' => $editor->image_upload['max_dimensions']['height'], + '#size' => 8, + '#maxlength' => 8, + '#min' => 1, + '#max' => 99999, + '#placeholder' => 'height', + '#field_suffix' => 'pixels', + '#states' => $show_if_image_uploads_enabled, + ); + + return $form; +} diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module index e9a44f06b8c..3302f16cd86 100644 --- a/core/modules/editor/editor.module +++ b/core/modules/editor/editor.module @@ -8,6 +8,8 @@ use Drupal\file\Entity\File; use Drupal\editor\Entity\Editor; use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Entity\EntityInterface; +use Drupal\field\Field; /** * Implements hook_help(). @@ -384,3 +386,182 @@ function editor_pre_render_format($element) { return $element; } + +/** + * Implements hook_entity_insert(). + */ +function editor_entity_insert(EntityInterface $entity) { + $referenced_files_by_field = _editor_get_file_uuids_by_field($entity); + foreach ($referenced_files_by_field as $field => $uuids) { + _editor_record_file_usage($uuids, $entity); + } +} + +/** + * Implements hook_entity_update(). + */ +function editor_entity_update(EntityInterface $entity) { + // On new revisions, all files are considered to be a new usage and no + // deletion of previous file usages are necessary. + if (!empty($entity->original) && $entity->getRevisionId() != $entity->original->getRevisionId()) { + $referenced_files_by_field = _editor_get_file_uuids_by_field($entity); + foreach ($referenced_files_by_field as $field => $uuids) { + _editor_record_file_usage($uuids, $entity); + } + } + // On modified revisions, detect which file references have been added (and + // record their usage) and which ones have been removed (delete their usage). + // File references that existed both in the previous version of the revision + // and in the new one don't need their usage to be updated. + else { + $original_uuids_by_field = _editor_get_file_uuids_by_field($entity->original); + $uuids_by_field = _editor_get_file_uuids_by_field($entity); + + // Detect file usages that should be incremented. + foreach ($uuids_by_field as $field => $uuids) { + $added_files = array_diff($uuids_by_field[$field], $original_uuids_by_field[$field]); + _editor_record_file_usage($added_files, $entity); + } + + // Detect file usages that should be decremented. + foreach ($original_uuids_by_field as $field => $uuids) { + $removed_files = array_diff($original_uuids_by_field[$field], $uuids_by_field[$field]); + _editor_delete_file_usage($removed_files, $entity, 1); + } + } +} + +/** + * Implements hook_entity_delete(). + */ +function editor_entity_delete(EntityInterface $entity) { + $referenced_files_by_field = _editor_get_file_uuids_by_field($entity); + foreach ($referenced_files_by_field as $field => $uuids) { + _editor_delete_file_usage($uuids, $entity, 0); + } +} + +/** + * Implements hook_entity_revision_delete(). + */ +function editor_entity_revision_delete(EntityInterface $entity) { + $referenced_files_by_field = _editor_get_file_uuids_by_field($entity); + foreach ($referenced_files_by_field as $field => $uuids) { + _editor_delete_file_usage($uuids, $entity, 1); + } +} + +/** + * Records file usage of files referenced by processed text fields. + * + * Every referenced file that does not yet have the FILE_STATUS_PERMANENT state, + * will be given that state. + * + * @param array $uuids + * An array of file entity UUIDs. + * @param EntityInterface $entity + * An entity whose fields to inspect for file references. + */ +function _editor_record_file_usage(array $uuids, EntityInterface $entity) { + foreach ($uuids as $uuid) { + $file = entity_load_by_uuid('file', $uuid); + if ($file->status !== FILE_STATUS_PERMANENT) { + $file->status = FILE_STATUS_PERMANENT; + $file->save(); + } + file_usage()->add($file, 'editor', $entity->entityType(), $entity->id()); + } +} + +/** + * Deletes file usage of files referenced by processed text fields. + * + * @param array $uuids + * An array of file entity UUIDs. + * @param EntityInterface $entity + * An entity whose fields to inspect for file references. + * @param $count + * The number of references to delete. Should be 1 when deleting a single + * revision and 0 when deleting an entity entirely. + * + * @see Drupal\file\FileUsage\FileUsageInterface::delete() + */ +function _editor_delete_file_usage(array $uuids, EntityInterface $entity, $count) { + foreach ($uuids as $uuid) { + $file = entity_load_by_uuid('file', $uuid); + file_usage()->delete($file, 'editor', $entity->entityType(), $entity->id(), $count); + } +} + +/** + * Finds all files referenced (data-editor-file-uuid) by processed text fields. + * + * @param EntityInterface $entity + * An entity whose fields to analyze. + * + * @return array + * An array of file entity UUIDs. + */ +function _editor_get_file_uuids_by_field(EntityInterface $entity) { + $uuids = array(); + + $processed_text_fields = _editor_get_processed_text_fields($entity); + foreach ($processed_text_fields as $processed_text_field) { + $text = $entity->get($processed_text_field)->value; + $uuids[$processed_text_field] = _editor_parse_file_uuids($text); + } + return $uuids; +} + +/** + * Determines the text fields on an entity that have text processing enabled. + * + * @param EntityInterface $entity + * An entity whose fields to analyze. + * + * @return array + * The names of the fields on this entity that have text processing enabled. + */ +function _editor_get_processed_text_fields(EntityInterface $entity) { + $properties = $entity->getPropertyDefinitions(); + if (empty($properties)) { + return array(); + } + + // Find all configurable fields, because only they could have a + // text_processing setting. + $configurable_fields = array_keys(array_filter($properties, function ($definition) { + return isset($definition['configurable']) && $definition['configurable'] === TRUE; + })); + if (empty($configurable_fields)) { + return array(); + } + + // Only return fields that have text processing enabled. + return array_filter($configurable_fields, function ($field) use ($entity) { + $settings = Field::fieldInfo() + ->getInstance($entity->entityType(), $entity->bundle(), $field) + ->getFieldSettings(); + return isset($settings['text_processing']) && $settings['text_processing'] === '1'; + }); +} + +/** + * Parse an HTML snippet for any data-editor-file-uuid attributes. + * + * @param string $text + * The partial (X)HTML snippet to load. Invalid markup will be corrected on + * import. + * + * @return array + * An array of all found UUIDs. + */ +function _editor_parse_file_uuids($text) { + $dom = filter_dom_load($text); + $xpath = new \DOMXPath($dom); + $uuids = array(); + foreach ($xpath->query('//*[@data-editor-file-uuid]') as $node) { + $uuids[] = $node->getAttribute('data-editor-file-uuid'); + } + return $uuids; +} diff --git a/core/modules/editor/lib/Drupal/editor/Entity/Editor.php b/core/modules/editor/lib/Drupal/editor/Entity/Editor.php index 1183f80f809..54949ec9301 100644 --- a/core/modules/editor/lib/Drupal/editor/Entity/Editor.php +++ b/core/modules/editor/lib/Drupal/editor/Entity/Editor.php @@ -47,12 +47,19 @@ class Editor extends ConfigEntityBase implements EditorInterface { public $editor; /** - * The array of settings for the text editor. + * The array of text editor plugin-specific settings for the text editor. * * @var array */ public $settings = array(); + /** + * The array of image upload settings for the text editor. + * + * @var array + */ + public $image_upload = array(); + /** * Overrides Drupal\Core\Entity\Entity::id(). */ diff --git a/core/modules/editor/lib/Drupal/editor/Form/EditorImageDialog.php b/core/modules/editor/lib/Drupal/editor/Form/EditorImageDialog.php index b20f35f50c8..080721091ca 100644 --- a/core/modules/editor/lib/Drupal/editor/Form/EditorImageDialog.php +++ b/core/modules/editor/lib/Drupal/editor/Form/EditorImageDialog.php @@ -13,6 +13,8 @@ use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Ajax\HtmlCommand; use Drupal\editor\Ajax\EditorDialogSave; use Drupal\Core\Ajax\CloseModalDialogCommand; +use Drupal\Core\StreamWrapper\LocalStream; +use Drupal\file\FileInterface; /** * Provides an image dialog for text editors. @@ -42,16 +44,50 @@ class EditorImageDialog implements FormInterface { $form['#prefix'] = '
'; $form['#suffix'] = '
'; - // Everything under the "attributes" key is merged directly into the - // generated img tag's attributes. - $form['attributes']['src'] = array( - '#title' => t('URL'), - '#type' => 'textfield', - '#default_value' => isset($input['src']) ? $input['src'] : '', - '#maxlength' => 2048, + $editor = editor_load($filter_format->format); + + // Construct strings to use in the upload validators. + if (!empty($editor->image_upload['dimensions'])) { + $max_dimensions = $editor->image_upload['dimensions']['max_width'] . 'x' . $editor->image_upload['dimensions']['max_height']; + } + else { + $max_dimensions = 0; + } + $max_filesize = min(parse_size($editor->image_upload['max_size']), file_upload_max_size()); + + $existing_file = isset($input['data-editor-file-uuid']) ? entity_load_by_uuid('file', $input['data-editor-file-uuid']) : NULL; + $fid = $existing_file ? $existing_file->id() : NULL; + + $form['fid'] = array( + '#title' => t('Image'), + '#type' => 'managed_file', + '#upload_location' => $editor->image_upload['scheme'] . '://' .$editor->image_upload['directory'], + '#default_value' => $fid ? array($fid) : NULL, + '#upload_validators' => array( + 'file_validate_extensions' => array('gif png jpg jpeg'), + 'file_validate_size' => array($max_filesize), + 'file_validate_image_resolution' => array($max_dimensions), + ), '#required' => TRUE, ); + $form['attributes']['src'] = array( + '#title' => t('URL'), + '#type' => 'textfield', + '#default_value' => isset($input['src']) ? $input['src'] : '', + '#maxlength' => 2048, + '#required' => TRUE, + ); + + // If the editor has image uploads enabled, show a managed_file form item, + // otherwise show a (file URL) text form item. + if ($editor->image_upload['status'] === '1') { + $form['attributes']['src']['#access'] = FALSE; + } + else { + $form['fid']['#access'] = FALSE; + } + $form['attributes']['alt'] = array( '#title' => t('Alternative text'), '#type' => 'textfield', @@ -120,6 +156,14 @@ class EditorImageDialog implements FormInterface { public function submitForm(array &$form, array &$form_state) { $response = new AjaxResponse(); + // Convert any uploaded files from the FID values to data-editor-file-uuid + // attributes. + if (!empty($form_state['values']['fid'][0])) { + $file = file_load($form_state['values']['fid'][0]); + $form_state['values']['attributes']['src'] = file_create_url($file->getFileUri()); + $form_state['values']['attributes']['data-editor-file-uuid'] = $file->uuid(); + } + if (form_get_errors()) { unset($form['#prefix'], $form['#suffix']); $status_messages = array('#theme' => 'status_messages'); diff --git a/core/modules/editor/lib/Drupal/editor/Tests/EditorFileUsageTest.php b/core/modules/editor/lib/Drupal/editor/Tests/EditorFileUsageTest.php new file mode 100644 index 00000000000..80384ae0f3a --- /dev/null +++ b/core/modules/editor/lib/Drupal/editor/Tests/EditorFileUsageTest.php @@ -0,0 +1,117 @@ + 'Text Editor file usage', + 'description' => 'Tests tracking of file usage by the Text Editor module.', + 'group' => 'Text Editor', + ); + } + + function setUp() { + parent::setUp(); + $this->installSchema('system', 'url_alias'); + $this->installSchema('node', 'node'); + $this->installSchema('node', 'node_access'); + $this->installSchema('node', 'node_field_data'); + $this->installSchema('node', 'node_field_revision'); + $this->installSchema('file', 'file_managed'); + $this->installSchema('file', 'file_usage'); + + // Add text formats. + $filtered_html_format = entity_create('filter_format', array( + 'format' => 'filtered_html', + 'name' => 'Filtered HTML', + 'weight' => 0, + 'filters' => array(), + )); + $filtered_html_format->save(); + + // Set up text editor. + $editor = entity_create('editor', array( + 'format' => 'filtered_html', + 'editor' => 'unicorn', + )); + $editor->save(); + + // Create a node type for testing. + $type = entity_create('node_type', array('type' => 'page', 'name' => 'page')); + $type->save(); + } + + /** + * Tests the configurable text editor manager. + */ + function testEditorEntityHooks() { + $image = entity_create('file', array()); + $image->setFileUri('core/misc/druplicon.png'); + $image->setFilename(drupal_basename($image->getFileUri())); + $image->save(); + $this->assertIdentical(array(), file_usage()->listUsage($image), 'The image has zero usages.'); + + // Test editor_entity_insert(): increment. + $node = entity_create('node', array( + 'type' => 'page', + 'title' => 'test', + 'body' => array( + 'value' => '

Hello, world!

', + 'format' => 'filtered_html', + ) + )); + $node->save(); + $this->assertIdentical(array('editor' => array('node' => array(1 => '1'))), file_usage()->listUsage($image), 'The image has 1 usage.'); + + // Test editor_entity_update(): increment, twice, by creating new revisions. + $node->setNewRevision(TRUE); + $node->save(); + $second_revision_id = $node->getRevisionId(); + $node->setNewRevision(TRUE); + $node->save(); + $this->assertIdentical(array('editor' => array('node' => array(1 => '3'))), file_usage()->listUsage($image), 'The image has 3 usages.'); + + // Test hook_entity_update(): decrement, by modifying the last revision: + // remove the data- attribute from the body field. + $body = $node->get('body')->offsetGet(0)->get('value'); + $original_value = $body->getValue(); + $new_value = str_replace('data-editor-file-uuid', 'data-editor-file-uuid-modified', $original_value); + $body->setValue($new_value); + $node->save(); + $this->assertIdentical(array('editor' => array('node' => array(1 => '2'))), file_usage()->listUsage($image), 'The image has 2 usages.'); + + // Test hook_entity_update(): increment, by modifying the last revision: + // readd the data- attribute to the body field. + $node->get('body')->offsetGet(0)->get('value')->setValue($original_value); + $node->save(); + $this->assertIdentical(array('editor' => array('node' => array(1 => '3'))), file_usage()->listUsage($image), 'The image has 3 usages.'); + + // Test editor_entity_revision_delete(): decrement, by deleting a revision. + entity_revision_delete('node', $second_revision_id); + $this->assertIdentical(array('editor' => array('node' => array(1 => '2'))), file_usage()->listUsage($image), 'The image has 2 usages.'); + + // Test editor_entity_delete(). + $node->delete(); + $this->assertIdentical(array(), file_usage()->listUsage($image), 'The image has zero usages again.'); + } + +} diff --git a/core/modules/text/lib/Drupal/text/TextProcessed.php b/core/modules/text/lib/Drupal/text/TextProcessed.php index 9a1910f8e53..352420de387 100644 --- a/core/modules/text/lib/Drupal/text/TextProcessed.php +++ b/core/modules/text/lib/Drupal/text/TextProcessed.php @@ -88,4 +88,5 @@ class TextProcessed extends TypedData { // throw new ReadOnlyException('Unable to set a computed property.'); } } + } diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/area/Text.php b/core/modules/views/lib/Drupal/views/Plugin/views/area/Text.php index 645e10df10e..71124a20323 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/views/area/Text.php +++ b/core/modules/views/lib/Drupal/views/Plugin/views/area/Text.php @@ -39,7 +39,7 @@ class Text extends TokenizeAreaPluginBase { '#default_value' => $this->options['content'], '#rows' => 6, '#format' => isset($this->options['format']) ? $this->options['format'] : filter_default_format(), - '#wysiwyg' => FALSE, + '#editor' => FALSE, ); } diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/exposed_form/InputRequired.php b/core/modules/views/lib/Drupal/views/Plugin/views/exposed_form/InputRequired.php index baf851660b5..4cf72765973 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/views/exposed_form/InputRequired.php +++ b/core/modules/views/lib/Drupal/views/Plugin/views/exposed_form/InputRequired.php @@ -41,7 +41,7 @@ class InputRequired extends ExposedFormPluginBase { '#description' => t('Text to display instead of results until the user selects and applies an exposed filter.'), '#default_value' => $this->options['text_input_required'], '#format' => isset($this->options['text_input_required_format']) ? $this->options['text_input_required_format'] : filter_default_format(), - '#wysiwyg' => FALSE, + '#editor' => FALSE, ); } diff --git a/core/profiles/standard/config/editor.editor.basic_html.yml b/core/profiles/standard/config/editor.editor.basic_html.yml index cf4a60011f3..e3406852cac 100644 --- a/core/profiles/standard/config/editor.editor.basic_html.yml +++ b/core/profiles/standard/config/editor.editor.basic_html.yml @@ -20,5 +20,13 @@ settings: plugins: stylescombo: styles: '' +image_upload: + status: '1' + scheme: public + directory: inline-images + max_size: '' + max_dimensions: + width: '' + height: '' status: '1' langcode: und diff --git a/core/profiles/standard/config/editor.editor.full_html.yml b/core/profiles/standard/config/editor.editor.full_html.yml index c0111f9d7bf..eb01c1cb266 100644 --- a/core/profiles/standard/config/editor.editor.full_html.yml +++ b/core/profiles/standard/config/editor.editor.full_html.yml @@ -30,5 +30,13 @@ settings: plugins: stylescombo: styles: '' +image_upload: + status: '1' + scheme: public + directory: inline-images + max_size: '' + max_dimensions: + width: '' + height: '' status: '1' langcode: und