/** * resolves rrule of given event(s) * * @param Tinebase_Record_RecordSet|Calendar_Model_Event $_events */ public static function resolveRrule($_events) { $events = $_events instanceof Tinebase_Record_RecordSet ? $_events : array($_events); foreach ($events as $event) { if ($event->rrule) { $event->rrule = Calendar_Model_Rrule::getRruleFromString($event->rrule); } } }
/** * imcomplete rrule clauses should be filled in automatically */ public function testIncompleteRrule() { $event = $this->_getRecurEvent(); $event->rrule = 'FREQ=WEEKLY'; $persistentEvent = $this->_controller->create(clone $event); $this->assertEquals(Calendar_Model_Rrule::getWeekStart(), $persistentEvent->rrule->wkst, 'wkst not normalized'); $this->assertEquals('TH', $persistentEvent->rrule->byday, 'byday not normalized'); $rrule = Calendar_Model_Rrule::getRruleFromString('FREQ=MONTHLY'); $rrule->normalize($event); $this->assertEquals(20, $rrule->bymonthday, 'bymonthday not normalized'); $rrule = Calendar_Model_Rrule::getRruleFromString('FREQ=MONTHLY;BYDAY=1TH'); $rrule->normalize($event); $this->assertEquals(NULL, $rrule->bymonthday, 'bymonthday must not be added'); $rrule = Calendar_Model_Rrule::getRruleFromString('FREQ=YEARLY'); $rrule->normalize($event); $this->assertEquals(5, $rrule->bymonth, 'bymonth not normalized'); $this->assertEquals(20, $rrule->bymonthday, 'bymonthday not normalized'); $rrule = Calendar_Model_Rrule::getRruleFromString('FREQ=YEARLY;BYDAY=1TH'); $rrule->normalize($event); $this->assertEquals(5, $rrule->bymonth, 'bymonth not normalized'); $this->assertEquals(NULL, $rrule->bymonthday, 'bymonthday must not be added'); }
public function testGetTranslatedRule() { $locale = new Zend_Locale('en'); $translation = Tinebase_Translation::getTranslation('Calendar', $locale); $asserts = array('FREQ=DAILY;INTERVAL=1' => 'Daily', 'FREQ=DAILY;INTERVAL=3' => 'Every 3rd day', 'FREQ=WEEKLY;INTERVAL=3;WKST=SU;BYDAY=TU,WE,TH' => 'Every 3rd week on Tuesday, Wednesday and Thursday', 'FREQ=MONTHLY;INTERVAL=2;BYDAY=-1FR' => 'Every 2nd month on the last Friday', 'FREQ=MONTHLY;INTERVAL=1;BYDAY=2TH' => 'Monthly every second Thursday', 'FREQ=MONTHLY;INTERVAL=3;BYMONTHDAY=24' => 'Every 3rd month on the 24th', 'FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=5' => 'Monthly on the 5th', 'FREQ=YEARLY;INTERVAL=1;BYMONTH=7;BYMONTHDAY=10' => 'Yearly on the 10th of July'); foreach ($asserts as $rruleString => $expected) { $translated = Calendar_Model_Rrule::getRruleFromString($rruleString); $this->assertEquals($translated, $translated); } }
/** * (non-PHPdoc) * @see Tinebase_Record_Abstract::diff() */ public function diff($record, $omitFields = array()) { $checkRrule = false; if (!in_array('rrule', $omitFields)) { $omitFields[] = 'rrule'; $checkRrule = true; } $diff = parent::diff($record, $omitFields); if ($checkRrule) { $ownRrule = !$this->rrule instanceof Calendar_Model_Rrule ? Calendar_Model_Rrule::getRruleFromString((string) $this->rrule) : $this->rrule; $recordRrule = !$record->rrule instanceof Calendar_Model_Rrule ? Calendar_Model_Rrule::getRruleFromString($record->rrule) : $record->rrule; $rruleDiff = $ownRrule->diff($recordRrule); // don't take small ( < one day) rrule_until changes as diff if ($ownRrule->until instanceof Tinebase_DateTime && (isset($rruleDiff->diff['until']) || array_key_exists('until', $rruleDiff->diff)) && $rruleDiff->diff['until'] instanceof Tinebase_DateTime && abs($rruleDiff->diff['until']->getTimestamp() - $ownRrule->until->getTimestamp()) < 86400) { $rruleDiffArray = $rruleDiff->diff; unset($rruleDiffArray['until']); $rruleDiff->diff = $rruleDiffArray; } if (!empty($rruleDiff->diff)) { $diffArray = $diff->diff; $diffArray['rrule'] = $rruleDiff; $diff->diff = $diffArray; } } return $diff; }
/** * update to 8.3 * - normalize all rrules */ public function update_2() { // find all events with rrule $eventIds = $this->_db->query("SELECT " . $this->_db->quoteIdentifier('id') . " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_events") . " WHERE " . $this->_db->quoteIdentifier("rrule") . " IS NOT NULL")->fetchAll(Zend_Db::FETCH_ASSOC); // NOTE: we need a generic sql BE to circumvent calendar specific acl issues $eventBE = new Tinebase_Backend_Sql(array('modelName' => 'Calendar_Model_Event', 'tableName' => 'cal_events', 'modlogActive' => false)); foreach ($eventIds as $eventId) { $event = $eventBE->get($eventId['id']); $oldRruleString = (string) $event->rrule; $rrule = Calendar_Model_Rrule::getRruleFromString($oldRruleString); $rrule->normalize($event); if ($oldRruleString != (string) $rrule) { $event->rrule = (string) $rrule; try { $eventBE->update($event); } catch (Tinebase_Exception_Record_Validation $terv) { Tinebase_Exception::log($terv, null, $event->toArray()); } catch (Tinebase_Exception_UnexpectedValue $teuv) { Tinebase_Exception::log($teuv, null, $event->toArray()); } } } $this->setApplicationVersion('Calendar', '8.3'); }
public function testUpdateRecuingDtstart() { $event = $this->_getEvent(); $event->rrule = 'FREQ=DAILY;INTERVAL=1;UNTIL=2009-04-30 13:30:00'; $event->exdate = array(new Tinebase_DateTime('2009-04-07 13:00:00')); $persistentEvent = $this->_controller->create($event); $exception = clone $persistentEvent; $exception->dtstart->addDay(2); $exception->dtend->addDay(2); $exception->setId(NULL); unset($exception->rrule); unset($exception->exdate); $exception->recurid = $exception->uid . '-' . $exception->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG); $persitentException = $this->_controller->create($exception); $persistentEvent->dtstart->addHour(5); $persistentEvent->dtend->addHour(5); $updatedEvent = $this->_controller->update($persistentEvent); $updatedException = $this->_controller->get($persitentException->getId()); $this->assertEquals(1, count($updatedEvent->exdate), 'failed to reset exdate'); $this->assertEquals('2009-04-08 18:00:00', $updatedEvent->exdate[0]->get(Tinebase_Record_Abstract::ISO8601LONG), 'failed to update exdate'); $this->assertEquals('2009-04-08 18:00:00', substr($updatedException->recurid, -19), 'failed to update persistent exception'); $this->assertEquals('2009-04-30 13:30:00', Calendar_Model_Rrule::getRruleFromString($updatedEvent->rrule)->until->get(Tinebase_Record_Abstract::ISO8601LONG), 'until in rrule must not be changed'); $this->assertEquals('2009-04-30 13:30:00', $updatedEvent->rrule_until->get(Tinebase_Record_Abstract::ISO8601LONG), 'rrule_until must not be changed'); sleep(1); // wait for modlog $updatedEvent->dtstart->subHour(5); $updatedEvent->dtend->subHour(5); $secondUpdatedEvent = $this->_controller->update($updatedEvent); $secondUpdatedException = $this->_controller->get($persitentException->getId()); $this->assertEquals('2009-04-08 13:00:00', $secondUpdatedEvent->exdate[0]->get(Tinebase_Record_Abstract::ISO8601LONG), 'failed to update exdate (sub)'); $this->assertEquals('2009-04-08 13:00:00', substr($secondUpdatedException->recurid, -19), 'failed to update persistent exception (sub)'); }
/** * (non-PHPdoc) * @see ActiveSync_Frontend_Abstract::toSyncrotonModel() * @todo handle BusyStatus */ public function toSyncrotonModel($entry, array $options = array()) { if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) { Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " calendar data " . print_r($entry->toArray(), true)); } $syncrotonEvent = new Syncroton_Model_Event(); foreach ($this->_mapping as $syncrotonProperty => $tine20Property) { if (empty($entry->{$tine20Property}) && $entry->{$tine20Property} != '0' || count($entry->{$tine20Property}) === 0) { continue; } switch ($tine20Property) { case 'alarms': $entry->{$tine20Property}->sort('alarm_time'); $alarm = $entry->alarms->getFirstRecord(); if ($alarm instanceof Tinebase_Model_Alarm) { // NOTE: option minutes_before is always calculated by Calendar_Controller_Event::_inspectAlarmSet $minutesBefore = (int) $alarm->getOption('minutes_before'); // avoid negative alarms which may break phones if ($minutesBefore >= 0) { $syncrotonEvent->{$syncrotonProperty} = $minutesBefore; } } break; case 'attendee': if ($this->_device->devicetype === Syncroton_Model_Device::TYPE_IPHONE && $entry->container_id !== $this->_getDefaultContainerId()) { continue; } // fill attendee cache Calendar_Model_Attender::resolveAttendee($entry->{$tine20Property}, FALSE); $attendees = array(); foreach ($entry->{$tine20Property} as $attenderObject) { $attendee = new Syncroton_Model_EventAttendee(); $attendee->name = $attenderObject->getName(); $attendee->email = $attenderObject->getEmail(); $acsType = array_search($attenderObject->role, $this->_attendeeTypeMapping); $attendee->attendeeType = $acsType ? $acsType : Syncroton_Model_EventAttendee::ATTENDEE_TYPE_REQUIRED; $acsStatus = array_search($attenderObject->status, $this->_attendeeStatusMapping); $attendee->attendeeStatus = $acsStatus ? $acsStatus : Syncroton_Model_EventAttendee::ATTENDEE_STATUS_UNKNOWN; $attendees[] = $attendee; } $syncrotonEvent->{$syncrotonProperty} = $attendees; // set own status if (($ownAttendee = Calendar_Model_Attender::getOwnAttender($entry->attendee)) !== null && ($busyType = array_search($ownAttendee->status, $this->_busyStatusMapping)) !== false) { $syncrotonEvent->busyStatus = $busyType; } break; case 'class': $syncrotonEvent->{$syncrotonProperty} = $entry->{$tine20Property} == Calendar_Model_Event::CLASS_PRIVATE ? 2 : 0; break; case 'description': $syncrotonEvent->{$syncrotonProperty} = new Syncroton_Model_EmailBody(array('type' => Syncroton_Model_EmailBody::TYPE_PLAINTEXT, 'data' => $entry->{$tine20Property})); break; case 'dtend': if ($entry->{$tine20Property} instanceof DateTime) { if ($entry->is_all_day_event == true) { // whole day events ends at 23:59:59 in Tine 2.0 but 00:00 the next day in AS $dtend = clone $entry->{$tine20Property}; $dtend->addSecond($dtend->get('s') == 59 ? 1 : 0); $dtend->addMinute($dtend->get('i') == 59 ? 1 : 0); $syncrotonEvent->{$syncrotonProperty} = $dtend; } else { $syncrotonEvent->{$syncrotonProperty} = $entry->{$tine20Property}; } } break; case 'dtstart': if ($entry->{$tine20Property} instanceof DateTime) { $syncrotonEvent->{$syncrotonProperty} = $entry->{$tine20Property}; } break; case 'exdate': // handle exceptions of repeating events if ($entry->{$tine20Property} instanceof Tinebase_Record_RecordSet && $entry->{$tine20Property}->count() > 0) { $exceptions = array(); foreach ($entry->exdate as $exdate) { $exception = new Syncroton_Model_EventException(); // send the Deleted element only, when needed // HTC devices ignore the value(0 or 1) of the Deleted element if ((int) $exdate->is_deleted === 1) { $exception->deleted = 1; } $exception->exceptionStartTime = $exdate->getOriginalDtStart(); if ((int) $exdate->is_deleted === 0) { $exceptionSyncrotonEvent = $this->toSyncrotonModel($exdate); foreach ($exception->getProperties() as $property) { if (isset($exceptionSyncrotonEvent->{$property})) { $exception->{$property} = $exceptionSyncrotonEvent->{$property}; } } unset($exceptionSyncrotonEvent); } $exceptions[] = $exception; } $syncrotonEvent->{$syncrotonProperty} = $exceptions; } break; case 'rrule': if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " calendar rrule " . $entry->{$tine20Property}); } $rrule = Calendar_Model_Rrule::getRruleFromString($entry->{$tine20Property}); $recurrence = new Syncroton_Model_EventRecurrence(); // required fields switch ($rrule->freq) { case Calendar_Model_Rrule::FREQ_DAILY: $recurrence->type = Syncroton_Model_EventRecurrence::TYPE_DAILY; break; case Calendar_Model_Rrule::FREQ_WEEKLY: $recurrence->type = Syncroton_Model_EventRecurrence::TYPE_WEEKLY; $recurrence->dayOfWeek = $this->_convertDayToBitMask($rrule->byday); break; case Calendar_Model_Rrule::FREQ_MONTHLY: if (!empty($rrule->bymonthday)) { $recurrence->type = Syncroton_Model_EventRecurrence::TYPE_MONTHLY; $recurrence->dayOfMonth = $rrule->bymonthday; } else { $weekOfMonth = (int) substr($rrule->byday, 0, -2); $weekOfMonth = $weekOfMonth == -1 ? 5 : $weekOfMonth; $dayOfWeek = substr($rrule->byday, -2); $recurrence->type = Syncroton_Model_EventRecurrence::TYPE_MONTHLY_DAYN; $recurrence->weekOfMonth = $weekOfMonth; $recurrence->dayOfWeek = $this->_convertDayToBitMask($dayOfWeek); } break; case Calendar_Model_Rrule::FREQ_YEARLY: if (!empty($rrule->bymonthday)) { $recurrence->type = Syncroton_Model_EventRecurrence::TYPE_YEARLY; $recurrence->dayOfMonth = $rrule->bymonthday; $recurrence->monthOfYear = $rrule->bymonth; } else { $weekOfMonth = (int) substr($rrule->byday, 0, -2); $weekOfMonth = $weekOfMonth == -1 ? 5 : $weekOfMonth; $dayOfWeek = substr($rrule->byday, -2); $recurrence->type = Syncroton_Model_EventRecurrence::TYPE_YEARLY_DAYN; $recurrence->weekOfMonth = $weekOfMonth; $recurrence->dayOfWeek = $this->_convertDayToBitMask($dayOfWeek); $recurrence->monthOfYear = $rrule->bymonth; } break; } // required field $recurrence->interval = $rrule->interval ? $rrule->interval : 1; if ($rrule->count) { $recurrence->occurrences = $rrule->count; } else { if ($rrule->until instanceof DateTime) { $recurrence->until = $rrule->until; } } $syncrotonEvent->{$syncrotonProperty} = $recurrence; break; case 'tags': $syncrotonEvent->{$syncrotonProperty} = $entry->{$tine20Property}->name; break; default: $syncrotonEvent->{$syncrotonProperty} = $entry->{$tine20Property}; break; } } $timeZoneConverter = ActiveSync_TimezoneConverter::getInstance(Tinebase_Core::getLogger(), Tinebase_Core::get(Tinebase_Core::CACHE)); $syncrotonEvent->timezone = $timeZoneConverter->encodeTimezone(Tinebase_Core::getUserTimezone()); $syncrotonEvent->meetingStatus = 1; $syncrotonEvent->dtStamp = $entry->creation_time; $syncrotonEvent->uID = $entry->uid; $this->_addOrganizer($syncrotonEvent, $entry); return $syncrotonEvent; }
/** * creates an exception instance of a recurring event * * NOTE: deleting persistent exceptions is done via a normal delete action * and handled in the deleteInspection * * @param Calendar_Model_Event $_event * @param bool $_deleteInstance * @param bool $_allFollowing * @param bool $_checkBusyConflicts * @return Calendar_Model_Event exception Event | updated baseEvent * * @todo replace $_allFollowing param with $range * @deprecated replace with create/update/delete */ public function createRecurException($_event, $_deleteInstance = FALSE, $_allFollowing = FALSE, $_checkBusyConflicts = FALSE) { $baseEvent = $this->getRecurBaseEvent($_event); if ($baseEvent->last_modified_time != $_event->last_modified_time) { if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) { Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " It is not allowed to create recur instance if it is clone of base event"); } throw new Tinebase_Timemachine_Exception_ConcurrencyConflict('concurrency conflict!'); } // // Maybe Later // // exdates needs to stay in baseEvents container // if ($_event->container_id != $baseEvent->container_id) { // throw new Calendar_Exception_ExdateContainer(); // } // check if this is an exception to the first occurence if ($baseEvent->getId() == $_event->getId()) { if ($_allFollowing) { throw new Exception('please edit or delete complete series!'); } // NOTE: if the baseEvent gets a time change, we can't compute the recurdid w.o. knowing the original dtstart $recurid = $baseEvent->setRecurId($baseEvent->getId()); unset($baseEvent->recurid); $_event->recurid = $recurid; } // just do attender status update if user has no edit grant if ($this->_doContainerACLChecks && !$baseEvent->{Tinebase_Model_Grants::GRANT_EDIT}) { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " user has no editGrant for event: '{$baseEvent->getId()}'. Only creating exception for attendee status"); } if ($_event->attendee instanceof Tinebase_Record_RecordSet) { foreach ($_event->attendee as $attender) { if ($attender->status_authkey) { $exceptionAttender = $this->attenderStatusCreateRecurException($_event, $attender, $attender->status_authkey, $_allFollowing); } } } if (!isset($exceptionAttender)) { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG) && $_event->attendee instanceof Tinebase_Record_RecordSet) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " Failed to update attendee: " . print_r($_event->attendee->toArray(), true)); } throw new Tinebase_Exception_AccessDenied('Failed to update attendee, status authkey might be missing'); } return $this->get($exceptionAttender->cal_event_id); } // NOTE: recurid is computed by rrule recur computations and therefore is already part of the event. if (empty($_event->recurid)) { throw new Exception('recurid must be present to create exceptions!'); } // we do notifications ourself $sendNotifications = $this->sendNotifications(FALSE); // EDIT for baseEvent is checked above, CREATE, DELETE for recur exceptions is implied with it $doContainerACLChecks = $this->doContainerACLChecks(FALSE); $db = $this->_backend->getAdapter(); $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($db); $exdate = new Tinebase_DateTime(substr($_event->recurid, -19)); $exdates = is_array($baseEvent->exdate) ? $baseEvent->exdate : array(); $originalDtstart = $_event->getOriginalDtStart(); $originalEvent = Calendar_Model_Rrule::computeNextOccurrence($baseEvent, new Tinebase_Record_RecordSet('Calendar_Model_Event'), $originalDtstart); if ($_allFollowing != TRUE) { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " Adding exdate for: '{$_event->recurid}'"); } array_push($exdates, $exdate); $baseEvent->exdate = $exdates; $updatedBaseEvent = $this->update($baseEvent, FALSE); if ($_deleteInstance == FALSE) { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " Creating persistent exception for: '{$_event->recurid}'"); } if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) { Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " Recur exception: " . print_r($_event->toArray(), TRUE)); } $_event->base_event_id = $baseEvent->getId(); $_event->setId(NULL); unset($_event->rrule); unset($_event->exdate); foreach (array('attendee', 'notes', 'alarms') as $prop) { if ($_event->{$prop} instanceof Tinebase_Record_RecordSet) { $_event->{$prop}->setId(NULL); } } $originalDtstart = $_event->getOriginalDtStart(); $dtStartHasDiff = $originalDtstart->compare($_event->dtstart) != 0; // php52 compat if (!$dtStartHasDiff) { $attendees = $_event->attendee; unset($_event->attendee); } $note = $_event->notes; unset($_event->notes); $persistentExceptionEvent = $this->create($_event, $_checkBusyConflicts); if (!$dtStartHasDiff) { // we save attendee seperatly to preserve their attributes if ($attendees instanceof Tinebase_Record_RecordSet) { $attendees->cal_event_id = $persistentExceptionEvent->getId(); $calendar = Tinebase_Container::getInstance()->getContainerById($_event->container_id); foreach ($attendees as $attendee) { $this->_createAttender($attendee, $_event, TRUE, $calendar); $this->_increaseDisplayContainerContentSequence($attendee, $persistentExceptionEvent, Tinebase_Model_ContainerContent::ACTION_CREATE); } } } // @todo save notes and add a update note -> what was updated? -> modlog is also missing $persistentExceptionEvent = $this->get($persistentExceptionEvent->getId()); } } else { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " shorten recur series for/to: '{$_event->recurid}'"); } // split past/future exceptions $pastExdates = array(); $futureExdates = array(); foreach ($exdates as $exdate) { $exdate->isLater($_event->dtstart) ? $futureExdates[] = $exdate : ($pastExdates[] = $exdate); } $persistentExceptionEvents = $this->getRecurExceptions($_event); $pastPersistentExceptionEvents = new Tinebase_Record_RecordSet('Calendar_Model_Event'); $futurePersistentExceptionEvents = new Tinebase_Record_RecordSet('Calendar_Model_Event'); foreach ($persistentExceptionEvents as $persistentExceptionEvent) { $persistentExceptionEvent->getOriginalDtStart()->isLater($_event->dtstart) ? $futurePersistentExceptionEvents->addRecord($persistentExceptionEvent) : $pastPersistentExceptionEvents->addRecord($persistentExceptionEvent); } // update baseEvent $rrule = Calendar_Model_Rrule::getRruleFromString($baseEvent->rrule); if (isset($rrule->count)) { // get all occurences and find the split $exdate = $baseEvent->exdate; $baseEvent->exdate = NULL; //$baseCountOccurrence = Calendar_Model_Rrule::computeNextOccurrence($baseEvent, new Tinebase_Record_RecordSet('Calendar_Model_Event'), $baseEvent->rrule_until, $baseCount); $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($baseEvent, new Tinebase_Record_RecordSet('Calendar_Model_Event'), $baseEvent->dtstart, $baseEvent->rrule_until); $baseEvent->exdate = $exdate; $originalDtstart = $_event->getOriginalDtStart(); foreach ($recurSet as $idx => $rInstance) { if ($rInstance->dtstart >= $originalDtstart) { break; } } $rrule->count = $idx + 1; } else { $lastBaseOccurence = Calendar_Model_Rrule::computeNextOccurrence($baseEvent, new Tinebase_Record_RecordSet('Calendar_Model_Event'), $_event->getOriginalDtStart()->subSecond(1), -1); $rrule->until = $lastBaseOccurence ? $lastBaseOccurence->getOriginalDtStart() : $baseEvent->dtstart; } $baseEvent->rrule = (string) $rrule; $baseEvent->exdate = $pastExdates; // NOTE: we don't want implicit attendee updates //$updatedBaseEvent = $this->update($baseEvent, FALSE); $this->_inspectEvent($baseEvent); $updatedBaseEvent = parent::update($baseEvent); if ($_deleteInstance == TRUE) { // delete all future persistent events $this->delete($futurePersistentExceptionEvents->getId()); } else { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " create new recur series for/at: '{$_event->recurid}'"); } // NOTE: in order to move exceptions correctly in time we need to find out the original dtstart // and create the new baseEvent with this time. A following update also updates its exceptions $originalDtstart = new Tinebase_DateTime(substr($_event->recurid, -19)); $adoptedDtstart = clone $_event->dtstart; $dtStartHasDiff = $adoptedDtstart->compare($originalDtstart) != 0; // php52 compat $eventLength = $_event->dtstart->diff($_event->dtend); $_event->dtstart = clone $originalDtstart; $_event->dtend = clone $originalDtstart; $_event->dtend->add($eventLength); // adopt count if (isset($rrule->count)) { $baseCount = $rrule->count; $rrule = Calendar_Model_Rrule::getRruleFromString($_event->rrule); $rrule->count = $rrule->count - $baseCount; $_event->rrule = (string) $rrule; } $_event->setId(Tinebase_Record_Abstract::generateUID()); $_event->uid = $futurePersistentExceptionEvents->uid = Tinebase_Record_Abstract::generateUID(); $_event->setId(Tinebase_Record_Abstract::generateUID()); $futurePersistentExceptionEvents->setRecurId($_event->getId()); unset($_event->recurid); unset($_event->base_event_id); foreach (array('attendee', 'notes', 'alarms') as $prop) { if ($_event->{$prop} instanceof Tinebase_Record_RecordSet) { $_event->{$prop}->setId(NULL); } } $_event->exdate = $futureExdates; $attendees = $_event->attendee; unset($_event->attendee); $note = $_event->notes; unset($_event->notes); $persistentExceptionEvent = $this->create($_event, $_checkBusyConflicts && $dtStartHasDiff); // we save attendee separately to preserve their attributes if ($attendees instanceof Tinebase_Record_RecordSet) { foreach ($attendees as $attendee) { $this->_createAttender($attendee, $persistentExceptionEvent, true); } } // @todo save notes and add a update note -> what was updated? -> modlog is also missing $persistentExceptionEvent = $this->get($persistentExceptionEvent->getId()); foreach ($futurePersistentExceptionEvents as $futurePersistentExceptionEvent) { $this->update($futurePersistentExceptionEvent, FALSE); } if ($dtStartHasDiff) { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " new recur series has adpted dtstart -> update to adopt exceptions'"); } $persistentExceptionEvent->dtstart = clone $adoptedDtstart; $persistentExceptionEvent->dtend = clone $adoptedDtstart; $persistentExceptionEvent->dtend->add($eventLength); $persistentExceptionEvent = $this->update($persistentExceptionEvent, $_checkBusyConflicts); } } } Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId); // restore original notification handling $this->sendNotifications($sendNotifications); $notificationAction = $_deleteInstance ? 'deleted' : 'changed'; $notificationEvent = $_deleteInstance ? $_event : $persistentExceptionEvent; // restore acl $this->doContainerACLChecks($doContainerACLChecks); // send notifications if ($this->_sendNotifications && $_event->mute != 1) { // NOTE: recur exception is a fake event from client. // this might lead to problems, so we wrap the calls try { if (count($_event->attendee) > 0) { $_event->attendee->bypassFilters = TRUE; } $_event->created_by = $baseEvent->created_by; $this->doSendNotifications($notificationEvent, Tinebase_Core::getUser(), $notificationAction, $originalEvent); } catch (Exception $e) { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getTraceAsString()); } Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " could not send notification {$e->getMessage()}"); } } return $_deleteInstance ? $updatedBaseEvent : $persistentExceptionEvent; }
/** * append event data to xml element * * @todo handle BusyStatus * @todo handle TimeZone data * * @param DOMElement $_domParrent the parrent xml node * @param string $_folderId the local folder id * @param boolean $_withBody retrieve body of entry */ public function appendXML(DOMElement $_domParrent, $_collectionData, $_serverId) { $data = $_serverId instanceof Tinebase_Record_Abstract ? $_serverId : $this->_contentController->get($_serverId); if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) { Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " calendar data " . print_r($data->toArray(), true)); } // add calendar namespace $_domParrent->ownerDocument->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Calendar', 'uri:Calendar'); foreach ($this->_mapping as $key => $value) { if (!empty($data->{$value}) || $data->{$value} == '0') { $nodeContent = null; switch ($value) { case 'dtend': if ($data->{$value} instanceof DateTime) { if ($data->is_all_day_event == true) { // whole day events ends at 23:59:59 in Tine 2.0 but 00:00 the next day in AS $dtend = clone $data->dtend; $dtend->addSecond($dtend->get('s') == 59 ? 1 : 0); $dtend->addMinute($dtend->get('i') == 59 ? 1 : 0); $nodeContent = $dtend->format('Ymd\\THis') . 'Z'; } else { $nodeContent = $data->dtend->format('Ymd\\THis') . 'Z'; } } break; case 'dtstart': if ($data->{$value} instanceof DateTime) { $nodeContent = $data->{$value}->format('Ymd\\THis') . 'Z'; } break; default: $nodeContent = $data->{$value}; break; } // skip empty elements if ($nodeContent === null || $nodeContent == '') { Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Value for {$key} is empty. Skip element."); continue; } // strip off any non printable control characters if (!ctype_print($nodeContent)) { $nodeContent = $this->removeControlChars($nodeContent); } $node = $_domParrent->ownerDocument->createElementNS('uri:Calendar', $key); $node->appendChild($_domParrent->ownerDocument->createTextNode($nodeContent)); $_domParrent->appendChild($node); } } if (!empty($data->description)) { if (version_compare($this->_device->acsversion, '12.0', '>=') === true) { $body = $_domParrent->appendChild(new DOMElement('Body', null, 'uri:AirSyncBase')); $body->appendChild(new DOMElement('Type', 1, 'uri:AirSyncBase')); // create a new DOMElement ... $dataTag = new DOMElement('Data', null, 'uri:AirSyncBase'); // ... append it to parent node aka append it to the document ... $body->appendChild($dataTag); // ... and now add the content (DomText takes care of special chars) $dataTag->appendChild(new DOMText($data->description)); } else { // create a new DOMElement ... $node = new DOMElement('Body', null, 'uri:Calendar'); // ... append it to parent node aka append it to the document ... $_domParrent->appendChild($node); // ... and now add the content (DomText takes care of special chars) $node->appendChild(new DOMText($data->description)); } } if (!empty($data->alarms)) { $alarm = $data->alarms->getFirstRecord(); if ($alarm instanceof Tinebase_Model_Alarm) { // NOTE: option minutes_before is always calculated by Calendar_Controller_Event::_inspectAlarmSet $minutesBefore = (int) $alarm->getOption('minutes_before'); if ($minutesBefore >= 0) { $_domParrent->appendChild(new DOMElement('Reminder', $minutesBefore, 'uri:Calendar')); } } } if (!empty($data->rrule)) { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " calendar rrule " . $data->rrule); } $rrule = Calendar_Model_Rrule::getRruleFromString($data->rrule); $recurrence = $_domParrent->appendChild(new DOMElement('Recurrence', null, 'uri:Calendar')); // required fields switch ($rrule->freq) { case Calendar_Model_Rrule::FREQ_DAILY: $recurrence->appendChild(new DOMElement('Type', self::RECUR_TYPE_DAILY, 'uri:Calendar')); break; case Calendar_Model_Rrule::FREQ_WEEKLY: $recurrence->appendChild(new DOMElement('Type', self::RECUR_TYPE_WEEKLY, 'uri:Calendar')); $recurrence->appendChild(new DOMElement('DayOfWeek', $this->_convertDayToBitMask($rrule->byday), 'uri:Calendar')); break; case Calendar_Model_Rrule::FREQ_MONTHLY: if (!empty($rrule->bymonthday)) { $recurrence->appendChild(new DOMElement('Type', self::RECUR_TYPE_MONTHLY, 'uri:Calendar')); $recurrence->appendChild(new DOMElement('DayOfMonth', $rrule->bymonthday, 'uri:Calendar')); } else { $weekOfMonth = (int) substr($rrule->byday, 0, -2); $weekOfMonth = $weekOfMonth == -1 ? 5 : $weekOfMonth; $dayOfWeek = substr($rrule->byday, -2); $recurrence->appendChild(new DOMElement('Type', self::RECUR_TYPE_MONTHLY_DAYN, 'uri:Calendar')); $recurrence->appendChild(new DOMElement('WeekOfMonth', $weekOfMonth, 'uri:Calendar')); $recurrence->appendChild(new DOMElement('DayOfWeek', $this->_convertDayToBitMask($dayOfWeek), 'uri:Calendar')); } break; case Calendar_Model_Rrule::FREQ_YEARLY: if (!empty($rrule->bymonthday)) { $recurrence->appendChild(new DOMElement('Type', self::RECUR_TYPE_YEARLY, 'uri:Calendar')); $recurrence->appendChild(new DOMElement('DayOfMonth', $rrule->bymonthday, 'uri:Calendar')); $recurrence->appendChild(new DOMElement('MonthOfYear', $rrule->bymonth, 'uri:Calendar')); } else { $weekOfMonth = (int) substr($rrule->byday, 0, -2); $weekOfMonth = $weekOfMonth == -1 ? 5 : $weekOfMonth; $dayOfWeek = substr($rrule->byday, -2); $recurrence->appendChild(new DOMElement('Type', self::RECUR_TYPE_YEARLY_DAYN, 'uri:Calendar')); $recurrence->appendChild(new DOMElement('WeekOfMonth', $weekOfMonth, 'uri:Calendar')); $recurrence->appendChild(new DOMElement('DayOfWeek', $this->_convertDayToBitMask($dayOfWeek), 'uri:Calendar')); $recurrence->appendChild(new DOMElement('MonthOfYear', $rrule->bymonth, 'uri:Calendar')); } break; } // required field $recurrence->appendChild(new DOMElement('Interval', $rrule->interval, 'uri:Calendar')); if ($rrule->until instanceof DateTime) { $recurrence->appendChild(new DOMElement('Until', $rrule->until->format('Ymd\\THis') . 'Z', 'uri:Calendar')); } // handle exceptions of repeating events if ($data->exdate instanceof Tinebase_Record_RecordSet && $data->exdate->count() > 0) { $exceptionsTag = $_domParrent->appendChild(new DOMElement('Exceptions', null, 'uri:Calendar')); foreach ($data->exdate as $exception) { $exceptionTag = $exceptionsTag->appendChild(new DOMElement('Exception', null, 'uri:Calendar')); $exceptionTag->appendChild(new DOMElement('Deleted', (int) $exception->is_deleted, 'uri:Calendar')); $exceptionTag->appendChild(new DOMElement('ExceptionStartTime', $exception->getOriginalDtStart()->format('Ymd\\THis') . 'Z', 'uri:Calendar')); if ((int) $exception->is_deleted === 0) { $this->appendXML($exceptionTag, $_collectionData, $exception); } } } } if (count($data->attendee) > 0) { // fill attendee cache Calendar_Model_Attender::resolveAttendee($data->attendee, FALSE); $attendees = $_domParrent->ownerDocument->createElementNS('uri:Calendar', 'Attendees'); foreach ($data->attendee as $attenderObject) { $attendee = $attendees->appendChild(new DOMElement('Attendee', null, 'uri:Calendar')); $attendee->appendChild(new DOMElement('Name', $attenderObject->getName(), 'uri:Calendar')); $attendee->appendChild(new DOMElement('Email', $attenderObject->getEmail(), 'uri:Calendar')); if (version_compare($this->_device->acsversion, '12.0', '>=') === true) { $acsType = array_search($attenderObject->role, $this->_attendeeTypeMapping); $attendee->appendChild(new DOMElement('AttendeeType', $acsType ? $acsType : self::ATTENDEE_TYPE_REQUIRED, 'uri:Calendar')); $acsStatus = array_search($attenderObject->status, $this->_attendeeStatusMapping); $attendee->appendChild(new DOMElement('AttendeeStatus', $acsStatus ? $acsStatus : self::ATTENDEE_STATUS_UNKNOWN, 'uri:Calendar')); } } if ($attendees->hasChildNodes()) { $_domParrent->appendChild($attendees); } // set own status if (($ownAttendee = Calendar_Model_Attender::getOwnAttender($data->attendee)) !== null && ($busyType = array_search($ownAttendee->status, $this->_busyStatusMapping)) !== false) { $_domParrent->appendChild(new DOMElement('BusyStatus', $busyType, 'uri:Calendar')); } } $timeZoneConverter = ActiveSync_TimezoneConverter::getInstance(Tinebase_Core::getLogger(), Tinebase_Core::get(Tinebase_Core::CACHE)); $_domParrent->appendChild(new DOMElement('Timezone', $timeZoneConverter->encodeTimezone(Tinebase_Core::get(Tinebase_Core::USERTIMEZONE)), 'uri:Calendar')); $_domParrent->appendChild(new DOMElement('MeetingStatus', 1, 'uri:Calendar')); $_domParrent->appendChild(new DOMElement('Sensitivity', 0, 'uri:Calendar')); $_domParrent->appendChild(new DOMElement('DtStamp', $data->creation_time->format('Ymd\\THis') . 'Z', 'uri:Calendar')); $_domParrent->appendChild(new DOMElement('UID', $data->uid, 'uri:Calendar')); if (!empty($data->organizer)) { try { $contact = Addressbook_Controller_Contact::getInstance()->get($data->organizer); $_domParrent->appendChild(new DOMElement('OrganizerName', $contact->n_fileas, 'uri:Calendar')); $_domParrent->appendChild(new DOMElement('OrganizerEmail', $contact->email, 'uri:Calendar')); } catch (Tinebase_Exception_AccessDenied $e) { // set the current account as organizer // if organizer is not set, you can not edit the event on the Motorola Milestone $_domParrent->appendChild(new DOMElement('OrganizerName', Tinebase_Core::getUser()->accountFullName, 'uri:Calendar')); if (isset(Tinebase_Core::getUser()->accountEmailAddress)) { $_domParrent->appendChild(new DOMElement('OrganizerEmail', Tinebase_Core::getUser()->accountEmailAddress, 'uri:Calendar')); } } } else { // set the current account as organizer // if organizer is not set, you can not edit the event on the Motorola Milestone $_domParrent->appendChild(new DOMElement('OrganizerName', Tinebase_Core::getUser()->accountFullName, 'uri:Calendar')); if (isset(Tinebase_Core::getUser()->accountEmailAddress)) { $_domParrent->appendChild(new DOMElement('OrganizerEmail', Tinebase_Core::getUser()->accountEmailAddress, 'uri:Calendar')); } } if (isset($data->tags) && count($data->tags) > 0) { $categories = $_domParrent->appendChild(new DOMElement('Categories', null, 'uri:Calendar')); foreach ($data->tags as $tag) { $categories->appendChild(new DOMElement('Category', $tag, 'uri:Calendar')); } } }
/** * test xml generation for IPhone * * FIXME fix this test! -> seems to fail depending on the current time / date */ public function testAppendXml_dailyEvent() { $imp = new DOMImplementation(); $dtd = $imp->createDocumentType('AirSync', "-//AIRSYNC//DTD AirSync//EN", "http://www.microsoft.com/"); $testDom = $imp->createDocument('uri:AirSync', 'Sync', $dtd); $testDom->formatOutput = true; $testDom->encoding = 'utf-8'; $testDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Calendar', 'uri:Calendar'); $collections = $testDom->documentElement->appendChild($testDom->createElementNS('uri:AirSync', 'Collections')); $collection = $collections->appendChild($testDom->createElementNS('uri:AirSync', 'Collection')); $commands = $collection->appendChild($testDom->createElementNS('uri:AirSync', 'Commands')); $add = $commands->appendChild($testDom->createElementNS('uri:AirSync', 'Add')); $appData = $add->appendChild($testDom->createElementNS('uri:AirSync', 'ApplicationData')); $controller = new ActiveSync_Controller_Calendar($this->objects['deviceIPhone'], new Tinebase_DateTime()); $controller->appendXML($appData, null, $this->objects['eventDaily']->getId(), array()); // namespace === uri:Calendar $this->assertEquals(ActiveSync_Controller_Calendar::RECUR_TYPE_DAILY, @$testDom->getElementsByTagNameNS('uri:Calendar', 'Type')->item(0)->nodeValue, $testDom->saveXML()); $this->assertEquals(4, @$testDom->getElementsByTagNameNS('uri:Calendar', 'Exception')->length, $testDom->saveXML()); $this->assertEquals(4, @$testDom->getElementsByTagNameNS('uri:Calendar', 'ExceptionStartTime')->length, $testDom->saveXML()); $this->assertEquals(3, @$testDom->getElementsByTagNameNS('uri:Calendar', 'Subject')->length, $testDom->saveXML()); $endTime = $this->objects['eventDaily']->dtend->format("Ymd\\THis") . 'Z'; $this->assertEquals($endTime, @$testDom->getElementsByTagNameNS('uri:Calendar', 'EndTime')->item(0)->nodeValue, $testDom->saveXML()); $untilTime = Calendar_Model_Rrule::getRruleFromString($this->objects['eventDaily']->rrule)->until->format("Ymd\\THis") . 'Z'; $this->assertEquals($untilTime, @$testDom->getElementsByTagNameNS('uri:Calendar', 'Until')->item(0)->nodeValue, $testDom->saveXML()); }