Security update for Core, with self-updated composer
[yaffs-website] / web / core / modules / quickedit / js / quickedit.js
1 /**
2 * DO NOT EDIT THIS FILE.
3 * See the following change record for more information,
4 * https://www.drupal.org/node/2815083
5 * @preserve
6 **/
7
8 (function ($, _, Backbone, Drupal, drupalSettings, JSON, storage) {
9   var options = $.extend(drupalSettings.quickedit, {
10     strings: {
11       quickEdit: Drupal.t('Quick edit')
12     }
13   });
14
15   var fieldsMetadataQueue = [];
16
17   var fieldsAvailableQueue = [];
18
19   var contextualLinksQueue = [];
20
21   var entityInstancesTracker = {};
22
23   Drupal.behaviors.quickedit = {
24     attach: function attach(context) {
25       $('body').once('quickedit-init').each(initQuickEdit);
26
27       var $fields = $(context).find('[data-quickedit-field-id]').once('quickedit');
28       if ($fields.length === 0) {
29         return;
30       }
31
32       $(context).find('[data-quickedit-entity-id]').once('quickedit').each(function (index, entityElement) {
33         processEntity(entityElement);
34       });
35
36       $fields.each(function (index, fieldElement) {
37         processField(fieldElement);
38       });
39
40       contextualLinksQueue = _.filter(contextualLinksQueue, function (contextualLink) {
41         return !initializeEntityContextualLink(contextualLink);
42       });
43
44       fetchMissingMetadata(function (fieldElementsWithFreshMetadata) {
45         _.each(fieldElementsWithFreshMetadata, processField);
46
47         contextualLinksQueue = _.filter(contextualLinksQueue, function (contextualLink) {
48           return !initializeEntityContextualLink(contextualLink);
49         });
50       });
51     },
52     detach: function detach(context, settings, trigger) {
53       if (trigger === 'unload') {
54         deleteContainedModelsAndQueues($(context));
55       }
56     }
57   };
58
59   Drupal.quickedit = {
60     app: null,
61
62     collections: {
63       entities: null,
64
65       fields: null
66     },
67
68     editors: {},
69
70     metadata: {
71       has: function has(fieldID) {
72         return storage.getItem(this._prefixFieldID(fieldID)) !== null;
73       },
74       add: function add(fieldID, metadata) {
75         storage.setItem(this._prefixFieldID(fieldID), JSON.stringify(metadata));
76       },
77       get: function get(fieldID, key) {
78         var metadata = JSON.parse(storage.getItem(this._prefixFieldID(fieldID)));
79         return typeof key === 'undefined' ? metadata : metadata[key];
80       },
81       _prefixFieldID: function _prefixFieldID(fieldID) {
82         return 'Drupal.quickedit.metadata.' + fieldID;
83       },
84       _unprefixFieldID: function _unprefixFieldID(fieldID) {
85         return fieldID.substring(26);
86       },
87       intersection: function intersection(fieldIDs) {
88         var prefixedFieldIDs = _.map(fieldIDs, this._prefixFieldID);
89         var intersection = _.intersection(prefixedFieldIDs, _.keys(sessionStorage));
90         return _.map(intersection, this._unprefixFieldID);
91       }
92     }
93   };
94
95   var permissionsHashKey = Drupal.quickedit.metadata._prefixFieldID('permissionsHash');
96   var permissionsHashValue = storage.getItem(permissionsHashKey);
97   var permissionsHash = drupalSettings.user.permissionsHash;
98   if (permissionsHashValue !== permissionsHash) {
99     if (typeof permissionsHash === 'string') {
100       _.chain(storage).keys().each(function (key) {
101         if (key.substring(0, 26) === 'Drupal.quickedit.metadata.') {
102           storage.removeItem(key);
103         }
104       });
105     }
106     storage.setItem(permissionsHashKey, permissionsHash);
107   }
108
109   $(document).on('drupalContextualLinkAdded', function (event, data) {
110     if (data.$region.is('[data-quickedit-entity-id]')) {
111       if (!data.$region.is('[data-quickedit-entity-instance-id]')) {
112         data.$region.once('quickedit');
113         processEntity(data.$region.get(0));
114       }
115       var contextualLink = {
116         entityID: data.$region.attr('data-quickedit-entity-id'),
117         entityInstanceID: data.$region.attr('data-quickedit-entity-instance-id'),
118         el: data.$el[0],
119         region: data.$region[0]
120       };
121
122       if (!initializeEntityContextualLink(contextualLink)) {
123         contextualLinksQueue.push(contextualLink);
124       }
125     }
126   });
127
128   function extractEntityID(fieldID) {
129     return fieldID.split('/').slice(0, 2).join('/');
130   }
131
132   function initQuickEdit(bodyElement) {
133     Drupal.quickedit.collections.entities = new Drupal.quickedit.EntityCollection();
134     Drupal.quickedit.collections.fields = new Drupal.quickedit.FieldCollection();
135
136     Drupal.quickedit.app = new Drupal.quickedit.AppView({
137       el: bodyElement,
138       model: new Drupal.quickedit.AppModel(),
139       entitiesCollection: Drupal.quickedit.collections.entities,
140       fieldsCollection: Drupal.quickedit.collections.fields
141     });
142   }
143
144   function processEntity(entityElement) {
145     var entityID = entityElement.getAttribute('data-quickedit-entity-id');
146     if (!entityInstancesTracker.hasOwnProperty(entityID)) {
147       entityInstancesTracker[entityID] = 0;
148     } else {
149       entityInstancesTracker[entityID]++;
150     }
151
152     var entityInstanceID = entityInstancesTracker[entityID];
153     entityElement.setAttribute('data-quickedit-entity-instance-id', entityInstanceID);
154   }
155
156   function processField(fieldElement) {
157     var metadata = Drupal.quickedit.metadata;
158     var fieldID = fieldElement.getAttribute('data-quickedit-field-id');
159     var entityID = extractEntityID(fieldID);
160
161     var entityElementSelector = '[data-quickedit-entity-id="' + entityID + '"]';
162     var $entityElement = $(entityElementSelector);
163
164     if (!$entityElement.length) {
165       throw 'Quick Edit could not associate the rendered entity field markup (with [data-quickedit-field-id="' + fieldID + '"]) with the corresponding rendered entity markup: no parent DOM node found with [data-quickedit-entity-id="' + entityID + '"]. This is typically caused by the theme\'s template for this entity type forgetting to print the attributes.';
166     }
167     var entityElement = $(fieldElement).closest($entityElement);
168
169     if (entityElement.length === 0) {
170       var $lowestCommonParent = $entityElement.parents().has(fieldElement).first();
171       entityElement = $lowestCommonParent.find($entityElement);
172     }
173     var entityInstanceID = entityElement.get(0).getAttribute('data-quickedit-entity-instance-id');
174
175     if (!metadata.has(fieldID)) {
176       fieldsMetadataQueue.push({
177         el: fieldElement,
178         fieldID: fieldID,
179         entityID: entityID,
180         entityInstanceID: entityInstanceID
181       });
182       return;
183     }
184
185     if (metadata.get(fieldID, 'access') !== true) {
186       return;
187     }
188
189     if (Drupal.quickedit.collections.entities.findWhere({ entityID: entityID, entityInstanceID: entityInstanceID })) {
190       initializeField(fieldElement, fieldID, entityID, entityInstanceID);
191     } else {
192         fieldsAvailableQueue.push({ el: fieldElement, fieldID: fieldID, entityID: entityID, entityInstanceID: entityInstanceID });
193       }
194   }
195
196   function initializeField(fieldElement, fieldID, entityID, entityInstanceID) {
197     var entity = Drupal.quickedit.collections.entities.findWhere({
198       entityID: entityID,
199       entityInstanceID: entityInstanceID
200     });
201
202     $(fieldElement).addClass('quickedit-field');
203
204     var field = new Drupal.quickedit.FieldModel({
205       el: fieldElement,
206       fieldID: fieldID,
207       id: fieldID + '[' + entity.get('entityInstanceID') + ']',
208       entity: entity,
209       metadata: Drupal.quickedit.metadata.get(fieldID),
210       acceptStateChange: _.bind(Drupal.quickedit.app.acceptEditorStateChange, Drupal.quickedit.app)
211     });
212
213     Drupal.quickedit.collections.fields.add(field);
214   }
215
216   function fetchMissingMetadata(callback) {
217     if (fieldsMetadataQueue.length) {
218       var fieldIDs = _.pluck(fieldsMetadataQueue, 'fieldID');
219       var fieldElementsWithoutMetadata = _.pluck(fieldsMetadataQueue, 'el');
220       var entityIDs = _.uniq(_.pluck(fieldsMetadataQueue, 'entityID'), true);
221
222       entityIDs = _.difference(entityIDs, Drupal.quickedit.metadata.intersection(entityIDs));
223       fieldsMetadataQueue = [];
224
225       $.ajax({
226         url: Drupal.url('quickedit/metadata'),
227         type: 'POST',
228         data: {
229           'fields[]': fieldIDs,
230           'entities[]': entityIDs
231         },
232         dataType: 'json',
233         success: function success(results) {
234           _.each(results, function (fieldMetadata, fieldID) {
235             Drupal.quickedit.metadata.add(fieldID, fieldMetadata);
236           });
237
238           callback(fieldElementsWithoutMetadata);
239         }
240       });
241     }
242   }
243
244   function loadMissingEditors(callback) {
245     var loadedEditors = _.keys(Drupal.quickedit.editors);
246     var missingEditors = [];
247     Drupal.quickedit.collections.fields.each(function (fieldModel) {
248       var metadata = Drupal.quickedit.metadata.get(fieldModel.get('fieldID'));
249       if (metadata.access && _.indexOf(loadedEditors, metadata.editor) === -1) {
250         missingEditors.push(metadata.editor);
251
252         Drupal.quickedit.editors[metadata.editor] = false;
253       }
254     });
255     missingEditors = _.uniq(missingEditors);
256     if (missingEditors.length === 0) {
257       callback();
258       return;
259     }
260
261     var loadEditorsAjax = Drupal.ajax({
262       url: Drupal.url('quickedit/attachments'),
263       submit: { 'editors[]': missingEditors }
264     });
265
266     var realInsert = Drupal.AjaxCommands.prototype.insert;
267     loadEditorsAjax.commands.insert = function (ajax, response, status) {
268       _.defer(callback);
269       realInsert(ajax, response, status);
270     };
271
272     loadEditorsAjax.execute();
273   }
274
275   function initializeEntityContextualLink(contextualLink) {
276     var metadata = Drupal.quickedit.metadata;
277
278     function hasFieldWithPermission(fieldIDs) {
279       for (var i = 0; i < fieldIDs.length; i++) {
280         var fieldID = fieldIDs[i];
281         if (metadata.get(fieldID, 'access') === true) {
282           return true;
283         }
284       }
285       return false;
286     }
287
288     function allMetadataExists(fieldIDs) {
289       return fieldIDs.length === metadata.intersection(fieldIDs).length;
290     }
291
292     var fields = _.where(fieldsAvailableQueue, {
293       entityID: contextualLink.entityID,
294       entityInstanceID: contextualLink.entityInstanceID
295     });
296     var fieldIDs = _.pluck(fields, 'fieldID');
297
298     if (fieldIDs.length === 0) {
299       return false;
300     } else if (hasFieldWithPermission(fieldIDs)) {
301         var entityModel = new Drupal.quickedit.EntityModel({
302           el: contextualLink.region,
303           entityID: contextualLink.entityID,
304           entityInstanceID: contextualLink.entityInstanceID,
305           id: contextualLink.entityID + '[' + contextualLink.entityInstanceID + ']',
306           label: Drupal.quickedit.metadata.get(contextualLink.entityID, 'label')
307         });
308         Drupal.quickedit.collections.entities.add(entityModel);
309
310         var entityDecorationView = new Drupal.quickedit.EntityDecorationView({
311           el: contextualLink.region,
312           model: entityModel
313         });
314         entityModel.set('entityDecorationView', entityDecorationView);
315
316         _.each(fields, function (field) {
317           initializeField(field.el, field.fieldID, contextualLink.entityID, contextualLink.entityInstanceID);
318         });
319         fieldsAvailableQueue = _.difference(fieldsAvailableQueue, fields);
320
321         var initContextualLink = _.once(function () {
322           var $links = $(contextualLink.el).find('.contextual-links');
323           var contextualLinkView = new Drupal.quickedit.ContextualLinkView($.extend({
324             el: $('<li class="quickedit"><a href="" role="button" aria-pressed="false"></a></li>').prependTo($links),
325             model: entityModel,
326             appModel: Drupal.quickedit.app.model
327           }, options));
328           entityModel.set('contextualLinkView', contextualLinkView);
329         });
330
331         loadMissingEditors(initContextualLink);
332
333         return true;
334       } else if (allMetadataExists(fieldIDs)) {
335           return true;
336         }
337
338     return false;
339   }
340
341   function deleteContainedModelsAndQueues($context) {
342     $context.find('[data-quickedit-entity-id]').addBack('[data-quickedit-entity-id]').each(function (index, entityElement) {
343       var entityModel = Drupal.quickedit.collections.entities.findWhere({ el: entityElement });
344       if (entityModel) {
345         var contextualLinkView = entityModel.get('contextualLinkView');
346         contextualLinkView.undelegateEvents();
347         contextualLinkView.remove();
348
349         entityModel.get('entityDecorationView').remove();
350
351         entityModel.destroy();
352       }
353
354       function hasOtherRegion(contextualLink) {
355         return contextualLink.region !== entityElement;
356       }
357
358       contextualLinksQueue = _.filter(contextualLinksQueue, hasOtherRegion);
359     });
360
361     $context.find('[data-quickedit-field-id]').addBack('[data-quickedit-field-id]').each(function (index, fieldElement) {
362       Drupal.quickedit.collections.fields.chain().filter(function (fieldModel) {
363         return fieldModel.get('el') === fieldElement;
364       }).invoke('destroy');
365
366       function hasOtherFieldElement(field) {
367         return field.el !== fieldElement;
368       }
369
370       fieldsMetadataQueue = _.filter(fieldsMetadataQueue, hasOtherFieldElement);
371       fieldsAvailableQueue = _.filter(fieldsAvailableQueue, hasOtherFieldElement);
372     });
373   }
374 })(jQuery, _, Backbone, Drupal, drupalSettings, window.JSON, window.sessionStorage);