edit.module | 10 ++- edit.routing.yml | 6 +- js/edit.js | 44 ++++++++----- lib/Drupal/edit/EditBundle.php | 3 +- lib/Drupal/edit/EditController.php | 21 +++---- lib/Drupal/edit/EditorAttacher.php | 65 ------------------- lib/Drupal/edit/EditorAttacherInterface.php | 24 ------- lib/Drupal/edit/EditorSelector.php | 51 +++++++++------ lib/Drupal/edit/EditorSelectorInterface.php | 9 +++ lib/Drupal/edit/MetadataGenerator.php | 80 ++++++++++++++++++++++++ lib/Drupal/edit/MetadataGeneratorInterface.php | 41 ++++++++++++ 11 files changed, 212 insertions(+), 142 deletions(-) diff --git a/edit.module b/edit.module index fffdfe2..e3beec0 100644 --- a/edit.module +++ b/edit.module @@ -63,6 +63,10 @@ function edit_toolbar() { ), ); + // Include the attachments and settings for all available editors. + $attachments = drupal_container()->get('edit.editor.selector')->getAllEditorAttachments(); + $tab['edit']['tray']['#attached'] = array_merge_recursive($tab['edit']['tray']['#attached'], $attachments); + return $tab; } @@ -108,7 +112,7 @@ function edit_library_info() { // Basic settings. array( 'data' => array('edit' => array( - 'accessURL' => url('edit/access'), + 'metadataURL' => url('edit/metadata'), 'fieldFormURL' => url('edit/form/!entity_type/!id/!field_name/!langcode/!view_mode'), 'rerenderProcessedTextURL' => url('edit/text/!entity_type/!id/!field_name/!langcode/!view_mode'), 'context' => 'body', @@ -139,7 +143,9 @@ function edit_library_info() { * Implements hook_preprocess_HOOK() for field.tpl.php. */ function edit_preprocess_field(&$variables) { - drupal_container()->get('edit.editor.attacher')->preprocessField($variables); + $element = $variables['element']; + $entity = $element['#object']; + $variables['attributes']['data-edit-id'] = $entity->entityType() . ':' . $entity->id() . ':' . $element['#field_name'] . ':' . $element['#language'] . ':' . $element['#view_mode']; } /** diff --git a/edit.routing.yml b/edit.routing.yml index 6705078..f63dc82 100644 --- a/edit.routing.yml +++ b/edit.routing.yml @@ -1,7 +1,7 @@ -edit_access: - pattern: '/edit/access' +edit_metadata: + pattern: '/edit/metadata' defaults: - _controller: '\Drupal\edit\EditController::access' + _controller: '\Drupal\edit\EditController::metadata' requirements: _permission: 'access in-place editing' diff --git a/js/edit.js b/js/edit.js index 1e8b6ab..8b8d7af 100644 --- a/js/edit.js +++ b/js/edit.js @@ -17,7 +17,7 @@ var $messages; Drupal.edit = Drupal.edit || {}; -Drupal.edit.accessCache = Drupal.edit.accessCache || {}; +Drupal.edit.metadataCache = Drupal.edit.metadataCache || {}; /** * Attach toggling behavior and in-place editing. @@ -25,30 +25,42 @@ Drupal.edit.accessCache = Drupal.edit.accessCache || {}; Drupal.behaviors.edit = { attach: function(context) { var $context = $(context); - var $fields = $context.find('.edit-field'); + var $fields = $context.find('[data-edit-id]'); // Initialize the Edit app. $context.find('#toolbar-tab-edit').once('edit-init', Drupal.edit.init); - var annotateFieldAccess = function(field) { - if (_.has(Drupal.edit.accessCache, field.editID)) { - var access = Drupal.edit.accessCache[field.editID]; - field.$el.addClass((access) ? 'edit-allowed' : 'edit-disallowed'); + var annotateField = function(field) { + if (_.has(Drupal.edit.metadataCache, field.editID)) { + var meta = Drupal.edit.metadataCache[field.editID]; + field.$el + .attr('data-edit-field-label', meta.label) + .attr('aria-label', meta.aria) + .addClass('edit-field edit-type-' + meta.editor) + .addClass((meta.access) ? 'edit-allowed' : 'edit-disallowed'); + if (meta.editor === 'direct-with-wysiwyg') { + field.$el + // This editor also uses the Backbone.syncDirect saving mechanism. + .addClass('edit-type-direct') + .attr('data-edit-text-format', meta.format) + .addClass((meta.formatHasTransformations) ? 'edit-text-with-transformation-filters' : 'edit-text-without-transformation-filters'); + } + return true; } return false; }; - // Find all fields in the context without access metadata. + // Find all fields in the context without metadata. var fieldsToAnnotate = _.map($fields.not('.edit-allowed, .edit-disallowed'), function(el) { var $el = $(el); return { $el: $el, editID: $el.attr('data-edit-id') }; }); - // Fields whose access is known (typically when they were just modified) can - // be annotated immediately, those remaining must be checked on the server. + // Fields whose metadata is known (typically when they were just modified) + // can be annotated immediately, those remaining must be requested. var remainingFieldsToAnnotate = _.reduce(fieldsToAnnotate, function(result, field) { - if (!annotateFieldAccess(field)) { + if (!annotateField(field)) { result.push(field); } return result; @@ -58,20 +70,20 @@ Drupal.behaviors.edit = { Drupal.edit.app.findEditableProperties($context); if (remainingFieldsToAnnotate.length) { - $(function() { + $(window).ready(function() { $.ajax({ - url: drupalSettings.edit.accessURL, + url: drupalSettings.edit.metadataURL, type: 'POST', data: { 'fields[]' : _.pluck(remainingFieldsToAnnotate, 'editID') }, dataType: 'json', success: function(results) { - // Update the access cache. - _.each(results, function(access, editID) { - Drupal.edit.accessCache[editID] = access; + // Update the metadata cache. + _.each(results, function(metadata, editID) { + Drupal.edit.metadataCache[editID] = metadata; }); // Annotate the remaining fields based on the updated access cache. - _.each(remainingFieldsToAnnotate, annotateFieldAccess); + _.each(remainingFieldsToAnnotate, annotateField); // As soon as there is at least one editable field, show the Edit // tab in the toolbar. diff --git a/lib/Drupal/edit/EditBundle.php b/lib/Drupal/edit/EditBundle.php index 4655033..d92fd73 100644 --- a/lib/Drupal/edit/EditBundle.php +++ b/lib/Drupal/edit/EditBundle.php @@ -29,7 +29,8 @@ class EditBundle extends Bundle { $container->register('edit.editor.selector', 'Drupal\edit\EditorSelector') ->addArgument(new Reference('plugin.manager.edit.processed_text_editor')); - $container->register('edit.editor.attacher', 'Drupal\edit\EditorAttacher') + $container->register('edit.metadata.generator', 'Drupal\edit\MetadataGenerator') + ->addArgument(new Reference('access_check.edit.entity_field')) ->addArgument(new Reference('edit.editor.selector')); } diff --git a/lib/Drupal/edit/EditController.php b/lib/Drupal/edit/EditController.php index c97fac0..eca4201 100644 --- a/lib/Drupal/edit/EditController.php +++ b/lib/Drupal/edit/EditController.php @@ -12,7 +12,6 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Entity\EntityInterface; -use Drupal\edit\Access\EditEntityFieldAccessCheckInterface; use Drupal\edit\Ajax\FieldFormCommand; use Drupal\edit\Ajax\FieldFormSavedCommand; use Drupal\edit\Ajax\FieldFormValidationErrorsCommand; @@ -24,22 +23,23 @@ use Drupal\edit\Ajax\FieldRenderedWithoutTransformationFiltersCommand; class EditController extends ContainerAware { /** - * Determines which fields a user may edit. + * Returns the metadata for a set of fields. * * Given a list of field edit IDs as POST parameters, run access checks on the * entity and field level to determine whether the current user may edit them. + * Also retrieves other metadata. * * @return \Symfony\Component\HttpFoundation\JsonResponse * The JSON response. */ - public function access() { + public function metadata() { if (!isset($_POST['fields'])) { throw new NotFoundHttpException(); } $fields = $_POST['fields']; - $accessChecker = drupal_container()->get('access_check.edit.entity_field'); + $metadataGenerator = drupal_container()->get('edit.metadata.generator'); - $results = array(); + $metadata = array(); foreach ($fields as $field) { list($entity_type, $entity_id, $field_name, $langcode, $view_mode) = explode(':', $field); @@ -53,22 +53,17 @@ class EditController extends ContainerAware { } // Validate the field name and language. - if (!$field_name || !field_info_instance($entity->entityType(), $field_name, $entity->bundle())) { + if (!$field_name || !($instance = field_info_instance($entity->entityType(), $field_name, $entity->bundle()))) { throw new NotFoundHttpException(); } if (!$langcode || (field_valid_language($langcode) !== $langcode)) { throw new NotFoundHttpException(); } - if ($accessChecker->accessEditEntityField($entity, $field_name)) { - $results[$field] = TRUE; - } - else { - $results[$field] = FALSE; - } + $metadata[$field] = $metadataGenerator->generate($entity, $instance, $langcode, $view_mode); } - return new JsonResponse($results); + return new JsonResponse($metadata); } /** diff --git a/lib/Drupal/edit/EditorAttacher.php b/lib/Drupal/edit/EditorAttacher.php deleted file mode 100644 index 3843edc..0000000 --- a/lib/Drupal/edit/EditorAttacher.php +++ /dev/null @@ -1,65 +0,0 @@ -editorSelector = $editor_selector; - } - - /** - * Implements \Drupal\edit\EditorAttacherInterface::preprocessField(). - */ - public function preprocessField(&$variables) { - $element = $variables['element']; - $entity = $element['#object']; - $field_name = $element['#field_name']; - $instance = field_info_instance($entity->entityType(), $field_name, $entity->bundle()); - if ($editor = $this->editorSelector->getEditor($element['#formatter'], $instance, $element['#items'])) { - // Attributes needed to make the element editable. - $variables['attributes']['data-edit-field-label'] = $instance['label']; - $variables['attributes']['data-edit-id'] = $entity->entityType() . ':' . $entity->id() . ':' . $field_name . ':' . $element['#language'] . ':' . $element['#view_mode']; - $variables['attributes']['aria-label'] = t('Entity @type @id, field @field', array('@type' => $entity->entityType(), '@id' => $entity->id(), '@field' => $instance['label'])); - $variables['attributes']['class'][] = 'edit-field'; - $variables['attributes']['class'][] = 'edit-type-' . $editor; - - // Additional attributes for WYSIWYG editor integration. - if ($editor == 'direct-with-wysiwyg') { - $variables['attributes']['class'][] = 'edit-type-direct'; - $format_id = $element['#items'][0]['format']; - $variables['attributes']['data-edit-text-format'] = $format_id; - $variables['attributes']['class'][] = $this->textFormatHasTransformationFilters($format_id) ? 'edit-text-with-transformation-filters' : 'edit-text-without-transformation-filters'; - } - } - } - - /** - * Returns whether the text format has transformation filters. - */ - protected function textFormatHasTransformationFilters($format_id) { - return (bool) count(array_intersect(array(FILTER_TYPE_TRANSFORM_REVERSIBLE, FILTER_TYPE_TRANSFORM_IRREVERSIBLE), filter_get_filter_types_by_format($format_id))); - } - -} diff --git a/lib/Drupal/edit/EditorAttacherInterface.php b/lib/Drupal/edit/EditorAttacherInterface.php deleted file mode 100644 index 4605ee3..0000000 --- a/lib/Drupal/edit/EditorAttacherInterface.php +++ /dev/null @@ -1,24 +0,0 @@ -getProcessedTextEditorPlugin(); + if (!isset($this->processedTextEditorPlugin)) { + return array(); + } + + $js = array(); + + // Add library and settings for the selected processed text editor plugin. + $definition = $this->processedTextEditorPlugin->getDefinition(); + if (!empty($definition['library'])) { + $js['library'][] = array($definition['library']['module'], $definition['library']['name']); + } + $this->processedTextEditorPlugin->addJsSettings(); + + // Also add the setting to register it with Create.js + if (!empty($definition['propertyEditorName'])) { + $js['js'][] = array( + 'data' => array( + 'edit' => array( + 'wysiwygEditorWidgetName' => $definition['propertyEditorName'], + ), + ), + 'type' => 'setting' + ); + } + + return $js; + } + + /** * Returns the plugin to use for the 'direct-with-wysiwyg' editor. * * @return \Drupal\edit\Plugin\ProcessedTextEditorInterface @@ -127,24 +160,6 @@ class EditorSelector implements EditorSelectorInterface { $plugin_ids = array_keys($definitions); $plugin_id = $plugin_ids[0]; $this->processedTextEditorPlugin = $this->processedTextEditorManager->createInstance($plugin_id); - - // Add JavaScript required by this plugin, including the setting to - // register it with Create.js. - // @todo For compatibility with render caching, this should be done - // with #attached rather than drupal_add_*() functions. However, this - // is called from the context of edit_preprocess_field(), and - // #attached does not bubble up from theme preprocess functions: - // http://drupal.org/node/495968#comment-3639542. - $definition = $this->processedTextEditorPlugin->getDefinition(); - if (!empty($definition['library'])) { - drupal_add_library($definition['library']['module'], $definition['library']['name']); - } - $this->processedTextEditorPlugin->addJsSettings(); - if (!empty($definition['propertyEditorName'])) { - drupal_add_js(array('edit' => array( - 'wysiwygEditorWidgetName' => $definition['propertyEditorName'], - )), 'setting'); - } } } return $this->processedTextEditorPlugin; diff --git a/lib/Drupal/edit/EditorSelectorInterface.php b/lib/Drupal/edit/EditorSelectorInterface.php index c1ce47a..ed7e2c8 100644 --- a/lib/Drupal/edit/EditorSelectorInterface.php +++ b/lib/Drupal/edit/EditorSelectorInterface.php @@ -43,4 +43,13 @@ interface EditorSelectorInterface { */ public function getEditor($formatter_type, FieldInstance $instance, array $items); + /** + * Returns the attachments for all editors. + * + * @return array + * An array of attachments, for use with #attached. + * + * @see drupal_process_attached() + */ + public function getAllEditorAttachments(); } diff --git a/lib/Drupal/edit/MetadataGenerator.php b/lib/Drupal/edit/MetadataGenerator.php new file mode 100644 index 0000000..bce1e5b --- /dev/null +++ b/lib/Drupal/edit/MetadataGenerator.php @@ -0,0 +1,80 @@ +accessChecker = $access_checker; + $this->editorSelector = $editor_selector; + } + + /** + * Implements \Drupal\edit\MetadataGeneratorInterface::generate(). + */ + public function generate(EntityInterface $entity, FieldInstance $instance, $langcode, $view_mode) { + $field_name = $instance['field_name']; + $label = $instance['label']; + $formatter_id = $instance->getFormatter($view_mode)->getPluginId(); + $items = $entity->get($field_name); + $items = $items[$langcode]; + $editor = $this->editorSelector->getEditor($formatter_id, $instance, $items); + $metadata = array( + 'label' => $label, + 'access' => $this->accessChecker->accessEditEntityField($entity, $field_name), + 'editor' => $editor, + 'aria' => t('Entity @type @id, field @field', array('@type' => $entity->entityType(), '@id' => $entity->id(), '@field' => $label)), + ); + // Additional metadata for WYSIWYG editor integration. + if ($editor === 'direct-with-wysiwyg') { + $format_id = $items[0]['format']; + $metadata['format'] = $format_id; + $metadata['formatHasTransformations'] = $this->textFormatHasTransformationFilters($format_id); + } + return $metadata; + } + + /** + * Returns whether the text format has transformation filters. + */ + protected function textFormatHasTransformationFilters($format_id) { + return (bool) count(array_intersect(array(FILTER_TYPE_TRANSFORM_REVERSIBLE, FILTER_TYPE_TRANSFORM_IRREVERSIBLE), filter_get_filter_types_by_format($format_id))); + } + +} diff --git a/lib/Drupal/edit/MetadataGeneratorInterface.php b/lib/Drupal/edit/MetadataGeneratorInterface.php new file mode 100644 index 0000000..9e4fb5d --- /dev/null +++ b/lib/Drupal/edit/MetadataGeneratorInterface.php @@ -0,0 +1,41 @@ +