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' => '
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