/** * return Calendar_Model_Event and convert contact id to model if needed * * @return Calendar_Model_Event */ public function getRecord() { if (!$this->_event instanceof Calendar_Model_Event) { Calendar_Controller_MSEventFacade::getInstance()->assertEventFacadeParams($this->_container); $this->_event = Calendar_Controller_MSEventFacade::getInstance()->get($this->_event); Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " " . print_r($this->_event->toArray(), true)); } return $this->_event; }
/** * Computes the Recurrence set of the given event leaving out $_event->exdate and $_exceptions * * @todo respect rrule_until! * * @param Calendar_Model_Event $_event * @param Tinebase_Record_RecordSet $_exceptions * @param Tinebase_DateTime $_from * @param Tinebase_DateTime $_until * @return Tinebase_Record_RecordSet * @throws Tinebase_Exception_UnexpectedValue */ public static function computeRecurrenceSet($_event, $_exceptions, $_from, $_until) { if (!$_event->dtstart instanceof Tinebase_DateTime) { throw new Tinebase_Exception_UnexpectedValue('Event needs DateTime dtstart: ' . print_r($_event->toArray(), TRUE)); } $rrule = new Calendar_Model_Rrule(NULL, TRUE); $rrule->setFromString($_event->rrule); $exceptionRecurIds = self::getExceptionsRecurIds($_event, $_exceptions); $recurSet = new Tinebase_Record_RecordSet('Calendar_Model_Event'); switch ($rrule->freq) { case self::FREQ_DAILY: self::_computeRecurDaily($_event, $rrule, $exceptionRecurIds, $_from, $_until, $recurSet); break; case self::FREQ_WEEKLY: // default BYDAY clause if (!$rrule->byday) { $rrule->byday = array_search($_event->dtstart->format('w'), self::$WEEKDAY_DIGIT_MAP); } if (!$rrule->wkst) { $rrule->wkst = self::getWeekStart(); } $weekDays = array_keys(self::$WEEKDAY_DIGIT_MAP); array_splice($weekDays, 0, 0, array_splice($weekDays, array_search($rrule->wkst, $weekDays))); $dailyrrule = clone $rrule; $dailyrrule->freq = self::FREQ_DAILY; $dailyrrule->interval = 7 * $rrule->interval; $eventLength = $_event->dtstart->diff($_event->dtend); foreach (explode(',', $rrule->byday) as $recurWeekDay) { // NOTE: in weekly computation, each wdays base event is a recur instance itself $baseEvent = clone $_event; // NOTE: skipping must be done in organizer_tz $baseEvent->dtstart->setTimezone($_event->originator_tz); $direction = array_search($recurWeekDay, $weekDays) >= array_search(array_search($baseEvent->dtstart->format('w'), self::$WEEKDAY_DIGIT_MAP), $weekDays) ? +1 : -1; self::skipWday($baseEvent->dtstart, $recurWeekDay, $direction, TRUE); $baseEvent->dtstart->setTimezone('UTC'); $baseEvent->dtend = clone $baseEvent->dtstart; $baseEvent->dtend->add($eventLength); self::_computeRecurDaily($baseEvent, $dailyrrule, $exceptionRecurIds, $_from, $_until, $recurSet); // check if base event (recur instance) needs to be added to the set if ($baseEvent->dtstart > $_event->dtstart && $baseEvent->dtstart >= $_from && $baseEvent->dtstart < $_until) { if (!in_array($baseEvent->setRecurId($baseEvent->getId()), $exceptionRecurIds)) { self::addRecurrence($baseEvent, $recurSet); } } } break; case self::FREQ_MONTHLY: if ($rrule->byday) { self::_computeRecurMonthlyByDay($_event, $rrule, $exceptionRecurIds, $_from, $_until, $recurSet); } else { self::_computeRecurMonthlyByMonthDay($_event, $rrule, $exceptionRecurIds, $_from, $_until, $recurSet); } break; case self::FREQ_YEARLY: $yearlyrrule = clone $rrule; $yearlyrrule->freq = self::FREQ_MONTHLY; $yearlyrrule->interval = 12; $baseEvent = clone $_event; $originatorsDtstart = clone $baseEvent->dtstart; $originatorsDtstart->setTimezone($_event->originator_tz); // @TODO respect BYMONTH if ($rrule->bymonth && $rrule->bymonth != $originatorsDtstart->format('n')) { // adopt $diff = (12 + $rrule->bymonth - $originatorsDtstart->format('n')) % 12; // NOTE: skipping must be done in organizer_tz $baseEvent->dtstart->setTimezone($_event->originator_tz); $baseEvent->dtend->setTimezone($_event->originator_tz); $baseEvent->dtstart->addMonth($diff); $baseEvent->dtend->addMonth($diff); $baseEvent->dtstart->setTimezone('UTC'); $baseEvent->dtend->setTimezone('UTC'); // check if base event (recur instance) needs to be added to the set if ($baseEvent->dtstart->isLater($_from) && $baseEvent->dtstart->isEarlier($_until)) { if (!in_array($baseEvent->setRecurId($baseEvent->getId()), $exceptionRecurIds)) { self::addRecurrence($baseEvent, $recurSet); } } } if ($rrule->byday) { self::_computeRecurMonthlyByDay($baseEvent, $yearlyrrule, $exceptionRecurIds, $_from, $_until, $recurSet); } else { self::_computeRecurMonthlyByMonthDay($baseEvent, $yearlyrrule, $exceptionRecurIds, $_from, $_until, $recurSet); } break; } return $recurSet; }
/** * (non-PHPdoc) * @see ActiveSync_Frontend_Abstract::toTineModel() */ public function toTineModel(Syncroton_Model_IEntry $data, $entry = null) { if ($entry instanceof Calendar_Model_Event) { $event = $entry; } else { $event = new Calendar_Model_Event(array(), true); } if ($data instanceof Syncroton_Model_Event) { $data->copyFieldsFromParent(); } // Update seq to entries seq to prevent concurrent update $event->seq = $entry['seq']; if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) { Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " Event before mapping: " . print_r($event->toArray(), true)); } foreach ($this->_mapping as $syncrotonProperty => $tine20Property) { if (!isset($data->{$syncrotonProperty})) { if ($tine20Property === 'description' && $this->_device->devicetype == Syncroton_Model_Device::TYPE_IPHONE) { // @see #8230: added alarm to event on iOS 6.1 -> description removed // this should be removed when Tine 2.0 / Syncroton supports ghosted properties if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) { Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' Unsetting description'); } unset($event->{$tine20Property}); } else { if ($tine20Property === 'attendee' && $entry && $this->_device->devicetype === Syncroton_Model_Device::TYPE_IPHONE && $entry->container_id !== $this->_getDefaultContainerId()) { // keep attendees as the are / they were not sent to the device before } else { if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) { Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' Removing ' . $tine20Property); } $event->{$tine20Property} = null; } } continue; } switch ($tine20Property) { case 'alarms': // handled after switch statement break; case 'attendee': if ($entry && $this->_device->devicetype === Syncroton_Model_Device::TYPE_IPHONE && $entry->container_id !== $this->_getDefaultContainerId()) { // keep attendees as the are / they were not sent to the device before continue; } $newAttendees = array(); foreach ($data->{$syncrotonProperty} as $attendee) { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " attendee email " . $attendee->email); } if (isset($attendee->attendeeType) && (isset($this->_attendeeTypeMapping[$attendee->attendeeType]) || array_key_exists($attendee->attendeeType, $this->_attendeeTypeMapping))) { $role = $this->_attendeeTypeMapping[$attendee->attendeeType]; } else { $role = Calendar_Model_Attender::ROLE_REQUIRED; } // AttendeeStatus send only on repsonse if (preg_match('/(?P<firstName>\\S*) (?P<lastNameName>\\S*)/', $attendee->name, $matches)) { $firstName = $matches['firstName']; $lastName = $matches['lastNameName']; } else { $firstName = null; $lastName = $attendee->name; } // @todo handle resources $newAttendees[] = array('userType' => Calendar_Model_Attender::USERTYPE_USER, 'firstName' => $firstName, 'lastName' => $lastName, 'role' => $role, 'email' => $attendee->email); } Calendar_Model_Attender::emailsToAttendee($event, $newAttendees); break; case 'class': $event->{$tine20Property} = $data->{$syncrotonProperty} == 2 ? Calendar_Model_Event::CLASS_PRIVATE : Calendar_Model_Event::CLASS_PUBLIC; break; case 'exdate': // handle exceptions from recurrence $exdates = new Tinebase_Record_RecordSet('Calendar_Model_Event'); foreach ($data->{$syncrotonProperty} as $exception) { if ($exception->deleted == 0) { $eventException = $this->toTineModel($exception); $eventException->last_modified_time = new Tinebase_DateTime($this->_syncTimeStamp); $eventException->recurid = new Tinebase_DateTime($exception->exceptionStartTime); $eventException->is_deleted = false; } else { $eventException = new Calendar_Model_Event(array('recurid' => new Tinebase_DateTime($exception->exceptionStartTime), 'is_deleted' => true)); } $eventException->seq = $entry['seq']; $exdates->addRecord($eventException); } $event->{$tine20Property} = $exdates; break; case 'description': // @todo check $data->$fieldName->Type and convert to/from HTML if needed if ($data->{$syncrotonProperty} instanceof Syncroton_Model_EmailBody) { $event->{$tine20Property} = $data->{$syncrotonProperty}->data; } else { if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) { Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' Removing description.'); } $event->{$tine20Property} = null; } break; case 'rrule': // handle recurrence if ($data->{$syncrotonProperty} instanceof Syncroton_Model_EventRecurrence && isset($data->{$syncrotonProperty}->type)) { $rrule = new Calendar_Model_Rrule(); switch ($data->{$syncrotonProperty}->type) { case Syncroton_Model_EventRecurrence::TYPE_DAILY: $rrule->freq = Calendar_Model_Rrule::FREQ_DAILY; break; case Syncroton_Model_EventRecurrence::TYPE_WEEKLY: $rrule->freq = Calendar_Model_Rrule::FREQ_WEEKLY; $rrule->byday = $this->_convertBitMaskToDay($data->{$syncrotonProperty}->dayOfWeek); break; case Syncroton_Model_EventRecurrence::TYPE_MONTHLY: $rrule->freq = Calendar_Model_Rrule::FREQ_MONTHLY; $rrule->bymonthday = $data->{$syncrotonProperty}->dayOfMonth; break; case Syncroton_Model_EventRecurrence::TYPE_MONTHLY_DAYN: $rrule->freq = Calendar_Model_Rrule::FREQ_MONTHLY; $week = $data->{$syncrotonProperty}->weekOfMonth; $day = $data->{$syncrotonProperty}->dayOfWeek; $byDay = $week == 5 ? -1 : $week; $byDay .= $this->_convertBitMaskToDay($day); $rrule->byday = $byDay; break; case Syncroton_Model_EventRecurrence::TYPE_YEARLY: $rrule->freq = Calendar_Model_Rrule::FREQ_YEARLY; $rrule->bymonth = $data->{$syncrotonProperty}->monthOfYear; $rrule->bymonthday = $data->{$syncrotonProperty}->dayOfMonth; break; case Syncroton_Model_EventRecurrence::TYPE_YEARLY_DAYN: $rrule->freq = Calendar_Model_Rrule::FREQ_YEARLY; $rrule->bymonth = $data->{$syncrotonProperty}->monthOfYear; $week = $data->{$syncrotonProperty}->weekOfMonth; $day = $data->{$syncrotonProperty}->dayOfWeek; $byDay = $week == 5 ? -1 : $week; $byDay .= $this->_convertBitMaskToDay($day); $rrule->byday = $byDay; break; } $rrule->interval = isset($data->{$syncrotonProperty}->interval) ? $data->{$syncrotonProperty}->interval : 1; if (isset($data->{$syncrotonProperty}->occurrences)) { $rrule->count = $data->{$syncrotonProperty}->occurrences; $rrule->until = null; } else { if (isset($data->{$syncrotonProperty}->until)) { $rrule->count = null; $rrule->until = new Tinebase_DateTime($data->{$syncrotonProperty}->until); } else { $rrule->count = null; $rrule->until = null; } } $event->rrule = $rrule; } break; default: if ($data->{$syncrotonProperty} instanceof DateTime) { $event->{$tine20Property} = new Tinebase_DateTime($data->{$syncrotonProperty}); } else { $event->{$tine20Property} = $data->{$syncrotonProperty}; } break; } } // whole day events ends at 23:59:59 in Tine 2.0 but 00:00 the next day in AS if (isset($event->is_all_day_event) && $event->is_all_day_event == 1) { $event->dtend->subSecond(1); } // decode timezone data if (isset($data->timezone)) { $timeZoneConverter = ActiveSync_TimezoneConverter::getInstance(Tinebase_Core::getLogger(), Tinebase_Core::get(Tinebase_Core::CACHE)); try { $timezone = $timeZoneConverter->getTimezone($data->timezone, Tinebase_Core::getUserTimezone()); $event->originator_tz = $timezone; } catch (ActiveSync_TimezoneNotFoundException $e) { Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ . " timezone data not found " . $data->timezone); $event->originator_tz = Tinebase_Core::getUserTimezone(); } if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " timezone data " . $event->originator_tz); } } $this->_handleAlarms($data, $event); $this->_handleBusyStatus($data, $event); // event should be valid now $event->isValid(); if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " eventData " . print_r($event->toArray(), true)); } return $event; }
/** * 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); } }
/** * apply time diff * * @param Calendar_Model_Event $newEvent * @param Calendar_Model_Event $fromEvent * @param Calendar_Model_Event $baseEvent */ protected function _applyTimeDiff($newEvent, $fromEvent, $baseEvent = NULL) { if (!$baseEvent) { $baseEvent = $newEvent; } if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) { Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' New event: ' . print_r($newEvent->toArray(), TRUE)); } if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) { Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' From event: ' . print_r($fromEvent->toArray(), TRUE)); } // compute time diff (NOTE: if the $fromEvent is the baseEvent, it has no recurid) $originalDtStart = $fromEvent->recurid ? new Tinebase_DateTime(substr($fromEvent->recurid, -19), 'UTC') : clone $baseEvent->dtstart; $dtstartDiff = $originalDtStart->diff($fromEvent->dtstart); if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " Dtstart diff: " . $dtstartDiff->format('%H:%M:%i')); } $eventDuration = $fromEvent->dtstart->diff($fromEvent->dtend); if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " Duration diff: " . $dtstartDiff->format('%H:%M:%i')); } $newEvent->dtstart = clone $baseEvent->dtstart; $newEvent->dtstart->add($dtstartDiff); $newEvent->dtend = clone $newEvent->dtstart; $newEvent->dtend->add($eventDuration); }
/** * testUpdateEvent */ public function testUpdateEvent() { $event = new Calendar_Model_Event($this->testCreateEvent(), true); $event->dtstart->addHour(5); $event->dtend->addHour(5); $event->description = 'are you kidding?'; $eventData = $event->toArray(); foreach ($eventData['attendee'] as $key => $attenderData) { if ($eventData['attendee'][$key]['user_id'] != $this->_getTestUserContact()->getId()) { unset($eventData['attendee'][$key]); } } $updatedEventData = $this->_uit->saveEvent($eventData); $this->_assertJsonEvent($eventData, $updatedEventData, 'failed to update event'); return $updatedEventData; }