Merge branch '2014.11' into 2014.11-develop
[tine20] / tine20 / Calendar / Controller / EventNotifications.php
1 <?php
2 /**
3  * Calendar Event Notifications
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-2013 Metaways Infosystems GmbH (http://www.metaways.de)
9  */
10
11 /**
12  * Calendar Event Notifications
13  *
14  * @package     Calendar
15  */
16  class Calendar_Controller_EventNotifications
17  {
18      const NOTIFICATION_LEVEL_NONE                      =  0;
19      const NOTIFICATION_LEVEL_INVITE_CANCEL             = 10;
20      const NOTIFICATION_LEVEL_EVENT_RESCHEDULE          = 20;
21      const NOTIFICATION_LEVEL_EVENT_UPDATE              = 30;
22      const NOTIFICATION_LEVEL_ATTENDEE_STATUS_UPDATE    = 40;
23      
24      const INVITATION_ATTACHMENT_MAX_FILESIZE           = 2097152; // 2 MB
25      
26     /**
27      * @var Calendar_Controller_EventNotifications
28      */
29     private static $_instance = NULL;
30     
31     /**
32      * don't clone. Use the singleton.
33      *
34      */
35     private function __clone() 
36     {
37     }
38     
39     /**
40      * the singleton pattern
41      *
42      * @return Calendar_Controller_EventNotifications
43      */
44     public static function getInstance() 
45     {
46         if (self::$_instance === NULL) {
47             self::$_instance = new Calendar_Controller_EventNotifications();
48         }
49         
50         return self::$_instance;
51     }
52     
53     /**
54      * constructor
55      * 
56      */
57     private function __construct()
58     {
59         
60     }
61     
62     /**
63      * get updates of human interest
64      * 
65      * @param  Calendar_Model_Event $_event
66      * @param  Calendar_Model_Event $_oldEvent
67      * @return array
68      */
69     protected function _getUpdates($_event, $_oldEvent)
70     {
71         // check event details
72         $diff = $_event->diff($_oldEvent)->diff;
73         
74         $orderedUpdateFieldOfInterest = array(
75             'dtstart', 'dtend', 'rrule', 'summary', 'location', 'description',
76             'transp', 'priority', 'status', 'class',
77             'url', 'is_all_day_event', 'originator_tz', /*'tags', 'notes',*/
78         );
79         
80         $updates = array();
81         foreach ($orderedUpdateFieldOfInterest as $field) {
82             if ((isset($diff[$field]) || array_key_exists($field, $diff))) {
83                 $updates[$field] = $diff[$field];
84             }
85         }
86         
87         // rrule legacy
88         if ((isset($updates['rrule']) || array_key_exists('rrule', $updates))) {
89             $updates['rrule'] = $_oldEvent->rrule;
90         }
91         
92         // check for organizer update
93         if (Tinebase_Record_Abstract::convertId($_event['organizer'], 'Addressbook_Model_Contact') != 
94             Tinebase_Record_Abstract::convertId($_oldEvent['organizer'], 'Addressbook_Model_Contact')) {
95             
96             $updates['organizer'] = $_event->resolveOrganizer();
97         }
98         
99         // check attendee updates
100         $attendeeMigration = Calendar_Model_Attender::getMigration($_oldEvent->attendee, $_event->attendee);
101         foreach ($attendeeMigration['toUpdate'] as $attendee) {
102             $oldAttendee = Calendar_Model_Attender::getAttendee($_oldEvent->attendee, $attendee);
103             if ($attendee->status == $oldAttendee->status) {
104                 $attendeeMigration['toUpdate']->removeRecord($attendee);
105             }
106         }
107         
108         foreach($attendeeMigration as $action => $migration) {
109             Calendar_Model_Attender::resolveAttendee($migration, FALSE);
110             if (! count($migration)) {
111                 unset($attendeeMigration[$action]);
112             }
113         }
114         
115         if (! empty($attendeeMigration)) {
116             $updates['attendee'] = $attendeeMigration;
117         }
118         
119         return $updates;
120     }
121     
122     /**
123      * send notifications 
124      * 
125      * @param Calendar_Model_Event       $_event
126      * @param Tinebase_Model_FullAccount $_updater
127      * @param Sting                      $_action
128      * @param Calendar_Model_Event       $_oldEvent
129      * @param Tinebase_Model_Alarm       $_alarm
130      * @return void
131      */
132     public function doSendNotifications($_event, $_updater, $_action, $_oldEvent=NULL, $_alarm=NULL)
133     {
134         // we only send notifications to attendee
135         if (! $_event->attendee instanceof Tinebase_Record_RecordSet) {
136             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
137                 . " Event has no attendee");
138             return;
139         }
140
141         if ($_event->dtend === NULL) {
142             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
143                 . " ". print_r($_event->toArray(), TRUE));
144             throw new Tinebase_Exception_UnexpectedValue('no dtend set in event');
145         }
146         
147         if (Tinebase_DateTime::now()->subHour(1)->isLater($_event->dtend)) {
148             if ($_action == 'alarm' || ! ($_event->isRecurException() || $_event->rrule)) {
149                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
150                     . " Skip notifications to past events");
151                 return;
152             }
153         }
154         
155         $notificationPeriodConfig = Calendar_Config::getInstance()->get(Calendar_Config::MAX_NOTIFICATION_PERIOD_FROM);
156         if (Tinebase_DateTime::now()->subWeek($notificationPeriodConfig)->isLater($_event->dtend)) {
157             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
158                 . " Skip notifications to past events (MAX_NOTIFICATION_PERIOD_FROM: " . $notificationPeriodConfig . " week(s))");
159             return;
160         }
161         
162         // lets resolve attendee once as batch to fill cache
163         $attendee = clone $_event->attendee;
164         Calendar_Model_Attender::resolveAttendee($attendee);
165         
166         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
167             . " " . print_r($_event->toArray(), true));
168         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
169             . " Notification action: " . $_action);
170         
171         switch ($_action) {
172             case 'alarm':
173                 foreach($_event->attendee as $attender) {
174                     if (Calendar_Model_Attender::isAlarmForAttendee($attender, $_alarm)) {
175                         $this->sendNotificationToAttender($attender, $_event, $_updater, $_action, self::NOTIFICATION_LEVEL_NONE);
176                     }
177                 }
178                 break;
179             case 'booked':
180             case 'created':
181             case 'deleted':
182                 foreach($_event->attendee as $attender) {
183                     $this->sendNotificationToAttender($attender, $_event, $_updater, $_action, self::NOTIFICATION_LEVEL_INVITE_CANCEL);
184                 }
185                 break;
186             case 'changed':
187                 $attendeeMigration = Calendar_Model_Attender::getMigration($_oldEvent->attendee, $_event->attendee);
188                 
189                 foreach ($attendeeMigration['toCreate'] as $attender) {
190                     $this->sendNotificationToAttender($attender, $_event, $_updater, 'created', self::NOTIFICATION_LEVEL_INVITE_CANCEL);
191                 }
192                 
193                 foreach ($attendeeMigration['toDelete'] as $attender) {
194                     $this->sendNotificationToAttender($attender, $_oldEvent, $_updater, 'deleted', self::NOTIFICATION_LEVEL_INVITE_CANCEL);
195                 }
196                 
197                 // NOTE: toUpdate are all attendee to be notified
198                 if (count($attendeeMigration['toUpdate']) > 0) {
199                     $updates = $this->_getUpdates($_event, $_oldEvent);
200                     
201                     if (empty($updates)) {
202                         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " empty update, nothing to notify about");
203                         return;
204                     }
205                     
206                     // compute change type
207                     if (count(array_intersect(array('dtstart', 'dtend'), array_keys($updates))) > 0) {
208                         $notificationLevel = self::NOTIFICATION_LEVEL_EVENT_RESCHEDULE;
209                     } else if (count(array_diff(array_keys($updates), array('attendee'))) > 0) {
210                         $notificationLevel = self::NOTIFICATION_LEVEL_EVENT_UPDATE;
211                     } else {
212                         $notificationLevel = self::NOTIFICATION_LEVEL_ATTENDEE_STATUS_UPDATE;
213                     }
214                     
215                     // send notifications
216                     foreach ($attendeeMigration['toUpdate'] as $attender) {
217                         $this->sendNotificationToAttender($attender, $_event, $_updater, 'changed', $notificationLevel, $updates);
218                     }
219                 }
220                 
221                 break;
222                 
223             default:
224                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " unknown action '$_action'");
225                 break;
226                 
227         }
228         
229         // SEND REPLY/COUNTER to external organizer
230         if ($_event->organizer && ! $_event->resolveOrganizer()->account_id && count($_event->attendee) == 1) {
231             $updates = array('attendee' => array('toUpdate' => $_event->attendee));
232             $organizer = new Calendar_Model_Attender(array(
233                 'user_type'  => Calendar_Model_Attender::USERTYPE_USER,
234                 'user_id'    => $_event->resolveOrganizer()
235             ));
236             $this->sendNotificationToAttender($organizer, $_event, $_updater, 'changed', self::NOTIFICATION_LEVEL_ATTENDEE_STATUS_UPDATE, $updates);
237         }
238     }
239
240     /**
241      * send notification to a single attender
242      * 
243      * @param Calendar_Model_Attender    $_attender
244      * @param Calendar_Model_Event       $_event
245      * @param Tinebase_Model_FullAccount $_updater
246      * @param string                     $_action
247      * @param string                     $_notificationLevel
248      * @param array                      $_updates
249      * @return void
250      *
251      * TODO this needs major refactoring
252      */
253     public function sendNotificationToAttender(Calendar_Model_Attender $_attender, $_event, $_updater, $_action, $_notificationLevel, $_updates = NULL)
254     {
255         try {
256             $organizer = $_event->resolveOrganizer();
257             $organizerAccountId = $organizer->account_id;
258             $attendee = $_attender->getResolvedUser();
259
260             if ($attendee instanceof Addressbook_Model_List) {
261                 // don't send notification to lists as we already resolved the list members for individual mails
262                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
263                     . " Skip notification for list " . $attendee->name);
264                 return;
265             }
266
267             $attendeeAccountId = $_attender->getUserAccountId();
268             
269             $prefUserId = $attendeeAccountId ? $attendeeAccountId :
270                           ($organizerAccountId ? $organizerAccountId : 
271                           ($_event->created_by));
272             
273             try {
274                 $prefUser = Tinebase_User::getInstance()->getFullUserById($prefUserId);
275             } catch (Exception $e) {
276                 $prefUser = Tinebase_Core::getUser();
277                 $prefUserId = $prefUser->getId();
278             }
279             
280             // get prefered language, timezone and notification level
281             $locale = Tinebase_Translation::getLocale(Tinebase_Core::getPreference()->getValueForUser(Tinebase_Preference::LOCALE, $prefUserId));
282             $timezone = Tinebase_Core::getPreference()->getValueForUser(Tinebase_Preference::TIMEZONE, $prefUserId);
283             $translate = Tinebase_Translation::getTranslation('Calendar', $locale);
284             $sendLevel        = Tinebase_Core::getPreference('Calendar')->getValueForUser(Calendar_Preference::NOTIFICATION_LEVEL, $prefUserId);
285             $sendOnOwnActions = Tinebase_Core::getPreference('Calendar')->getValueForUser(Calendar_Preference::SEND_NOTIFICATION_OF_OWN_ACTIONS, $prefUserId);
286             
287             // external (non account) notification
288             if (! $attendeeAccountId) {
289                 // external organizer needs status updates
290                 $sendLevel = is_object($organizer) && $_attender->getEmail() == $organizer->getPreferedEmailAddress() ? 40 : 30;
291                 $sendOnOwnActions = false;
292             }
293
294             $recipients = array($attendee);
295
296             $this->_handleResourceEditors($_attender, $_notificationLevel, $recipients, $_action, $sendLevel);
297
298             // check if user wants this notification NOTE: organizer gets mails unless she set notificationlevel to NONE
299             // NOTE prefUser is organizer for external notifications
300             if (($attendeeAccountId == $_updater->getId() && ! $sendOnOwnActions) 
301                 || ($sendLevel < $_notificationLevel && (
302                         ((is_object($organizer) && method_exists($attendee, 'getPreferedEmailAddress') && $attendee->getPreferedEmailAddress() != $organizer->getPreferedEmailAddress())
303                         || (is_object($organizer) && !method_exists($attendee, 'getPreferedEmailAddress') && $attendee->email != $organizer->getPreferedEmailAddress()))
304                         || $sendLevel == self::NOTIFICATION_LEVEL_NONE)
305                    )
306                 ) {
307                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
308                     . " Preferred notification level not reached -> skipping notification for {$_attender->getEmail()}");
309                 return;
310             }
311
312             $method = NULL; // NOTE $method gets set in _getSubject as referenced param
313             $messageSubject = $this->_getSubject($_event, $_notificationLevel, $_action, $_updates, $timezone, $locale, $translate, $method, $_attender);
314
315             // we don't send iMIP parts to external attendee if config is active
316             if (Calendar_Config::getInstance()->get(Calendar_Config::DISABLE_EXTERNAL_IMIP) && ! $attendeeAccountId) {
317                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
318                     . " External iMIP is disabled.");
319                 $method = NULL;
320             }
321
322             $view = new Zend_View();
323             $view->setScriptPath(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'views');
324             
325             $view->translate    = $translate;
326             $view->timezone     = $timezone;
327             
328             $view->event        = $_event;
329             $view->updater      = $_updater;
330             $view->updates      = $_updates;
331             
332             $messageBody = $view->render('eventNotification.php');
333             
334             $calendarPart = null;
335             $attachments = $this->_getAttachments($method, $_event, $_action, $_updater, $calendarPart);
336             
337             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " receiver: '{$_attender->getEmail()}'");
338             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " subject: '$messageSubject'");
339             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " body: $messageBody");
340
341             $sender = $_action == 'alarm' ? $prefUser : $_updater;
342             Tinebase_Notification::getInstance()->send($sender, $recipients, $messageSubject, $messageBody, $calendarPart, $attachments);
343         } catch (Exception $e) {
344             Tinebase_Exception::log($e);
345             return;
346         }
347     }
348
349      /**
350       * @param $attender
351       * @param $_notificationLevel
352       * @param $recipients
353       * @param $action
354       * @param $sendLevel
355       * @return bool
356       */
357      protected function _handleResourceEditors($attender, $_notificationLevel, &$recipients, &$action, &$sendLevel)
358      {
359          // Add additional recipients for resources
360          if ($attender->user_type !== Calendar_Model_Attender::USERTYPE_RESOURCE
361          || ! Calendar_Config::getInstance()->get(Calendar_Config::RESOURCE_MAIL_FOR_EDITORS)) {
362              
363              return true;
364          }
365          
366          $resource = Calendar_Controller_Resource::getInstance()->get($attender->user_id);
367          if ($resource->suppress_notification) {
368              if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
369                      . " Do not send Notifications for this resource: ". $resource->name);
370              return true;
371          }
372
373          if ($action == 'created') {
374              $action = 'booked';
375          }
376
377          // Consider all notification level (Resource Users have a send level of 30)
378          $sendLevel = self::NOTIFICATION_LEVEL_ATTENDEE_STATUS_UPDATE;
379          $recipients = array_merge($recipients,
380              Calendar_Controller_Resource::getInstance()->getNotificationRecipients(
381                  Calendar_Controller_Resource::getInstance()->get($attender->user_id),
382                  $_notificationLevel
383              )
384          );
385      }
386     
387     /**
388      * get notification subject and method
389      * 
390      * @param Calendar_Model_Event $_event
391      * @param string $_notificationLevel
392      * @param string $_action
393      * @param array $_updates
394      * @param string $timezone
395      * @param Zend_Locale $locale
396      * @param Zend_Translate $translate
397      * @param atring $method
398      * @param Calendar_Model_Attender
399      * @return string
400      * @throws Tinebase_Exception_UnexpectedValue
401      */
402     protected function _getSubject($_event, $_notificationLevel, $_action, $_updates, $timezone, $locale, $translate, &$method, Calendar_Model_Attender $attender)
403     {
404         $startDateString = Tinebase_Translation::dateToStringInTzAndLocaleFormat($_event->dtstart, $timezone, $locale);
405         $endDateString = Tinebase_Translation::dateToStringInTzAndLocaleFormat($_event->dtend, $timezone, $locale);
406         
407         switch ($_action) {
408             case 'alarm':
409                 $messageSubject = sprintf($translate->_('Alarm for event "%1$s" at %2$s'), $_event->summary, $startDateString);
410                 break;
411             case 'created':
412                 $messageSubject = sprintf($translate->_('Event invitation "%1$s" at %2$s'), $_event->summary, $startDateString);
413                 $method = Calendar_Model_iMIP::METHOD_REQUEST;
414                 break;
415             case 'booked':
416                 if ($attender->user_type !== Calendar_Model_Attender::USERTYPE_RESOURCE) {
417                     throw new Tinebase_Exception_UnexpectedValue('not a resource');
418                 }
419                 $resource = Calendar_Controller_Resource::getInstance()->get($attender->user_id);
420                 $messageSubject = sprintf(
421                     $translate->_('Resource "%1$s" was booked for "%2$s" at %3$s'),
422                     $resource->name,
423                     $_event->summary,
424                     $startDateString
425                 );
426                 $method = Calendar_Model_iMIP::METHOD_REQUEST;
427                 break;
428             case 'deleted':
429                 $messageSubject = sprintf($translate->_('Event "%1$s" at %2$s has been canceled' ), $_event->summary, $startDateString);
430                 $method = Calendar_Model_iMIP::METHOD_CANCEL;
431                 break;
432             case 'changed':
433                 switch ($_notificationLevel) {
434                     case self::NOTIFICATION_LEVEL_EVENT_RESCHEDULE:
435                         if ((isset($_updates['dtstart']) || array_key_exists('dtstart', $_updates))) {
436                             $oldStartDateString = Tinebase_Translation::dateToStringInTzAndLocaleFormat($_updates['dtstart'], $timezone, $locale);
437                             $messageSubject = sprintf($translate->_('Event "%1$s" has been rescheduled from %2$s to %3$s' ), $_event->summary, $oldStartDateString, $startDateString);
438                             $method = Calendar_Model_iMIP::METHOD_REQUEST;
439                             break;
440                         }
441                         // fallthrough if dtstart didn't change
442                         
443                     case self::NOTIFICATION_LEVEL_EVENT_UPDATE:
444                         $messageSubject = sprintf($translate->_('Event "%1$s" at %2$s has been updated' ), $_event->summary, $startDateString);
445                         $method = Calendar_Model_iMIP::METHOD_REQUEST;
446                         break;
447                         
448                     case self::NOTIFICATION_LEVEL_ATTENDEE_STATUS_UPDATE:
449                         if(! empty($_updates['attendee']) && ! empty($_updates['attendee']['toUpdate']) && count($_updates['attendee']['toUpdate']) == 1) {
450                             // single attendee status update
451                             $attender = $_updates['attendee']['toUpdate']->getFirstRecord();
452                             
453                             switch ($attender->status) {
454                                 case Calendar_Model_Attender::STATUS_ACCEPTED:
455                                     $messageSubject = sprintf($translate->_('%1$s accepted event "%2$s" at %3$s' ), $attender->getName(), $_event->summary, $startDateString);
456                                     break;
457                                     
458                                 case Calendar_Model_Attender::STATUS_DECLINED:
459                                     $messageSubject = sprintf($translate->_('%1$s declined event "%2$s" at %3$s' ), $attender->getName(), $_event->summary, $startDateString);
460                                     break;
461                                     
462                                 case Calendar_Model_Attender::STATUS_TENTATIVE:
463                                     $messageSubject = sprintf($translate->_('Tentative response from %1$s for event "%2$s" at %3$s' ), $attender->getName(), $_event->summary, $startDateString);
464                                     break;
465                                     
466                                 case Calendar_Model_Attender::STATUS_NEEDSACTION:
467                                     $messageSubject = sprintf($translate->_('No response from %1$s for event "%2$s" at %3$s' ), $attender->getName(), $_event->summary, $startDateString);
468                                     break;
469                             }
470                         } else {
471                             $messageSubject = sprintf($translate->_('Attendee changes for event "%1$s" at %2$s' ), $_event->summary, $startDateString);
472                         }
473                         
474                         // we don't send iMIP parts to organizers with an account cause event is already up to date
475                         if ($_event->organizer && !$_event->resolveOrganizer()->account_id) {
476                             $method = Calendar_Model_iMIP::METHOD_REPLY;
477                         }
478                         break;
479                 }
480                 break;
481             default:
482                 $messageSubject = 'unknown action';
483                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " unknown action '$_action'");
484                 break;
485         }
486         
487         return $messageSubject;
488     }
489     
490     /**
491      * get notification attachments
492      * 
493      * @param string $method
494      * @param Calendar_Model_Event $event
495      * @param string $_action
496      * @param Tinebase_Model_FullAccount $updater
497      * @param Zend_Mime_Part $calendarPart
498      * @return array
499      */
500     protected function _getAttachments($method, $event, $_action, $updater, &$calendarPart)
501     {
502         if ($method === NULL) {
503             return array();
504         }
505         
506         $vcalendar = $this->_createVCalendar($event, $method, $updater);
507         
508         $calendarPart           = new Zend_Mime_Part($vcalendar->serialize());
509         $calendarPart->charset  = 'UTF-8';
510         $calendarPart->type     = 'text/calendar; method=' . $method;
511         $calendarPart->encoding = Zend_Mime::ENCODING_QUOTEDPRINTABLE;
512         
513         $attachment = new Zend_Mime_Part($vcalendar->serialize());
514         $attachment->type     = 'application/ics';
515         $attachment->encoding = Zend_Mime::ENCODING_QUOTEDPRINTABLE;
516         $attachment->disposition = Zend_Mime::DISPOSITION_ATTACHMENT;
517         $attachment->filename = 'event.ics';
518         
519         $attachments = array($attachment);
520         
521         // add other attachments (only on invitation)
522         if ($_action == 'created' || $_action == 'booked') {
523             $eventAttachments = $this->_getEventAttachments($event);
524             $attachments = array_merge($attachments, $eventAttachments);
525         }
526         
527         return $attachments;
528     }
529     
530     /**
531      * create iMIP VCALENDAR
532      * 
533      * @param Calendar_Model_Event $event
534      * @param string $method
535      * @param Tinebase_Model_FullAccount $updater
536      * @return Sabre\VObject\Component
537      */
538     protected function _createVCalendar($event, $method, $updater)
539     {
540         $converter = Calendar_Convert_Event_VCalendar_Factory::factory(Calendar_Convert_Event_VCalendar_Factory::CLIENT_GENERIC);
541         $converter->setMethod($method);
542         $vcalendar = $converter->fromTine20Model($event);
543         
544         foreach ($vcalendar->children() as $component) {
545             if ($component->name == 'VEVENT') {
546                 if ($method != Calendar_Model_iMIP::METHOD_REPLY && $event->organizer !== $updater->contact_id) {
547                     if (isset($component->{'ORGANIZER'})) {
548                         // in Tine 2.0 non organizers might be given the grant to update events
549                         // @see rfc6047 section 2.2.1 & rfc5545 section 3.2.18
550                         $component->{'ORGANIZER'}->add('SENT-BY', 'mailto:' . $updater->accountEmailAddress);
551                     }
552                 } else if ($method == Calendar_Model_iMIP::METHOD_REPLY) {
553                     // TODO in Tine 2.0 status updater might not be updater
554                     $component->{'REQUEST-STATUS'} = '2.0;Success';
555                 }
556             }
557         }
558         
559         return $vcalendar;
560     }
561     
562     /**
563      * get event attachments
564      * 
565      * @param Calendar_Model_Event $_event
566      * @return array of Zend_Mime_Part
567      */
568     protected function _getEventAttachments($_event)
569     {
570         $attachments = array();
571         foreach ($_event->attachments as $attachment) {
572             if ($attachment->size < self::INVITATION_ATTACHMENT_MAX_FILESIZE) {
573                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
574                     . " Adding attachment " . $attachment->name . ' to invitation mail');
575                 
576                 $path = Tinebase_Model_Tree_Node_Path::STREAMWRAPPERPREFIX
577                     . Tinebase_FileSystem_RecordAttachments::getInstance()->getRecordAttachmentPath($_event)
578                     . '/' . $attachment->name;
579                 
580                 $handle = fopen($path, 'r');
581                 $stream = fopen("php://temp", 'r+');
582                 stream_copy_to_stream($handle, $stream);
583                 rewind($stream);
584
585                 $part              = new Zend_Mime_Part($stream);
586                 $part->encoding    = Zend_Mime::ENCODING_BASE64; // ?
587                 $part->filename    = $attachment->name;
588                 $part->setTypeAndDispositionForAttachment($attachment->contenttype, $attachment->name);
589                 
590                 fclose($handle);
591                 
592                 $attachments[] = $part;
593                 
594             } else {
595                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
596                     . " Not adding attachment " . $attachment->name . ' to invitation mail (size: ' . Tinebase_Helper::convertToMegabytes($attachment-size) . ')');
597             }
598         }
599         
600         return $attachments;
601     }
602  }