0013280: can't set attendee status for non organisers via editDialog
[tine20] / tine20 / Tinebase / js / widgets / ActionUpdater.js
1 /*
2  * Tine 2.0
3  * 
4  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
5  * @author      Cornelius Weiss <c.weiss@metaways.de>
6  * @copyright   Copyright (c) 2007-2013 Metaways Infosystems GmbH (http://www.metaways.de)
7  */
8  
9  Ext.ns('Tine', 'Tine.widgets');
10  
11  Tine.widgets.ActionUpdater = function(config) {
12     config = config || {};
13     var actions = config.actions || [];
14     config.actions = [];
15     
16     Ext.apply(this, config);
17
18     if (this.recordClass) {
19         this.grantsPath = this.recordClass.getMeta('grantsPath');
20     }
21     this.addActions(actions);
22  };
23
24  Tine.widgets.ActionUpdater.prototype = {
25     /**
26      * @cfg {Array|Toolbar} actions
27      * all actions to update
28      */
29     actions: null,
30     
31     /**
32      * @cfg {Bool} evalGrants
33      * should grants of a grant-aware records be evaluated (defaults to true)
34      */
35     evalGrants: true,
36
37     recordClass: null,
38
39     grantsPath: null,
40     
41     /**
42      * add actions to update
43      * @param {Array|Toolbar} actions
44      */
45     addActions: function(actions) {
46         switch (typeof(actions)) {
47             case 'object':
48                 if (typeof(actions.each) == 'function') {
49                     actions.each(this.addAction, this);
50                 } else {
51                     for (var action in actions) {
52                         this.addAction(actions[action]);
53                     }
54                 }
55             break;
56             case 'array':
57                 for (var i=0; i<actions.length; i++) {
58                     this.addAction(actions[i]);
59                 }
60             break;
61         }
62     },
63     
64     /**
65      * add a single action to update
66      * @param {Ext.Action} action
67      */
68     addAction: function(action) {
69         // register action once only!
70         if (this.actions.indexOf(action) >= 0) {
71             return;
72         }
73
74         // if action has to initialConfig it's no Ext.Action!
75         if (action && action.initialConfig) {
76             
77             // in some cases our actionUpdater config is not in the initial config
78             // this happens for direct extensions of button class, like the notes button
79             if (action.requiredGrant) {
80                 Ext.applyIf(action.initialConfig, {
81                     requiredGrant: action.requiredGrant,
82                     actionUpdater: action.actionUpdater,
83                     allowMultiple: action.allowMultiple,
84                     singularText: action.singularText,
85                     pluralText: action.pluralText,
86                     translationObject: action.translationObject,
87                     selections: []
88                 });
89             }
90             
91             this.actions.push(action);
92
93             if (action.initialConfig.menu) {
94                 this.addActions(action.initialConfig.menu.items ?
95                     action.initialConfig.menu.items :
96                     action.initialConfig.menu
97                 );
98             }
99         }
100     },
101     
102     /**
103      * performs the actual update
104      * @param {Array|SelectionModel} records
105      */
106     updateActions: function(records) {
107         var isFilterSelect = false,
108             selectionModel = null;
109
110         if (typeof(records.getSelections) == 'function') {
111             isFilterSelect = records.isFilterSelect;
112             selectionModel = records;
113             records = records.getSelections();
114         } else if (typeof(records.beginEdit) == 'function') {
115             records = [records];
116         }
117         
118         var grants = this.getGrantsSum(records);
119         
120         this.each(function(action) {
121             // action updater opt-in fn has precedence over generic action updater!
122             var actionUpdater = action.actionUpdater || action.initialConfig.actionUpdater;
123             if (typeof(actionUpdater) == 'function') {
124                 var scope = action.scope || action.initialConfig.scope || window;
125                 actionUpdater.call(scope, action, grants, records, isFilterSelect);
126             } else {
127                 this.defaultUpdater(action, grants, records, isFilterSelect);
128             }
129
130             // reference selection into action
131             action.initialConfig.selections = records;
132             action.initialConfig.isFilterSelect = isFilterSelect;
133             action.initialConfig.selectionModel = selectionModel;
134
135         }, this);
136         
137     },
138     
139     /**
140      * default action updater
141      * 
142      * - sets disabled status based on grants and required grant
143      * - sets disabled status based on allowMutliple
144      * - sets action text (signular/plural text)
145      * 
146      * @param {Ext.Action} action
147      * @param {Object} grants
148      * @param {Object} records
149      * @param {Boolean} isFilterSelect
150      */
151     defaultUpdater: function(action, grants, records, isFilterSelect) {
152         var nCondition = records.length != 0 && (records.length > 1 ? action.initialConfig.allowMultiple : true),
153             mCondition = records && records.length > 1,
154             grantCondition = (! this.evalGrants) || grants[action.initialConfig.requiredGrant];
155
156         // @todo discuss if actions are only to be touched if requiredGrant is set
157         if (action.initialConfig.requiredGrant && action.initialConfig.requiredGrant != 'addGrant') {
158             action.setDisabled(! (grantCondition && nCondition));
159         }
160         
161         // disable on filter selection if required
162         if(action.disableOnFilterSelection && isFilterSelect) {
163             Tine.log.debug("disable on filter selection");
164             action.setDisabled(true);
165             return;
166         }
167         
168         if (nCondition) {
169             if (mCondition) {
170                 if (action.initialConfig.requiredMultipleRight) {
171                     var right = action.initialConfig.requiredMultipleRight.split('_');
172                     if (right.length == 2) {
173                         action.setDisabled(!Tine.Tinebase.common.hasRight(right[0], action.initialConfig.scope.app.name, right[1]));
174                     } else {
175                         Tine.log.debug('multiple edit right was not properly applied');
176                     }
177                 }
178                 if(action.initialConfig.requiredMultipleGrant) {
179                     var hasRight = true;
180                     if(Ext.isArray(records)) {
181                         Ext.each(records, function(record) {
182                             if(record.get('container_id') && record.get('container_id').account_grants) {
183                                 hasRight = (hasRight && (record.get('container_id').account_grants[action.initialConfig.requiredMultipleGrant]));
184                             } else {
185                                 return false;
186                             }
187                         }, this);
188                         action.setDisabled(! hasRight);
189                     }
190                 }
191             }
192             if (action.initialConfig.singularText && action.initialConfig.pluralText && action.initialConfig.translationObject) {
193                 var text = action.initialConfig.translationObject.n_(action.initialConfig.singularText, action.initialConfig.pluralText, records.length);
194                 action.setText(text);
195             }
196         }
197     },
198     
199     /**
200      * Calls the specified function for each action
201      * 
202      * @param {Function} fn The function to call. The action is passed as the first parameter.
203      * Returning <tt>false</tt> aborts and exits the iteration.
204      * @param {Object} scope (optional) The scope in which to call the function (defaults to the action).
205      */
206     each: function(fn, scope) {
207         for (var i=0; i<this.actions.length; i++) {
208             if (fn.call(scope||this.actions[i], this.actions[i]) === false) break;
209         }
210     },
211     
212     /**
213      * calculats the grants sum of the given record(s)
214      * 
215      * @param  {Array}  records
216      * @return {Object} grantName: sum
217      */
218     getGrantsSum: function(records) {
219
220         var _ = window.lodash,
221             defaultGrant = records.length == 0 ? false : true,
222             grants = {
223                 addGrant:       defaultGrant,
224                 adminGrant:     defaultGrant,
225                 deleteGrant:    defaultGrant,
226                 editGrant:      defaultGrant,
227                 readGrant:      defaultGrant,
228                 exportGrant:    defaultGrant,
229                 syncGrant:      defaultGrant
230             };
231         
232         if (! this.evalGrants) {
233             return grants;
234         }
235         
236         var recordGrants;
237         for (var i=0; i<records.length; i++) {
238             recordGrants = _.get(records[i], this.grantsPath);
239             
240             for (var grant in grants) {
241                 if (grants.hasOwnProperty(grant) && recordGrants && recordGrants.hasOwnProperty(grant)) {
242                     grants[grant] = grants[grant] & recordGrants[grant];
243                 }
244             }
245         }
246         // if calculated admin right is true, overwrite all grants with true
247         if(grants.adminGrant) {
248             for (var grant in grants) {
249                 grants[grant] = true;
250             }
251         }
252         
253         return grants;
254     }
255 };
256  
257 /**
258  * sets text and disabled status of a set of actions according to the grants 
259  * of a set of records
260  * @legacy use ActionUpdater Obj. instead!
261  * 
262  * @param {Array|SelectionModel} records
263  * @param {Array|Toolbar}        actions
264  * @param {String}               containerField
265  * @param {Bool}                 skipGrants evaluation
266  */
267 Tine.widgets.actionUpdater = function(records, actions, containerField, skipGrants) {
268     if (!containerField) {
269         containerField = 'container_id';
270     }
271
272     if (typeof(records.getSelections) == 'function') {
273         records = records.getSelections();
274     } else if (typeof(records.beginEdit) == 'function') {
275         records = [records];
276     }
277     
278     // init grants
279     var defaultGrant = records.length == 0 ? false : true;
280     var grants = {
281         addGrant:    defaultGrant,
282         adminGrant:  defaultGrant,
283         deleteGrant: defaultGrant,
284         editGrant:   defaultGrant,
285         exportGrant: defaultGrant,
286         readGrant:   defaultGrant,
287         syncGrant:   defaultGrant
288     };
289     
290     // calculate sum of grants
291     for (var i=0; i<records.length; i++) {
292         var recordGrants = records[i].get(containerField) ? records[i].get(containerField).account_grants : {};
293         for (var grant in grants) {
294             grants[grant] = grants[grant] & recordGrants[grant];
295         }
296     }
297
298     // if calculated admin right is true, overwrite all grants with true
299     if(grants.adminGrant) {
300         for (var grant in grants) {
301             grants[grant] = true;
302         }
303     }
304
305     /**
306      * action iterator
307      */
308     var actionIterator = function(action) {
309         var initialConfig = action.initialConfig;
310         if (initialConfig) {
311             
312             // happens for direct extensions of button class, like the notes button
313             if (action.requiredGrant) {
314                 initialConfig = {
315                     requiredGrant: action.requiredGrant,
316                     allowMultiple: action.allowMultiple,
317                     singularText: action.singularText,
318                     pluralText: action.pluralText,
319                     translationObject: action.translationObject
320                 };
321             }
322             
323             // NOTE: we don't handle add action for the moment!
324             var requiredGrant = initialConfig.requiredGrant;
325             if (requiredGrant && requiredGrant != 'addGrant') {
326                 var enable = skipGrants || grants[requiredGrant];
327                 if (records.length > 1 && ! initialConfig.allowMultiple) {
328                     enable = false;
329                 }
330                 if (records.length == 0) {
331                     enable = false;
332                 }
333                 
334                 action.setDisabled(!enable);
335                 if (initialConfig.singularText && initialConfig.pluralText && initialConfig.translationObject) {
336                     var text = initialConfig.translationObject.n_(initialConfig.singularText, initialConfig.pluralText, records.length);
337                     action.setText(text);
338                 }
339             }
340         }
341     };
342     
343     /**
344      * call action iterator
345      */
346     switch (typeof(actions)) {
347         case 'object':
348             if (typeof(actions.each) == 'function') {
349                 actions.each(actionIterator, this);
350             } else {
351                 for (var action in actions) {
352                     actionIterator(actions[action]);
353                 }
354             }
355         break;
356         case 'array':
357             for (var i=0; i<actions.length; i++) {
358                 actionIterator(actions[i]);
359             }
360         break;
361     }
362     
363 };