/**
  * converts an iTIP event to a tine20 event
  * 
  * @param Calendar_Model_Event $_event
  * @param Calendar_Model_Event $_currentEvent (not iTIP!)
  */
 protected function _fromiTIP($_event, $_currentEvent)
 {
     if (!$_event->rrule) {
         $_event->exdate = NULL;
     }
     if ($_event->exdate instanceof Tinebase_Record_RecordSet) {
         try {
             $currExdates = $this->_eventController->getRecurExceptions($_event, TRUE);
             $this->getAlarms($currExdates);
             $currClientExdates = $this->_eventController->getRecurExceptions($_event, TRUE, $this->getEventFilter());
             $this->getAlarms($currClientExdates);
         } catch (Tinebase_Exception_NotFound $e) {
             $currExdates = NULL;
             $currClientExdates = NULL;
         }
         foreach ($_event->exdate as $idx => $exdate) {
             try {
                 $this->_prepareException($_event, $exdate);
             } catch (Exception $e) {
             }
             $currExdate = $currExdates instanceof Tinebase_Record_RecordSet ? $currExdates->filter('recurid', $exdate->recurid)->getFirstRecord() : NULL;
             if ($exdate->is_deleted) {
                 // reset implicit filter fallouts and mark as don't touch (seq = -1)
                 $currClientExdate = $currClientExdates instanceof Tinebase_Record_RecordSet ? $currClientExdates->filter('recurid', $exdate->recurid)->getFirstRecord() : NULL;
                 if ($currClientExdate && $currClientExdate->is_deleted) {
                     $_event->exdate[$idx] = $currExdate;
                     $currExdate->seq = -1;
                     continue;
                 }
             }
             $this->_fromiTIP($exdate, $currExdate ? $currExdate : clone $_currentEvent);
         }
     }
     // assert organizer
     $_event->organizer = $_event->organizer ?: ($_currentEvent->organizer ?: $this->_calendarUser->user_id);
     $this->_addAttendeeWithoutEmail($_event, $_currentEvent);
     $CUAttendee = Calendar_Model_Attender::getAttendee($_event->attendee, $this->_calendarUser);
     $currentCUAttendee = Calendar_Model_Attender::getAttendee($_currentEvent->attendee, $this->_calendarUser);
     $isOrganizer = $_event->isOrganizer($this->_calendarUser);
     // remove perspective
     if ($CUAttendee && !$isOrganizer) {
         $CUAttendee->transp = $_event->transp;
         $_event->transp = $_currentEvent->transp ? $_currentEvent->transp : $_event->transp;
     }
     // apply changes to original alarms
     $_currentEvent->alarms = $_currentEvent->alarms instanceof Tinebase_Record_RecordSet ? $_currentEvent->alarms : new Tinebase_Record_RecordSet('Tinebase_Model_Alarm');
     $_event->alarms = $_event->alarms instanceof Tinebase_Record_RecordSet ? $_event->alarms : new Tinebase_Record_RecordSet('Tinebase_Model_Alarm');
     foreach ($_currentEvent->alarms as $currentAlarm) {
         if (Calendar_Model_Attender::isAlarmForAttendee($this->_calendarUser, $currentAlarm)) {
             $alarmUpdate = Calendar_Controller_Alarm::getMatchingAlarm($_event->alarms, $currentAlarm);
             if ($alarmUpdate) {
                 // we could map the alarm => save ack & snooze options
                 if ($dtAck = Calendar_Controller_Alarm::getAcknowledgeTime($alarmUpdate)) {
                     Calendar_Controller_Alarm::setAcknowledgeTime($currentAlarm, $dtAck, $this->getCalendarUser()->user_id);
                 }
                 if ($dtSnooze = Calendar_Controller_Alarm::getSnoozeTime($alarmUpdate)) {
                     Calendar_Controller_Alarm::setSnoozeTime($currentAlarm, $dtSnooze, $this->getCalendarUser()->user_id);
                 }
                 $_event->alarms->removeRecord($alarmUpdate);
             } else {
                 // alarm is to be skiped/deleted
                 if (!$currentAlarm->getOption('attendee')) {
                     Calendar_Controller_Alarm::skipAlarm($currentAlarm, $this->_calendarUser);
                 } else {
                     $_currentEvent->alarms->removeRecord($currentAlarm);
                 }
             }
         }
     }
     if (!$isOrganizer) {
         $_event->alarms->setOption('attendee', Calendar_Controller_Alarm::attendeeToOption($this->_calendarUser));
     }
     $_event->alarms->merge($_currentEvent->alarms);
     // assert organizer for personal calendars to be calendar owner
     if ($this->_currentEventFacadeContainer && $this->_currentEventFacadeContainer->getId() == $_event->container_id && $this->_currentEventFacadeContainer->type == Tinebase_Model_Container::TYPE_PERSONAL && !$_event->hasExternalOrganizer()) {
         $_event->organizer = $this->_calendarUser->user_id;
     }
     // in MS world only cal_user can do status updates
     if ($CUAttendee) {
         $CUAttendee->status_authkey = $currentCUAttendee ? $currentCUAttendee->status_authkey : NULL;
     }
 }
 /**
  * send notifications 
  * 
  * @param Calendar_Model_Event       $_event
  * @param Tinebase_Model_FullAccount $_updater
  * @param Sting                      $_action
  * @param Calendar_Model_Event       $_oldEvent
  * @param Tinebase_Model_Alarm       $_alarm
  * @return void
  */
 public function doSendNotifications($_event, $_updater, $_action, $_oldEvent = NULL, $_alarm = NULL)
 {
     // we only send notifications to attendee
     if (!$_event->attendee instanceof Tinebase_Record_RecordSet) {
         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " Event has no attendee");
         }
         return;
     }
     if ($_event->dtend === NULL) {
         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " " . print_r($_event->toArray(), TRUE));
         }
         throw new Tinebase_Exception_UnexpectedValue('no dtend set in event');
     }
     if (Tinebase_DateTime::now()->subHour(1)->isLater($_event->dtend)) {
         if ($_action == 'alarm' || !($_event->isRecurException() || $_event->rrule)) {
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " Skip notifications to past events");
             }
             return;
         }
     }
     $notificationPeriodConfig = Calendar_Config::getInstance()->get(Calendar_Config::MAX_NOTIFICATION_PERIOD_FROM);
     if (Tinebase_DateTime::now()->subWeek($notificationPeriodConfig)->isLater($_event->dtend)) {
         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " Skip notifications to past events (MAX_NOTIFICATION_PERIOD_FROM: " . $notificationPeriodConfig . " week(s))");
         }
         return;
     }
     // lets resolve attendee once as batch to fill cache
     $attendee = clone $_event->attendee;
     Calendar_Model_Attender::resolveAttendee($attendee);
     if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
         Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " " . print_r($_event->toArray(), true));
     }
     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
         Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " Notification action: " . $_action);
     }
     switch ($_action) {
         case 'alarm':
             foreach ($_event->attendee as $attender) {
                 if (Calendar_Model_Attender::isAlarmForAttendee($attender, $_alarm)) {
                     $this->sendNotificationToAttender($attender, $_event, $_updater, $_action, self::NOTIFICATION_LEVEL_NONE);
                 }
             }
             break;
         case 'booked':
         case 'created':
         case 'deleted':
             foreach ($_event->attendee as $attender) {
                 $this->sendNotificationToAttender($attender, $_event, $_updater, $_action, self::NOTIFICATION_LEVEL_INVITE_CANCEL);
             }
             break;
         case 'changed':
             $attendeeMigration = Calendar_Model_Attender::getMigration($_oldEvent->attendee, $_event->attendee);
             foreach ($attendeeMigration['toCreate'] as $attender) {
                 $this->sendNotificationToAttender($attender, $_event, $_updater, 'created', self::NOTIFICATION_LEVEL_INVITE_CANCEL);
             }
             foreach ($attendeeMigration['toDelete'] as $attender) {
                 $this->sendNotificationToAttender($attender, $_oldEvent, $_updater, 'deleted', self::NOTIFICATION_LEVEL_INVITE_CANCEL);
             }
             // NOTE: toUpdate are all attendee to be notified
             if (count($attendeeMigration['toUpdate']) > 0) {
                 $updates = $this->_getUpdates($_event, $_oldEvent);
                 if (empty($updates)) {
                     Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " empty update, nothing to notify about");
                     return;
                 }
                 // compute change type
                 if (count(array_intersect(array('dtstart', 'dtend'), array_keys($updates))) > 0) {
                     $notificationLevel = self::NOTIFICATION_LEVEL_EVENT_RESCHEDULE;
                 } else {
                     if (count(array_diff(array_keys($updates), array('attendee'))) > 0) {
                         $notificationLevel = self::NOTIFICATION_LEVEL_EVENT_UPDATE;
                     } else {
                         $notificationLevel = self::NOTIFICATION_LEVEL_ATTENDEE_STATUS_UPDATE;
                     }
                 }
                 // send notifications
                 foreach ($attendeeMigration['toUpdate'] as $attender) {
                     $this->sendNotificationToAttender($attender, $_event, $_updater, 'changed', $notificationLevel, $updates);
                 }
             }
             break;
         default:
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " unknown action '{$_action}'");
             }
             break;
     }
     // SEND REPLY/COUNTER to external organizer
     if ($_event->organizer && !$_event->resolveOrganizer()->account_id && count($_event->attendee) == 1) {
         $updates = array('attendee' => array('toUpdate' => $_event->attendee));
         $organizer = new Calendar_Model_Attender(array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $_event->resolveOrganizer()));
         $this->sendNotificationToAttender($organizer, $_event, $_updater, 'changed', self::NOTIFICATION_LEVEL_ATTENDEE_STATUS_UPDATE, $updates);
     }
 }