0573aa0c814df2fcc27d505f301b608665c16c1f
[tine20] / tine20 / Calendar / js / Model.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-2012 Metaways Infosystems GmbH (http://www.metaways.de)
7  */
8
9 Ext.ns('Tine.Calendar', 'Tine.Calendar.Model');
10
11 /**
12  * @namespace Tine.Calendar.Model
13  * @class Tine.Calendar.Model.Event
14  * @extends Tine.Tinebase.data.Record
15  * Event record definition
16  */
17 Tine.Calendar.Model.Event = Tine.Tinebase.data.Record.create(Tine.Tinebase.Model.genericFields.concat([
18     { name: 'id' },
19     { name: 'dtend', type: 'date', dateFormat: Date.patterns.ISO8601Long },
20     { name: 'transp' },
21     // ical common fields
22     { name: 'class' },
23     { name: 'description' },
24     { name: 'geo' },
25     { name: 'location' },
26     { name: 'organizer' },
27     { name: 'priority' },
28     { name: 'status' },
29     { name: 'summary' },
30     { name: 'url' },
31     { name: 'uid' },
32     // ical common fields with multiple appearance
33     //{ name: 'attach' },
34     { name: 'attendee' },
35     { name: 'alarms'},
36     { name: 'tags' },
37     { name: 'notes'},
38     { name: 'attachments'},
39     //{ name: 'contact' },
40     //{ name: 'related' },
41     //{ name: 'resources' },
42     //{ name: 'rstatus' },
43     // scheduleable interface fields
44     { name: 'dtstart', type: 'date', dateFormat: Date.patterns.ISO8601Long },
45     { name: 'recurid' },
46     // scheduleable interface fields with multiple appearance
47     { name: 'exdate' },
48     //{ name: 'exrule' },
49     //{ name: 'rdate' },
50     { name: 'rrule' },
51     { name: 'is_all_day_event', type: 'bool'},
52     { name: 'rrule_until', type: 'date', dateFormat: Date.patterns.ISO8601Long },
53     { name: 'originator_tz' },
54     // grant helper fields
55     {name: 'readGrant'   , type: 'bool'},
56     {name: 'editGrant'   , type: 'bool'},
57     {name: 'deleteGrant' , type: 'bool'},
58     {name: 'editGrant'   , type: 'bool'}
59 ]), {
60     appName: 'Calendar',
61     modelName: 'Event',
62     idProperty: 'id',
63     titleProperty: 'summary',
64     // ngettext('Event', 'Events', n); gettext('Events');
65     recordName: 'Event',
66     recordsName: 'Events',
67     containerProperty: 'container_id',
68     // ngettext('Calendar', 'Calendars', n); gettext('Calendars');
69     containerName: 'Calendar',
70     containersName: 'Calendars',
71     copyOmitFields: ['uid', 'recurid'],
72     
73     /**
74      * mark record out of current filter
75      * 
76      * @type Boolean
77      */
78     outOfFilter: false,
79     
80     /**
81      * returns displaycontainer with orignialcontainer as fallback
82      * 
83      * @return {Array}
84      */
85     getDisplayContainer: function() {
86         var displayContainer = this.get('container_id');
87         var currentAccountId = Tine.Tinebase.registry.get('currentAccount').accountId;
88         
89         var attendeeStore = this.getAttendeeStore();
90         
91         attendeeStore.each(function(attender) {
92             var userAccountId = attender.getUserAccountId();
93             if (userAccountId == currentAccountId) {
94                 var container = attender.get('displaycontainer_id');
95                 if (container) {
96                     displayContainer = container;
97                 }
98                 return false;
99             }
100         }, this);
101         
102         return displayContainer;
103     },
104     
105     /**
106      * is this event a recuring base event?
107      * 
108      * @return {Boolean}
109      */
110     isRecurBase: function() {
111         return !!this.get('rrule') && !this.get('recurid');
112     },
113     
114     /**
115      * is this event a recuring exception?
116      * 
117      * @return {Boolean}
118      */
119     isRecurException: function() {
120         return !! this.get('recurid') && ! this.isRecurInstance();
121     },
122     
123     /**
124      * is this event an recuring event instance?
125      * 
126      * @return {Boolean}
127      */
128     isRecurInstance: function() {
129         return this.id && this.id.match(/^fakeid/);
130     },
131     
132     /**
133      * returns store of attender objects
134      * 
135      * @param  {Array} attendeeData
136      * @return {Ext.data.Store}
137      */
138     getAttendeeStore: function() {
139         return Tine.Calendar.Model.Attender.getAttendeeStore(this.get('attendee'));
140     },
141     
142     /**
143      * returns attender record of current account if exists, else false
144      */
145     getMyAttenderRecord: function() {
146         var attendeeStore = this.getAttendeeStore();
147         return Tine.Calendar.Model.Attender.getAttendeeStore.getMyAttenderRecord(attendeeStore);
148     }
149 });
150
151
152 /**
153  * @namespace Tine.Calendar.Model
154  * 
155  * get default data for a new event
156  * @todo: attendee according to calendar selection
157  *  
158  * @return {Object} default data
159  * @static
160  */ 
161 Tine.Calendar.Model.Event.getDefaultData = function() {
162     var app = Tine.Tinebase.appMgr.get('Calendar'),
163         dtstart = new Date().clearTime().add(Date.HOUR, (new Date().getHours() + 1)),
164         // if dtstart is out of current period, take start of current period
165         mainPanel = app.getMainScreen().getCenterPanel(),
166         period = mainPanel.getCalendarPanel(mainPanel.activeView).getView().getPeriod(),
167         container = app.getMainScreen().getWestPanel().getContainerTreePanel().getDefaultContainer(),
168         prefs = app.getRegistry().get('preferences');
169         
170     if (period.from.getTime() > dtstart.getTime() || period.until.getTime() < dtstart.getTime()) {
171         dtstart = period.from.clearTime(true).add(Date.HOUR, 9);
172     }
173     
174     var data = {
175         summary: '',
176         dtstart: dtstart,
177         dtend: dtstart.add(Date.HOUR, 1),
178         container_id: container,
179         transp: 'OPAQUE',
180         editGrant: true,
181         organizer: Tine.Tinebase.registry.get('userContact'),
182         attendee: [
183             Ext.apply(Tine.Calendar.Model.Attender.getDefaultData(), {
184                 user_type: 'user',
185                 user_id: Tine.Tinebase.registry.get('userContact'),
186                 status: 'ACCEPTED'
187             })
188         ]
189     };
190     
191     if (prefs.get('defaultalarmenabled')) {
192         data.alarms = [{minutes_before: parseInt(prefs.get('defaultalarmminutesbefore'), 10)}];
193     }
194     
195     return data;
196 };
197
198 Tine.Calendar.Model.Event.getFilterModel = function() {
199     var app = Tine.Tinebase.appMgr.get('Calendar');
200     
201     return [
202         {label: _('Quick Search'), field: 'query', operators: ['contains']},
203         {label: app.i18n._('Summary'), field: 'summary'},
204         {label: app.i18n._('Location'), field: 'location'},
205         {label: app.i18n._('Description'), field: 'description'},
206         {filtertype: 'tine.widget.container.filtermodel', app: app, recordClass: Tine.Calendar.Model.Event, /*defaultOperator: 'in',*/ defaultValue: {path: Tine.Tinebase.container.getMyNodePath()}},
207         {filtertype: 'calendar.attendee'},
208         {
209             label: app.i18n._('Attendee Status'),
210             field: 'attender_status',
211             filtertype: 'tine.widget.keyfield.filter', 
212             app: app, 
213             keyfieldName: 'attendeeStatus', 
214             defaultOperator: 'notin',
215             defaultValue: ['DECLINED']
216         },
217         {
218             label: app.i18n._('Attendee Role'),
219             field: 'attender_role',
220             filtertype: 'tine.widget.keyfield.filter', 
221             app: app, 
222             keyfieldName: 'attendeeRoles'
223         },
224         {filtertype: 'addressbook.contact', field: 'organizer', label: app.i18n._('Organizer')},
225         {filtertype: 'tinebase.tag', app: app}
226     ];
227 };
228
229 // register calendar filters in addressbook
230 Tine.widgets.grid.ForeignRecordFilter.OperatorRegistry.register('Addressbook', 'Contact', {
231     foreignRecordClass: 'Calendar.Event',
232     linkType: 'foreignId', 
233     filterName: 'ContactAttendeeFilter',
234     // _('Event (as attendee)')
235     label: 'Event (as attendee)'
236 });
237 Tine.widgets.grid.ForeignRecordFilter.OperatorRegistry.register('Addressbook', 'Contact', {
238     foreignRecordClass: 'Calendar.Event',
239     linkType: 'foreignId', 
240     filterName: 'ContactOrganizerFilter',
241     // _('Event (as organizer)')
242     label: 'Event (as organizer)'
243 });
244
245 // example for explicit definition
246 //Tine.widgets.grid.FilterRegistry.register('Addressbook', 'Contact', {
247 //    filtertype: 'foreignrecord',
248 //    foreignRecordClass: 'Calendar.Event',
249 //    linkType: 'foreignId', 
250 //    filterName: 'ContactAttendeeFilter',
251 //    // _('Event attendee')
252 //    label: 'Event attendee'
253 //});
254
255 /**
256  * @namespace Tine.Calendar.Model
257  * @class Tine.Calendar.Model.EventJsonBackend
258  * @extends Tine.Tinebase.data.RecordProxy
259  * 
260  * JSON backend for events
261  */
262 Tine.Calendar.Model.EventJsonBackend = Ext.extend(Tine.Tinebase.data.RecordProxy, {
263     
264     /**
265      * Creates a recuring event exception
266      * 
267      * @param {Tine.Calendar.Model.Event} event
268      * @param {Boolean} deleteInstance
269      * @param {Boolean} deleteAllFollowing
270      * @param {Object} options
271      * @return {String} transaction id
272      */
273     createRecurException: function(event, deleteInstance, deleteAllFollowing, checkBusyConflicts, options) {
274         options = options || {};
275         options.params = options.params || {};
276         options.beforeSuccess = function(response) {
277             return [this.recordReader(response)];
278         };
279         
280         var p = options.params;
281         p.method = this.appName + '.createRecurException';
282         p.recordData = event.data;
283         p.deleteInstance = deleteInstance ? 1 : 0;
284         p.deleteAllFollowing = deleteAllFollowing ? 1 : 0;
285         p.checkBusyConflicts = checkBusyConflicts ? 1 : 0;
286         
287         return this.doXHTTPRequest(options);
288     },
289     
290     /**
291      * delete a recuring event series
292      * 
293      * @param {Tine.Calendar.Model.Event} event
294      * @param {Object} options
295      * @return {String} transaction id
296      */
297     deleteRecurSeries: function(event, options) {
298         options = options || {};
299         options.params = options.params || {};
300         
301         var p = options.params;
302         p.method = this.appName + '.deleteRecurSeries';
303         p.recordData = event.data;
304         
305         return this.doXHTTPRequest(options);
306     },
307     
308     
309     /**
310      * updates a recuring event series
311      * 
312      * @param {Tine.Calendar.Model.Event} event
313      * @param {Object} options
314      * @return {String} transaction id
315      */
316     updateRecurSeries: function(event, checkBusyConflicts, options) {
317         options = options || {};
318         options.params = options.params || {};
319         options.beforeSuccess = function(response) {
320             return [this.recordReader(response)];
321         };
322         
323         var p = options.params;
324         p.method = this.appName + '.updateRecurSeries';
325         p.recordData = event.data;
326         p.checkBusyConflicts = checkBusyConflicts ? 1 : 0;
327         
328         return this.doXHTTPRequest(options);
329     }
330 });
331
332 /*
333  * default event backend
334  */
335 if (Tine.Tinebase.widgets) {
336     Tine.Calendar.backend = new Tine.Calendar.Model.EventJsonBackend({
337         appName: 'Calendar',
338         modelName: 'Event',
339         recordClass: Tine.Calendar.Model.Event
340     });
341 } else {
342     Tine.Calendar.backend = new Tine.Tinebase.data.MemoryBackend({
343         appName: 'Calendar',
344         modelName: 'Event',
345         recordClass: Tine.Calendar.Model.Event
346     });
347 }
348
349 /**
350  * @namespace Tine.Calendar.Model
351  * @class Tine.Calendar.Model.Attender
352  * @extends Tine.Tinebase.data.Record
353  * Attender Record Definition
354  */
355 Tine.Calendar.Model.Attender = Tine.Tinebase.data.Record.create([
356     {name: 'id'},
357     {name: 'cal_event_id'},
358     {name: 'user_id', sortType: Tine.Tinebase.common.accountSortType },
359     {name: 'user_type'},
360     {name: 'role', type: 'keyField', keyFieldConfigName: 'attendeeRoles'},
361     {name: 'quantity'},
362     {name: 'status', type: 'keyField', keyFieldConfigName: 'attendeeStatus'},
363     {name: 'status_authkey'},
364     {name: 'displaycontainer_id'},
365     {name: 'alarm_ack_time', type: 'date', dateFormat: Date.patterns.ISO8601Long},
366     {name: 'alarm_snooze_time', type: 'date', dateFormat: Date.patterns.ISO8601Long},
367     {name: 'transp'},
368     {name: 'checked'} // filter grid helper field
369 ], {
370     appName: 'Calendar',
371     modelName: 'Attender',
372     idProperty: 'id',
373     titleProperty: 'name',
374     // ngettext('Attender', 'Attendee', n); gettext('Attendee');
375     recordName: 'Attender',
376     recordsName: 'Attendee',
377     containerProperty: 'cal_event_id',
378     // ngettext('Event', 'Events', n); gettext('Events');
379     containerName: 'Event',
380     containersName: 'Events',
381     
382     /**
383      * gets name of attender
384      * 
385      * @return {String}
386      *
387     getName: function() {
388         var user_id = this.get('user_id');
389         if (! user_id) {
390             return Tine.Tinebase.appMgr.get('Calendar').i18n._('No Information');
391         }
392         
393         var userData = (typeof user_id.get == 'function') ? user_id.data : user_id;
394     },
395     */
396     
397     /**
398      * returns account_id if attender is/has a user account
399      * 
400      * @return {String}
401      */
402     getUserAccountId: function() {
403         var user_type = this.get('user_type');
404         if (user_type == 'user' || user_type == 'groupmember') {
405             var user_id = this.get('user_id');
406             if (! user_id) {
407                 return null;
408             }
409             
410             // we expect user_id to be a user or contact object or record
411             if (typeof user_id.get == 'function') {
412                 if (user_id.get('contact_id')) {
413                     // user_id is a account record
414                     return user_id.get('accountId');
415                 } else {
416                     // user_id is a contact record
417                     return user_id.get('account_id');
418                 }
419             } else if (user_id.hasOwnProperty('contact_id')) {
420                 // user_id contains account data
421                 return user_id.accountId;
422             } else if (user_id.hasOwnProperty('account_id')) {
423                 // user_id contains contact data
424                 return user_id.account_id;
425             }
426             
427             // this might happen if contact resolved, due to right restrictions
428             return user_id;
429             
430         }
431         return null;
432     },
433     
434     /**
435      * returns id of attender of any kind
436      */
437     getUserId: function() {
438         var user_id = this.get('user_id');
439         if (! user_id) {
440             return null;
441         }
442         
443         var userData = (typeof user_id.get == 'function') ? user_id.data : user_id;
444         
445         if (!userData) {
446             return null;
447         }
448         
449         if (typeof userData != 'object') {
450             return userData;
451         }
452         
453         switch (this.get('user_type')) {
454             case 'user':
455             case 'groupmember':
456             case 'memberOf':
457                 if (userData.hasOwnProperty('contact_id')) {
458                     // userData contains account
459                     return userData.contact_id;
460                 } else if (userData.hasOwnProperty('account_id')) {
461                     // userData contains contact
462                     return userData.id;
463                 }
464                 break;
465             default:
466                 return userData.id
467                 break;
468         }
469     }
470 });
471
472 /**
473  * @namespace Tine.Calendar.Model
474  * 
475  * get default data for a new attender
476  *  
477  * @return {Object} default data
478  * @static
479  */ 
480 Tine.Calendar.Model.Attender.getDefaultData = function() {
481     return {
482         user_type: 'user',
483         role: 'REQ',
484         quantity: 1,
485         status: 'NEEDS-ACTION'
486     };
487 };
488
489 /**
490  * @namespace Tine.Calendar.Model
491  * 
492  * creates store of attender objects
493  * 
494  * @param  {Array} attendeeData
495  * @return {Ext.data.Store}
496  * @static
497  */ 
498 Tine.Calendar.Model.Attender.getAttendeeStore = function(attendeeData) {
499     var attendeeStore = new Ext.data.SimpleStore({
500         fields: Tine.Calendar.Model.Attender.getFieldDefinitions(),
501         sortInfo: {field: 'user_id', direction: 'ASC'}
502     });
503     
504     Ext.each(attendeeData, function(attender) {
505         if (attender) {
506             var record = new Tine.Calendar.Model.Attender(attender, attender.id && Ext.isString(attender.id) ? attender.id : Ext.id());
507             attendeeStore.addSorted(record);
508         }
509     });
510     
511     return attendeeStore;
512 };
513
514 /**
515  * returns attender record of current account if exists, else false
516  * @static
517  */
518 Tine.Calendar.Model.Attender.getAttendeeStore.getMyAttenderRecord = function(attendeeStore) {
519         var currentAccountId = Tine.Tinebase.registry.get('currentAccount').accountId;
520         var myRecord = false;
521         
522         attendeeStore.each(function(attender) {
523             var userAccountId = attender.getUserAccountId();
524             if (userAccountId == currentAccountId) {
525                 myRecord = attender;
526                 return false;
527             }
528         }, this);
529         
530         return myRecord;
531     }
532     
533 /**
534  * returns attendee record of given attendee if exists, else false
535  * @static
536  */
537 Tine.Calendar.Model.Attender.getAttendeeStore.getAttenderRecord = function(attendeeStore, attendee) {
538     var attendeeRecord = false;
539     
540     attendeeStore.each(function(r) {
541         if (r.get('user_type') == attendee.get('user_type') && r.getUserId() == attendee.getUserId()) {
542             attendeeRecord = r;
543             return false;
544         }
545     }, this);
546     
547     return attendeeRecord;
548 }
549
550 /**
551  * @namespace Tine.Calendar.Model
552  * @class Tine.Calendar.Model.Resource
553  * @extends Tine.Tinebase.data.Record
554  * Resource Record Definition
555  */
556 Tine.Calendar.Model.Resource = Tine.Tinebase.data.Record.create(Tine.Tinebase.Model.genericFields.concat([
557     {name: 'id'},
558     {name: 'name'},
559     {name: 'description'},
560     {name: 'email'},
561     {name: 'is_location', type: 'bool'},
562     {name: 'tags'},
563     {name: 'notes'},
564     {name: 'grants'}
565 ]), {
566     appName: 'Calendar',
567     modelName: 'Resource',
568     idProperty: 'id',
569     titleProperty: 'name',
570     // ngettext('Resource', 'Resources', n); gettext('Resources');
571     recordName: 'Resource',
572     recordsName: 'Resources'
573 });
574
575 /**
576  * @namespace   Tine.Calendar.Model
577  * @class       Tine.Calendar.Model.iMIP
578  * @extends     Tine.Tinebase.data.Record
579  * iMIP Record Definition
580  */
581 Tine.Calendar.Model.iMIP = Tine.Tinebase.data.Record.create([
582     {name: 'id'},
583     {name: 'ics'},
584     {name: 'method'},
585     {name: 'originator'},
586     {name: 'userAgent'},
587     {name: 'event'},
588     {name: 'existing_event'},
589     {name: 'preconditions'}
590 ], {
591     appName: 'Calendar',
592     modelName: 'iMIP',
593     idProperty: 'id'
594 });