/**
  * 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');
 }
Example #6
0
 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;
 }
Example #9
0
 /**
  * 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());
 }