7cd2b588d4d65d652a56039c73a75a910645c4be
[tine20] / tine20 / Calendar / Model / Event.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Calendar
6  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
7  * @author      Cornelius Weiss <c.weiss@metaways.de>
8  * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
9  *
10  */
11
12 /**
13  * Model of an event
14  * 
15  * Recuring Notes: 
16  *  - deleted recurring exceptions are stored in exdate (array of datetimes)
17  *  - modified recurring exceptions have their own event with recurid set the uid-dtstart
18  *    of the originators event (@see RFC2445)
19  *  - as id is unique, each modified recurring event has its own id
20  *  - rrule is stored in RCF2445 format
21  *  - the rrule_until is redundat to the rrule until property for fast queries
22  *  - we don't use rrule count, they are converted to an until
23  *  - like always in tine, we save all dates in UTC, but to correctly compute
24  *    recurring events, we also save the timezone of the organizer
25  *  - despite RFC2445 we have an expicit isAllDayEvent property
26  * 
27  * @package Calendar
28  * @property Tinebase_Record_RecordSet alarms
29  * @property Tinebase_DateTime creation_time
30  * @property string is_all_day_event
31  * @property string originator_tz
32  * @property string seq
33  * @property string uid
34  * @property int container_id
35  */
36 class Calendar_Model_Event extends Tinebase_Record_Abstract
37 {
38     const TRANSP_TRANSP        = 'TRANSPARENT';
39     const TRANSP_OPAQUE        = 'OPAQUE';
40     
41     const CLASS_PUBLIC         = 'PUBLIC';
42     const CLASS_PRIVATE        = 'PRIVATE';
43     //const CLASS_CONFIDENTIAL   = 'CONFIDENTIAL';
44     
45     const STATUS_CONFIRMED     = 'CONFIRMED';
46     const STATUS_TENTATIVE     = 'TENTATIVE';
47     const STATUS_CANCELED      = 'CANCELED';
48     
49     const RANGE_ALL           = 'ALL';
50     const RANGE_THIS          = 'THIS';
51     const RANGE_THISANDFUTURE = 'THISANDFUTURE';
52     
53     /**
54      * key in $_validators/$_properties array for the filed which 
55      * represents the identifier
56      * 
57      * @var string
58      */
59     protected $_identifier = 'id';
60     
61     /**
62      * application the record belongs to
63      *
64      * @var string
65      */
66     protected $_application = 'Calendar';
67     
68     /**
69      * validators
70      *
71      * @var array
72      */
73     protected $_validators = array(
74         // tine record fields
75         'id'                   => array(Zend_Filter_Input::ALLOW_EMPTY => true,  /*'Alnum'*/),
76         'container_id'         => array(Zend_Filter_Input::ALLOW_EMPTY => true,  'Int'  ),
77         'created_by'           => array(Zend_Filter_Input::ALLOW_EMPTY => true,         ),
78         'creation_time'        => array(Zend_Filter_Input::ALLOW_EMPTY => true          ),
79         'last_modified_by'     => array(Zend_Filter_Input::ALLOW_EMPTY => true          ),
80         'last_modified_time'   => array(Zend_Filter_Input::ALLOW_EMPTY => true          ),
81         'is_deleted'           => array(Zend_Filter_Input::ALLOW_EMPTY => true          ),
82         'deleted_time'         => array(Zend_Filter_Input::ALLOW_EMPTY => true          ),
83         'deleted_by'           => array(Zend_Filter_Input::ALLOW_EMPTY => true          ),
84         'seq'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true,  'Int'  ),
85         // calendar only fields
86         'dtend'                => array(Zend_Filter_Input::ALLOW_EMPTY => true          ),
87         'transp'               => array(
88             Zend_Filter_Input::ALLOW_EMPTY => true,
89             array('InArray', array(self::TRANSP_OPAQUE, self::TRANSP_TRANSP))
90         ),
91         // ical common fields
92         'class'                => array(
93             Zend_Filter_Input::ALLOW_EMPTY => true,
94             array('InArray', array(self::CLASS_PUBLIC, self::CLASS_PRIVATE, /*self::CLASS_CONFIDENTIAL*/))
95         ),
96         'description'          => array(Zend_Filter_Input::ALLOW_EMPTY => true          ),
97         'geo'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => NULL),
98         'location'             => array(Zend_Filter_Input::ALLOW_EMPTY => true          ),
99         'organizer'            => array(Zend_Filter_Input::ALLOW_EMPTY => false,        ),
100         'priority'             => array(Zend_Filter_Input::ALLOW_EMPTY => true, 'Int'   ),
101         'status'            => array(
102             Zend_Filter_Input::ALLOW_EMPTY => true,
103             array('InArray', array(self::STATUS_CONFIRMED, self::STATUS_TENTATIVE, self::STATUS_CANCELED))
104         ),
105         'summary'              => array(Zend_Filter_Input::ALLOW_EMPTY => true          ),
106         'url'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true          ),
107         'uid'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true          ),
108         // ical common fields with multiple appearance
109         //'attach'                => array(Zend_Filter_Input::ALLOW_EMPTY => true         ),
110         'attendee'              => array(Zend_Filter_Input::ALLOW_EMPTY => true         ), // RecordSet of Calendar_Model_Attender
111         'alarms'                => array(Zend_Filter_Input::ALLOW_EMPTY => true         ), // RecordSet of Tinebase_Model_Alarm
112         'tags'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true         ), // originally categories handled by Tinebase_Tags
113         'notes'                 => array(Zend_Filter_Input::ALLOW_EMPTY => true         ), // originally comment handled by Tinebase_Notes
114         'relations'             => array(Zend_Filter_Input::ALLOW_EMPTY => true         ),
115         'attachments'           => array(Zend_Filter_Input::ALLOW_EMPTY => true),
116         
117         //'contact'               => array(Zend_Filter_Input::ALLOW_EMPTY => true         ),
118         //'related'               => array(Zend_Filter_Input::ALLOW_EMPTY => true         ),
119         //'resources'             => array(Zend_Filter_Input::ALLOW_EMPTY => true         ),
120         //'rstatus'               => array(Zend_Filter_Input::ALLOW_EMPTY => true         ),
121         // ical scheduleable interface fields
122         'dtstart'               => array(Zend_Filter_Input::ALLOW_EMPTY => true         ),
123         'recurid'               => array(Zend_Filter_Input::ALLOW_EMPTY => true         ),
124         // ical scheduleable interface fields with multiple appearance
125         'exdate'                => array(Zend_Filter_Input::ALLOW_EMPTY => true         ), //  array of Tinebase_DateTimeTinebase_DateTime's
126         //'exrule'                => array(Zend_Filter_Input::ALLOW_EMPTY => true         ),
127         //'rdate'                 => array(Zend_Filter_Input::ALLOW_EMPTY => true         ),
128         'rrule'                 => array(Zend_Filter_Input::ALLOW_EMPTY => true         ),
129         // calendar helper fields
130
131         'is_all_day_event'      => array(Zend_Filter_Input::ALLOW_EMPTY => true         ),
132         'rrule_until'           => array(Zend_Filter_Input::ALLOW_EMPTY => true         ),
133         'originator_tz'         => array(Zend_Filter_Input::ALLOW_EMPTY => true         ),
134
135         // relations
136         'relations'             => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => NULL),
137         'customfields'          => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => array()),
138         
139         // grant helper fields
140         Tinebase_Model_Grants::GRANT_FREEBUSY => array(Zend_Filter_Input::ALLOW_EMPTY => true),
141         Tinebase_Model_Grants::GRANT_READ     => array(Zend_Filter_Input::ALLOW_EMPTY => true),
142         Tinebase_Model_Grants::GRANT_SYNC     => array(Zend_Filter_Input::ALLOW_EMPTY => true),
143         Tinebase_Model_Grants::GRANT_EXPORT   => array(Zend_Filter_Input::ALLOW_EMPTY => true),
144         Tinebase_Model_Grants::GRANT_EDIT     => array(Zend_Filter_Input::ALLOW_EMPTY => true),
145         Tinebase_Model_Grants::GRANT_DELETE   => array(Zend_Filter_Input::ALLOW_EMPTY => true),
146         Tinebase_Model_Grants::GRANT_PRIVATE  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
147     );
148     
149     /**
150      * datetime fields
151      *
152      * @var array
153      */
154     protected $_datetimeFields = array(
155         'creation_time', 
156         'last_modified_time', 
157         'deleted_time', 
158         'completed', 
159         'dtstart', 
160         'dtend', 
161         'exdate',
162         //'rdate',
163         'rrule_until',
164     );
165     
166     /**
167      * name of fields that should be omited from modlog
168      *
169      * @var array list of modlog omit fields
170      */
171     protected $_modlogOmitFields = array(
172         Tinebase_Model_Grants::GRANT_READ,
173         Tinebase_Model_Grants::GRANT_SYNC,
174         Tinebase_Model_Grants::GRANT_EXPORT,
175         Tinebase_Model_Grants::GRANT_EDIT,
176         Tinebase_Model_Grants::GRANT_DELETE,
177         Tinebase_Model_Grants::GRANT_PRIVATE,
178     );
179     
180     /**
181      * sets record related properties
182      * 
183      * @param string _name of property
184      * @param mixed _value of property
185      * @throws Tinebase_Exception_UnexpectedValue
186      * @return void
187      */
188     public function __set($_name, $_value)
189     {
190         // ensure exdate as array
191         if ($_name == 'exdate' && ! empty($_value) && ! is_array($_value) && ! $_value instanceof Tinebase_Record_RecordSet ) {
192             $_value = array($_value);
193         }
194         
195         if ($_name == 'attendee' && is_array($_value)) {
196             $_value = new Tinebase_Record_RecordSet('Calendar_Model_Attender', $_value);
197         }
198         
199         if ($_name == 'rrule' && is_string($_value) && ! empty($_value)) {
200             // normalize rrule
201             $_value = new Calendar_Model_Rrule($_value);
202             $_value = (string) $_value;
203         }
204         parent::__set($_name, $_value);
205     }
206     
207     /**
208      * the constructor
209      * it is needed because we have more validation fields in Calendars
210      * 
211      * @param mixed $_data
212      * @param bool $bypassFilters sets {@see this->bypassFilters}
213      * @param bool $convertDates sets {@see $this->convertDates}
214      */
215     public function __construct($_data = NULL, $_bypassFilters = false, $_convertDates = true)
216     {
217         $this->_filters['organizer'] = new Zend_Filter_Empty(NULL);
218         
219         parent::__construct($_data, $_bypassFilters, $_convertDates);
220     }
221     
222     /**
223      * (non-PHPdoc)
224      * @see Tinebase_Record_Abstract::diff()
225      */
226     public function diff($record, $omitFields = array())
227     {
228         $checkRrule = false;
229         if (! in_array('rrule', $omitFields)) {
230             $omitFields[] = 'rrule';
231             $checkRrule = true;
232         }
233         
234         $diff = parent::diff($record, $omitFields);
235         
236         if ($checkRrule) {
237             $ownRrule    = ! $this->rrule instanceof Calendar_Model_Rrule ? Calendar_Model_Rrule::getRruleFromString((string) $this->rrule) : $this->rrule;
238             $recordRrule = ! $record->rrule instanceof Calendar_Model_Rrule ? Calendar_Model_Rrule::getRruleFromString($record->rrule) : $record->rrule;
239             
240             $rruleDiff = $ownRrule->diff($recordRrule);
241             
242             // don't take small ( < one day) rrule_until changes as diff
243             if (
244                     $ownRrule->until instanceof Tinebase_DateTime 
245                     && (isset($rruleDiff->diff['until']) || array_key_exists('until', $rruleDiff->diff)) && $rruleDiff->diff['until'] instanceof Tinebase_DateTime
246                     && abs($rruleDiff->diff['until']->getTimestamp() - $ownRrule->until->getTimestamp()) < 86400
247             ){
248                 $rruleDiffArray = $rruleDiff->diff;
249                 unset($rruleDiffArray['until']);
250                 $rruleDiff->diff = $rruleDiffArray;
251             }
252             
253             if (! empty($rruleDiff->diff)) {
254                 $diffArray = $diff->diff;
255                 $diffArray['rrule'] = $rruleDiff;
256                 
257                 $diff->diff = $diffArray;
258             }
259         }
260         
261         return $diff;
262     }
263     /**
264      * add current user to attendee if he's organizer
265      * 
266      * @param bool $ifOrganizer      only add current user if he's organizer
267      * @param bool $ifNoOtherAttendee  only add current user if no other attendee are present
268      */
269     public function assertCurrentUserAsAttendee($ifOrganizer = TRUE, $ifNoOtherAttendee = FALSE)
270     {
271         if ($ifNoOtherAttendee && $this->attendee instanceof Tinebase_Record_RecordSet && $this->attendee->count() > 0) {
272             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
273                     __METHOD__ . '::' . __LINE__ . " not adding current user as attendee as other attendee are present.");
274             return;
275         }
276         
277         $ownAttender = Calendar_Model_Attender::getOwnAttender($this->attendee);
278         
279         if (! $ownAttender) {
280             if ($ifOrganizer && $this->organizer && $this->organizer != Tinebase_Core::getUser()->contact_id) {
281                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
282                     __METHOD__ . '::' . __LINE__ . " not adding current user as attendee as current user is not organizer.");
283             }
284             
285             else {
286                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
287                     __METHOD__ . '::' . __LINE__ . " adding current user as attendee.");
288                 
289                 $newAttender = new Calendar_Model_Attender(array(
290                     'user_id'   => Tinebase_Core::getUser()->contact_id,
291                     'user_type' => Calendar_Model_Attender::USERTYPE_USER,
292                     'status'    => Calendar_Model_Attender::STATUS_ACCEPTED,
293                     'role'      => Calendar_Model_Attender::ROLE_REQUIRED
294                 ));
295                 
296                 if (! $this->attendee instanceof Tinebase_Record_RecordSet) {
297                     $this->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender');
298                 }
299                 $this->attendee->addRecord($newAttender);
300             }
301         }
302     }
303     
304     /**
305      * returns the original dtstart of a recur series exception event 
306      *  -> when the event should have started with no exception
307      * 
308      * @return Tinebase_DateTime
309      */
310     public function getOriginalDtStart()
311     {
312         $origianlDtStart = $this->dtstart instanceof stdClass ? clone $this->dtstart : $this->dtstart;
313         
314         if ($this->isRecurException()) {
315             if ($this->recurid instanceof DateTime) {
316                 $origianlDtStart = clone $this->recurid;
317             } else if (is_string($this->recurid)) {
318                 $origianlDtStartString = substr($this->recurid, -19);
319                 if (! Tinebase_DateTime::isDate($origianlDtStartString)) {
320                     throw new Tinebase_Exception_InvalidArgument('recurid does not contain a valid original start date');
321                 }
322                 
323                 $origianlDtStart = new Tinebase_DateTime($origianlDtStartString, 'UTC');
324             }
325         }
326         
327         return $origianlDtStart;
328     }
329     
330     /**
331      * gets translated field name
332      * 
333      * NOTE: this has to be done explicitly as our field names are technically 
334      *       and have no translations
335      *       
336      * @param string         $_field
337      * @param Zend_Translate $_translation
338      * @return string
339      */
340     public static function getTranslatedFieldName($_field, $_translation)
341     {
342         $t = $_translation;
343         switch ($_field) {
344             case 'dtstart':           return $t->_('Start');
345             case 'dtend':             return $t->_('End');
346             case 'transp':            return $t->_('Blocking');
347             case 'class':             return $t->_('Classification');
348             case 'description':       return $t->_('Description');
349             case 'location':          return $t->_('Location');
350             case 'organizer':         return $t->_('Organizer');
351             case 'priority':          return $t->_('Priority');
352             case 'status':            return $t->_('Status');
353             case 'summary':           return $t->_('Summary');
354             case 'url':               return $t->_('Url');
355             case 'rrule':             return $t->_('Recurrance rule');
356             case 'is_all_day_event':  return $t->_('Is all day event');
357             case 'originator_tz':     return $t->_('Organizer timezone');
358             default:                  return $_field;
359         }
360     }
361     
362     /**
363      * gets translated value
364      * 
365      * NOTE: This is needed for values like Yes/No, Datetimes, etc.
366      * 
367      * @param  string           $_field
368      * @param  mixed            $_value
369      * @param  Zend_Translate   $_translation
370      * @param  string           $_timezone
371      * @return string
372      */
373     public static function getTranslatedValue($_field, $_value, $_translation, $_timezone)
374     {
375         if ($_value instanceof Tinebase_DateTime) {
376             $locale = new Zend_Locale($_translation->getAdapter()->getLocale());
377             return Tinebase_Translation::dateToStringInTzAndLocaleFormat($_value, $_timezone, $locale, 'datetime', true);
378         }
379         
380         switch ($_field) {
381             case 'transp':
382                 return $_value && $_value == Calendar_Model_Event::TRANSP_TRANSP ? $_translation->_('No') : $_translation->_('Yes');
383             case 'organizer':
384                 if (! $_value instanceof Addressbook_Model_Contact) {
385                     $organizer = Addressbook_Controller_Contact::getInstance()->getMultiple($_value, TRUE)->getFirstRecord();
386                 }
387                 return $organizer instanceof Addressbook_Model_Contact ? $organizer->n_fileas : '';
388             case 'rrule':
389                 if ($_value) {
390                     $rrule = $_value instanceof Calendar_Model_Rrule ? $_value : new Calendar_Model_Rrule($_value);
391                     return $rrule->getTranslatedRule($_translation);
392                 }
393             default:
394                 return $_value;
395         }
396     }
397     
398     /**
399      * checks event for given grant
400      * 
401      * @param  string $_grant
402      * @return bool
403      */
404     public function hasGrant($_grant)
405     {
406         $hasGrant = (isset($this->_properties[$_grant]) || array_key_exists($_grant, $this->_properties)) && (bool)$this->{$_grant};
407         
408         if ($this->class !== Calendar_Model_Event::CLASS_PUBLIC) {
409             $hasGrant &= (
410                 // private grant
411                 $this->{Tinebase_Model_Grants::GRANT_PRIVATE} ||
412                 // I'm organizer
413                 Tinebase_Core::getUser()->contact_id == ($this->organizer instanceof Addressbook_Model_Contact ? $this->organizer->getId() : $this->organizer) ||
414                 // I'm attendee
415                 Calendar_Model_Attender::getOwnAttender($this->attendee)
416             );
417         }
418         
419         return $hasGrant;
420     }
421     
422     /**
423      * event is an exception of a recur event series
424      * 
425      * @return boolean
426      */
427     public function isRecurException()
428     {
429         return !!$this->recurid;
430     }
431     
432     /**
433      * sets recurId of this model
434      * 
435      * @return string recurid which was set
436      */
437     public function setRecurId()
438     {
439         if (! ($this->uid && $this->dtstart)) {
440             throw new Exception ('uid _and_ dtstart must be set to generate recurid');
441         }
442         
443         // make sure we store recurid in utc
444         $dtstart = $this->getOriginalDtStart();
445         $dtstart->setTimezone('UTC');
446         
447         $this->recurid = $this->uid . '-' . $dtstart->get(Tinebase_Record_Abstract::ISO8601LONG);
448         
449         return $this->recurid;
450     }
451     
452     /**
453      * sets rrule until helper field
454      *
455      * @return void
456      */
457     public function setRruleUntil()
458     {
459         if (empty($this->rrule)) {
460             $this->rrule_until = NULL;
461         } else {
462             $rrule = $this->rrule;
463             if (! $this->rrule instanceof Calendar_Model_Rrule) {
464                 $rrule = new Calendar_Model_Rrule(array());
465                 $rrule->setFromString($this->rrule);
466                 $this->rrule = $rrule;
467             }
468             
469             if (isset($rrule->count)) {
470                 $this->rrule_until = NULL;
471                 $exdates = $this->exdate;
472                 $this->exdate = NULL;
473                 
474                 $lastOccurrence = Calendar_Model_Rrule::computeNextOccurrence($this, new Tinebase_Record_RecordSet('Calendar_Model_Event'), $this->dtend, $rrule->count -1);
475                 $this->rrule_until = $lastOccurrence->dtend;
476                 $this->exdate = $exdates;
477             } else {
478                 // set until to end of day in organizers timezone.
479                 // NOTE: this is in contrast to the iCal spec which says until should be the
480                 //       dtstart of the last occurence. But as the client with the name of the
481                 //       spec sets it to the end of the day, we do it also.
482                 if ($rrule->until instanceof Tinebase_DateTime) {
483                     $rrule->until->setTimezone($this->originator_tz);
484                     // NOTE: subSecond cause some clients send 00:00:00 for midnight
485                     $rrule->until->subSecond(1)->setTime(23, 59, 59);
486                     $rrule->until->setTimezone('UTC');
487                 }
488                 
489                 $this->rrule_until = $rrule->until;
490             }
491         }
492         
493         if ($this->rrule_until && $this->rrule_until < $this->dtstart) {
494             throw new Tinebase_Exception_Record_Validation('rrule until must not be before dtstart');
495         }
496     }
497     
498     /**
499      * cleans up data to only contain freebusy infos
500      * removes all fields except dtstart/dtend/id/modlog fields
501      * 
502      * @return boolean TRUE if cleanup took place
503      */
504     public function doFreeBusyCleanup()
505     {
506         if ($this->hasGrant(Tinebase_Model_Grants::GRANT_READ)) {
507            return FALSE;
508         }
509         
510         $this->_properties = array_intersect_key($this->_properties, array_flip(array(
511             'id', 
512             'dtstart', 
513             'dtend',
514             'transp',
515             'seq',
516             'uid',
517             'is_all_day_event',
518             'rrule',
519             'rrule_until',
520             'recurid',
521             'exdate',
522             'originator_tz',
523             'attendee', // if we remove this, we need to adopt attendee resolveing
524             'container_id',
525             'created_by',
526             'creation_time',
527             'last_modified_by',
528             'last_modified_time',
529             'is_deleted',
530             'deleted_time',
531             'deleted_by',
532         )));
533         
534         return TRUE;
535     }
536     
537     /**
538      * sets the record related properties from user generated input.
539      * 
540      * Input-filtering and validation by Zend_Filter_Input can enabled and disabled
541      *
542      * @param array $_data            the new data to set
543      * @throws Tinebase_Exception_Record_Validation when content contains invalid or missing data
544      */
545     public function setFromArray(array $_data)
546     {
547         if (empty($_data['geo'])) {
548             $_data['geo'] = NULL;
549         }
550         
551         if (empty($_data['class'])) {
552             $_data['class'] = self::CLASS_PUBLIC;
553         }
554         
555         if (empty($_data['priority'])) {
556             $_data['priority'] = NULL;
557         }
558         
559         if (empty($_data['status'])) {
560             $_data['status'] = self::STATUS_CONFIRMED;
561         }
562         
563         if (isset($_data['container_id']) && is_array($_data['container_id'])) {
564             $_data['container_id'] = $_data['container_id']['id'];
565         }
566         
567         if (isset($_data['organizer']) && is_array($_data['organizer'])) {
568             $_data['organizer'] = $_data['organizer']['id'];
569         }
570         
571         if (isset($_data['attendee']) && is_array($_data['attendee'])) {
572             $_data['attendee'] = new Tinebase_Record_RecordSet('Calendar_Model_Attender', $_data['attendee'], $this->bypassFilters, $this->convertDates);
573         }
574         
575         if (isset($_data['rrule']) && ! empty($_data['rrule']) && ! $_data['rrule'] instanceof Calendar_Model_Rrule) {
576             // rrule can be array or string
577             $_data['rrule'] = new Calendar_Model_Rrule($_data['rrule'], $this->bypassFilters, $this->convertDates);
578         }
579         
580         if (isset($_data['alarms']) && is_array($_data['alarms'])) {
581             $_data['alarms'] = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', $_data['alarms'], TRUE, $this->convertDates);
582         }
583         
584         parent::setFromArray($_data);
585     }
586     
587     /**
588      * checks if event matches period filter
589      * 
590      * @param Calendar_Model_PeriodFilter $_period
591      * @return boolean
592      */
593     public function isInPeriod(Calendar_Model_PeriodFilter $_period)
594     {
595         $result = TRUE;
596         
597         if ($this->dtend->compare($_period->getFrom()) == -1 || $this->dtstart->compare($_period->getUntil()) == 1) {
598             $result = FALSE;
599         }
600         
601         return $result;
602     }
603     
604     /**
605      * returns TRUE if comparison detects a resechedule / significant change
606      * 
607      * @param  Calendar_Model_Event $_event
608      * @return bool
609      */
610     public function isRescheduled($_event)
611     {
612         $diff = $this->diff($_event)->diff;
613         
614         return (isset($diff['dtstart']) || array_key_exists('dtstart', $diff))
615             || (! $this->is_all_day_event && (isset($diff['dtend']) || array_key_exists('dtend', $diff)))
616             || (isset($diff['rrule']) || array_key_exists('rrule', $diff));
617     }
618     
619     /**
620      * sets and returns the addressbook entry of the organizer
621      * 
622      * @return Addressbook_Model_Contact
623      */
624     public function resolveOrganizer()
625     {
626         if (! empty($this->organizer) && ! $this->organizer instanceof Addressbook_Model_Contact) {
627             $contacts = Addressbook_Controller_Contact::getInstance()->getMultiple($this->organizer, TRUE);
628             if (count($contacts)) {
629                 $this->organizer = $contacts->getFirstRecord();
630             }
631         }
632         
633         return $this->organizer;
634     }
635     
636     /**
637      * checks if given attendee is organizer of this event
638      * 
639      * @param Calendar_Model_Attender $_attendee
640      */
641     public function isOrganizer($_attendee=NULL)
642     {
643         $organizerContactId = NULL;
644         if ($_attendee && in_array($_attendee->user_type, array(Calendar_Model_Attender::USERTYPE_USER, Calendar_Model_Attender::USERTYPE_GROUPMEMBER))) {
645             $organizerContactId = $_attendee->user_id instanceof Tinebase_Record_Abstract ? $_attendee->user_id->getId() : $_attendee->user_id;
646         } else {
647             $organizerContactId = Tinebase_Core::getUser()->contact_id;
648         }
649         
650         return $organizerContactId == ($this->organizer instanceof Tinebase_Record_Abstract ? $this->organizer->getId() : $this->organizer);
651     }
652 }