diff --git a/core/modules/edit/css/edit.css b/core/modules/edit/css/edit.css deleted file mode 100644 index 65c7f38..0000000 --- a/core/modules/edit/css/edit.css +++ /dev/null @@ -1,410 +0,0 @@ -/** - * Animations. - */ -.edit-animate-invisible { - opacity: 0; -} - -.edit-animate-fast { --webkit-transition: all .2s ease; - -moz-transition: all .2s ease; - -ms-transition: all .2s ease; - -o-transition: all .2s ease; - transition: all .2s ease; -} - -.edit-animate-default { - -webkit-transition: all .4s ease; - -moz-transition: all .4s ease; - -ms-transition: all .4s ease; - -o-transition: all .4s ease; - transition: all .4s ease; -} - -.edit-animate-slow { --webkit-transition: all .6s ease; - -moz-transition: all .6s ease; - -ms-transition: all .6s ease; - -o-transition: all .6s ease; - transition: all .6s ease; -} - -.edit-animate-delay-veryfast { - -webkit-transition-delay: .05s; - -moz-transition-delay: .05s; - -ms-transition-delay: .05s; - -o-transition-delay: .05s; - transition-delay: .05s; -} - -.edit-animate-delay-fast { - -webkit-transition-delay: .2s; - -moz-transition-delay: .2s; - -ms-transition-delay: .2s; - -o-transition-delay: .2s; - transition-delay: .2s; -} - -.edit-animate-disable-width { - -webkit-transition: width 0s; - -moz-transition: width 0s; - -ms-transition: width 0s; - -o-transition: width 0s; - transition: width 0s; -} - -.edit-animate-only-visibility { - -webkit-transition: opacity .2s ease; - -moz-transition: opacity .2s ease; - -ms-transition: opacity .2s ease; - -o-transition: opacity .2s ease; - transition: opacity .2s ease; -} - -.edit-animate-only-background-and-padding { - -webkit-transition: background, padding .2s ease; - -moz-transition: background, padding .2s ease; - -ms-transition: background, padding .2s ease; - -o-transition: background, padding .2s ease; - transition: background, padding .2s ease; -} - - - -/** - * Toolbar. - */ -.icon-edit:before { - background-image: url("../images/icon-edit.png"); -} -.icon-edit:active:before, -.active .icon-edit:before { - background-image: url("../images/icon-edit-active.png"); -} -.toolbar .tray.edit.active { - z-index: 340; -} -.toolbar .icon-edit.edit-nothing-editable-hidden { - display: none; -} -/* In-place editing doesn't work in the overlay, so always hide the tab. */ -.overlay-open .toolbar .icon-edit { - display: none; -} - - - -/** - * Edit mode: overlay + candidate editables + editables being edited. - * - * Note: every class is prefixed with "edit-" to prevent collisions with modules - * or themes. In IPE-specific DOM subtrees, this is not necessary. - */ - -#edit_overlay { - position: fixed; - z-index: 250; - width: 100%; - height: 100%; - background-color: #fff; - background-color: rgba(255,255,255,.5); - top: 0; - left: 0; -} - -/* Editable. */ -.edit-editable { - z-index: 300; - position: relative; -} -.edit-editable:focus { - outline: none; -} -.edit-field.edit-editable, -.edit-field.edit-type-direct .edit-editable { - box-shadow: 0 0 1px 1px #4d9de9; -} - -/* Highlighted (hovered) editable. */ -.edit-editable.edit-highlighted { - min-width: 200px; -} -.edit-field.edit-editable.edit-highlighted, -.edit-form.edit-editable.edit-highlighted, -.edit-field.edit-type-direct .edit-editable.edit-highlighted { - box-shadow: 0 0 1px 1px #0199ff, 0 0 3px 3px rgba(153, 153, 153, .5); -} -.edit-field.edit-editable.edit-highlighted.edit-validation-error, -.edit-form.edit-editable.edit-highlighted.edit-validation-error, -.edit-field.edit-type-direct .edit-editable.edit-highlighted.edit-validation-error { - box-shadow: 0 0 1px 1px red, 0 0 3px 3px rgba(153, 153, 153, .5); -} -.edit-form.edit-editable .form-item .error { - border: 1px solid #eea0a0; -} - - -/* Editing (focused) editable. */ -.edit-form.edit-editable.edit-editing, -.edit-field.edit-type-direct .edit-editable.edit-editing { - /* In the latest design, there's no special styling when editing as opposed to - * just hovering. - * This will be necessary again for http://drupal.org/node/1844220. - */ -} - - - - -/** - * Edit mode: modal. - */ -#edit_modal { - z-index: 350; - position: fixed; - top: 40%; - left: 40%; - box-shadow: 3px 3px 5px #333; - background-color: white; - border: 1px solid #0199ff; - font-family: 'Droid sans', 'Lucida Grande', sans-serif; -} - -#edit_modal .main { - font-size: 130%; - margin: 25px; - padding-left: 40px; - background: transparent url('../images/attention.png') no-repeat; -} - -#edit_modal .actions { - border-top: 1px solid #ddd; - padding: 3px inherit; - text-align: right; - background: #f5f5f5; -} - -/* Modal active: prevent user from interacting with toolbar & editables. */ -.edit-form-container.edit-belowoverlay, -.edit-toolbar-container.edit-belowoverlay, -.edit-validation-errors.edit-belowoverlay { - z-index: 210; -} -.edit-editable.edit-belowoverlay { - z-index: 200; -} - - - - -/** - * Edit mode: type=direct. - */ -.edit-validation-errors { - z-index: 300; - position: relative; -} - -.edit-validation-errors .messages.error { - position: absolute; - top: 6px; - left: -5px; - margin: 0; - border: none; - box-shadow: 0 0 1px 1px red, 0 0 3px 3px rgba(153, 153, 153, .5); - background-color: white; -} - - - - -/** - * Edit mode: type=form. - */ -#edit_backstage { - display: none; -} - -.edit-form { - position: absolute; - z-index: 300; - box-shadow: 0 0 30px 4px #4f4f4f; - max-width: 35em; -} - -.edit-form .placeholder { - min-height: 22px; -} - -/* Default form styling overrides. */ -.edit-form form { padding: 1em; } -.edit-form .form-item { margin: 0; } -.edit-form .form-wrapper { margin: .5em; } -.edit-form .form-wrapper .form-wrapper { margin: inherit; } -.edit-form .form-actions { display: none; } -.edit-form input { max-width: 100%; } - - - - -/** - * Edit mode: toolbars - */ - -/* Trick: wrap statically positioned elements in relatively positioned element - without changing its location. This allows us to absolutely position the - toolbar. -*/ -.edit-toolbar-container, -.edit-form-container { - position: relative; - padding: 0; - border: 0; - margin: 0; - vertical-align: baseline; - z-index: 310; -} -.edit-toolbar-container { - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - -o-user-select: none; - user-select: none; -} - -.edit-toolbar-heightfaker { - height: auto; - position: absolute; - bottom: 1px; - box-shadow: 0 0 1px 1px #0199ff, 0 0 3px 3px rgba(153, 153, 153, .5); - background: #fff; -} - -/* The toolbar; these are not necessarily visible. */ -.edit-toolbar { - position: relative; - height: 100%; - font-family: 'Droid sans', 'Lucida Grande', sans-serif; -} -.edit-toolbar-heightfaker { - clip: rect(-1000px, 1000px, auto, -1000px); /* Remove bottom box-shadow. */ -} -/* Exception: when used for a directly WYSIWYG editable field that is actively - being edited. */ -.edit-type-direct-with-wysiwyg .edit-editing .edit-toolbar-heightfaker { - width: 100%; - clip: auto; -} - - -/* The toolbar contains toolgroups; these are visible. */ -.edit-toolgroup { - float: left; /* LTR */ -} - -/* Info toolgroup. */ -.edit-toolgroup.info { - float: left; /* LTR */ - font-weight: bolder; - padding: 0 5px; - background: #fff url('../images/throbber.gif') no-repeat -60px 60px; -} -.edit-toolgroup.info.loading { - padding-right: 35px; - background-position: 90% 50%; -} - -/* Operations toolgroup. */ -.edit-toolgroup.ops { - float: right; /* LTR */ - margin-left: 5px; -} - -.edit-toolgroup.wysiwyg-tabs { - float: right; -} -.edit-toolgroup.wysiwyg { - clear: left; - width: 100%; - padding-left: 0; -} - - - -/** - * Edit mode: buttons (in both modal and toolbar). - */ -#edit_modal button, -.edit-toolbar button { - float: left; /* LTR */ - display: block; - height: 29px; - min-width: 29px; - padding: 3px 6px 6px 6px; - margin: 4px 5px 1px 0; - border: 1px solid #fff; - border-radius: 3px; - color: white; - text-decoration: none; - font-size: 13px; - cursor: pointer; -} -#edit_modal button { - float: none; - display: inline-block; -} - -/* Button with icons. */ -#edit_modal button span, -.edit-toolbar button span { - width: 22px; - height: 19px; - display: block; - float: left; -} -.edit-toolbar span.close { - background: url('../images/close.png') no-repeat 3px 2px; - text-indent: -999em; - direction: ltr; -} - -.edit-toolbar button.blank-button { - color: black; - background-color: #fff; - font-weight: bolder; -} - -#edit_modal button.blue-button, -.edit-toolbar button.blue-button { - color: white; - background-image: -webkit-linear-gradient(top, #6fc2f2 0%, #4e97c0 100%); - background-image: -moz-linear-gradient(top, #6fc2f2 0%, #4e97c0 100%); - background-image: linear-gradient(top, #6fc2f2 0%, #4e97c0 100%); - border-radius: 5px; -} - -#edit_modal button.gray-button, -.edit-toolbar button.gray-button { - color: #666; - background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #ccc 100%); - background-image: -moz-linear-gradient(top, #f5f5f5 0%, #ccc 100%); - background-image: linear-gradient(top, #f5f5f5 0%, #ccc 100%); - border-radius: 5px; -} - -#edit_modal button.blue-button:hover, -.edit-toolbar button.blue-button:hover, -#edit_modal button.blue-button:active, -.edit-toolbar button.blue-button:active { - border: 1px solid #55a5d3; - box-shadow: 0 2px 1px rgba(0,0,0,0.2); -} - -#edit_modal button.gray-button:hover, -.edit-toolbar button.gray-button:hover, -#edit_modal button.gray-button:active, -.edit-toolbar button.gray-button:active { - border: 1px solid #cdcdcd; - box-shadow: 0 2px 1px rgba(0,0,0,0.1); -} diff --git a/core/modules/edit/edit.info b/core/modules/edit/edit.info deleted file mode 100644 index 4074a7b..0000000 --- a/core/modules/edit/edit.info +++ /dev/null @@ -1,6 +0,0 @@ -name = Edit -description = In-place content editing. -package = Core -core = 8.x -version = VERSION -dependencies[] = field diff --git a/core/modules/edit/edit.module b/core/modules/edit/edit.module deleted file mode 100644 index e3beec0..0000000 --- a/core/modules/edit/edit.module +++ /dev/null @@ -1,159 +0,0 @@ - array( - 'title' => t('Access in-place editing'), - ), - ); -} - -/** - * Implements hook_toolbar(). - */ -function edit_toolbar() { - if (!user_access('access in-place editing')) { - return; - } - - $tab['edit'] = array( - 'tab' => array( - 'title' => t('Edit'), - 'href' => '', - 'html' => FALSE, - 'attributes' => array( - 'class' => array('icon', 'icon-edit', 'edit-nothing-editable-hidden'), - ), - ), - 'tray' => array( - '#attached' => array( - 'library' => array( - array('edit', 'edit'), - ), - ), - ), - ); - - // 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; -} - -/** - * Implements hook_library(). - */ -function edit_library_info() { - $path = drupal_get_path('module', 'edit'); - $options = array( - 'scope' => 'footer', - 'attributes' => array('defer' => TRUE), - ); - $libraries['edit'] = array( - 'title' => 'Edit: in-place editing', - 'website' => 'http://drupal.org/project/edit', - 'version' => VERSION, - 'js' => array( - // Core. - $path . '/js/edit.js' => $options, - $path . '/js/app.js' => $options, - // Routers. - $path . '/js/routers/edit-router.js' => $options, - // Models. - $path . '/js/models/edit-app-model.js' => $options, - // Views. - $path . '/js/views/propertyeditordecoration-view.js' => $options, - $path . '/js/views/menu-view.js' => $options, - $path . '/js/views/modal-view.js' => $options, - $path . '/js/views/overlay-view.js' => $options, - $path . '/js/views/toolbar-view.js' => $options, - // Backbone.sync implementation on top of Drupal forms. - $path . '/js/backbone.drupalform.js' => $options, - // VIE service. - $path . '/js/viejs/EditService.js' => $options, - // Create.js subclasses. - $path . '/js/createjs/editable.js' => $options, - $path . '/js/createjs/storage.js' => $options, - $path . '/js/createjs/editingWidgets/formwidget.js' => $options, - $path . '/js/createjs/editingWidgets/drupalcontenteditablewidget.js' => $options, - // Other. - $path . '/js/util.js' => $options, - $path . '/js/theme.js' => $options, - // Basic settings. - array( - 'data' => array('edit' => array( - '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', - )), - 'type' => 'setting', - ), - ), - 'css' => array( - $path . '/css/edit.css' => array(), - ), - 'dependencies' => array( - array('system', 'jquery'), - array('system', 'underscore'), - array('system', 'backbone'), - array('system', 'vie.core'), - array('system', 'create.editonly'), - array('system', 'jquery.form'), - array('system', 'drupal.form'), - array('system', 'drupal.ajax'), - array('system', 'drupalSettings'), - ), - ); - - return $libraries; -} - -/** - * Implements hook_preprocess_HOOK() for field.tpl.php. - */ -function edit_preprocess_field(&$variables) { - $element = $variables['element']; - $entity = $element['#object']; - $variables['attributes']['data-edit-id'] = $entity->entityType() . ':' . $entity->id() . ':' . $element['#field_name'] . ':' . $element['#language'] . ':' . $element['#view_mode']; -} - -/** - * Form constructor for the field editing form. - * - * @ingroup forms - */ -function edit_field_form(array $form, array &$form_state, EntityInterface $entity, $field_name) { - $form_handler = new EditFieldForm(); - return $form_handler->build($form, $form_state, $entity, $field_name); -} diff --git a/core/modules/edit/edit.routing.yml b/core/modules/edit/edit.routing.yml deleted file mode 100644 index f63dc82..0000000 --- a/core/modules/edit/edit.routing.yml +++ /dev/null @@ -1,22 +0,0 @@ -edit_metadata: - pattern: '/edit/metadata' - defaults: - _controller: '\Drupal\edit\EditController::metadata' - requirements: - _permission: 'access in-place editing' - -edit_field_form: - pattern: '/edit/form/{entity_type}/{entity}/{field_name}/{langcode}/{view_mode}' - defaults: - _controller: '\Drupal\edit\EditController::fieldForm' - requirements: - _permission: 'access in-place editing' - _access_edit_entity_field: 'TRUE' - -edit_text: - pattern: '/edit/text/{entity_type}/{entity}/{field_name}/{langcode}/{view_mode}' - defaults: - _controller: '\Drupal\edit\EditController::getUntransformedText' - requirements: - _permission: 'access in-place editing' - _access_edit_entity_field: 'TRUE' diff --git a/core/modules/edit/images/attention.png b/core/modules/edit/images/attention.png deleted file mode 100644 index 6a35d1d..0000000 --- a/core/modules/edit/images/attention.png +++ /dev/null @@ -1,4 +0,0 @@ -PNG - - IHDR *}`PLTEl՟FZݱ|В8ʂx՜nϏ۫@EN;[cgUH7 tRNS =g- Su- 4_IDATx^}ʇ @Qzn/g!ul6 ; 0!f>>Ǐ kν_΁j㜻!0-@>8,i vҤrlWn?B(ijk*yT%Pvs=b_v>@?k& -a|NciKBFUD^']d`5+5P :IENDB` \ No newline at end of file diff --git a/core/modules/edit/images/close.png b/core/modules/edit/images/close.png deleted file mode 100644 index e3f98b8..0000000 --- a/core/modules/edit/images/close.png +++ /dev/null @@ -1,4 +0,0 @@ -PNG - - IHDR(-S`PLTE>+tRNS```00miIDATx^=   H4ͼ!KsfQGx"LCyל׽(ux;z KA.Jo -E wy/2cdD@Ҕ L؅O%8F?QIENDB` \ No newline at end of file diff --git a/core/modules/edit/images/icon-edit-active.png b/core/modules/edit/images/icon-edit-active.png deleted file mode 100644 index ad84761..0000000 --- a/core/modules/edit/images/icon-edit-active.png +++ /dev/null @@ -1,3 +0,0 @@ -PNG - - IHDRj `PLTE[tRNS@ P00p`ϟDƙIDATxe DQ8Ϩ/BDU9xV+D\?x@qWcF8wicS B}?v;Vf.V$JgX=Kضp0XS"iRw\:LL\~;Z5wu 5E)LIENDB` \ No newline at end of file diff --git a/core/modules/edit/images/icon-edit.png b/core/modules/edit/images/icon-edit.png deleted file mode 100644 index 4f0dcc2..0000000 --- a/core/modules/edit/images/icon-edit.png +++ /dev/null @@ -1,5 +0,0 @@ -PNG - - IHDRj PLTE̻ʪ̡̜ˠ̣̽¼ǷʨªZ(e+tRNSϟ `π@`0p0p`0PϟϟcIDATxeW0{W -H"ʵ,y {Hpyo?mf,RBRxB vL;&LPJaRb\(Tbn(1wϔJ)ԈkS -58äT^4P6c}[i <ާ'-+HP>KIENDB` \ No newline at end of file diff --git a/core/modules/edit/images/throbber.gif b/core/modules/edit/images/throbber.gif deleted file mode 100644 index f2603e8..0000000 --- a/core/modules/edit/images/throbber.gif +++ /dev/null @@ -1,6 +0,0 @@ -GIF89aŽ{{{! NETSCAPE2.0! ,@`)KkŏA|ad0L9~\8L Ǹ0, i{qBC~H'JRĨ`f4&a! ,` sɺ(t34M!-0,l#))9 !q(i<hB 3˥(`  ,9%cm -b0AY_e! ,dRj:ړtG8$c02P hi,ǑQ0X[LEck5`ڭP2F! a @q dfz{lQzK! ,`BjR:$BPFq(ˢ J@0i-2͇ Qck6[G`m:pQ4fqow! ,d1j}MS@S\ H9#IrPØi8f..r$ł|nl -*T\![͂l,Q0@(KFMO{ ql{! ,^I 3a!P(Ol~`8 LÇ!@$Lgx|f ,`"`Jʼn"cUP -GAw< tz! ,h9C4kk\ &I&l )F-P@ch H!ш4< -  x@R`0C"8h0BfgX(rc}Y - 1!,aйҚX]mKl[)"aĢ\*"$t& #9@1pP`,`I,8 S 8U,Q`(d `3-qH$ 1T{; \ No newline at end of file diff --git a/core/modules/edit/js/app.js b/core/modules/edit/js/app.js deleted file mode 100644 index 00bba20..0000000 --- a/core/modules/edit/js/app.js +++ /dev/null @@ -1,528 +0,0 @@ -/** - * @file - * A Backbone View that is the central app controller. - */ -(function ($, _, Backbone, Drupal, VIE) { - -"use strict"; - - Drupal.edit = Drupal.edit || {}; - Drupal.edit.EditAppView = Backbone.View.extend({ - vie: null, - domService: null, - - // Configuration for state handling. - states: [], - activeEditorStates: [], - singleEditorStates: [], - - // State. - $entityElements: null, - - /** - * Implements Backbone Views' initialize() function. - */ - initialize: function() { - _.bindAll(this, 'appStateChange', 'acceptEditorStateChange', 'editorStateChange'); - - // VIE instance for Edit. - this.vie = new VIE(); - // Use our custom DOM parsing service until RDFa is available. - this.vie.use(new this.vie.EditService()); - this.domService = this.vie.service('edit'); - - // Instantiate configuration for state handling. - this.states = [ - null, 'inactive', 'candidate', 'highlighted', - 'activating', 'active', 'changed', 'saving', 'saved', 'invalid' - ]; - this.activeEditorStates = ['activating', 'active']; - this.singleEditorStates = _.union(['highlighted'], this.activeEditorStates); - - this.$entityElements = $([]); - - // Use Create's Storage widget. - this.$el.createStorage({ - vie: this.vie, - editableNs: 'createeditable' - }); - - // Instantiate OverlayView. - var overlayView = new Drupal.edit.views.OverlayView({ - el: (Drupal.theme('editOverlay', {})), - model: this.model - }); - - // Instantiate MenuView. - var editMenuView = new Drupal.edit.views.MenuView({ - el: this.el, - model: this.model - }); - - // When view/edit mode is toggled in the menu, update the editor widgets. - this.model.on('change:isViewing', this.appStateChange); - }, - - /** - * Finds editable properties within a given context. - * - * Finds editable properties, registers them with the app, updates their - * state to match the current app state. - * - * @param $context - * A jQuery-wrapped context DOM element within which will be searched. - */ - findEditableProperties: function($context) { - var that = this; - var newState = (this.model.get('isViewing')) ? 'inactive' : 'candidate'; - - this.domService.findSubjectElements($context).each(function() { - var $element = $(this); - - // Ignore editable properties for which we've already set up Create.js. - if (that.$entityElements.index($element) !== -1) { - return; - } - - $element - // Instantiate an EditableEntity widget. - .createEditable({ - vie: that.vie, - disabled: true, - state: 'inactive', - acceptStateChange: that.acceptEditorStateChange, - statechange: function(event, data) { - that.editorStateChange(data.previous, data.current, data.propertyEditor); - }, - decoratePropertyEditor: function(data) { - that.decorateEditor(data.propertyEditor); - } - }) - // This event is triggered just before Edit removes an EditableEntity - // widget, so that we can do proper clean-up. - .on('destroyedPropertyEditor.edit', function(event, editor) { - that.undecorateEditor(editor); - that.$entityElements = that.$entityElements.not($(this)); - - }) - // Transition the new PropertyEditor into the current state. - .createEditable('setState', newState); - - // Add this new EditableEntity widget element to the list. - that.$entityElements = that.$entityElements.add($element); - }); - }, - - /** - * Sets the state of PropertyEditor widgets when edit mode begins or ends. - * - * Should be called whenever EditAppModel's "isViewing" changes. - */ - appStateChange: function() { - // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133, https://github.com/bergie/create/issues/140) - // We're currently setting the state on EditableEntity widgets instead of - // PropertyEditor widgets, because of - // https://github.com/bergie/create/issues/133. - var newState = (this.model.get('isViewing')) ? 'inactive' : 'candidate'; - this.$entityElements.each(function() { - $(this).createEditable('setState', newState); - }); - // Manage the page's tab indexes. - if (newState === 'candidate') { - this._manageDocumentFocus(); - Drupal.edit.setMessage(Drupal.t('In place edit mode is active'), Drupal.t('Page navigation is limited to editable items.'), Drupal.t('Press escape to exit')); - } - else if (newState === 'inactive') { - this._releaseDocumentFocusManagement(); - Drupal.edit.setMessage(Drupal.t('Edit mode is inactive.'), Drupal.t('Resume normal page navigation')); - } - }, - - /** - * Accepts or reject editor (PropertyEditor) state changes. - * - * This is what ensures that the app is in control of what happens. - * - * @param from - * The previous state. - * @param to - * The new state. - * @param predicate - * The predicate of the property for which the state change is happening. - * @param context - * The context that is trying to trigger the state change. - * @param callback - * The callback function that should receive the state acceptance result. - */ - acceptEditorStateChange: function(from, to, predicate, context, callback) { - var accept = true; - - // If the app is in view mode, then reject all state changes except for - // those to 'inactive'. - if (this.model.get('isViewing')) { - if (to !== 'inactive') { - accept = false; - } - } - // Handling of edit mode state changes is more granular. - else { - // In general, enforce the states sequence. Disallow going back from a - // "later" state to an "earlier" state, except in explicitly allowed - // cases. - if (_.indexOf(this.states, from) > _.indexOf(this.states, to)) { - accept = false; - // Allow: activating/active -> candidate. - // Necessary to stop editing a property. - if (_.indexOf(this.activeEditorStates, from) !== -1 && to === 'candidate') { - accept = true; - } - // Allow: changed/invalid -> candidate. - // Necessary to stop editing a property when it is changed or invalid. - else if ((from === 'changed' || from === 'invalid') && to === 'candidate') { - accept = true; - } - // Allow: highlighted -> candidate. - // Necessary to stop highlighting a property. - else if (from === 'highlighted' && to === 'candidate') { - accept = true; - } - // Allow: saved -> candidate. - // Necessary when successfully saved a property. - else if (from === 'saved' && to === 'candidate') { - accept = true; - } - // Allow: invalid -> saving. - // Necessary to be able to save a corrected, invalid property. - else if (from === 'invalid' && to === 'saving') { - accept = true; - } - } - - // If it's not against the general principle, then here are more - // disallowed cases to check. - if (accept) { - // Ensure only one editor (field) at a time may be higlighted or active. - if (from === 'candidate' && _.indexOf(this.singleEditorStates, to) !== -1) { - if (this.model.get('highlightedEditor') || this.model.get('activeEditor')) { - accept = false; - } - } - // Reject going from activating/active to candidate because of a - // mouseleave. - else if (_.indexOf(this.activeEditorStates, from) !== -1 && to === 'candidate') { - if (context && context.reason === 'mouseleave') { - accept = false; - } - } - // When attempting to stop editing a changed/invalid property, ask for - // confirmation. - else if ((from === 'changed' || from === 'invalid') && to === 'candidate') { - if (context && context.reason === 'mouseleave') { - accept = false; - } - else { - // Check whether the transition has been confirmed? - if (context && context.confirmed) { - accept = true; - } - // Confirm this transition. - else { - // The callback will be called from the helper function. - this._confirmStopEditing(callback); - return; - } - } - } - } - } - - callback(accept); - }, - - /** - * Asks the user to confirm whether he wants to stop editing via a modal. - * - * @param acceptCallback - * The callback function as passed to acceptEditorStateChange(). This - * callback function will be called with the user's choice. - * - * @see acceptEditorStateChange() - */ - _confirmStopEditing: function(acceptCallback) { - // Only instantiate if there isn't a modal instance visible yet. - if (!this.model.get('activeModal')) { - var that = this; - var modal = new Drupal.edit.views.ModalView({ - model: this.model, - message: Drupal.t('You have unsaved changes'), - buttons: [ - { action: 'discard', classes: 'gray-button', label: Drupal.t('Discard changes') }, - { action: 'save', type: 'submit', classes: 'blue-button', label: Drupal.t('Save') } - ], - callback: function(action) { - // The active modal has been removed. - that.model.set('activeModal', null); - if (action === 'discard') { - acceptCallback(true); - } - else { - acceptCallback(false); - var editor = that.model.get('activeEditor'); - editor.options.widget.setState('saving', editor.options.property); - } - } - }); - this.model.set('activeModal', modal); - // The modal will set the activeModal property on the model when rendering - // to prevent multiple modals from being instantiated. - modal.render(); - } - else { - // Reject as there is still an open transition waiting for confirmation. - acceptCallback(false); - } - }, - - /** - * Reacts to editor (PropertyEditor) state changes; tracks global state. - * - * @param from - * The previous state. - * @param to - * The new state. - * @param editor - * The PropertyEditor widget object. - */ - editorStateChange: function(from, to, editor) { - // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133) - // Get rid of this once that issue is solved. - if (!editor) { - return; - } - else { - editor.stateChange(from, to); - } - - // Keep track of the highlighted editor in the global state. - if (_.indexOf(this.singleEditorStates, to) !== -1 && this.model.get('highlightedEditor') !== editor) { - this.model.set('highlightedEditor', editor); - } - else if (this.model.get('highlightedEditor') === editor && to === 'candidate') { - this.model.set('highlightedEditor', null); - } - - // Keep track of the active editor in the global state. - if (_.indexOf(this.activeEditorStates, to) !== -1 && this.model.get('activeEditor') !== editor) { - this.model.set('activeEditor', editor); - Drupal.edit.setMessage(Drupal.t('An editor is active')); - } - else if (this.model.get('activeEditor') === editor && to === 'candidate') { - // Discarded if it transitions from a changed state to 'candidate'. - if (from === 'changed' || from === 'invalid') { - // Retrieve the storage widget from DOM. - var createStorageWidget = this.$el.data('createStorage'); - // Revert changes in the model, this will trigger the direct editable - // content to be reset and redrawn. - createStorageWidget.revertChanges(editor.options.entity); - } - this.model.set('activeEditor', null); - } - - // Propagate the state change to the decoration and toolbar views. - // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133) - // Uncomment this once that issue is solved. - // editor.decorationView.stateChange(from, to); - // editor.toolbarView.stateChange(from, to); - }, - - /** - * Decorates an editor (PropertyEditor). - * - * Upon the page load, all appropriate editors are initialized and decorated - * (i.e. even before anything of the editing UI becomes visible; even before - * edit mode is enabled). - * - * @param editor - * The PropertyEditor widget object. - */ - decorateEditor: function(editor) { - // Toolbars are rendered "on-demand" (highlighting or activating). - // They are a sibling element before the editor's DOM element. - editor.toolbarView = new Drupal.edit.views.ToolbarView({ - editor: editor, - $storageWidgetEl: this.$el - }); - - // Decorate the editor's DOM element depending on its state. - editor.decorationView = new Drupal.edit.views.PropertyEditorDecorationView({ - el: editor.element, - editor: editor, - toolbarId: editor.toolbarView.getId() - }); - - // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133) - // Get rid of this once that issue is solved. - editor.options.widget.element.on('createeditablestatechange', function(event, data) { - editor.decorationView.stateChange(data.previous, data.current); - editor.toolbarView.stateChange(data.previous, data.current); - }); - }, - - /** - * Undecorates an editor (PropertyEditor). - * - * Whenever a property has been updated, the old HTML will be replaced by - * the new (re-rendered) HTML. The EditableEntity widget will be destroyed, - * as will be the PropertyEditor widget. This method ensures Edit's editor - * views also are removed properly. - * - * @param editor - * The PropertyEditor widget object. - */ - undecorateEditor: function(editor) { - editor.toolbarView.undelegateEvents(); - editor.toolbarView.remove(); - delete editor.toolbarView; - editor.decorationView.undelegateEvents(); - // Don't call .remove() on the decoration view, because that would remove - // a potentially rerendered field. - delete editor.decorationView; - }, - - /** - * Makes elements other than the editables unreachable via the tab key. - * - * @todo refactoring. - * - * This method is currently overloaded, handling elements of state modeling - * and application control. The state of the application is spread between - * this view, its model and aspects of the UI widgets in Create.js. In order - * to drive focus management from the application state (and have it - * influence that state of the application), we need to distall state out - * of Create.js components. - * - * This method introduces behaviors that support accessibility of the edit - * application. Although not yet integrated into the application properly, - * it does provide us with the opportunity to collect feedback from - * users who will interact with edit primarily through keyboard input. We - * want this feedback sooner than we can have a refactored application. - */ - _manageDocumentFocus: function () { - var editablesSelector = '.edit-candidate.edit-editable'; - var inputsSelector = 'a:visible, button:visible, input:visible, textarea:visible, select:visible'; - var $editables = $(editablesSelector) - .attr({ - 'tabindex': 0, - 'role': 'button' - }); - // Instantiate a variable to hold the editable element in the set. - var $currentEditable; - // We're using simple function scope to manage 'this' for the internal - // handler, so save this as that. - var that = this; - // Turn on focus management. - $(document).on('keydown.edit', function (event) { - var activeEditor, editableEntity, predicate; - // Handle esc key press. Close any active editors. - if (event.keyCode === 27) { - event.preventDefault(); - activeEditor = that.model.get('activeEditor'); - if (activeEditor) { - editableEntity = activeEditor.options.widget; - predicate = activeEditor.options.property; - editableEntity.setState('candidate', predicate, { reason: 'overlay' }); - } - else { - $(editablesSelector).trigger('tabOut.edit'); - // This should move into the state management for the app model. - location.hash = "#view"; - that.model.set('isViewing', true); - } - return; - } - // Handle enter or space key presses. - if (event.keyCode === 13 || event.keyCode === 32) { - if ($currentEditable && $currentEditable.is(editablesSelector)) { - $currentEditable.trigger('click'); - // Squelch additional handlers. - event.preventDefault(); - return; - } - } - // Handle tab key presses. - if (event.keyCode === 9) { - var context = ''; - // Include the view mode toggle with the editables selector. - var selector = editablesSelector + ', #toolbar-tab-edit'; - activeEditor = that.model.get('activeEditor'); - var $confirmDialog = $('#edit_modal'); - // If the edit modal is active, that is the tabbing context. - if ($confirmDialog.length) { - context = $confirmDialog; - selector = inputsSelector; - if (!$currentEditable || $currentEditable.is(editablesSelector)) { - $currentEditable = $(selector, context).eq(-1); - } - } - // If an editor is active, then the tabbing context is the editor and - // its toolbar. - else if (activeEditor) { - context = $(activeEditor.$formContainer).add(activeEditor.toolbarView.$el); - // Include the view mode toggle with the editables selector. - selector = inputsSelector; - if (!$currentEditable || $currentEditable.is(editablesSelector)) { - $currentEditable = $(selector, context).eq(-1); - } - } - // Otherwise the tabbing context is the list of editable predicates. - var $editables = $(selector, context); - if (!$currentEditable) { - $currentEditable = $editables.eq(-1); - } - var count = $editables.length - 1; - var index = $editables.index($currentEditable); - // Navigate backwards. - if (event.shiftKey) { - // Beginning of the set, loop to the end. - if (index === 0) { - index = count; - } - else { - index -= 1; - } - } - // Navigate forewards. - else { - // End of the set, loop to the start. - if (index === count) { - index = 0; - } - else { - index += 1; - } - } - // Tab out of the current editable. - $currentEditable.trigger('tabOut.edit'); - // Update the current editable. - $currentEditable = $editables - .eq(index) - .focus() - .trigger('tabIn.edit'); - // Squelch additional handlers. - event.preventDefault(); - event.stopPropagation(); - } - }); - // Set focus on the edit button initially. - $('#toolbar-tab-edit').focus(); - }, - /** - * Removes key management and edit accessibility features from the DOM. - */ - _releaseDocumentFocusManagement: function () { - $(document).off('keydown.edit'); - $('.edit-allowed.edit-field').removeAttr('tabindex role'); - } - }); - -})(jQuery, _, Backbone, Drupal, VIE); diff --git a/core/modules/edit/js/backbone.drupalform.js b/core/modules/edit/js/backbone.drupalform.js deleted file mode 100644 index ba79e76..0000000 --- a/core/modules/edit/js/backbone.drupalform.js +++ /dev/null @@ -1,164 +0,0 @@ -/** - * @file - * Backbone.sync implementation for Edit. This is the beating heart. - */ -(function (jQuery, Backbone, Drupal) { - -"use strict"; - -Backbone.defaultSync = Backbone.sync; -Backbone.sync = function(method, model, options) { - if (options.editor.options.editorName === 'form') { - return Backbone.syncDrupalFormWidget(method, model, options); - } - else { - return Backbone.syncDirect(method, model, options); - } -}; - -/** - * Performs syncing for "form" PredicateEditor widgets. - * - * Implemented on top of Form API and the AJAX commands framework. Sets up - * scoped AJAX command closures specifically for a given PredicateEditor widget - * (which contains a pre-existing form). By submitting the form through - * Drupal.ajax and leveraging Drupal.ajax' ability to have scoped (per-instance) - * command implementations, we are able to update the VIE model, re-render the - * form when there are validation errors and ensure no Drupal.ajax memory leaks. - * - * @see Drupal.edit.util.form - */ -Backbone.syncDrupalFormWidget = function(method, model, options) { - if (method === 'update') { - var predicate = options.editor.options.property; - - var $formContainer = options.editor.$formContainer; - var $submit = $formContainer.find('.edit-form-submit'); - var base = $submit.attr('id'); - - // Successfully saved. - Drupal.ajax[base].commands.editFieldFormSaved = function(ajax, response, status) { - Drupal.edit.util.form.unajaxifySaving(jQuery(ajax.element)); - - // Call Backbone.sync's success callback with the rerendered field. - var changedAttributes = {}; - // @todo: POSTPONED_ON(Drupal core, http://drupal.org/node/1784216) - // Once full JSON-LD support in Drupal core lands, we can ensure that the - // models that VIE maintains are properly updated. - changedAttributes[predicate] = undefined; - changedAttributes[predicate + '/rendered'] = response.data; - options.success(changedAttributes); - }; - - // Unsuccessfully saved; validation errors. - Drupal.ajax[base].commands.editFieldFormValidationErrors = function(ajax, response, status) { - // Call Backbone.sync's error callback with the validation error messages. - options.error(response.data); - }; - - // The edit_field_form AJAX command is only called upon loading the form for - // the first time, and when there are validation errors in the form; Form - // API then marks which form items have errors. Therefor, we have to replace - // the existing form, unbind the existing Drupal.ajax instance and create a - // new Drupal.ajax instance. - Drupal.ajax[base].commands.editFieldForm = function(ajax, response, status) { - Drupal.edit.util.form.unajaxifySaving(jQuery(ajax.element)); - - Drupal.ajax.prototype.commands.insert(ajax, { - data: response.data, - selector: '#' + $formContainer.attr('id') + ' form' - }); - - // Create a Drupa.ajax instance for the re-rendered ("new") form. - var $newSubmit = $formContainer.find('.edit-form-submit'); - Drupal.edit.util.form.ajaxifySaving({ nocssjs: false }, $newSubmit); - }; - - // Click the form's submit button; the scoped AJAX commands above will - // handle the server's response. - $submit.trigger('click.edit'); - } -}; - -/** -* Performs syncing for "direct" PredicateEditor widgets. - * - * @see Backbone.syncDrupalFormWidget() - * @see Drupal.edit.util.form - */ -Backbone.syncDirect = function(method, model, options) { - if (method === 'update') { - var fillAndSubmitForm = function(value) { - jQuery('#edit_backstage form') - // Fill in the value in any that isn't hidden or a submit button. - .find(':input[type!="hidden"][type!="submit"]:not(select)').val(value).end() - // Submit the form. - .find('.edit-form-submit').trigger('click.edit'); - }; - var entity = options.editor.options.entity; - var predicate = options.editor.options.property; - var value = model.get(predicate); - - // If form doesn't already exist, load it and then submit. - if (jQuery('#edit_backstage form').length === 0) { - var formOptions = { - propertyID: Drupal.edit.util.calcPropertyID(entity, predicate), - $editorElement: options.editor.element, - nocssjs: true - }; - Drupal.edit.util.form.load(formOptions, function(form, ajax) { - // Create a backstage area for storing forms that are hidden from view - // (hence "backstage" — since the editing doesn't happen in the form, it - // happens "directly" in the content, the form is only used for saving). - jQuery(Drupal.theme('editBackstage', { id: 'edit_backstage' })).appendTo('body'); - // Direct forms are stuffed into #edit_backstage, apparently. - jQuery('#edit_backstage').append(form); - // Disable the browser's HTML5 validation; we only care about server- - // side validation. (Not disabling this will actually cause problems - // because browsers don't like to set HTML5 validation errors on hidden - // forms.) - jQuery('#edit_backstage form').attr('novalidate', true); - var $submit = jQuery('#edit_backstage form .edit-form-submit'); - var base = Drupal.edit.util.form.ajaxifySaving(formOptions, $submit); - - // Successfully saved. - Drupal.ajax[base].commands.editFieldFormSaved = function (ajax, response, status) { - Drupal.edit.util.form.unajaxifySaving(jQuery(ajax.element)); - jQuery('#edit_backstage form').remove(); - - // Call Backbone.sync's success callback with the rerendered field. - var changedAttributes = {}; - // @todo: POSTPONED_ON(Drupal core, http://drupal.org/node/1784216) - // Once full JSON-LD support in Drupal core lands, we can ensure that the - // models that VIE maintains are properly updated. - changedAttributes[predicate] = jQuery(response.data).find('.field-item').html(); - changedAttributes[predicate + '/rendered'] = response.data; - options.success(changedAttributes); - }; - - // Unsuccessfully saved; validation errors. - Drupal.ajax[base].commands.editFieldFormValidationErrors = function(ajax, response, status) { - // Call Backbone.sync's error callback with the validation error messages. - options.error(response.data); - }; - - // The editFieldForm AJAX command is only called upon loading the form - // for the first time, and when there are validation errors in the form; - // Form API then marks which form items have errors. This is useful for - // "form" editors, but pointless for "direct" editors: the form itself - // won't be visible at all anyway! Therefor, we ignore the new form and - // we continue to use the existing form. - Drupal.ajax[base].commands.editFieldForm = function(ajax, response, status) { - // no-op - }; - - fillAndSubmitForm(value); - }); - } - else { - fillAndSubmitForm(value); - } - } -}; - -})(jQuery, Backbone, Drupal); diff --git a/core/modules/edit/js/createjs/editable.js b/core/modules/edit/js/createjs/editable.js deleted file mode 100644 index aac1ed2..0000000 --- a/core/modules/edit/js/createjs/editable.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @file - * Determines which editor to use based on a class attribute. - */ -(function (jQuery, drupalSettings) { - -"use strict"; - - jQuery.widget('Drupal.createEditable', jQuery.Midgard.midgardEditable, { - _create: function() { - this.vie = this.options.vie; - - this.options.domService = 'edit'; - this.options.predicateSelector = '*'; //'.edit-field.edit-allowed'; - - this.options.editors.direct = { - widget: 'drupalContentEditableWidget', - options: {} - }; - this.options.editors['direct-with-wysiwyg'] = { - widget: drupalSettings.edit.wysiwygEditorWidgetName, - options: {} - }; - this.options.editors.form = { - widget: 'drupalFormWidget', - options: {} - }; - - jQuery.Midgard.midgardEditable.prototype._create.call(this); - }, - - _propertyEditorName: function(data) { - if (jQuery(this.element).hasClass('edit-type-direct')) { - if (jQuery(this.element).hasClass('edit-type-direct-with-wysiwyg')) { - return 'direct-with-wysiwyg'; - } - return 'direct'; - } - return 'form'; - } - }); - -})(jQuery, drupalSettings); diff --git a/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js b/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js deleted file mode 100644 index c773e6e..0000000 --- a/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js +++ /dev/null @@ -1,110 +0,0 @@ -/** - * @file - * Override of Create.js' default "base" (plain contentEditable) widget. - */ -(function (jQuery, Drupal) { - -"use strict"; - - jQuery.widget('Drupal.drupalContentEditableWidget', jQuery.Create.editWidget, { - - /** - * Implements jQuery UI widget factory's _init() method. - * - * @todo: POSTPONED_ON(Create.js, https://github.com/bergie/create/issues/142) - * Get rid of this once that issue is solved. - */ - _init: function() {}, - - /** - * Implements Create's _initialize() method. - */ - _initialize: function() { - var that = this; - - // Sets the state to 'activated' upon clicking the element. - this.element.on("click.edit", function(event) { - event.stopPropagation(); - event.preventDefault(); - that.options.activated(); - }); - - // Sets the state to 'changed' whenever the content has changed. - var before = jQuery.trim(this.element.text()); - this.element.on('keyup paste', function (event) { - if (that.options.disabled) { - return; - } - var current = jQuery.trim(that.element.text()); - if (before !== current) { - before = current; - that.options.changed(current); - } - }); - }, - - /** - * Makes this PropertyEditor widget react to state changes. - */ - stateChange: function(from, to) { - switch (to) { - case 'inactive': - break; - case 'candidate': - if (from !== 'inactive') { - // Removes the "contenteditable" attribute. - this.disable(); - this._removeValidationErrors(); - this._cleanUp(); - } - break; - case 'highlighted': - break; - case 'activating': - break; - case 'active': - // Sets the "contenteditable" attribute to "true". - this.enable(); - break; - case 'changed': - break; - case 'saving': - this._removeValidationErrors(); - break; - case 'saved': - break; - case 'invalid': - break; - } - }, - - /** - * Removes validation errors' markup changes, if any. - * - * Note: this only needs to happen for type=direct, because for type=direct, - * the property DOM element itself is modified; this is not the case for - * type=form. - */ - _removeValidationErrors: function() { - this.element - .removeClass('edit-validation-error') - .next('.edit-validation-errors').remove(); - }, - - /** - * Cleans up after the widget has been saved. - * - * Note: this is where the Create.Storage and accompanying Backbone.sync - * abstractions "leak" implementation details. That is only the case because - * we have to use Drupal's Form API as a transport mechanism. It is - * unfortunately a stateful transport mechanism, and that's why we have to - * clean it up here. This clean-up is only necessary when canceling the - * editing of a property after having attempted to save at least once. - */ - _cleanUp: function() { - Drupal.edit.util.form.unajaxifySaving(jQuery('#edit_backstage form .edit-form-submit')); - jQuery('#edit_backstage form').remove(); - } - }); - -})(jQuery, Drupal); diff --git a/core/modules/edit/js/createjs/editingWidgets/formwidget.js b/core/modules/edit/js/createjs/editingWidgets/formwidget.js deleted file mode 100644 index f7c77cd..0000000 --- a/core/modules/edit/js/createjs/editingWidgets/formwidget.js +++ /dev/null @@ -1,150 +0,0 @@ -/** - * @file - * Form-based Create.js widget for structured content in Drupal. - */ -(function ($, Drupal) { - -"use strict"; - - $.widget('Drupal.drupalFormWidget', $.Create.editWidget, { - - id: null, - $formContainer: null, - - /** - * Implements jQuery UI widget factory's _init() method. - * - * @todo: POSTPONED_ON(Create.js, https://github.com/bergie/create/issues/142) - * Get rid of this once that issue is solved. - */ - _init: function() {}, - - /** - * Implements Create's _initialize() method. - */ - _initialize: function() { - // Sets the state to 'activating' upon clicking the element. - var that = this; - this.element.on("click.edit", function(event) { - event.stopPropagation(); - event.preventDefault(); - that.options.activating(); - }); - }, - - /** - * Makes this PropertyEditor widget react to state changes. - */ - stateChange: function(from, to) { - switch (to) { - case 'inactive': - break; - case 'candidate': - if (from !== 'inactive') { - this.disable(); - } - break; - case 'highlighted': - break; - case 'activating': - this.enable(); - break; - case 'active': - break; - case 'changed': - break; - case 'saving': - break; - case 'saved': - break; - case 'invalid': - break; - } - }, - - /** - * Enables the widget. - */ - enable: function () { - var $editorElement = $(this.options.widget.element); - var propertyID = Drupal.edit.util.calcPropertyID(this.options.entity, this.options.property); - - // Generate a DOM-compatible ID for the form container DOM element. - this.id = 'edit-form-for-' + propertyID.replace(/\//g, '_'); - - // Render form container. - this.$formContainer = $(Drupal.theme('editFormContainer', { - id: this.id, - loadingMsg: Drupal.t('Loading…')} - )); - this.$formContainer - .find('.edit-form') - .addClass('edit-editable edit-highlighted edit-editing') - .attr('role', 'dialog') - .css('background-color', $editorElement.css('background-color')); - - // Insert form container in DOM. - if ($editorElement.css('display') === 'inline') { - // @todo: POSTPONED_ON(Drupal core, title/author/date as Entity Properties) - // This is untested in Drupal 8, because in Drupal 8 we don't yet - // have the ability to edit the node title/author/date, because they - // haven't been converted into Entity Properties yet, and they're the - // only examples in core of "display: inline" properties. - this.$formContainer.prependTo($editorElement.offsetParent()); - - var pos = $editorElement.position(); - this.$formContainer.css('left', pos.left).css('top', pos.top); - } - else { - this.$formContainer.insertBefore($editorElement); - } - - // Load form, insert it into the form container and attach event handlers. - var widget = this; - var formOptions = { - propertyID: propertyID, - $editorElement: $editorElement, - nocssjs: false - }; - Drupal.edit.util.form.load(formOptions, function(form, ajax) { - Drupal.ajax.prototype.commands.insert(ajax, { - data: form, - selector: '#' + widget.id + ' .placeholder' - }); - - var $submit = widget.$formContainer.find('.edit-form-submit'); - Drupal.edit.util.form.ajaxifySaving(formOptions, $submit); - widget.$formContainer - .on('formUpdated.edit', ':input', function () { - // Sets the state to 'changed'. - widget.options.changed(); - }) - .on('keypress.edit', 'input', function (event) { - if (event.keyCode === 13) { - return false; - } - }); - - // Sets the state to 'activated'. - widget.options.activated(); - }); - }, - - /** - * Disables the widget. - */ - disable: function () { - if (this.$formContainer === null) { - return; - } - - Drupal.edit.util.form.unajaxifySaving(this.$formContainer.find('.edit-form-submit')); - this.$formContainer - .off('change.edit', ':input') - .off('keypress.edit', 'input') - .remove(); - this.$formContainer = null; - } - }); - -})(jQuery, Drupal); diff --git a/core/modules/edit/js/createjs/storage.js b/core/modules/edit/js/createjs/storage.js deleted file mode 100644 index 580ff82..0000000 --- a/core/modules/edit/js/createjs/storage.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @file - * Subclasses jQuery.Midgard.midgardStorage to have consistent namespaces. - */ -(function(jQuery) { - -"use strict"; - - jQuery.widget('Drupal.createStorage', jQuery.Midgard.midgardStorage, {}); - -})(jQuery); diff --git a/core/modules/edit/js/edit.js b/core/modules/edit/js/edit.js deleted file mode 100644 index 41a7f49..0000000 --- a/core/modules/edit/js/edit.js +++ /dev/null @@ -1,147 +0,0 @@ -/** - * @file - * Behaviors for Edit, including the one that initializes Edit's EditAppView. - */ -(function ($, _, Backbone, Drupal, drupalSettings) { - -"use strict"; - -/** - * The edit ARIA live message area. - * - * @todo Eventually the messages area should be converted into a Backbone View - * that will respond to changes in the application's model. For the initial - * implementation, we will call the Drupal.edit.setMessage method when an aural - * message should be read by the user agent. - */ -var $messages; - -Drupal.edit = Drupal.edit || {}; -Drupal.edit.metadataCache = Drupal.edit.metadataCache || {}; - -/** - * Attach toggling behavior and in-place editing. - */ -Drupal.behaviors.edit = { - attach: function(context) { - var $context = $(context); - var $fields = $context.find('[data-edit-id]'); - - // Initialize the Edit app. - $context.find('#toolbar-tab-edit').once('edit-init', Drupal.edit.init); - - var annotateField = function(field) { - if (_.has(Drupal.edit.metadataCache, field.editID)) { - var meta = Drupal.edit.metadataCache[field.editID]; - - field.$el.addClass((meta.access) ? 'edit-allowed' : 'edit-disallowed'); - if (meta.access) { - field.$el - .attr('data-edit-field-label', meta.label) - .attr('aria-label', meta.aria) - .addClass('edit-field edit-type-' + meta.editor); - 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 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 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 (!annotateField(field)) { - result.push(field); - } - return result; - }, []); - - // Make fields that could be annotated immediately available for editing. - Drupal.edit.app.findEditableProperties($context); - - if (remainingFieldsToAnnotate.length) { - $(window).ready(function() { - $.ajax({ - url: drupalSettings.edit.metadataURL, - type: 'POST', - data: { 'fields[]' : _.pluck(remainingFieldsToAnnotate, 'editID') }, - dataType: 'json', - success: function(results) { - // 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, annotateField); - - // As soon as there is at least one editable field, show the Edit - // tab in the toolbar. - if ($fields.filter('.edit-allowed').length) { - $('.toolbar .icon-edit.edit-nothing-editable-hidden') - .removeClass('edit-nothing-editable-hidden'); - } - - // Find editable fields, make them editable. - Drupal.edit.app.findEditableProperties($context); - } - }); - }); - } - } -}; - -Drupal.edit.init = function() { - // Append a messages element for appending interaction updates for screen - // readers. - $messages = $(Drupal.theme('editMessageBox')).appendTo($(this).parent()); - // Instantiate EditAppView, which is the controller of it all. EditAppModel - // instance tracks global state (viewing/editing in-place). - var appModel = new Drupal.edit.models.EditAppModel(); - var app = new Drupal.edit.EditAppView({ - el: $('body'), - model: appModel - }); - - // Instantiate EditRouter. - var editRouter = new Drupal.edit.routers.EditRouter({ - appModel: appModel - }); - - // Start Backbone's history/route handling. - Backbone.history.start(); - - // For now, we work with a singleton app, because for Drupal.behaviors to be - // able to discover new editable properties that get AJAXed in, it must know - // with which app instance they should be associated. - Drupal.edit.app = app; -}; - -/** - * Places the message in the edit ARIA live message area. - * - * The message will be read by speaking User Agents. - * - * @param {String} message - * A string to be inserted into the message area. - */ -Drupal.edit.setMessage = function(message) { - var args = Array.prototype.slice.call(arguments); - args.unshift('editMessage'); - $messages.html(Drupal.theme.apply(this, args)); -}; - -})(jQuery, _, Backbone, Drupal, drupalSettings); diff --git a/core/modules/edit/js/models/edit-app-model.js b/core/modules/edit/js/models/edit-app-model.js deleted file mode 100644 index b6ff36f..0000000 --- a/core/modules/edit/js/models/edit-app-model.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @file - * A Backbone Model that models the current Edit application state. - */ -(function(Backbone, Drupal) { - -"use strict"; - -Drupal.edit = Drupal.edit || {}; -Drupal.edit.models = Drupal.edit.models || {}; -Drupal.edit.models.EditAppModel = Backbone.Model.extend({ - defaults: { - // We always begin in view mode. - isViewing: true, - highlightedEditor: null, - activeEditor: null, - // Reference to a ModalView-instance if a transition requires confirmation. - activeModal: null - } -}); - -})(Backbone, Drupal); diff --git a/core/modules/edit/js/routers/edit-router.js b/core/modules/edit/js/routers/edit-router.js deleted file mode 100644 index d160ad4..0000000 --- a/core/modules/edit/js/routers/edit-router.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * @file - * A Backbone Router enabling URLs to make the user enter edit mode directly. - */ -(function(Backbone, Drupal) { - -"use strict"; - -Drupal.edit = Drupal.edit || {}; -Drupal.edit.routers = {}; -Drupal.edit.routers.EditRouter = Backbone.Router.extend({ - - appModel: null, - - routes: { - "edit": "edit", - "view": "view", - "": "view" - }, - - initialize: function(options) { - this.appModel = options.appModel; - - var that = this; - this.appModel.on('change:isViewing', function() { - that.navigate(that.appModel.get('isViewing') ? '#view' : '#edit'); - }); - }, - - edit: function() { - this.appModel.set('isViewing', false); - }, - - view: function(query, page) { - var that = this; - - // If there's an active editor, attempt to set its state to 'candidate', and - // then act according to the user's choice. - var activeEditor = this.appModel.get('activeEditor'); - if (activeEditor) { - var editableEntity = activeEditor.options.widget; - var predicate = activeEditor.options.property; - editableEntity.setState('candidate', predicate, { reason: 'menu' }, function(accepted) { - if (accepted) { - that.appModel.set('isViewing', true); - } - else { - that.appModel.set('isViewing', false); - } - }); - } - // Otherwise, we can switch to view mode directly. - else { - that.appModel.set('isViewing', true); - } - } -}); - -})(Backbone, Drupal); diff --git a/core/modules/edit/js/theme.js b/core/modules/edit/js/theme.js deleted file mode 100644 index 80dcbef..0000000 --- a/core/modules/edit/js/theme.js +++ /dev/null @@ -1,175 +0,0 @@ -/** - * @file - * Provides overridable theme functions for all of Edit's client-side HTML. - */ -(function($, Drupal) { - -"use strict"; - -/** - * Theme function for the overlay of the Edit module. - * - * @param settings - * An object with the following keys: - * - None. - * @return - * The corresponding HTML. - */ -Drupal.theme.editOverlay = function(settings) { - var html = ''; - html += '
'; - return html; -}; - -/** - * Theme function for a "backstage" for the Edit module. - * - * @param settings - * An object with the following keys: - * - id: the id to apply to the backstage. - * @return - * The corresponding HTML. - */ -Drupal.theme.editBackstage = function(settings) { - var html = ''; - html += '
'; - return html; -}; - -/** - * Theme function for a modal of the Edit module. - * - * @param settings - * An object with the following keys: - * - None. - * @return - * The corresponding HTML. - */ -Drupal.theme.editModal = function(settings) { - var classes = 'edit-animate-slow edit-animate-invisible edit-animate-delay-veryfast'; - var html = ''; - html += ''; - return html; -}; - -/** - * Theme function for a toolbar container of the Edit module. - * - * @param settings - * An object with the following keys: - * - id: the id to apply to the toolbar container. - * @return - * The corresponding HTML. - */ -Drupal.theme.editToolbarContainer = function(settings) { - var html = ''; - html += '
'; - html += '
'; - html += '
'; - html += '
'; - html += '
'; - return html; -}; - -/** - * Theme function for a toolbar toolgroup of the Edit module. - * - * @param settings - * An object with the following keys: - * - classes: the class of the toolgroup. - * - buttons: @see Drupal.theme.prototype.editButtons(). - * @return - * The corresponding HTML. - */ -Drupal.theme.editToolgroup = function(settings) { - var classes = 'edit-toolgroup edit-animate-slow edit-animate-invisible edit-animate-delay-veryfast'; - var html = ''; - html += '
'; - html += Drupal.theme('editButtons', { buttons: settings.buttons }); - html += '
'; - return html; -}; - -/** - * Theme function for buttons of the Edit module. - * - * Can be used for the buttons both in the toolbar toolgroups and in the modal. - * - * @param settings - * An object with the following keys: - * - buttons: an array of objects with the following keys: - * - type: the type of the button (defaults to 'button') - * - classes: the classes of the button. - * - label: the label of the button. - * - action: sets a data-edit-modal-action attribute. - * @return - * The corresponding HTML. - */ -Drupal.theme.editButtons = function(settings) { - var html = ''; - for (var i = 0; i < settings.buttons.length; i++) { - var button = settings.buttons[i]; - if (!button.hasOwnProperty('type')) { - button.type = 'button'; - } - - html += '
'; - return html; -}; - -/** - * A region to post messages that a screen reading UA will announce. - * - * @return {String} - * A string representing a DOM fragment. - */ -Drupal.theme.editMessageBox = function() { - return '
'; -}; - -/** - * Wrap message strings in p tags. - * - * @return {String} - * A string representing a DOM fragment. - */ -Drupal.theme.editMessage = function() { - var messages = Array.prototype.slice.call(arguments); - var output = ''; - for (var i = 0; i < messages.length; i++) { - output += '

' + messages[i] + '

'; - } - return output; -}; - -})(jQuery, Drupal); diff --git a/core/modules/edit/js/util.js b/core/modules/edit/js/util.js deleted file mode 100644 index 8ed9a2b..0000000 --- a/core/modules/edit/js/util.js +++ /dev/null @@ -1,142 +0,0 @@ -/** - * @file - * Provides utility functions for Edit. - */ -(function($, Drupal, drupalSettings) { - -"use strict"; - -Drupal.edit = Drupal.edit || {}; -Drupal.edit.util = Drupal.edit.util || {}; - -Drupal.edit.util.constants = {}; -Drupal.edit.util.constants.transitionEnd = "transitionEnd.edit webkitTransitionEnd.edit transitionend.edit msTransitionEnd.edit oTransitionEnd.edit"; - -Drupal.edit.util.calcPropertyID = function(entity, predicate) { - return entity.getSubjectUri() + '/' + predicate; -}; - -Drupal.edit.util.buildUrl = function(id, urlFormat) { - var parts = id.split('/'); - return Drupal.formatString(decodeURIComponent(urlFormat), { - '!entity_type': parts[0], - '!id' : parts[1], - '!field_name' : parts[2], - '!langcode' : parts[3], - '!view_mode' : parts[4] - }); -}; - -/** - * Loads rerendered processed text for a given property. - * - * Leverages Drupal.ajax' ability to have scoped (per-instance) command - * implementations to be able to call a callback. - * - * @param options - * An object with the following keys: - * - $editorElement (required): the PredicateEditor DOM element. - * - propertyID (required): the property ID that uniquely identifies the - * property for which this form will be loaded. - * - callback (required: A callback function that will receive the rerendered - * processed text. - */ -Drupal.edit.util.loadRerenderedProcessedText = function(options) { - // Create a Drupal.ajax instance to load the form. - Drupal.ajax[options.propertyID] = new Drupal.ajax(options.propertyID, options.$editorElement, { - url: Drupal.edit.util.buildUrl(options.propertyID, drupalSettings.edit.rerenderProcessedTextURL), - event: 'edit-internal.edit', - submit: { nocssjs : true }, - progress: { type : null } // No progress indicator. - }); - // Implement a scoped editFieldRenderedWithoutTransformationFilters AJAX - // command: calls the callback. - Drupal.ajax[options.propertyID].commands.editFieldRenderedWithoutTransformationFilters = function(ajax, response, status) { - options.callback(response.data); - // Delete the Drupal.ajax instance that called this very function. - delete Drupal.ajax[options.propertyID]; - options.$editorElement.off('edit-internal.edit'); - }; - // This will ensure our scoped editFieldRenderedWithoutTransformationFilters - // AJAX command gets called. - options.$editorElement.trigger('edit-internal.edit'); -}; - -Drupal.edit.util.form = { - /** - * Loads a form, calls a callback to inserts. - * - * Leverages Drupal.ajax' ability to have scoped (per-instance) command - * implementations to be able to call a callback. - * - * @param options - * An object with the following keys: - * - $editorElement (required): the PredicateEditor DOM element. - * - propertyID (required): the property ID that uniquely identifies the - * property for which this form will be loaded. - * - nocssjs (required): boolean indicating whether no CSS and JS should be - * returned (necessary when the form is invisible to the user). - * @param callback - * A callback function that will receive the form to be inserted, as well as - * the ajax object, necessary if the callback wants to perform other AJAX - * commands. - */ - load: function(options, callback) { - // Create a Drupal.ajax instance to load the form. - Drupal.ajax[options.propertyID] = new Drupal.ajax(options.propertyID, options.$editorElement, { - url: Drupal.edit.util.buildUrl(options.propertyID, drupalSettings.edit.fieldFormURL), - event: 'edit-internal.edit', - submit: { nocssjs : options.nocssjs }, - progress: { type : null } // No progress indicator. - }); - // Implement a scoped editFieldForm AJAX command: calls the callback. - Drupal.ajax[options.propertyID].commands.editFieldForm = function(ajax, response, status) { - callback(response.data, ajax); - // Delete the Drupal.ajax instance that called this very function. - delete Drupal.ajax[options.propertyID]; - options.$editorElement.off('edit-internal.edit'); - }; - // This will ensure our scoped editFieldForm AJAX command gets called. - options.$editorElement.trigger('edit-internal.edit'); - }, - - /** - * Creates a Drupal.ajax instance that is used to save a form. - * - * @param options - * An object with the following keys: - * - nocssjs (required): boolean indicating whether no CSS and JS should be - * returned (necessary when the form is invisible to the user). - * - * @return - * The key of the Drupal.ajax instance. - */ - ajaxifySaving: function(options, $submit) { - // Re-wire the form to handle submit. - var element_settings = { - url: $submit.closest('form').attr('action'), - setClick: true, - event: 'click.edit', - progress: { type:'throbber' }, - submit: { nocssjs : options.nocssjs } - }; - var base = $submit.attr('id'); - - Drupal.ajax[base] = new Drupal.ajax(base, $submit[0], element_settings); - - return base; - }, - - /** - * Cleans up the Drupal.ajax instance that is used to save the form. - * - * @param $submit - * The jQuery-wrapped submit DOM element that should be unajaxified. - */ - unajaxifySaving: function($submit) { - delete Drupal.ajax[$submit.attr('id')]; - $submit.off('click.edit'); - } -}; - -})(jQuery, Drupal, drupalSettings); diff --git a/core/modules/edit/js/viejs/EditService.js b/core/modules/edit/js/viejs/EditService.js deleted file mode 100644 index f52a6c0..0000000 --- a/core/modules/edit/js/viejs/EditService.js +++ /dev/null @@ -1,297 +0,0 @@ -/** - * @file - * VIE DOM parsing service for Edit. - */ -(function(jQuery, _, VIE, Drupal, drupalSettings) { - -"use strict"; - - VIE.prototype.EditService = function (options) { - var defaults = { - name: 'edit', - subjectSelector: '.edit-field.edit-allowed' - }; - this.options = _.extend({}, defaults, options); - - this.views = []; - this.vie = null; - this.name = this.options.name; - }; - - VIE.prototype.EditService.prototype = { - load: function (loadable) { - var correct = loadable instanceof this.vie.Loadable; - if (!correct) { - throw new Error('Invalid Loadable passed'); - } - - var element; - if (!loadable.options.element) { - if (typeof document === 'undefined') { - return loadable.resolve([]); - } else { - element = drupalSettings.edit.context; - } - } else { - element = loadable.options.element; - } - - var entities = this.readEntities(element); - loadable.resolve(entities); - }, - - _getViewForElement:function (element, collectionView) { - var viewInstance; - - jQuery.each(this.views, function () { - if (jQuery(this.el).get(0) === element.get(0)) { - if (collectionView && !this.template) { - return true; - } - viewInstance = this; - return false; - } - }); - return viewInstance; - }, - - _registerEntityView:function (entity, element, isNew) { - if (!element.length) { - return; - } - - // Let's only have this overhead for direct types. Form-based editors are - // handled in backbone.drupalform.js and the PropertyEditor instance. - if (!jQuery(element).hasClass('edit-type-direct')) { - return; - } - - var service = this; - var viewInstance = this._getViewForElement(element); - if (viewInstance) { - return viewInstance; - } - - viewInstance = new this.vie.view.Entity({ - model:entity, - el:element, - tagName:element.get(0).nodeName, - vie:this.vie, - service:this.name - }); - - this.views.push(viewInstance); - - return viewInstance; - }, - - save: function(saveable) { - var correct = saveable instanceof this.vie.Savable; - if (!correct) { - throw "Invalid Savable passed"; - } - - if (!saveable.options.element) { - // FIXME: we could find element based on subject - throw "Unable to write entity to edit.module-markup, no element given"; - } - - if (!saveable.options.entity) { - throw "Unable to write to edit.module-markup, no entity given"; - } - - var $element = jQuery(saveable.options.element); - this._writeEntity(saveable.options.entity, saveable.options.element); - saveable.resolve(); - }, - - _writeEntity:function (entity, element) { - var service = this; - this.findPredicateElements(this.getElementSubject(element), element, true).each(function () { - var predicateElement = jQuery(this); - var predicate = service.getElementPredicate(predicateElement); - if (!entity.has(predicate)) { - return true; - } - - var value = entity.get(predicate); - if (value && value.isCollection) { - // Handled by CollectionViews separately - return true; - } - if (value === service.readElementValue(predicate, predicateElement)) { - return true; - } - // Unlike in the VIE's RdfaService no (re-)mapping needed here. - predicateElement.html(value); - }); - return true; - }, - - // The edit-id data attribute contains the full identifier of - // each entity element in the format - // `::::`. - _getID: function (element) { - var id = jQuery(element).attr('data-edit-id'); - if (!id) { - id = jQuery(element).closest('[data-edit-id]').attr('data-edit-id'); - } - return id; - }, - - // Returns the "URI" of an entity of an element in format - // `/`. - getElementSubject: function (element) { - return this._getID(element).split(':').slice(0, 2).join('/'); - }, - - // Returns the field name for an element in format - // `//`. - // (Slashes instead of colons because the field name is no namespace.) - getElementPredicate: function (element) { - if (!this._getID(element)) { - throw new Error('Could not find predicate for element'); - } - return this._getID(element).split(':').slice(2, 5).join('/'); - }, - - getElementType: function (element) { - return this._getID(element).split(':').slice(0, 1)[0]; - }, - - // Reads all editable entities (currently each Drupal field is considered an - // entity, in the future Drupal entities should be mapped to VIE entities) - // from DOM and returns the VIE enties it found. - readEntities: function (element) { - var service = this; - var entities = []; - var entityElements = jQuery(this.options.subjectSelector, element); - entityElements = entityElements.add(jQuery(element).filter(this.options.subjectSelector)); - entityElements.each(function () { - var entity = service._readEntity(jQuery(this)); - if (entity) { - entities.push(entity); - } - }); - return entities; - }, - - // Returns a filled VIE Entity instance for a DOM element. The Entity - // is also registered in the VIE entities collection. - _readEntity: function (element) { - var subject = this.getElementSubject(element); - var type = this.getElementType(element); - var entity = this._readEntityPredicates(subject, element, false); - if (jQuery.isEmptyObject(entity)) { - return null; - } - entity['@subject'] = subject; - if (type) { - entity['@type'] = this._registerType(type, element); - } - - var entityInstance = new this.vie.Entity(entity); - entityInstance = this.vie.entities.addOrUpdate(entityInstance, { - updateOptions: { - silent: true, - ignoreChanges: true - } - }); - - this._registerEntityView(entityInstance, element); - return entityInstance; - }, - - _registerType: function (typeId, element) { - typeId = ''; - var type = this.vie.types.get(typeId); - if (!type) { - this.vie.types.add(typeId, []); - type = this.vie.types.get(typeId); - } - - var predicate = this.getElementPredicate(element); - if (type.attributes.get(predicate)) { - return type; - } - - var label = element.data('edit-field-label'); - var range = 'Form'; - if (element.hasClass('edit-type-direct')) { - range = 'Direct'; - } - if (element.hasClass('edit-type-direct-with-wysiwyg')) { - range = 'Wysiwyg'; - } - type.attributes.add(predicate, [range], 0, 1, { - label: element.data('edit-field-label') - }); - - return type; - }, - - _readEntityPredicates: function (subject, element, emptyValues) { - var entityPredicates = {}; - var service = this; - this.findPredicateElements(subject, element, true).each(function () { - var predicateElement = jQuery(this); - var predicate = service.getElementPredicate(predicateElement); - if (!predicate) { - return; - } - var value = service.readElementValue(predicate, predicateElement); - if (value === null && !emptyValues) { - return; - } - - entityPredicates[predicate] = value; - entityPredicates[predicate + '/rendered'] = predicateElement[0].outerHTML; - }); - return entityPredicates; - }, - - readElementValue : function(predicate, element) { - // Unlike in RdfaService there is parsing needed here. - if (element.hasClass('edit-type-form')) { - return undefined; - } - else { - return jQuery.trim(element.html()); - } - }, - - // Subject elements are the DOM elements containing a single or multiple - // editable fields. - findSubjectElements: function (element) { - if (!element) { - element = drupalSettings.edit.context; - } - return jQuery(this.options.subjectSelector, element); - }, - - // Predicate Elements are the actual DOM elements that users will be able - // to edit. - findPredicateElements: function (subject, element, allowNestedPredicates, stop) { - var predicates = jQuery(); - // Make sure that element is wrapped by jQuery. - var $element = jQuery(element); - - // Form-type predicates - predicates = predicates.add($element.filter('.edit-type-form')); - - // Direct-type predicates - var direct = $element.filter('.edit-type-direct'); - predicates = predicates.add(direct.find('.field-item')); - - if (!predicates.length && !stop) { - var parentElement = $element.parent(this.options.subjectSelector); - if (parentElement.length) { - return this.findPredicateElements(subject, parentElement, allowNestedPredicates, true); - } - } - - return predicates; - } - }; - -})(jQuery, _, VIE, Drupal, drupalSettings); diff --git a/core/modules/edit/js/views/menu-view.js b/core/modules/edit/js/views/menu-view.js deleted file mode 100644 index ac7c4e4..0000000 --- a/core/modules/edit/js/views/menu-view.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * @file - * A Backbone View that provides the app-level interactive menu. - */ -(function($, _, Backbone, Drupal) { - -"use strict"; - -Drupal.edit = Drupal.edit || {}; -Drupal.edit.views = Drupal.edit.views || {}; -Drupal.edit.views.MenuView = Backbone.View.extend({ - - events: { - 'click #toolbar-tab-edit': 'editClickHandler' - }, - - /** - * Implements Backbone Views' initialize() function. - */ - initialize: function() { - _.bindAll(this, 'stateChange'); - this.model.on('change:isViewing', this.stateChange); - // @todo - // Re-implement hook_toolbar and the corresponding JavaScript behaviors - // once https://drupal.org/node/1847198 is resolved. The toolbar tray is - // necessary when the page request is processed because its render element - // has an #attached property with the Edit module library code assigned to - // it. Currently a toolbar tab is not passed as a renderable array, so - // #attached properties are not processed. The toolbar tray DOM element is - // unnecessary right now, so it is removed. - this.$el.find('#toolbar-tray-edit').remove(); - // Respond to clicks on other toolbar tabs. This temporary pending - // improvements to the toolbar module. - $('#toolbar-administration').on('click.edit', '.bar a:not(#toolbar-tab-edit)', _.bind(function (event) { - this.model.set('isViewing', true); - }, this)); - // We have to call stateChange() here because URL fragments are not passed - // to the server, thus the wrong anchor may be marked as active. - this.stateChange(); - }, - - /** - * Listens to app state changes. - */ - stateChange: function() { - var isViewing = this.model.get('isViewing'); - // Toggle the state of the Toolbar Edit tab based on the isViewing state. - this.$el.find('#toolbar-tab-edit') - .toggleClass('active', !isViewing) - .attr('aria-pressed', !isViewing); - // Manage the toolbar state until - // https://drupal.org/node/1847198 is resolved - if (!isViewing) { - // Remove the 'toolbar-tray-open' class from the body element. - this.$el.removeClass('toolbar-tray-open'); - // Deactivate any other active tabs and trays. - this.$el - .find('.bar a', '#toolbar-administration') - .not('#toolbar-tab-edit') - .add('.tray', '#toolbar-administration') - .removeClass('active'); - // Set the height of the toolbar. - if ('toolbar' in Drupal) { - Drupal.toolbar.setHeight(); - } - } - }, - /** - * Handles clicks on the edit tab of the toolbar. - * - * @param {Object} event - */ - editClickHandler: function (event) { - var isViewing = this.model.get('isViewing'); - // Toggle the href of the Toolbar Edit tab based on the isViewing state. The - // href value should represent to state to be entered. - this.$el.find('#toolbar-tab-edit').attr('href', (isViewing) ? '#edit' : '#view'); - this.model.set('isViewing', !isViewing); - } -}); - -})(jQuery, _, Backbone, Drupal); diff --git a/core/modules/edit/js/views/modal-view.js b/core/modules/edit/js/views/modal-view.js deleted file mode 100644 index 2e3b49c..0000000 --- a/core/modules/edit/js/views/modal-view.js +++ /dev/null @@ -1,107 +0,0 @@ -/** - * @file - * A Backbone View that provides an interactive modal. - */ -(function($, Backbone, Drupal) { - -"use strict"; - -Drupal.edit = Drupal.edit || {}; -Drupal.edit.views = Drupal.edit.views || {}; -Drupal.edit.views.ModalView = Backbone.View.extend({ - - message: null, - buttons: null, - callback: null, - $elementsToHide: null, - - events: { - 'click button': 'onButtonClick' - }, - - /** - * Implements Backbone Views' initialize() function. - * - * @param options - * An object with the following keys: - * - message: a message to show in the modal. - * - buttons: a set of buttons with 'action's defined, ready to be passed to - * Drupal.theme.editButtons(). - * - callback: a callback that will receive the 'action' of the clicked - * button. - * - * @see Drupal.theme.editModal() - * @see Drupal.theme.editButtons() - */ - initialize: function(options) { - this.message = options.message; - this.buttons = options.buttons; - this.callback = options.callback; - }, - - /** - * Implements Backbone Views' render() function. - */ - render: function() { - // Step 1: move certain UI elements below the overlay. - var editor = this.model.get('activeEditor'); - this.$elementsToHide = $([]) - .add((editor.element.hasClass('edit-belowoverlay')) ? null : editor.element) - .add(editor.toolbarView.$el) - .add((editor.options.editorName === 'form') ? editor.$formContainer : editor.element.next('.edit-validation-errors')); - this.$elementsToHide.addClass('edit-belowoverlay'); - - // Step 2: the modal. When the user makes a choice, the UI elements that - // were moved below the overlay will be restored, and the callback will be - // called. - this.setElement(Drupal.theme('editModal', {})); - this.$el.appendTo('body'); - // Template. - this.$('.main p').text(this.message); - var $actions = $(Drupal.theme('editButtons', { 'buttons' : this.buttons})); - this.$('.actions').append($actions); - - // Step 3; show the modal with an animation. - var that = this; - setTimeout(function() { - that.$el.removeClass('edit-animate-invisible'); - }, 0); - - Drupal.edit.setMessage(Drupal.t('Confirmation dialog open')); - }, - - /** - * When the user clicks on any of the buttons, the modal should be removed - * and the result should be passed to the callback. - * - * @param event - */ - onButtonClick: function(event) { - event.stopPropagation(); - event.preventDefault(); - - // Remove after animation. - var that = this; - this.$el - .addClass('edit-animate-invisible') - .on(Drupal.edit.util.constants.transitionEnd, function(e) { - that.remove(); - }); - - var action = $(event.target).attr('data-edit-modal-action'); - return this.callback(action); - }, - - /** - * Overrides Backbone Views' remove() function. - */ - remove: function() { - // Move the moved UI elements on top of the overlay again. - this.$elementsToHide.removeClass('edit-belowoverlay'); - - // Remove the modal itself. - this.$el.remove(); - } -}); - -})(jQuery, Backbone, Drupal); diff --git a/core/modules/edit/js/views/overlay-view.js b/core/modules/edit/js/views/overlay-view.js deleted file mode 100644 index 2113ab8..0000000 --- a/core/modules/edit/js/views/overlay-view.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * @file - * A Backbone View that provides the app-level overlay. - * - * The overlay sits on top of the existing content, the properties that are - * candidates for editing sit on top of the overlay. - */ -(function ($, _, Backbone, Drupal) { - -"use strict"; - -Drupal.edit = Drupal.edit || {}; -Drupal.edit.views = Drupal.edit.views || {}; -Drupal.edit.views.OverlayView = Backbone.View.extend({ - - events: { - 'click': 'onClick' - }, - - /** - * Implements Backbone Views' initialize() function. - */ - initialize: function (options) { - _.bindAll(this, 'stateChange'); - this.model.on('change:isViewing', this.stateChange); - // Add the overlay to the page. - this.$el - .addClass('edit-animate-slow edit-animate-invisible') - .hide() - .appendTo('body'); - }, - - /** - * Listens to app state changes. - */ - stateChange: function () { - if (this.model.get('isViewing')) { - this.remove(); - return; - } - this.render(); - }, - - /** - * Equates clicks anywhere on the overlay to clicking the active editor's (if - * any) "close" button. - * - * @param {Object} event - */ - onClick: function (event) { - event.preventDefault(); - var activeEditor = this.model.get('activeEditor'); - if (activeEditor) { - var editableEntity = activeEditor.options.widget; - var predicate = activeEditor.options.property; - editableEntity.setState('candidate', predicate, { reason: 'overlay' }); - } - else { - this.model.set('isViewing', true); - } - }, - - /** - * Reveal the overlay element. - */ - render: function () { - this.$el - .show() - .css('top', $('#navbar').outerHeight()) - .removeClass('edit-animate-invisible'); - }, - - /** - * Hide the overlay element. - */ - remove: function () { - var that = this; - this.$el - .addClass('edit-animate-invisible') - .on(Drupal.edit.util.constants.transitionEnd, function (event) { - that.$el.hide(); - }); - } -}); - -})(jQuery, _, Backbone, Drupal); diff --git a/core/modules/edit/js/views/propertyeditordecoration-view.js b/core/modules/edit/js/views/propertyeditordecoration-view.js deleted file mode 100644 index 269259a..0000000 --- a/core/modules/edit/js/views/propertyeditordecoration-view.js +++ /dev/null @@ -1,324 +0,0 @@ -/** - * @file - * A Backbone View that decorates a Property Editor widget. - * - * It listens to state changes of the property editor. - */ -(function($, Backbone, Drupal) { - -"use strict"; - -Drupal.edit = Drupal.edit || {}; -Drupal.edit.views = Drupal.edit.views || {}; -Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({ - - editor: null, - entity: null, - predicate : null, - editorName: null, - toolbarId: null, - - _widthAttributeIsEmpty: null, - - events: { - 'mouseenter.edit' : 'onMouseEnter', - 'mouseleave.edit' : 'onMouseLeave', - 'tabIn.edit': 'onMouseEnter', - 'tabOut.edit': 'onMouseLeave' - }, - - /** - * Implements Backbone Views' initialize() function. - * - * @param options - * An object with the following keys: - * - editor: the editor object with an 'options' object that has these keys: - * * entity: the VIE entity for the property. - * * property: the predicate of the property. - * * editorName: the editor name: 'form', 'direct' or - * 'direct-with-wysiwyg'. - * * widget: the parent EditableeEntity widget. - * - toolbarId: the ID attribute of the toolbar as rendered in the DOM. - */ - initialize: function(options) { - this.editor = options.editor; - this.toolbarId = options.toolbarId; - - this.entity = this.editor.options.entity; - this.predicate = this.editor.options.property; - this.editorName = this.editor.options.editorName; - - this.$el.css('background-color', this._getBgColor(this.$el)); - }, - - /** - * Listens to editor state changes. - */ - stateChange: function(from, to) { - switch (to) { - case 'inactive': - if (from !== null) { - this.undecorate(); - } - break; - case 'candidate': - this.decorate(); - if (from !== 'inactive') { - this.stopHighlight(); - if (from !== 'highlighted') { - this.stopEdit(this.editorName); - } - } - break; - case 'highlighted': - this.startHighlight(); - break; - case 'activating': - // NOTE: this step only exists for the 'form' editor! It is skipped by - // the 'direct' and 'direct-with-wysiwyg' editors, because no loading is - // necessary. - this.prepareEdit(this.editorName); - break; - case 'active': - if (this.editorName !== 'form') { - this.prepareEdit(this.editorName); - } - this.startEdit(this.editorName); - break; - case 'changed': - break; - case 'saving': - break; - case 'saved': - break; - case 'invalid': - break; - } - }, - - /** - * Starts hover: transition to 'highlight' state. - * - * @param event - */ - onMouseEnter: function(event) { - var that = this; - this._ignoreHoveringVia(event, '#' + this.toolbarId, function () { - var editableEntity = that.editor.options.widget; - editableEntity.setState('highlighted', that.predicate); - event.stopPropagation(); - }); - }, - - /** - * Stops hover: back to 'candidate' state. - * - * @param event - */ - onMouseLeave: function(event) { - var that = this; - this._ignoreHoveringVia(event, '#' + this.toolbarId, function () { - var editableEntity = that.editor.options.widget; - editableEntity.setState('candidate', that.predicate, { reason: 'mouseleave' }); - event.stopPropagation(); - }); - }, - - decorate: function () { - this.$el.addClass('edit-animate-fast edit-candidate edit-editable'); - }, - - undecorate: function () { - this.$el - .removeClass('edit-candidate edit-editable edit-highlighted edit-editing edit-belowoverlay'); - }, - - startHighlight: function () { - // Animations. - var that = this; - setTimeout(function() { - that.$el.addClass('edit-highlighted'); - }, 0); - }, - - stopHighlight: function() { - this.$el - .removeClass('edit-highlighted'); - }, - - prepareEdit: function(editorName) { - this.$el.addClass('edit-editing'); - - // While editing, don't show *any* other editors. - // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133) - // Revisit this. - $('.edit-candidate').not('.edit-editing').removeClass('edit-editable'); - - if (editorName === 'form') { - this.$el.addClass('edit-belowoverlay'); - } - }, - - startEdit: function(editorName) { - if (editorName !== 'form') { - this._pad(); - } - }, - - stopEdit: function(editorName) { - this.$el.removeClass('edit-highlighted edit-editing'); - - // Make the other editors show up again. - // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133) - // Revisit this. - $('.edit-candidate').addClass('edit-editable'); - - if (editorName === 'form') { - this.$el.removeClass('edit-belowoverlay'); - } - else { - this._unpad(); - } - }, - - _pad: function () { - var self = this; - - // Add 5px padding for readability. This means we'll freeze the current - // width and *then* add 5px padding, hence ensuring the padding is added "on - // the outside". - // 1) Freeze the width (if it's not already set); don't use animations. - if (this.$el[0].style.width === "") { - this._widthAttributeIsEmpty = true; - this.$el - .addClass('edit-animate-disable-width') - .css('width', this.$el.width()); - } - - // 2) Add padding; use animations. - var posProp = this._getPositionProperties(this.$el); - setTimeout(function() { - // Re-enable width animations (padding changes affect width too!). - self.$el.removeClass('edit-animate-disable-width'); - - // Pad the editable. - self.$el - .css({ - 'position': 'relative', - 'top': posProp.top - 5 + 'px', - 'left': posProp.left - 5 + 'px', - 'padding-top' : posProp['padding-top'] + 5 + 'px', - 'padding-left' : posProp['padding-left'] + 5 + 'px', - 'padding-right' : posProp['padding-right'] + 5 + 'px', - 'padding-bottom': posProp['padding-bottom'] + 5 + 'px', - 'margin-bottom': posProp['margin-bottom'] - 10 + 'px' - }); - }, 0); - }, - - _unpad: function () { - var self = this; - - // 1) Set the empty width again. - if (this._widthAttributeIsEmpty) { - this.$el - .addClass('edit-animate-disable-width') - .css('width', ''); - } - - // 2) Remove padding; use animations (these will run simultaneously with) - // the fading out of the toolbar as its gets removed). - var posProp = this._getPositionProperties(this.$el); - setTimeout(function() { - // Re-enable width animations (padding changes affect width too!). - self.$el.removeClass('edit-animate-disable-width'); - - // Unpad the editable. - self.$el - .css({ - 'position': 'relative', - 'top': posProp.top + 5 + 'px', - 'left': posProp.left + 5 + 'px', - 'padding-top' : posProp['padding-top'] - 5 + 'px', - 'padding-left' : posProp['padding-left'] - 5 + 'px', - 'padding-right' : posProp['padding-right'] - 5 + 'px', - 'padding-bottom': posProp['padding-bottom'] - 5 + 'px', - 'margin-bottom': posProp['margin-bottom'] + 10 + 'px' - }); - }, 0); - }, - - /** - * Gets the background color of an element (or the inherited one). - * - * @param $e - * A DOM element. - */ - _getBgColor: function($e) { - var c; - - if ($e === null || $e[0].nodeName === 'HTML') { - // Fallback to white. - return 'rgb(255, 255, 255)'; - } - c = $e.css('background-color'); - // TRICKY: edge case for Firefox' "transparent" here; this is a - // browser bug: https://bugzilla.mozilla.org/show_bug.cgi?id=635724 - if (c === 'rgba(0, 0, 0, 0)' || c === 'transparent') { - return this._getBgColor($e.parent()); - } - return c; - }, - - /** - * Gets the top and left properties of an element and convert extraneous - * values and information into numbers ready for subtraction. - * - * @param $e - * A DOM element. - */ - _getPositionProperties: function($e) { - var p, - r = {}, - props = [ - 'top', 'left', 'bottom', 'right', - 'padding-top', 'padding-left', 'padding-right', 'padding-bottom', - 'margin-bottom' - ]; - - var propCount = props.length; - for (var i = 0; i < propCount; i++) { - p = props[i]; - r[p] = parseInt(this._replaceBlankPosition($e.css(p)), 10); - } - return r; - }, - - /** - * Replaces blank or 'auto' CSS "position: " values with "0px". - * - * @param pos - * The value for a CSS position declaration. - */ - _replaceBlankPosition: function(pos) { - if (pos === 'auto' || !pos) { - pos = '0px'; - } - return pos; - }, - - /** - * Ignores hovering to/from the given closest element, but as soon as a hover - * occurs to/from *another* element, then call the given callback. - */ - _ignoreHoveringVia: function(event, closest, callback) { - if ($(event.relatedTarget).closest(closest).length > 0) { - event.stopPropagation(); - } - else { - callback(); - } - } -}); - -})(jQuery, Backbone, Drupal); diff --git a/core/modules/edit/js/views/toolbar-view.js b/core/modules/edit/js/views/toolbar-view.js deleted file mode 100644 index 899b9e3..0000000 --- a/core/modules/edit/js/views/toolbar-view.js +++ /dev/null @@ -1,465 +0,0 @@ -/** - * @file - * A Backbone View that provides an interactive toolbar (1 per property editor). - * - * It listens to state changes of the property editor. It also triggers state - * changes in response to user interactions with the toolbar, including saving. - */ -(function ($, _, Backbone, Drupal) { - -"use strict"; - -Drupal.edit = Drupal.edit || {}; -Drupal.edit.views = Drupal.edit.views || {}; -Drupal.edit.views.ToolbarView = Backbone.View.extend({ - - editor: null, - $storageWidgetEl: null, - - entity: null, - predicate : null, - editorName: null, - - _loader: null, - _loaderVisibleStart: 0, - - _id: null, - - events: { - 'click.edit button.label': 'onClickInfoLabel', - 'mouseleave.edit': 'onMouseLeave', - 'click.edit button.field-save': 'onClickSave', - 'click.edit button.field-close': 'onClickClose' - }, - - /** - * Implements Backbone Views' initialize() function. - * - * @param options - * An object with the following keys: - * - editor: the editor object with an 'options' object that has these keys: - * * entity: the VIE entity for the property. - * * property: the predicate of the property. - * * editorName: the editor name: 'form', 'direct' or - * 'direct-with-wysiwyg'. - * * element: the jQuery-wrapped editor DOM element - * - $storageWidgetEl: the DOM element on which the Create Storage widget is - * initialized. - */ - initialize: function(options) { - this.editor = options.editor; - this.$storageWidgetEl = options.$storageWidgetEl; - - this.entity = this.editor.options.entity; - this.predicate = this.editor.options.property; - this.editorName = this.editor.options.editorName; - - this._loader = null; - this._loaderVisibleStart = 0; - - // Generate a DOM-compatible ID for the toolbar DOM element. - var propertyID = Drupal.edit.util.calcPropertyID(this.entity, this.predicate); - this._id = 'edit-toolbar-for-' + propertyID.replace(/\//g, '_'); - }, - - /** - * Listens to editor state changes. - */ - stateChange: function(from, to) { - switch (to) { - case 'inactive': - // Nothing happens in this stage. - break; - case 'candidate': - if (from !== 'inactive') { - if (from !== 'highlighted' && this.editorName !== 'form') { - this._unpad(this.editorName); - } - this.remove(); - } - break; - case 'highlighted': - // As soon as we highlight, make sure we have a toolbar in the DOM (with at least a title). - this.render(); - this.startHighlight(); - break; - case 'activating': - this.setLoadingIndicator(true); - break; - case 'active': - this.startEdit(this.editorName); - this.setLoadingIndicator(false); - if (this.editorName !== 'form') { - this._pad(this.editorName); - } - if (this.editorName === 'direct-with-wysiwyg') { - this.insertWYSIWYGToolGroups(); - } - break; - case 'changed': - this.$el - .find('button.save') - .addClass('blue-button') - .removeClass('gray-button'); - break; - case 'saving': - this.setLoadingIndicator(true); - this.save(); - break; - case 'saved': - this.setLoadingIndicator(false); - break; - case 'invalid': - this.setLoadingIndicator(false); - break; - } - }, - - /** - * Saves a property. - * - * This method deals with the complexity of the editor-dependent ways of - * inserting updated content and showing validation error messages. - * - * One might argue that this does not belong in a view. However, there is no - * actual "save" logic here, that lives in Backbone.sync. This is just some - * glue code, along with the logic for inserting updated content as well as - * showing validation error messages, the latter of which is certainly okay. - */ - save: function() { - var that = this; - var editor = this.editor; - var editableEntity = editor.options.widget; - var entity = editor.options.entity; - var predicate = editor.options.property; - - // Use Create.js' Storage widget to handle saving. (Uses Backbone.sync.) - this.$storageWidgetEl.createStorage('saveRemote', entity, { - editor: editor, - - // Successfully saved without validation errors. - success: function (model) { - editableEntity.setState('saved', predicate); - - // Now that the changes to this property have been saved, the saved - // attributes are now the "original" attributes. - entity._originalAttributes = entity._previousAttributes = _.clone(entity.attributes); - - // Get data necessary to rerender property before it is unavailable. - var updatedProperty = entity.get(predicate + '/rendered'); - var $propertyWrapper = editor.element.closest('.edit-field'); - var $context = $propertyWrapper.parent(); - - editableEntity.setState('candidate', predicate); - // Unset the property, because it will be parsed again from the DOM, iff - // its new value causes it to still be rendered. - entity.unset(predicate, { silent: true }); - entity.unset(predicate + '/rendered', { silent: true }); - // Trigger event to allow for proper clean-up of editor-specific views. - editor.element.trigger('destroyedPropertyEditor.edit', editor); - - // Replace the old content with the new content. - $propertyWrapper.replaceWith(updatedProperty); - Drupal.attachBehaviors($context); - }, - - // Save attempted but failed due to validation errors. - error: function (validationErrorMessages) { - editableEntity.setState('invalid', predicate); - - if (that.editorName === 'form') { - editor.$formContainer - .find('.edit-form') - .addClass('edit-validation-error') - .find('form') - .prepend(validationErrorMessages); - } - else { - var $errors = $('
') - .append(validationErrorMessages); - editor.element - .addClass('edit-validation-error') - .after($errors); - } - } - }); - }, - - /** - * When the user clicks the info label, nothing should happen. - * @note currently redirects the click.edit-event to the editor DOM element. - * - * @param event - */ - onClickInfoLabel: function(event) { - event.stopPropagation(); - event.preventDefault(); - // Redirects the event to the editor DOM element. - this.editor.element.trigger('click.edit'); - }, - - /** - * A mouseleave to the editor doesn't matter; a mouseleave to something else - * counts as a mouseleave on the editor itself. - * - * @param event - */ - onMouseLeave: function(event) { - var el = this.editor.element[0]; - if (event.relatedTarget != el && !$.contains(el, event.relatedTarget)) { - this.editor.element.trigger('mouseleave.edit'); - } - event.stopPropagation(); - }, - - /** - * Upon clicking "Save", trigger a custom event to save this property. - * - * @param event - */ - onClickSave: function(event) { - event.stopPropagation(); - event.preventDefault(); - this.editor.options.widget.setState('saving', this.predicate); - }, - - /** - * Upon clicking "Close", trigger a custom event to stop editing. - * - * @param event - */ - onClickClose: function(event) { - event.stopPropagation(); - event.preventDefault(); - this.editor.options.widget.setState('candidate', this.predicate, { reason: 'cancel' }); - }, - - /** - * Indicates in the 'info' toolgroup that we're waiting for a server reponse. - * - * Prevents flickering loading indicator by only showing it after 0.6 seconds - * and if it is shown, only hiding it after another 0.6 seconds. - * - * @param bool enabled - * Whether the loading indicator should be displayed or not. - */ - setLoadingIndicator: function(enabled) { - var that = this; - if (enabled) { - this._loader = setTimeout(function() { - that.addClass('info', 'loading'); - that._loaderVisibleStart = new Date().getTime(); - }, 600); - } - else { - var currentTime = new Date().getTime(); - clearTimeout(this._loader); - if (this._loaderVisibleStart) { - setTimeout(function() { - that.removeClass('info', 'loading'); - }, this._loaderVisibleStart + 600 - currentTime); - } - this._loader = null; - this._loaderVisibleStart = 0; - } - }, - - startHighlight: function() { - // We get the label to show for this property from VIE's type system. - var label = this.predicate; - var attributeDef = this.entity.get('@type').attributes.get(this.predicate); - if (attributeDef && attributeDef.metadata) { - label = attributeDef.metadata.label; - } - - this.$el - .find('.edit-toolbar') - // Append the "info" toolgroup into the toolbar. - .append(Drupal.theme('editToolgroup', { - classes: 'info edit-animate-only-background-and-padding', - buttons: [ - { label: label, classes: 'blank-button label' } - ] - })); - - // Animations. - var that = this; - setTimeout(function () { - that.show('info'); - }, 0); - }, - - startEdit: function() { - this.$el - .addClass('edit-editing') - .find('.edit-toolbar') - // Append the "ops" toolgroup into the toolbar. - .append(Drupal.theme('editToolgroup', { - classes: 'ops', - buttons: [ - { label: Drupal.t('Save'), type: 'submit', classes: 'field-save save gray-button' }, - { label: '' + Drupal.t('Close') + '', classes: 'field-close close gray-button' } - ] - })); - this.show('ops'); - }, - - /** - * Adjusts the toolbar to accomodate padding on the PropertyEditor widget. - * - * @see PropertyEditorDecorationView._pad(). - */ - _pad: function(editorName) { - // The whole toolbar must move to the top when the property's DOM element - // is displayed inline. - if (this.editor.element.css('display') === 'inline') { - this.$el.css('top', parseInt(this.$el.css('top'), 10) - 5 + 'px'); - } - - // The toolbar must move to the top and the left. - var $hf = this.$el.find('.edit-toolbar-heightfaker'); - $hf.css({ bottom: '6px', left: '-5px' }); - // When using a WYSIWYG editor, the width of the toolbar must match the - // width of the editable. - if (editorName === 'direct-with-wysiwyg') { - $hf.css({ width: this.editor.element.width() + 10 }); - } - }, - - /** - * Undoes the changes made by _pad(). - * - * @see PropertyEditorDecorationView._unpad(). - */ - _unpad: function(editorName) { - // Move the toolbar back to its original position. - var $hf = this.$el.find('.edit-toolbar-heightfaker'); - $hf.css({ bottom: '1px', left: '' }); - // When using a WYSIWYG editor, restore the width of the toolbar. - if (editorName === 'direct-with-wysiwyg') { - $hf.css({ width: '' }); - } - }, - - insertWYSIWYGToolGroups: function() { - this.$el - .find('.edit-toolbar') - .append(Drupal.theme('editToolgroup', { - classes: 'wysiwyg-tabs', - buttons: [] - })) - .append(Drupal.theme('editToolgroup', { - classes: 'wysiwyg', - buttons: [] - })); - - // Animate the toolgroups into visibility. - var that = this; - setTimeout(function () { - that.show('wysiwyg-tabs'); - that.show('wysiwyg'); - }, 0); - }, - - /** - * Renders the Toolbar's markup into the DOM. - * - * Note: depending on whether the 'display' property of the $el for which a - * toolbar is being inserted into the DOM, it will be inserted differently. - */ - render: function () { - // Render toolbar. - this.setElement($(Drupal.theme('editToolbarContainer', { - id: this.getId() - }))); - - // Insert in DOM. - if (this.$el.css('display') === 'inline') { - this.$el.prependTo(this.editor.element.offsetParent()); - var pos = this.editor.element.position(); - this.$el.css('left', pos.left).css('top', pos.top); - } - else { - this.$el.insertBefore(this.editor.element); - } - - var that = this; - // Animate the toolbar into visibility. - setTimeout(function () { - that.$el.removeClass('edit-animate-invisible'); - }, 0); - }, - - remove: function () { - if (!this.$el) { - return; - } - - // Remove after animation. - var that = this; - var $el = this.$el; - this.$el - .addClass('edit-animate-invisible') - // Prevent this toolbar from being detected *while* it is being removed. - .removeAttr('id') - .find('.edit-toolbar .edit-toolgroup') - .addClass('edit-animate-invisible') - .on(Drupal.edit.util.constants.transitionEnd, function (e) { - $el.remove(); - }); - }, - - /** - * Calculates the ID for this toolbar container. - * - * Only used to make sane hovering behavior possible. - * - * @return string - * A string that can be used as the ID for this toolbar container. - */ - getId: function() { - return this._id; - }, - - /** - * Shows a toolgroup. - * - * @param string toolgroup - * A toolgroup name. - */ - show: function (toolgroup) { - this._find(toolgroup).removeClass('edit-animate-invisible'); - }, - - /** - * Adds classes to a toolgroup. - * - * @param string toolgroup - * A toolgroup name. - */ - addClass: function (toolgroup, classes) { - this._find(toolgroup).addClass(classes); - }, - - /** - * Removes classes from a toolgroup. - * - * @param string toolgroup - * A toolgroup name. - */ - removeClass: function (toolgroup, classes) { - this._find(toolgroup).removeClass(classes); - }, - - /** - * Finds a toolgroup. - * - * @param string toolgroup - * A toolgroup name. - */ - _find: function (toolgroup) { - return this.$el.find('.edit-toolbar .edit-toolgroup.' + toolgroup); - } -}); - -})(jQuery, _, Backbone, Drupal); diff --git a/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php b/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php deleted file mode 100644 index 82726c3..0000000 --- a/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php +++ /dev/null @@ -1,78 +0,0 @@ -getRequirements()); - } - - /** - * Implements AccessCheckInterface::access(). - */ - public function access(Route $route, Request $request) { - // @todo Request argument validation and object loading should happen - // elsewhere in the request processing pipeline: - // http://drupal.org/node/1798214. - $this->validateAndUpcastRequestAttributes($request); - - return $this->accessEditEntityField($request->attributes->get('entity'), $request->attributes->get('field_name')); - } - - /** - * Implements EntityFieldAccessCheckInterface::accessEditEntityField(). - */ - public function accessEditEntityField(EntityInterface $entity, $field_name) { - $entity_type = $entity->entityType(); - // @todo Generalize to all entity types: http://drupal.org/node/1839516. - return ($entity_type == 'node' && node_access('update', $entity) && field_access('edit', $field_name, $entity_type, $entity)); - } - - /** - * Validates and upcasts request attributes. - */ - protected function validateAndUpcastRequestAttributes(Request $request) { - // Load the entity. - if (!is_object($entity = $request->attributes->get('entity'))) { - $entity_id = $entity; - $entity_type = $request->attributes->get('entity_type'); - if (!$entity_type || !entity_get_info($entity_type)) { - throw new NotFoundHttpException(); - } - $entity = entity_load($entity_type, $entity_id); - if (!$entity) { - throw new NotFoundHttpException(); - } - $request->attributes->set('entity', $entity); - } - - // Validate the field name and language. - $field_name = $request->attributes->get('field_name'); - if (!$field_name || !field_info_instance($entity->entityType(), $field_name, $entity->bundle())) { - throw new NotFoundHttpException(); - } - $langcode = $request->attributes->get('langcode'); - if (!$langcode || (field_valid_language($langcode) !== $langcode)) { - throw new NotFoundHttpException(); - } - } - -} diff --git a/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheckInterface.php b/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheckInterface.php deleted file mode 100644 index fe6d918..0000000 --- a/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheckInterface.php +++ /dev/null @@ -1,22 +0,0 @@ -command = $command; - $this->data = $data; - } - - /** - * Implements Drupal\Core\Ajax\CommandInterface:render(). - */ - public function render() { - return array( - 'command' => $this->command, - 'data' => $this->data, - ); - } - -} diff --git a/core/modules/edit/lib/Drupal/edit/Ajax/FieldFormCommand.php b/core/modules/edit/lib/Drupal/edit/Ajax/FieldFormCommand.php deleted file mode 100644 index 76b01c5..0000000 --- a/core/modules/edit/lib/Drupal/edit/Ajax/FieldFormCommand.php +++ /dev/null @@ -1,27 +0,0 @@ -register('plugin.manager.edit.processed_text_editor', 'Drupal\edit\Plugin\ProcessedTextEditorManager'); - - $container->register('access_check.edit.entity_field', 'Drupal\edit\Access\EditEntityFieldAccessCheck') - ->addTag('access_check'); - - $container->register('edit.editor.selector', 'Drupal\edit\EditorSelector') - ->addArgument(new Reference('plugin.manager.edit.processed_text_editor')); - - $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/core/modules/edit/lib/Drupal/edit/EditController.php b/core/modules/edit/lib/Drupal/edit/EditController.php deleted file mode 100644 index 5d6f8ed..0000000 --- a/core/modules/edit/lib/Drupal/edit/EditController.php +++ /dev/null @@ -1,147 +0,0 @@ -request->get('fields'); - if (!isset($fields)) { - throw new NotFoundHttpException(); - } - $metadataGenerator = $this->container->get('edit.metadata.generator'); - - $metadata = array(); - foreach ($fields as $field) { - list($entity_type, $entity_id, $field_name, $langcode, $view_mode) = explode(':', $field); - - // Load the entity. - if (!$entity_type || !entity_get_info($entity_type)) { - throw new NotFoundHttpException(); - } - $entity = entity_load($entity_type, $entity_id); - if (!$entity) { - throw new NotFoundHttpException(); - } - - // Validate the field name and language. - 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(); - } - - $metadata[$field] = $metadataGenerator->generate($entity, $instance, $langcode, $view_mode); - } - - return new JsonResponse($metadata); - } - - /** - * Returns a single field edit form as an Ajax response. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity being edited. - * @param string $field_name - * The name of the field that is being edited. - * @param string $langcode - * The name of the language for which the field is being edited. - * @param string $view_mode - * The view mode the field should be rerendered in. - * @return \Drupal\Core\Ajax\AjaxResponse - * The Ajax response. - */ - public function fieldForm(EntityInterface $entity, $field_name, $langcode, $view_mode) { - $response = new AjaxResponse(); - - $form_state = array( - 'langcode' => $langcode, - 'no_redirect' => TRUE, - 'build_info' => array('args' => array($entity, $field_name)), - ); - $form = drupal_build_form('edit_field_form', $form_state); - - if (!empty($form_state['executed'])) { - // The form submission took care of saving the updated entity. Return the - // updated view of the field. - $entity = $form_state['entity']; - $output = field_view_field($entity->entityType(), $entity, $field_name, $view_mode, $langcode); - - $response->addCommand(new FieldFormSavedCommand(drupal_render($output))); - } - else { - $response->addCommand(new FieldFormCommand(drupal_render($form))); - - $errors = form_get_errors(); - if (count($errors)) { - $response->addCommand(new FieldFormValidationErrorsCommand(theme('status_messages'))); - } - } - - // When working with a hidden form, we don't want any CSS or JS to be loaded. - if (isset($_POST['nocssjs']) && $_POST['nocssjs'] === 'true') { - drupal_static_reset('drupal_add_css'); - drupal_static_reset('drupal_add_js'); - } - - return $response; - } - - /** - * Returns an Ajax response to render a text field without transformation filters. - * - * @param int $entity - * The entity of which a processed text field is being rerendered. - * @param string $field_name - * The name of the (processed text) field that that is being rerendered - * @param string $langcode - * The name of the language for which the processed text field is being - * rererendered. - * @param string $view_mode - * The view mode the processed text field should be rerendered in. - * @return \Drupal\Core\Ajax\AjaxResponse - * The Ajax response. - */ - public function getUntransformedText(EntityInterface $entity, $field_name, $langcode, $view_mode) { - $response = new AjaxResponse(); - - $output = field_view_field($entity->entityType(), $entity, $field_name, $view_mode, $langcode); - $langcode = $output['#language']; - // Direct text editing is only supported for single-valued fields. - $editable_text = check_markup($output['#items'][0]['value'], $output['#items'][0]['format'], $langcode, FALSE, array(FILTER_TYPE_TRANSFORM_REVERSIBLE, FILTER_TYPE_TRANSFORM_IRREVERSIBLE)); - $response->addCommand(new FieldRenderedWithoutTransformationFiltersCommand($editable_text)); - - return $response; - } - -} diff --git a/core/modules/edit/lib/Drupal/edit/EditorSelector.php b/core/modules/edit/lib/Drupal/edit/EditorSelector.php deleted file mode 100644 index 5c44954..0000000 --- a/core/modules/edit/lib/Drupal/edit/EditorSelector.php +++ /dev/null @@ -1,167 +0,0 @@ -processedTextEditorManager = $processed_text_editor_manager; - } - - /** - * Implements \Drupal\edit\EditorSelectorInterface::getEditor(). - */ - public function getEditor($formatter_type, FieldInstance $instance, array $items) { - // Check if the formatter defines an appropriate in-place editor. For - // example, text formatters displaying untrimmed text can choose to use the - // 'direct' editor. If the formatter doesn't specify, fall back to the - // 'form' editor, since that can work for any field. Formatter definitions - // can use 'disabled' to explicitly opt out of in-place editing. - $formatter_info = field_info_formatter_types($formatter_type); - $editor = isset($formatter_info['edit']['editor']) ? $formatter_info['edit']['editor'] : 'form'; - if ($editor == 'disabled') { - return; - } - - // The same text formatters can be used for single-valued and multivalued - // fields and for processed and unprocessed text, so we can't rely on the - // formatter definition for the final determination, because: - // - The direct editor does not work for multivalued fields. - // - Processed text can benefit from a WYSIWYG editor. - // - Empty processed text without an already selected format requires a form - // to select one. - // @todo The processed text logic is too coupled to text fields. Figure out - // how to generalize to other textual field types. - // @todo All of this might hint at formatter *definitions* not being the - // ideal place for editor specification. Moving the determination to - // something that works with instantiated formatters, not just their - // definitions, could alleviate that, but might come with its own - // challenges. - if ($editor == 'direct') { - $field = field_info_field($instance['field_name']); - if ($field['cardinality'] != 1) { - // The direct editor does not work for multivalued fields. - $editor = 'form'; - } - elseif (!empty($instance['settings']['text_processing'])) { - $format_id = $items[0]['format']; - if (isset($format_id)) { - $wysiwyg_plugin = $this->getProcessedTextEditorPlugin(); - if (isset($wysiwyg_plugin) && $wysiwyg_plugin->checkFormatCompatibility($format_id)) { - // Yay! Even though the text is processed, there's a WYSIWYG editor - // that can work with it. - $editor = 'direct-with-wysiwyg'; - } - else { - // @todo We might not have to downgrade all the way to 'form'. The - // 'direct' editor might be appropriate for some kinds of - // processed text. - $editor = 'form'; - } - } - else { - // If a format is not yet selected, a form is needed to select one. - $editor = 'form'; - } - } - } - - return $editor; - } - - /** - * Implements \Drupal\edit\EditorSelectorInterface::getAllEditorAttachments(). - */ - public function getAllEditorAttachments() { - $this->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 - * The editor plugin. - * - * @todo We currently only support one plugin (the first one returned by the - * manager) for the 'direct-with-wysiwyg' editor on any given page. Enhance - * this to allow different ones per element (e.g., Aloha for one text field - * and CKEditor for another one). - * - * @todo The terminology here is confusing. 'direct-with-wysiwyg' is one of - * several possible "editor"s for processed text. When using it, we need to - * integrate a particular WYSIWYG editor, which in Create.js is called a - * "PropertyEditor widget", but we're not yet including "widget" in the name - * of ProcessedTextEditorInterface to minimize confusion with Field API - * widgets. So, we're currently refering to these as "plugins", which is - * correct in that it's using Drupal's Plugin API, but less informative than - * naming it "widget" or similar. - */ - protected function getProcessedTextEditorPlugin() { - if (!isset($this->processedTextEditorPlugin)) { - $definitions = $this->processedTextEditorManager->getDefinitions(); - if (count($definitions)) { - $plugin_ids = array_keys($definitions); - $plugin_id = $plugin_ids[0]; - $this->processedTextEditorPlugin = $this->processedTextEditorManager->createInstance($plugin_id); - } - } - return $this->processedTextEditorPlugin; - } -} diff --git a/core/modules/edit/lib/Drupal/edit/EditorSelectorInterface.php b/core/modules/edit/lib/Drupal/edit/EditorSelectorInterface.php deleted file mode 100644 index ed7e2c8..0000000 --- a/core/modules/edit/lib/Drupal/edit/EditorSelectorInterface.php +++ /dev/null @@ -1,55 +0,0 @@ -init($form_state, $entity, $field_name); - } - - // Add the field form. - field_attach_form($form_state['entity']->entityType(), $form_state['entity'], $form, $form_state, $form_state['langcode'], array('field_name' => $form_state['field_name'])); - - // Add a submit button. Give it a class for easy JavaScript targeting. - $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => t('Save'), - '#attributes' => array('class' => array('edit-form-submit')), - ); - - // Add validation and submission handlers. - $form['#validate'][] = array($this, 'validate'); - $form['#submit'][] = array($this, 'submit'); - - // Simplify it for optimal in-place use. - $this->simplify($form, $form_state); - - return $form; - } - - /** - * Initialize the form state and the entity before the first form build. - */ - protected function init(array &$form_state, EntityInterface $entity, $field_name) { - // @todo Rather than special-casing $node->revision, invoke prepareEdit() - // once http://drupal.org/node/1863258 lands. - if ($entity->entityType() == 'node') { - $entity->setNewRevision(in_array('revision', variable_get('node_options_' . $entity->bundle(), array()))); - $entity->log = NULL; - } - - $form_state['entity'] = $entity; - $form_state['field_name'] = $field_name; - } - - /** - * Validates the form. - */ - public function validate(array $form, array &$form_state) { - $entity = $this->buildEntity($form, $form_state); - field_attach_form_validate($entity->entityType(), $entity, $form, $form_state, array('field_name' => $form_state['field_name'])); - } - - /** - * Saves the entity with updated values for the edited field. - */ - public function submit(array $form, array &$form_state) { - $form_state['entity'] = $this->buildEntity($form, $form_state); - $form_state['entity']->save(); - } - - /** - * Returns a cloned entity containing updated field values. - * - * Calling code may then validate the returned entity, and if valid, transfer - * it back to the form state and save it. - */ - protected function buildEntity(array $form, array &$form_state) { - $entity = clone $form_state['entity']; - - // @todo field_attach_submit() only "submits" to the in-memory $entity - // object, not to anywhere persistent. Consider renaming it to minimize - // confusion: http://drupal.org/node/1846648. - field_attach_submit($entity->entityType(), $entity, $form, $form_state, array('field_name' => $form_state['field_name'])); - - // @todo Refine automated log messages and abstract them to all entity - // types: http://drupal.org/node/1678002. - if ($entity->entityType() == 'node' && $entity->isNewRevision() && !isset($entity->log)) { - $instance = field_info_instance($entity->entityType(), $form_state['field_name'], $entity->bundle()); - $entity->log = t('Updated the %field-name field through in-place editing.', array('%field-name' => $instance['label'])); - } - - return $entity; - } - - /** - * Simplifies the field edit form for in-place editing. - * - * This function: - * - Hides the field label inside the form, because JavaScript displays it - * outside the form. - * - Adjusts textarea elements to fit their content. - * - * @param array $form - * An associative array containing the structure of the form. - */ - protected function simplify(array &$form, array &$form_state) { - $field_name = $form_state['field_name']; - $langcode = $form_state['langcode']; - - $widget_element =& $form[$field_name][$langcode]; - - // Hide the field label from displaying within the form, because JavaScript - // displays the equivalent label that was provided within an HTML data - // attribute of the field's display element outside of the form. Do this for - // widgets without child elements (like Option widgets) as well as for ones - // with per-delta elements. Skip single checkboxes, because their title is - // key to their UI. Also skip widgets with multiple subelements, because in - // that case, per-element labeling is informative. - $num_children = count(element_children($widget_element)); - if ($num_children == 0 && $widget_element['#type'] != 'checkbox') { - $widget_element['#title_display'] = 'invisible'; - } - if ($num_children == 1 && isset($widget_element[0]['value'])) { - // @todo While most widgets name their primary element 'value', not all - // do, so generalize this. - $widget_element[0]['value']['#title_display'] = 'invisible'; - } - - // Adjust textarea elements to fit their content. - if (isset($widget_element[0]['value']['#type']) && $widget_element[0]['value']['#type'] == 'textarea') { - $lines = count(explode("\n", $widget_element[0]['value']['#default_value'])); - $widget_element[0]['value']['#rows'] = $lines + 1; - } - } - -} diff --git a/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php b/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php deleted file mode 100644 index bdbdf10..0000000 --- a/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php +++ /dev/null @@ -1,87 +0,0 @@ -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']; - - // Early-return if user does not have access. - $access = $this->accessChecker->accessEditEntityField($entity, $field_name); - if (!$access) { - return array('access' => FALSE); - } - - $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' => TRUE, - '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/core/modules/edit/lib/Drupal/edit/MetadataGeneratorInterface.php b/core/modules/edit/lib/Drupal/edit/MetadataGeneratorInterface.php deleted file mode 100644 index 9e4fb5d..0000000 --- a/core/modules/edit/lib/Drupal/edit/MetadataGeneratorInterface.php +++ /dev/null @@ -1,41 +0,0 @@ -discovery = new AnnotatedClassDiscovery('edit', 'processed_text_editor'); - $this->discovery = new AlterDecorator($this->discovery, 'edit_wysiwyg'); - $this->discovery = new CacheDecorator($this->discovery, 'edit:wysiwyg'); - $this->factory = new DefaultFactory($this->discovery); - } - -} diff --git a/core/modules/edit/lib/Drupal/edit/Tests/EditorSelectionTest.php b/core/modules/edit/lib/Drupal/edit/Tests/EditorSelectionTest.php deleted file mode 100644 index 1aca55d..0000000 --- a/core/modules/edit/lib/Drupal/edit/Tests/EditorSelectionTest.php +++ /dev/null @@ -1,240 +0,0 @@ - 'In-place field editor selection', - 'description' => 'Tests in-place field editor selection.', - 'group' => 'Edit', - ); - } - - /** - * Sets the default field storage backend for fields created during tests. - */ - function setUp() { - parent::setUp(); - - $this->installSchema('system', 'variable'); - $this->enableModules(array('field', 'field_sql_storage', 'field_test')); - - // Set default storage backend. - variable_set('field_storage_default', $this->default_storage); - - // @todo Rather than using the real ProcessedTextEditorManager, which can - // find all text editor plugins in the codebase, create a mock one for - // testing that is populated with only the ones we want to test. - $text_editor_manager = new ProcessedTextEditorManager(); - - $this->editorSelector = new EditorSelector($text_editor_manager); - } - - /** - * Creates a field and an instance of it. - * - * @param string $field_name - * The field name. - * @param string $type - * The field type. - * @param int $cardinality - * The field's cardinality. - * @param string $label - * The field's label (used everywhere: widget label, formatter label). - * @param array $instance_settings - * @param string $widget_type - * The widget type. - * @param array $widget_settings - * The widget settings. - * @param string $formatter_type - * The formatter type. - * @param array $formatter_settings - * The formatter settings. - */ - function createFieldWithInstance($field_name, $type, $cardinality, $label, $instance_settings, $widget_type, $widget_settings, $formatter_type, $formatter_settings) { - $field = $field_name . '_field'; - $this->$field = array( - 'field_name' => $field_name, - 'type' => $type, - 'cardinality' => $cardinality, - ); - $this->$field_name = field_create_field($this->$field); - - $instance = $field_name . '_instance'; - $this->$instance = array( - 'field_name' => $field_name, - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'label' => $label, - 'description' => $label, - 'weight' => mt_rand(0, 127), - 'settings' => $instance_settings, - 'widget' => array( - 'type' => $widget_type, - 'label' => $label, - 'settings' => $widget_settings, - ), - 'display' => array( - 'default' => array( - 'label' => 'above', - 'type' => $formatter_type, - 'settings' => $formatter_settings - ), - ), - ); - field_create_instance($this->$instance); - } - - /** - * Retrieves the FieldInstance object for the given field and returns the - * editor that Edit selects. - */ - function getSelectedEditor($items, $field_name, $display = 'default') { - $field_instance = field_info_instance('test_entity', $field_name, 'test_bundle'); - return $this->editorSelector->getEditor($field_instance['display'][$display]['type'], $field_instance, $items); - } - - /** - * Tests a textual field, without/with text processing, with cardinality 1 and - * >1, always without a WYSIWYG editor present. - */ - function testText() { - $field_name = 'field_text'; - $this->createFieldWithInstance( - $field_name, 'text', 1, 'Simple text field', - // Instance settings. - array('text_processing' => 0), - // Widget type & settings. - 'text_textfield', - array('size' => 42), - // 'default' formatter type & settings. - 'text_default', - array() - ); - - // Pretend there is an entity with these items for the field. - $items = array(array('value' => 'Hello, world!', 'format' => 'full_html')); - - // Editor selection without text processing, with cardinality 1. - $this->assertEqual('direct', $this->getSelectedEditor($items, $field_name), "Without text processing, cardinality 1, the 'direct' editor is selected."); - - // Editor selection with text processing, cardinality 1. - $this->field_text_instance['settings']['text_processing'] = 1; - field_update_instance($this->field_text_instance); - $this->assertEqual('form', $this->getSelectedEditor($items, $field_name), "With text processing, cardinality 1, the 'form' editor is selected."); - - // Editor selection without text processing, cardinality 1 (again). - $this->field_text_instance['settings']['text_processing'] = 0; - field_update_instance($this->field_text_instance); - $this->assertEqual('direct', $this->getSelectedEditor($items, $field_name), "Without text processing again, cardinality 1, the 'direct' editor is selected."); - - // Editor selection without text processing, cardinality >1 - $this->field_text_field['cardinality'] = 2; - field_update_field($this->field_text_field); - $items[] = array('value' => 'Hallo, wereld!', 'format' => 'full_html'); - $this->assertEqual('form', $this->getSelectedEditor($items, $field_name), "Without text processing, cardinality >1, the 'form' editor is selected."); - - // Editor selection with text processing, cardinality >1 - $this->field_text_instance['settings']['text_processing'] = 1; - field_update_instance($this->field_text_instance); - $this->assertEqual('form', $this->getSelectedEditor($items, $field_name), "With text processing, cardinality >1, the 'form' editor is selected."); - } - - /** - * Tests a textual field, with text processing, with cardinality 1 and >1, - * always with a ProcessedTextEditor plug-in present, but with varying text - * format compatibility. - */ - function testTextWysiwyg() { - $field_name = 'field_textarea'; - $this->createFieldWithInstance( - $field_name, 'text', 1, 'Long text field', - // Instance settings. - array('text_processing' => 1), - // Widget type & settings. - 'text_textarea', - array('size' => 42), - // 'default' formatter type & settings. - 'text_default', - array() - ); - - // ProcessedTextEditor plug-in compatible with the full_html text format. - state()->set('edit_test.compatible_format', 'full_html'); - - // Pretend there is an entity with these items for the field. - $items = array(array('value' => 'Hello, world!', 'format' => 'filtered_html')); - - // Editor selection with cardinality 1, without compatible text format. - $this->assertEqual('form', $this->getSelectedEditor($items, $field_name), "Without cardinality 1, and the filtered_html text format, the 'form' editor is selected."); - - // Editor selection with cardinality 1, with compatible text format. - $items[0]['format'] = 'full_html'; - $this->assertEqual('direct-with-wysiwyg', $this->getSelectedEditor($items, $field_name), "With cardinality 1, and the full_html text format, the 'direct-with-wysiwyg' editor is selected."); - - // Editor selection with text processing, cardinality >1 - $this->field_textarea_field['cardinality'] = 2; - field_update_field($this->field_textarea_field); - $items[] = array('value' => 'Hallo, wereld!', 'format' => 'full_html'); - $this->assertEqual('form', $this->getSelectedEditor($items, $field_name), "With cardinality >1, and both items using the full_html text format, the 'form' editor is selected."); - } - - /** - * Tests a number field, with cardinality 1 and >1. - */ - function testNumber() { - $field_name = 'field_nr'; - $this->createFieldWithInstance( - $field_name, 'number_integer', 1, 'Simple number field', - // Instance settings. - array(), - // Widget type & settings. - 'number', - array(), - // 'default' formatter type & settings. - 'number_integer', - array() - ); - - // Pretend there is an entity with these items for the field. - $items = array(42, 43); - - // Editor selection with cardinality 1. - $this->assertEqual('form', $this->getSelectedEditor($items, $field_name), "With cardinality 1, the 'form' editor is selected."); - - // Editor selection with cardinality >1. - $this->field_nr_field['cardinality'] = 2; - field_update_field($this->field_nr_field); - $this->assertEqual('form', $this->getSelectedEditor($items, $field_name), "With cardinality >1, the 'form' editor is selected."); - } - -} diff --git a/core/modules/edit/tests/modules/edit_test.info b/core/modules/edit/tests/modules/edit_test.info deleted file mode 100644 index 4df4a3f..0000000 --- a/core/modules/edit/tests/modules/edit_test.info +++ /dev/null @@ -1,6 +0,0 @@ -name = Edit test -description = Support module for the Edit module tests. -core = 8.x -package = Testing -version = VERSION -hidden = TRUE diff --git a/core/modules/edit/tests/modules/edit_test.module b/core/modules/edit/tests/modules/edit_test.module deleted file mode 100644 index d74528d..0000000 --- a/core/modules/edit/tests/modules/edit_test.module +++ /dev/null @@ -1,6 +0,0 @@ -get('edit_test.compatible_format') == $format_id; - } - -} diff --git a/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityWrapper.php b/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityWrapper.php index a2e49e3..957bd4a 100644 --- a/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityWrapper.php +++ b/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityWrapper.php @@ -85,7 +85,7 @@ public function getId() { /** * Get the type URI. * - * @todo Once RdfMappingManager has a mapOutputTypes event, use that instead + * @todo Once RdfMappingManager has a mapBundleForOutput event, use that instead * of simply returning the site schema URI. */ public function getTypeUri() { @@ -93,12 +93,12 @@ public function getTypeUri() { $bundle = $this->entity->bundle(); switch ($this->format) { case 'drupal_jsonld': - $schema_path = SiteSchema::CONTENT_DEPLOYMENT; + $schema_id = SiteSchema::CONTENT_DEPLOYMENT; break; case 'jsonld': - $schema_path = SiteSchema::SYNDICATION; + $schema_id = SiteSchema::SYNDICATION; } - $schema = $this->siteSchemaManager->getSchema($schema_path); + $schema = $this->siteSchemaManager->getSchema($schema_id); return $schema->bundle($entity_type, $bundle)->getUri(); } diff --git a/core/modules/jsonld/lib/Drupal/jsonld/Tests/RdfSchemaSerializationTest.php b/core/modules/jsonld/lib/Drupal/jsonld/Tests/RdfSchemaSerializationTest.php index c4422aa..3cbfae4 100644 --- a/core/modules/jsonld/lib/Drupal/jsonld/Tests/RdfSchemaSerializationTest.php +++ b/core/modules/jsonld/lib/Drupal/jsonld/Tests/RdfSchemaSerializationTest.php @@ -46,7 +46,7 @@ function testSchemaSerialization() { $parsed_term = $decoded[0]; $this->assertEqual($parsed_term->{'@id'}, $bundle_schema->getUri(), 'JSON-LD for schema term uses correct @id.'); - $this->assertEqual($parsed_term->{'@type'}, 'http://www.w3.org/2000/01/rdf-schema#class', 'JSON-LD for schema term uses correct @type.'); + $this->assertEqual($parsed_term->{'@type'}, 'http://www.w3.org/2000/01/rdf-schema#Class', 'JSON-LD for schema term uses correct @type.'); // The @id and @type should be placed in the beginning of the array. $array_keys = array_keys((array) $parsed_term); $this->assertEqual(array('@id', '@type'), array_slice($array_keys, 0, 2), 'JSON-LD keywords are placed before other properties.'); diff --git a/core/modules/node/node.module b/core/modules/node/node.module index a37679c..d53f10e 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -811,53 +811,6 @@ function node_type_set_defaults($info = array()) { } /** - * Implements hook_rdf_mapping(). - */ -function node_rdf_mapping() { - return array( - array( - 'type' => 'node', - 'bundle' => RDF_DEFAULT_BUNDLE, - 'mapping' => array( - 'rdftype' => array('sioc:Item', 'foaf:Document'), - 'title' => array( - 'predicates' => array('dc:title'), - ), - 'created' => array( - 'predicates' => array('dc:date', 'dc:created'), - 'datatype' => 'xsd:dateTime', - 'callback' => 'date_iso8601', - ), - 'changed' => array( - 'predicates' => array('dc:modified'), - 'datatype' => 'xsd:dateTime', - 'callback' => 'date_iso8601', - ), - 'body' => array( - 'predicates' => array('content:encoded'), - ), - 'uid' => array( - 'predicates' => array('sioc:has_creator'), - 'type' => 'rel', - ), - 'name' => array( - 'predicates' => array('foaf:name'), - ), - 'comment_count' => array( - 'predicates' => array('sioc:num_replies'), - 'datatype' => 'xsd:integer', - ), - 'last_activity' => array( - 'predicates' => array('sioc:last_activity_date'), - 'datatype' => 'xsd:dateTime', - 'callback' => 'date_iso8601', - ), - ), - ), - ); -} - -/** * Determines whether a node hook exists. * * @param string $type diff --git a/core/modules/rdf/lib/Drupal/rdf/BundleRdfMappingStorageController.php b/core/modules/rdf/lib/Drupal/rdf/BundleRdfMappingStorageController.php new file mode 100644 index 0000000..051623a --- /dev/null +++ b/core/modules/rdf/lib/Drupal/rdf/BundleRdfMappingStorageController.php @@ -0,0 +1,19 @@ +siteSchema->bundle($entity->entity_type, $entity->bundle); + } + +} diff --git a/core/modules/rdf/lib/Drupal/rdf/EventSubscriber/MappingSubscriber.php b/core/modules/rdf/lib/Drupal/rdf/EventSubscriber/MappingSubscriber.php index 764c2f9..f58658d 100644 --- a/core/modules/rdf/lib/Drupal/rdf/EventSubscriber/MappingSubscriber.php +++ b/core/modules/rdf/lib/Drupal/rdf/EventSubscriber/MappingSubscriber.php @@ -35,11 +35,41 @@ public function mapTypesFromInput(\Drupal\rdf\MapTypesFromInputEvent $event) { } } + public function mapBundleForOutput(\Drupal\rdf\MapBundleForOutputEvent $event) { + $term_schema = $event->getTermSchema(); + // @todo When the $schema is SYNDICATION, allow sites to exclude the site + // schema URI from the mapping. + $site_schema_curie = $term_schema->getCurie(); + $event->addTypes(array($site_schema_curie)); + + $config = config($term_schema->getMappingConfigName()); + $types = $config->get('types'); + if (!empty($types)) { + $event->addTypes($types); + } + } + + public function mapFieldForOutput(\Drupal\rdf\MapFieldForOutputEvent $event) { + $term_schema = $event->getTermSchema(); + // @todo When the $schema is SYNDICATION, allow sites to exclude the site + // schema URI from the mapping. + $site_schema_curie = $term_schema->getCurie(); + $event->addPredicates(array($site_schema_curie)); + + $config = config($term_schema->getMappingConfigName()); + $curies = $config->get('properties'); + if (!empty($curies)) { + $event->addPredicates($curies); + } + } + /** * Implements EventSubscriberInterface::getSubscribedEvents(). */ static function getSubscribedEvents() { $events[RdfMappingEvents::MAP_TYPES_FROM_INPUT] = 'mapTypesFromInput'; + $events[RdfMappingEvents::MAP_BUNDLE_FOR_OUTPUT] = 'mapBundleForOutput'; + $events[RdfMappingEvents::MAP_FIELD_FOR_OUTPUT] = 'mapFieldForOutput'; return $events; } } diff --git a/core/modules/rdf/lib/Drupal/rdf/EventSubscriber/RouteSubscriber.php b/core/modules/rdf/lib/Drupal/rdf/EventSubscriber/RouteSubscriber.php index 123493b..cf04c94 100644 --- a/core/modules/rdf/lib/Drupal/rdf/EventSubscriber/RouteSubscriber.php +++ b/core/modules/rdf/lib/Drupal/rdf/EventSubscriber/RouteSubscriber.php @@ -50,10 +50,10 @@ public function routes(RouteBuildEvent $event) { foreach ($this->siteSchemaManager->getSchemas() as $schema) { $routes = $schema->getRoutes(); foreach ($routes as $controller => $pattern) { - $schema_path = $schema->getPath(); + $schema_id = $schema->getId(); $route = new Route($pattern, array( '_controller' => 'Drupal\rdf\SiteSchema\SchemaController::' . $controller, - 'schema_path' => $schema_path, + 'schema_id' => $schema_id, ), array( '_method' => 'GET', '_access' => 'TRUE', @@ -61,7 +61,7 @@ public function routes(RouteBuildEvent $event) { // Create the route name to use in the RouteCollection. Remove the // trailing slash and replace characters, so that a path such as // site-schema/syndication/ becomes rdf.site_schema.syndication. - $route_name = 'rdf.' . str_replace(array('-','/'), array('_', '.'), substr_replace($schema_path ,"",-1)); + $route_name = 'rdf.' . str_replace(array('-','/'), array('_', '.'), substr_replace($schema_id ,"",-1)); $collection->add($route_name, $route); } } diff --git a/core/modules/rdf/lib/Drupal/rdf/FieldRdfMappingStorageController.php b/core/modules/rdf/lib/Drupal/rdf/FieldRdfMappingStorageController.php new file mode 100644 index 0000000..04b5b7f --- /dev/null +++ b/core/modules/rdf/lib/Drupal/rdf/FieldRdfMappingStorageController.php @@ -0,0 +1,20 @@ +siteSchema->field($entity->entity_type, $entity->bundle, $entity->field_name); + } + +} diff --git a/core/modules/rdf/lib/Drupal/rdf/MapBundleForOutputEvent.php b/core/modules/rdf/lib/Drupal/rdf/MapBundleForOutputEvent.php new file mode 100644 index 0000000..59bfce0 --- /dev/null +++ b/core/modules/rdf/lib/Drupal/rdf/MapBundleForOutputEvent.php @@ -0,0 +1,59 @@ +termSchema = $term_schema; + $this->curies = array(); + } + + public function getTermSchema() { + return $this->termSchema; + } + + /** + * Add RDF types to the CURIEs array. + * + * @param array $curies + * + * @todo Add namespace IDs. + */ + public function addTypes($curies) { + if (!is_array($curies)) { + $curies = array($curies); + } + $this->curies = array_merge($this->curies, $curies); + } + + public function getCuries() { + return $this->curies; + } +} diff --git a/core/modules/rdf/lib/Drupal/rdf/MapFieldForOutputEvent.php b/core/modules/rdf/lib/Drupal/rdf/MapFieldForOutputEvent.php new file mode 100644 index 0000000..2294c3a --- /dev/null +++ b/core/modules/rdf/lib/Drupal/rdf/MapFieldForOutputEvent.php @@ -0,0 +1,71 @@ +termSchema = $term_schema; + $this->predicates = array(); + } + + public function getTermSchema() { + return $this->termSchema; + } + + /** + * Add CURIEs to the predicates array. + * + * @param array $curies + * + * @todo Add namespace IDs. + */ + public function addPredicates($curies) { + if (!is_array($curies)) { + $curies = array($curies); + } + $this->predicates = array_merge($this->predicates, $curies); + } + + public function getPredicates() { + return $this->predicates; + } + + public function getDatatype() { + return $this->datatype; + } + + public function getDatatypeCallback() { + return $this->datatype_callback; + } +} diff --git a/core/modules/rdf/lib/Drupal/rdf/Plugin/Core/Entity/BundleRdfMapping.php b/core/modules/rdf/lib/Drupal/rdf/Plugin/Core/Entity/BundleRdfMapping.php new file mode 100644 index 0000000..ec30afd --- /dev/null +++ b/core/modules/rdf/lib/Drupal/rdf/Plugin/Core/Entity/BundleRdfMapping.php @@ -0,0 +1,74 @@ +mid; + } +} diff --git a/core/modules/rdf/lib/Drupal/rdf/Plugin/Core/Entity/FieldRdfMapping.php b/core/modules/rdf/lib/Drupal/rdf/Plugin/Core/Entity/FieldRdfMapping.php new file mode 100644 index 0000000..7c1dc1c --- /dev/null +++ b/core/modules/rdf/lib/Drupal/rdf/Plugin/Core/Entity/FieldRdfMapping.php @@ -0,0 +1,95 @@ +mid; + } +} diff --git a/core/modules/rdf/lib/Drupal/rdf/RdfConstants.php b/core/modules/rdf/lib/Drupal/rdf/RdfConstants.php index b64ae91..4ac035f 100644 --- a/core/modules/rdf/lib/Drupal/rdf/RdfConstants.php +++ b/core/modules/rdf/lib/Drupal/rdf/RdfConstants.php @@ -12,8 +12,9 @@ */ abstract class RdfConstants { const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'; + const RDF_PROPERTY = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#Property'; // RDF Schema terms. - const RDFS_CLASS = 'http://www.w3.org/2000/01/rdf-schema#class'; + const RDFS_CLASS = 'http://www.w3.org/2000/01/rdf-schema#Class'; const RDFS_DOMAIN = 'http://www.w3.org/2000/01/rdf-schema#domain'; const RDFS_IS_DEFINED_BY = 'http://www.w3.org/2000/01/rdf-schema#isDefinedBy'; const RDFS_RANGE = 'http://www.w3.org/2000/01/rdf-schema#range'; diff --git a/core/modules/rdf/lib/Drupal/rdf/RdfMappingEvents.php b/core/modules/rdf/lib/Drupal/rdf/RdfMappingEvents.php index 0e3fdae..f74c791 100644 --- a/core/modules/rdf/lib/Drupal/rdf/RdfMappingEvents.php +++ b/core/modules/rdf/lib/Drupal/rdf/RdfMappingEvents.php @@ -26,4 +26,24 @@ */ const MAP_TYPES_FROM_INPUT = 'rdf.map_types_from_input'; + /** + * Maps a bundle to corresponding RDF URIs. + * + * In contrast to MAP_INPUT_TYPES, this event is triggered when RDF is being + * published. Modules can use this event to add RDF types to a mapping, which + * will then be published in the site's RDFa, JSON-LD, etc. However, a module + * does not have to use this event to add RDF types. RDF module manages an + * RDF mapping configuration file for each bundle which modules can also use + * to register mappings. + * + * @todo Add a note about this being cached once it is. + * + * @see \Drupal\rdf\RdfMappingManager + * + * @var string + */ + const MAP_BUNDLE_FOR_OUTPUT = 'rdf.map_bundle_for_output'; + + const MAP_FIELD_FOR_OUTPUT = 'rdf.map_field_for_output'; + } diff --git a/core/modules/rdf/lib/Drupal/rdf/RdfMappingManager.php b/core/modules/rdf/lib/Drupal/rdf/RdfMappingManager.php index 90588e9..6ab6341 100644 --- a/core/modules/rdf/lib/Drupal/rdf/RdfMappingManager.php +++ b/core/modules/rdf/lib/Drupal/rdf/RdfMappingManager.php @@ -10,6 +10,8 @@ use ReflectionClass; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\rdf\MapTypesFromInputEvent; +use Drupal\rdf\MapBundleForOutputEvent; +use Drupal\rdf\MapFieldForOutputEvent; use Drupal\rdf\RdfMappingEvents; use Drupal\rdf\SiteSchema\BundleSchema; use Drupal\rdf\SiteSchema\SiteSchema; @@ -76,6 +78,40 @@ public function getTypedDataIdsFromTypeUris($input_rdf_types) { } /** + * Convert Typed Data IDs to an array of RDF types. + * + * @param string $entity_type + * The entity type of the bundle which the types correspond to. + * @param string $bundle + * The name of the bundle which the types correspond to. + * @param string $schema_id + * The site schema which is being mapped to. + */ + public function getBundleMapping($entity_type, $bundle, $schema_id = SiteSchema::SYNDICATION) { + $site_schema = $this->siteSchemaManager->getSchema($schema_id); + $term_schema = $site_schema->bundle($entity_type, $bundle); + // Get the site schema URI which corresponds to the bundle. + $curies = $this->mapBundleForOutput($term_schema); + return $curies; + } + + public function getFieldMapping($entity_type, $bundle, $field_name, $schema_id = SiteSchema::SYNDICATION) { + $site_schema = $this->siteSchemaManager->getSchema($schema_id); + $term_schema = $site_schema->field($entity_type, $bundle, $field_name); + return $this->mapFieldForOutput($term_schema); + } + + public function getBundleMappingConfig($entity_type, $bundle, $schema_id = SiteSchema::SYNDICATION) { + $bundle_schema = $this->siteSchemaManager->getSchema($schema_id)->bundle($entity_type, $bundle); + return $bundle_schema->getMappingConfig(); + } + + public function getFieldMappingConfig($entity_type, $bundle, $field_name, $schema_id = SiteSchema::SYNDICATION) { + $field_schema = $this->siteSchemaManager->getSchema($schema_id)->field($entity_type, $bundle, $field_name); + return $field_schema->getMappingConfig(); + } + + /** * Map an array of incoming URIs to an internal site schema URI. * * @param array $input_rdf_types @@ -98,4 +134,30 @@ protected function mapTypesFromInput($input_rdf_types) { return $mapping_event->getSiteSchemaUri(); } + + protected function mapBundleForOutput($term_schema) { + $mapping = array(); + $typeMap = new MapBundleForOutputEvent($term_schema); + $this->dispatcher->dispatch(RdfMappingEvents::MAP_BUNDLE_FOR_OUTPUT, $typeMap); + $types = $typeMap->getCuries(); + if (!empty($types)) { + $mapping['types'] = $types; + } + return $mapping; + } + + protected function mapFieldForOutput($term_schema) { + $mapping = array(); + $map_event = new MapFieldForOutputEvent($term_schema); + $this->dispatcher->dispatch(RdfMappingEvents::MAP_FIELD_FOR_OUTPUT, $map_event); + + $predicates = $map_event->getPredicates(); + if (!empty($predicates)) { + $mapping['properties'] = $predicates; + $mapping['datatype'] = $map_event->getDatatype(); + $mapping['datatype_callback'] = $map_event->getDatatypeCallback(); + } + return array_filter($mapping); + } + } diff --git a/core/modules/rdf/lib/Drupal/rdf/RdfMappingStorageControllerBase.php b/core/modules/rdf/lib/Drupal/rdf/RdfMappingStorageControllerBase.php new file mode 100644 index 0000000..afbe5d7 --- /dev/null +++ b/core/modules/rdf/lib/Drupal/rdf/RdfMappingStorageControllerBase.php @@ -0,0 +1,40 @@ +siteSchema = drupal_container()->get('rdf.site_schema_manager')->getSchema(SiteSchema::SYNDICATION); + } + + /** + * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::save(). + */ + public function save(EntityInterface $entity) { + if (!isset($entity->mid)) { + $entity->mid = $this->getTermSchema($entity)->getMappingConfigId(); + } + parent::save($entity); + } + +} diff --git a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/BundleSchema.php b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/BundleSchema.php index 2c92696..e82b9ed 100644 --- a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/BundleSchema.php +++ b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/BundleSchema.php @@ -22,6 +22,8 @@ class BundleSchema extends EntitySchema { */ public static $uriPattern = '{entity_type}/{bundle}'; + protected $configPrefix = 'rdf.mapping.bundle'; + /** * The bundle that this term identifies. * @@ -45,11 +47,14 @@ public function __construct($site_schema, $entity_type, $bundle) { } /** - * Implements \Drupal\rdf\SiteSchema\SchemaTermInterface::getUri(). + * Implements \Drupal\rdf\SiteSchema\SchemaTermInterface::getPath(). */ - public function getUri() { - $path = str_replace(array('{entity_type}', '{bundle}'), array($this->entityType, $this->bundle), static::$uriPattern); - return $this->siteSchema->getUri() . $path; + public function getPath() { + return $this->siteSchema->getPath() . $this->prepareUriPattern(); + } + + protected function prepareUriPattern() { + return str_replace(array('{entity_type}', '{bundle}'), array($this->entityType, $this->bundle), static::$uriPattern); } /** @@ -61,4 +66,10 @@ public function getProperties() { return $properties; } + protected function getMappingConfigKeys() { + $keys = parent::getMappingConfigKeys(); + $keys['bundle'] = $this->bundle; + return $keys; + } + } diff --git a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/EntitySchema.php b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/EntitySchema.php index 48a69fc..a1665e8 100644 --- a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/EntitySchema.php +++ b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/EntitySchema.php @@ -54,11 +54,11 @@ public function getGraph() { } /** - * Implements \Drupal\rdf\SiteSchema\SchemaTermInterface::getUri(). + * Implements \Drupal\rdf\SiteSchema\SchemaTermInterface::getPath(). */ - public function getUri() { + public function getPath() { $path = str_replace('{entity_type}', $this->entityType , static::$uriPattern); - return $this->siteSchema->getUri() . $path; + return $this->siteSchema->getPath() . $path; } /** @@ -70,4 +70,11 @@ public function getProperties() { return $properties; } + protected function getMappingConfigKeys() { + return array( + 'schema_id' => $this->siteSchema->getId(), + 'entity_type' => $this->entityType, + ); + } + } diff --git a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/FieldSchema.php b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/FieldSchema.php new file mode 100644 index 0000000..0d56ca5 --- /dev/null +++ b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/FieldSchema.php @@ -0,0 +1,105 @@ +entityType = $entity_type; + $this->bundle = $bundle; + $this->fieldName = $field_name; + } + + /** + * Implements \Drupal\rdf\SiteSchema\SchemaTermInterface::getGraph(). + * + * @todo Loop through all fields and add their RDF descriptions. + */ + public function getGraph() { + // @todo Implement this. + } + + /** + * Implements \Drupal\rdf\SiteSchema\SchemaTermInterface::getPath(). + */ + public function getPath() { + return $this->siteSchema->getPath() . $this->prepareUriPattern(); + } + + protected function prepareUriPattern() { + return str_replace(array('{entity_type}', '{bundle}', '{field_name}'), array($this->entityType, $this->bundle, $this->fieldName), static::$uriPattern); + } + + /** + * Overrides \Drupal\rdf\SiteSchema\SchemaTermBase::getProperties(). + */ + public function getProperties() { + $properties = parent::getProperties(); + $properties[RdfConstants::RDF_TYPE] = RdfConstants::RDF_PROPERTY; + return $properties; + } + + protected function getMappingConfigKeys() { + return array( + 'schema_id' => $this->siteSchema->getId(), + 'entity_type' => $this->entityType, + 'bundle' => $this->bundle, + 'field_name' => $this->fieldName, + ); + } + +} diff --git a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SchemaController.php b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SchemaController.php index b60eb84..9617401 100644 --- a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SchemaController.php +++ b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SchemaController.php @@ -42,7 +42,7 @@ public function setContainer(ContainerInterface $container = NULL) { * The entity type. * @param string $bundle * The entity bundle. - * @param string $schema_path + * @param string $schema_id * The relative base path for the schema. * * @return \Symfony\Component\HttpFoundation\Response @@ -50,7 +50,7 @@ public function setContainer(ContainerInterface $container = NULL) { * * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException */ - public function bundle($entity_type, $bundle, $schema_path) { + public function bundle($entity_type, $bundle, $schema_id) { if (!$entity_info = entity_get_info($entity_type)) { throw new NotFoundHttpException(t('Entity type @entity_type not found', array('@entity_type' => $entity_type))); } @@ -60,7 +60,7 @@ public function bundle($entity_type, $bundle, $schema_path) { $serializer = $this->container->get('serializer'); $site_schema_manager = $this->container->get('rdf.site_schema_manager'); - $schema = $site_schema_manager->getSchema($schema_path); + $schema = $site_schema_manager->getSchema($schema_id); // @todo Remove hard-coded mimetype once we have proper conneg. $content = $serializer->serialize($schema->bundle($entity_type, $bundle), 'jsonld'); return new Response($content, 200, array('Content-type' => 'application/json')); diff --git a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SchemaTermBase.php b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SchemaTermBase.php index d7afd1c..edc7c34 100644 --- a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SchemaTermBase.php +++ b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SchemaTermBase.php @@ -28,6 +28,8 @@ */ protected $siteSchema; + protected $configPrefix; + /** * Constructor. * @@ -39,6 +41,17 @@ public function __construct($site_schema) { } /** + * Implements \Drupal\rdf\SiteSchema\SchemaTermInterface::getUri(). + */ + function getUri() { + return url($this->getPath(), array('absolute' => TRUE)); + } + + public function getCurie() { + return $this->siteSchema->getPrefix() . ':' . $this->prepareUriPattern(); + } + + /** * Implements \Drupal\rdf\SiteSchema\SchemaTermInterface::getProperties(). */ public function getProperties() { @@ -47,4 +60,26 @@ public function getProperties() { ); } + public function getMappingConfig() { + $config_name = $this->getMappingConfigName(); + $config = config($config_name); + + // If the entity type isn't set, then the config didn't exist before and the + // config factory created a new config entity. Fill in the keys and save it. + $entity_type = $config->get('entity_type'); + if (empty($entity_type)) { + $config->mid = $config_name; + foreach ($this->getMappingConfigKeys() as $key => $value) { + $config->set($key, $value); + } + $config->save(); + } + + return $config; + } + + public function getMappingConfigName() { + return $this->configPrefix . '.' . implode(':', $this->getMappingConfigKeys()); + } + } diff --git a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SchemaTermInterface.php b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SchemaTermInterface.php index 342b51e..239126e 100644 --- a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SchemaTermInterface.php +++ b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SchemaTermInterface.php @@ -41,4 +41,15 @@ public function getProperties(); * The URI of the term. */ public function getUri(); + + /** + * Get the relative path of the term. + * + * Implementations of this method will use the URI patterns defined in + * $uriPattern static variables and replace placeholders with actual values. + * + * @return string + * The relative path of the term. + */ + public function getPath(); } diff --git a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SiteSchema.php b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SiteSchema.php index e3153d1..dbb36d1 100644 --- a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SiteSchema.php +++ b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SiteSchema.php @@ -19,8 +19,8 @@ class SiteSchema { // are not intended to be extensible. If a site wants to use external // vocabulary terms, the appropriate way to do this is to use the RDF mapping // system. - const CONTENT_DEPLOYMENT = 'site-schema/content-deployment/'; - const SYNDICATION = 'site-schema/syndication/'; + const CONTENT_DEPLOYMENT = 'content-deployment'; + const SYNDICATION = 'syndication'; /** * The relative base path of the instantiated schema. @@ -29,20 +29,26 @@ class SiteSchema { */ protected $schemaPath; + protected $schemaId; + + protected $prefix; + /** * Constructor. * - * @param string $schema_path + * @param string $schema_id * The schema path constant, used to determine which schema to instantiate. * * @throws \UnexpectedValueException */ - public function __construct($schema_path) { + public function __construct($schema_id) { $valid_paths = array(self::CONTENT_DEPLOYMENT, self::SYNDICATION); - if (!in_array($schema_path, $valid_paths)) { - throw new \UnexpectedValueException(sprintf('%s is not a valid site schema path. Schema path must be one of %s.'), $schema_path, implode(', ', $valid_paths)); + if (!in_array($schema_id, $valid_paths)) { + throw new \UnexpectedValueException(sprintf('%s is not a valid site schema path. Schema path must be one of %s.'), $schema_id, implode(', ', $valid_paths)); } - $this->schemaPath = $schema_path; + $this->schemaId = $schema_id; + $this->schemaPath = 'site-schema/' . $schema_id . '/'; + $this->prefix = ($schema_id == self::CONTENT_DEPLOYMENT) ? 'site-cd' : 'site-syn'; } /** @@ -60,6 +66,13 @@ public function bundle($entity_type, $bundle) { } /** + * Get a field instance's term definition in this vocabulary. + */ + public function field($entity_type, $bundle, $field_name) { + return new FieldSchema($this, $entity_type, $bundle, $field_name); + } + + /** * Get the URI of the schema. * * @return string @@ -69,6 +82,10 @@ public function getUri() { return url($this->schemaPath, array('absolute' => TRUE)); } + public function getId() { + return $this->schemaId; + } + /** * Get the relative base path of the schema. */ @@ -76,6 +93,10 @@ public function getPath() { return $this->schemaPath; } + public function getPrefix() { + return $this->prefix; + } + /** * Get the routes for the types of terms defined in this schema. * diff --git a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SiteSchemaManager.php b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SiteSchemaManager.php index 7ca9ca8..6caa8fd 100644 --- a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SiteSchemaManager.php +++ b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/SiteSchemaManager.php @@ -65,8 +65,8 @@ public function getSchemas() { return $this->siteSchemas; } - public function getSchema($schema_path) { - return $this->siteSchemas[$schema_path]; + public function getSchema($schema_id) { + return $this->siteSchemas[$schema_id]; } /** diff --git a/core/modules/rdf/lib/Drupal/rdf/Tests/CrudTest.php b/core/modules/rdf/lib/Drupal/rdf/Tests/CrudTest.php index 3383f94..3fafc3b 100644 --- a/core/modules/rdf/lib/Drupal/rdf/Tests/CrudTest.php +++ b/core/modules/rdf/lib/Drupal/rdf/Tests/CrudTest.php @@ -19,7 +19,7 @@ class CrudTest extends WebTestBase { * * @var array */ - public static $modules = array('rdf', 'rdf_test'); + public static $modules = array('rdf', 'entity_test'); public static function getInfo() { return array( @@ -33,53 +33,54 @@ public static function getInfo() { * Tests inserting, loading, updating, and deleting RDF mappings. */ function testCRUD() { - // Verify loading of a default mapping. - $mapping = _rdf_mapping_load('test_entity', 'test_bundle'); - $this->assertTrue(count($mapping), 'Default mapping was found.'); + $entity_type = $bundle = 'entity_test'; + $mapping_manager = drupal_container()->get('rdf.mapping_manager'); + $bundle_mapping_config_name = "rdf.mapping.bundle.syndication:$entity_type:$bundle"; + $user_id_mapping_config_name = "rdf.mapping.field.syndication:$entity_type:$bundle:user_id"; - // Verify saving a mapping. - $mapping = array( - 'type' => 'crud_test_entity', - 'bundle' => 'crud_test_bundle', - 'mapping' => array( - 'rdftype' => array('sioc:Post'), - 'title' => array( - 'predicates' => array('dc:title'), - ), - 'uid' => array( - 'predicates' => array('sioc:has_creator', 'dc:creator'), - 'type' => 'rel', - ), - ), - ); - $this->assertTrue(rdf_mapping_save($mapping) === SAVED_NEW, 'Mapping was saved.'); - - // Read the raw record from the {rdf_mapping} table. - $result = db_query('SELECT * FROM {rdf_mapping} WHERE type = :type AND bundle = :bundle', array(':type' => $mapping['type'], ':bundle' => $mapping['bundle'])); - $stored_mapping = $result->fetchAssoc(); - $stored_mapping['mapping'] = unserialize($stored_mapping['mapping']); - $this->assertEqual($mapping, $stored_mapping, 'Mapping was stored properly in the {rdf_mapping} table.'); + // Save bundle mapping config. + $bundle_mapping = $mapping_manager->getBundleMappingConfig($entity_type, $bundle); + $bundle_mapping->set('types', array('sioc:Post'))->save(); + // Save field mapping config. + $user_id_mapping = $mapping_manager->getFieldMappingConfig($entity_type, $bundle, 'user_id'); + $user_id_mapping->set('properties', array('sioc:has_creator', 'dc:creator'))->save(); - // Verify loading of saved mapping. - $this->assertEqual($mapping['mapping'], _rdf_mapping_load($mapping['type'], $mapping['bundle']), 'Saved mapping loaded successfully.'); + // Test that config files were saved. + $bundle_mapping_configs = config_get_storage_names_with_prefix('rdf.mapping.bundle'); + $this->assertTrue(in_array($bundle_mapping_config_name, $bundle_mapping_configs), 'Bundle mapping config saved.'); + $field_mapping_configs = config_get_storage_names_with_prefix('rdf.mapping.field'); + $this->assertTrue(in_array($user_id_mapping_config_name, $field_mapping_configs), 'Field mapping config saved.'); - // Verify updating of mapping. - $mapping['mapping']['title'] = array( - 'predicates' => array('dc2:bar2'), - ); - $this->assertTrue(rdf_mapping_save($mapping) === SAVED_UPDATED, 'Mapping was updated.'); + // Test that config can be loaded. + $loaded_bundle_config = $mapping_manager->getBundleMappingConfig($entity_type, $bundle); + $bundle_config_array = $loaded_bundle_config->get(); + $this->assertTrue(!empty($bundle_config_array), 'Bundle mapping config loaded.'); + $loaded_field_config = $mapping_manager->getFieldMappingConfig($entity_type, $bundle, 'user_id'); + $field_config_array = $loaded_field_config->get(); + $this->assertTrue(!empty($field_config_array), 'Field mapping config loaded.'); - // Read the raw record from the {rdf_mapping} table. - $result = db_query('SELECT * FROM {rdf_mapping} WHERE type = :type AND bundle = :bundle', array(':type' => $mapping['type'], ':bundle' => $mapping['bundle'])); - $stored_mapping = $result->fetchAssoc(); - $stored_mapping['mapping'] = unserialize($stored_mapping['mapping']); - $this->assertEqual($mapping, $stored_mapping, 'Updated mapping was stored properly in the {rdf_mapping} table.'); + // Test that the values were saved correctly. + $types = config($bundle_mapping_config_name)->get('types'); + $this->assertTrue(in_array('sioc:Post', $types), 'Bundle mapping config values set properly.'); + $properties = config($user_id_mapping_config_name)->get('properties'); + $this->assertTrue(in_array('sioc:has_creator', $properties) && in_array('dc:creator', $properties), 'Field mapping config values set properly.'); - // Verify loading of saved mapping. - $this->assertEqual($mapping['mapping'], _rdf_mapping_load($mapping['type'], $mapping['bundle']), 'Saved mapping loaded successfully.'); + // Test that mapping can be updated. + $bundle_mapping->set('types', array('schema:Thing'))->save(); + $loaded_bundle_config = $mapping_manager->getBundleMappingConfig($entity_type, $bundle); + $types = $loaded_bundle_config->get('types'); + $this->assert(in_array('schema:Thing', $types) && !in_array('sioc:Post', $types), 'Bundle mapping was updated.'); + $user_id_mapping->set('properties', array('schema:author'))->save(); + $loaded_config = $mapping_manager->getFieldMappingConfig($entity_type, $bundle, 'user_id'); + $properties = $loaded_config->get('properties'); + $this->assert(in_array('schema:author', $properties) && !in_array('sioc:has_creator', $properties), 'Field mapping was updated.'); - // Verify deleting of mapping. - $this->assertTrue(rdf_mapping_delete($mapping['type'], $mapping['bundle']), 'Mapping was deleted.'); - $this->assertFalse(_rdf_mapping_load($mapping['type'], $mapping['bundle']), 'Deleted mapping is no longer found in the database.'); + // Test that the mapping can be deleted. + $bundle_mapping->delete(); + $bundle_mapping_configs = config_get_storage_names_with_prefix('rdf.mapping.bundle'); + $this->assertFalse(in_array($bundle_mapping_config_name, $bundle_mapping_configs), 'Bundle mapping config deleted.'); + $user_id_mapping->delete(); + $field_mapping_configs = config_get_storage_names_with_prefix('rdf.mapping.field'); + $this->assertFalse(in_array($user_id_mapping_config_name, $field_mapping_configs), 'Field mapping config deleted.'); } } diff --git a/core/modules/rdf/lib/Drupal/rdf/Tests/SiteSchemaTest.php b/core/modules/rdf/lib/Drupal/rdf/Tests/SiteSchemaTest.php index 0cfe07f..b8d20ce 100644 --- a/core/modules/rdf/lib/Drupal/rdf/Tests/SiteSchemaTest.php +++ b/core/modules/rdf/lib/Drupal/rdf/Tests/SiteSchemaTest.php @@ -44,7 +44,7 @@ function testSiteSchema() { $bundle_uri = url("$schema_path$entity_type/$bundle", array('absolute' => TRUE)); $bundle_properties = array( 'http://www.w3.org/2000/01/rdf-schema#isDefinedBy' => url($schema_path, array('absolute' => TRUE)), - 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' => 'http://www.w3.org/2000/01/rdf-schema#class', + 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' => 'http://www.w3.org/2000/01/rdf-schema#Class', 'http://www.w3.org/2000/01/rdf-schema#subClassOf' => url("$schema_path$entity_type", array('absolute' => TRUE)), ); diff --git a/core/modules/rdf/rdf.install b/core/modules/rdf/rdf.install index 10d3f8d..be2cde1 100644 --- a/core/modules/rdf/rdf.install +++ b/core/modules/rdf/rdf.install @@ -4,46 +4,3 @@ * @file * Install, update and uninstall functions for the rdf module. */ - -/** - * Implements hook_schema(). - */ -function rdf_schema() { - $schema['rdf_mapping'] = array( - 'description' => 'Stores custom RDF mappings for user defined content types or overriden module-defined mappings', - 'fields' => array( - 'type' => array( - 'type' => 'varchar', - 'length' => 128, - 'not null' => TRUE, - 'description' => 'The name of the entity type a mapping applies to (node, user, comment, etc.).', - ), - 'bundle' => array( - 'type' => 'varchar', - 'length' => 128, - 'not null' => TRUE, - 'description' => 'The name of the bundle a mapping applies to.', - ), - 'mapping' => array( - 'description' => 'The serialized mapping of the bundle type and fields to RDF terms.', - 'type' => 'blob', - 'not null' => FALSE, - 'size' => 'big', - 'serialize' => TRUE, - ), - ), - 'primary key' => array('type', 'bundle'), - ); - - return $schema; -} - -/** - * Implements hook_install(). - */ -function rdf_install() { - // Collect any RDF mappings that were declared by modules installed before - // this one. - $modules = module_implements('rdf_mapping'); - rdf_modules_installed($modules); -} diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module index 264a090..2cf869c 100644 --- a/core/modules/rdf/rdf.module +++ b/core/modules/rdf/rdf.module @@ -128,100 +128,34 @@ function rdf_get_namespaces() { * @param $type * An entity type. * @param $bundle - * (optional) A bundle name. + * A bundle name. * * @return * The mapping corresponding to the requested entity type/bundle pair or an * empty array. */ -function rdf_mapping_load($type, $bundle = RDF_DEFAULT_BUNDLE) { - // Retrieves the bundle-specific mapping from the entity info. - $entity_info = entity_get_info($type); - if (!empty($entity_info['bundles'][$bundle]['rdf_mapping'])) { - return $entity_info['bundles'][$bundle]['rdf_mapping']; - } - // If there is no mapping defined for this bundle, we return the default - // mapping that is defined for this entity type. - else { - return _rdf_get_default_mapping($type); - } -} - -/** - * @} End of "defgroup rdf". - */ - -/** - * Gets the default RDF mapping for a given entity type. - * - * @param $type - * An entity type, e.g. 'node' or 'comment'. - * - * @return - * The RDF mapping or an empty array if no mapping is defined for this entity - * type. - */ -function _rdf_get_default_mapping($type) { - $default_mappings = &drupal_static(__FUNCTION__); - - if (!isset($default_mappings)) { - // Get all of the modules that implement hook_rdf_mapping(). - $modules = module_implements('rdf_mapping'); - - // Only consider the default entity mapping definitions. - foreach ($modules as $module) { - $mappings = module_invoke($module, 'rdf_mapping'); - foreach ($mappings as $mapping) { - if ($mapping['bundle'] === RDF_DEFAULT_BUNDLE) { - $default_mappings[$mapping['type']] = $mapping['mapping']; - } +function rdf_mapping_load($type, $bundle) { + $mapping_manager = drupal_container()->get('rdf.mapping_manager'); + $mapping = array( + 'rdftypes' => $mapping_manager->getBundleMapping($type, $bundle), + ); + $entity = entity_create($type, array('type' => $bundle)); + + $properties = $entity->getPropertyDefinitions(); + if (!empty($properties)) { + foreach ($properties as $field_name => $field_info) { + $field_mapping = $mapping_manager->getFieldMapping($type, $bundle, $field_name); + if (!empty($field_mapping)) { + $mapping[$field_name] = $field_mapping; } } } - - return isset($default_mappings[$type]) ? $default_mappings[$type] : array(); + return $mapping; } /** - * Retrieves an RDF mapping from the database. - * - * @param $type - * The entity type the mapping refers to. - * @param $bundle - * The bundle the mapping refers to. - * - * @return - * An RDF mapping structure or, FALSE if the mapping does not exist. - */ -function _rdf_mapping_load($type, $bundle) { - $mappings = _rdf_mapping_load_multiple($type, array($bundle)); - return $mappings ? reset($mappings) : FALSE; -} - -/** - * Helper function to retrieve a set of RDF mappings from the database. - * - * @param $type - * The entity type of the mappings. - * @param $bundles - * The bundles the mappings refer to. - * - * @return - * An array of RDF mapping structures, or an empty array. + * @} End of "defgroup rdf". */ -function _rdf_mapping_load_multiple($type, array $bundles) { - $mappings = db_select('rdf_mapping') - ->fields(NULL, array('bundle', 'mapping')) - ->condition('type', $type) - ->condition('bundle', $bundles) - ->execute() - ->fetchAllKeyed(); - - foreach ($mappings as $bundle => $mapping) { - $mappings[$bundle] = unserialize($mapping); - } - return $mappings; -} /** * @addtogroup rdf @@ -229,61 +163,6 @@ function _rdf_mapping_load_multiple($type, array $bundles) { */ /** - * Saves an RDF mapping to the database. - * - * Takes a mapping structure returned by hook_rdf_mapping() implementations - * and creates or updates a record mapping for each encountered entity - * type/bundle pair. If available, adds default values for non-existent mapping - * keys. - * - * @param $mapping - * The RDF mapping to save. - * - * @return - * MergeQuery object that indicates the outcome of the operation. - */ -function rdf_mapping_save($mapping) { - // In the case where a field has a mapping defined in the default entity - // mapping, but a mapping is not specified in the bundle-specific mapping, - // then use the default mapping for that field. - $mapping['mapping'] += _rdf_get_default_mapping($mapping['type']); - - $status = db_merge('rdf_mapping') - ->key(array( - 'type' => $mapping['type'], - 'bundle' => $mapping['bundle'], - )) - ->fields(array( - 'mapping' => serialize($mapping['mapping']), - )) - ->execute(); - - entity_info_cache_clear(); - - return $status; -} - -/** - * Deletes the mapping for the given bundle from the database. - * - * @param $type - * The entity type the mapping refers to. - * @param $bundle - * The bundle the mapping refers to. - * - * @return - * TRUE if the mapping is deleted, FALSE if not. - */ -function rdf_mapping_delete($type, $bundle) { - $num_rows = db_delete('rdf_mapping') - ->condition('type', $type) - ->condition('bundle', $bundle) - ->execute(); - - return (bool) ($num_rows > 0); -} - -/** * Builds an array of RDFa attributes for a given mapping. * * This array will typically be passed through Drupal\Core\Template\Attribute @@ -345,98 +224,6 @@ function rdf_rdfa_attributes($mapping, $data = NULL) { */ /** - * Implements hook_modules_installed(). - * - * Checks if the installed modules have any RDF mapping definitions to declare - * and stores them in the rdf_mapping table. - * - * While both default entity mappings and specific bundle mappings can be - * defined in hook_rdf_mapping(), default entity mappings are not stored in the - * database. Only overridden mappings are stored in the database. The default - * entity mappings can be overriden by specific bundle mappings which are - * stored in the database and can be altered via the RDF CRUD mapping API. - */ -function rdf_modules_installed($modules) { - foreach ($modules as $module) { - $function = $module . '_rdf_mapping'; - if (function_exists($function)) { - foreach ($function() as $mapping) { - // Only the bundle mappings are saved in the database. - if ($mapping['bundle'] !== RDF_DEFAULT_BUNDLE) { - rdf_mapping_save($mapping); - } - } - } - } -} - -/** - * Implements hook_modules_uninstalled(). - */ -function rdf_modules_uninstalled($modules) { - // @todo Remove RDF mappings of uninstalled modules. -} - -/** - * Implements hook_entity_info_alter(). - * - * Adds the proper RDF mapping to each entity type/bundle pair. - * - * @todo May need to move the comment below to another place. - * This hook should not be used by modules to alter the bundle mappings. The UI - * should always be authoritative. UI mappings are stored in the database and - * if hook_entity_info_alter() was used to override module defined mappings, it - * would override the user defined mapping as well. - * - */ -function rdf_entity_info_alter(&$entity_info) { - // Loop through each entity type and its bundles. - foreach ($entity_info as $entity_type => $entity_type_info) { - if (!empty($entity_type_info['bundles'])) { - $bundles = array_keys($entity_type_info['bundles']); - $mappings = _rdf_mapping_load_multiple($entity_type, $bundles); - - foreach ($bundles as $bundle) { - if (isset($mappings[$bundle])) { - $entity_info[$entity_type]['bundles'][$bundle]['rdf_mapping'] = $mappings[$bundle]; - } - else { - // If no mapping was found in the database, assign the default RDF - // mapping for this entity type. - $entity_info[$entity_type]['bundles'][$bundle]['rdf_mapping'] = _rdf_get_default_mapping($entity_type); - } - } - } - } -} - -/** - * Implements hook_entity_load(). - */ -function rdf_entity_load($entities, $type) { - foreach ($entities as $entity) { - // Extracts the bundle of the entity being loaded. - $entity->rdf_mapping = rdf_mapping_load($type, $entity->bundle()); - } -} - -/** - * Implements hook_comment_load(). - */ -function rdf_comment_load($comments) { - foreach ($comments as $comment) { - // Pages with many comments can show poor performance. This information - // isn't needed until rdf_preprocess_comment() is called, but set it here - // to optimize performance for websites that implement an entity cache. - $comment->rdf_data['date'] = rdf_rdfa_attributes($comment->rdf_mapping['created'], $comment->created); - $comment->rdf_data['nid_uri'] = url('node/' . $comment->nid); - if ($comment->pid) { - $comment->rdf_data['pid_uri'] = url('comment/' . $comment->pid, array('fragment' => 'comment-' . $comment->pid)); - } - } -} - -/** * Implements hook_theme(). */ function rdf_theme() { @@ -511,14 +298,17 @@ function rdf_preprocess_node(&$variables) { // URI of the resource described within the HTML element, while the @typeof // attribute indicates its RDF type (e.g., foaf:Document, sioc:Person, and so // on.) + $mapping_manager = drupal_container()->get('rdf.mapping_manager'); + $bundle_mapping = $mapping_manager->getBundleMapping('node', $variables['type']); $variables['attributes']['about'] = empty($variables['node_url']) ? NULL: $variables['node_url']; - $variables['attributes']['typeof'] = empty($variables['node']->rdf_mapping['rdftype']) ? NULL : $variables['node']->rdf_mapping['rdftype']; + $variables['attributes']['typeof'] = empty($bundle_mapping['rdftype']) ? NULL : $bundle_mapping['rdftype']; // Adds RDFa markup to the title of the node. Because the RDFa markup is // added to the

tag which might contain HTML code, we specify an empty // datatype to ensure the value of the title read by the RDFa parsers is a // literal. - $variables['title_attributes']['property'] = empty($variables['node']->rdf_mapping['title']['predicates']) ? NULL : $variables['node']->rdf_mapping['title']['predicates']; + $title_mapping = $mapping_manager->getFieldMapping('node', $variables['type'], 'title'); + $variables['title_attributes']['property'] = empty($title_mapping['predicates']) ? NULL : $title_mapping['predicates']; $variables['title_attributes']['datatype'] = ''; // In full node mode, the title is not displayed by node.tpl.php so it is @@ -531,35 +321,38 @@ function rdf_preprocess_node(&$variables) { 'about' => $variables['node_url'], ), ); - if (!empty($variables['node']->rdf_mapping['title']['predicates'])) { - $element['#attributes']['property'] = $variables['node']->rdf_mapping['title']['predicates']; + if (!empty($title_mapping['predicates'])) { + $element['#attributes']['property'] = $title_mapping['predicates']; } drupal_add_html_head($element, 'rdf_node_title'); } // Adds RDFa markup for the date. - if (!empty($variables['node']->rdf_mapping['created'])) { - $date_attributes = rdf_rdfa_attributes($variables['node']->rdf_mapping['created'], $variables['node']->created); + $created_mapping = $mapping_manager->getFieldMapping('node', $variables['type'], 'created'); + if (!empty($created_mapping)) { + $date_attributes = rdf_rdfa_attributes($created_mapping, $variables['node']->created); $variables['rdf_template_variable_attributes']['date'] = $date_attributes; if ($variables['submitted']) { $variables['rdf_template_variable_attributes']['submitted'] = $date_attributes; } } // Adds RDFa markup for the relation between the node and its author. - if (!empty($variables['node']->rdf_mapping['uid'])) { - $variables['rdf_template_variable_attributes']['name']['rel'] = $variables['node']->rdf_mapping['uid']['predicates']; + $uid_mapping = $mapping_manager->getFieldMapping('node', $variables['type'], 'uid'); + if (!empty($uid_mapping)) { + $variables['rdf_template_variable_attributes']['name']['rel'] = $uid_mapping['predicates']; if ($variables['submitted']) { - $variables['rdf_template_variable_attributes']['submitted']['rel'] = $variables['node']->rdf_mapping['uid']['predicates']; + $variables['rdf_template_variable_attributes']['submitted']['rel'] = $uid_mapping['predicates']; } } // Adds RDFa markup annotating the number of comments a node has. - if (isset($variables['node']->comment_count) && !empty($variables['node']->rdf_mapping['comment_count']['predicates'])) { + $comment_count_mapping = $mapping_manager->getFieldMapping('node', $variables['type'], 'comment_count'); + if (isset($variables['node']->comment_count) && !empty($comment_count_mapping['predicates'])) { // Annotates the 'x comments' link in teaser view. if (isset($variables['content']['links']['comment']['#links']['comment-comments'])) { - $comment_count_attributes['property'] = $variables['node']->rdf_mapping['comment_count']['predicates']; + $comment_count_attributes['property'] = $comment_count_mapping['predicates']; $comment_count_attributes['content'] = $variables['node']->comment_count; - $comment_count_attributes['datatype'] = $variables['node']->rdf_mapping['comment_count']['datatype']; + $comment_count_attributes['datatype'] = $comment_count_mapping['datatype']; // According to RDFa parsing rule number 4, a new subject URI is created // from the href attribute if no rel/rev attribute is present. To get the // original node URL from the about attribute of the parent container we @@ -576,9 +369,9 @@ function rdf_preprocess_node(&$variables) { '#tag' => 'meta', '#attributes' => array( 'about' => $variables['node_url'], - 'property' => $variables['node']->rdf_mapping['comment_count']['predicates'], + 'property' => $comment_count_mapping['predicates'], 'content' => $variables['node']->comment_count, - 'datatype' => $variables['node']->rdf_mapping['comment_count']['datatype'], + 'datatype' => isset($comment_count_mapping['datatype']) ? $comment_count_mapping['datatype'] : NULL, ), ); drupal_add_html_head($element, 'rdf_node_comment_count'); diff --git a/core/profiles/standard/standard.install b/core/profiles/standard/standard.install index 48da6b1..2325c14 100644 --- a/core/profiles/standard/standard.install +++ b/core/profiles/standard/standard.install @@ -218,34 +218,6 @@ function standard_install() { node_add_body_field($type); } - // Insert default pre-defined RDF mapping into the database. - $rdf_mappings = array( - array( - 'type' => 'node', - 'bundle' => 'page', - 'mapping' => array( - 'rdftype' => array('foaf:Document'), - ), - ), - array( - 'type' => 'node', - 'bundle' => 'article', - 'mapping' => array( - 'field_image' => array( - 'predicates' => array('og:image', 'rdfs:seeAlso'), - 'type' => 'rel', - ), - 'field_tags' => array( - 'predicates' => array('dc:subject'), - 'type' => 'rel', - ), - ), - ), - ); - foreach ($rdf_mappings as $rdf_mapping) { - rdf_mapping_save($rdf_mapping); - } - // Default "Basic page" to not be promoted and have comments disabled. variable_set('node_options_page', array('status')); variable_set('comment_page', COMMENT_NODE_HIDDEN); @@ -432,4 +404,75 @@ function standard_install() { theme_enable(array('seven')); config('system.theme')->set('admin', 'seven')->save(); variable_set('node_admin_theme', '1'); + + // Save the RDF mapping configuration. + $rdf_mapping_manager = drupal_container()->get('rdf.mapping_manager'); + $bundle_mapping = array( + 'types' => array('foaf:Document', 'sioc:Item'), + ); + $shared_field_mappings = array( + 'title' => array( + 'properties' => array('dc:title'), + ), + 'created' => array( + 'properties' => array('dc:date', 'dc:created'), + 'datatype' => 'xsd:dateTime', + 'datatype_callback' => 'date_iso8601', + ), + 'changed' => array( + 'properties' => array('dc:modified'), + 'datatype' => 'xsd:dateTime', + 'datatype_callback' => 'date_iso8601', + ), + 'body' => array( + 'properties' => array('content:encoded'), + ), + 'uid' => array( + 'properties' => array('sioc:has_creator'), + 'type' => 'rel', + ), + 'name' => array( + 'properties' => array('foaf:name'), + ), + 'comment_count' => array( + 'properties' => array('sioc:num_replies'), + 'datatype' => 'xsd:integer', + ), + 'last_activity' => array( + 'properties' => array('sioc:last_activity_date'), + 'datatype' => 'xsd:dateTime', + 'datatype_callback' => 'date_iso8601', + ), + ); + // Save the bundle mapping and shared field mappings for both node bundles. + foreach (array('article', 'page') as $bundle) { + // Save bundle mapping config. + $config = $rdf_mapping_manager->getBundleMappingConfig('node', $bundle); + $bundle_mapping = array_merge($config->get(), $bundle_mapping); + $config->setData($bundle_mapping)->save(); + // Iterate over shared field mappings and save. + foreach ($shared_field_mappings as $field_name => $field_mapping) { + $config = $rdf_mapping_manager->getFieldMappingConfig('node', $bundle, $field_name); + $field_mapping = array_merge($config->get(), $field_mapping); + $config->setData($field_mapping)->save(); + } + } + + // Save the RDF mappings for fields which are unique to articles. + $field_image_mapping = array( + 'properties' => array('og:image', 'rdfs:seeAlso'), + 'type' => 'rel', + ); + $field_tags_mapping = array( + 'properties' => array('dc:subject'), + 'type' => 'rel', + ); + // Save field_image mapping. + $config = $rdf_mapping_manager->getFieldMappingConfig('node', 'article', 'field_image'); + $field_mapping = array_merge($config->get(), $field_image_mapping); + $config->setData($field_mapping)->save(); + // Save field_tags mapping. + $rdf_mapping_manager->getFieldMappingConfig('node', 'article', 'field_tags'); + $field_mapping = array_merge($config->get(), $field_tags_mapping); + $config->setData($field_mapping)->save(); } diff --git a/index.php b/index.php index 98bcddb..53c6199 100644 --- a/index.php +++ b/index.php @@ -37,6 +37,7 @@ // Create a request object from the HTTPFoundation. $request = Request::createFromGlobals(); +$mapping = rdf_mapping_load('node', 'article'); $response = $kernel->handle($request)->prepare($request)->send(); $kernel->terminate($request, $response);