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