public function testConvert() { $emailArrayFromClient = array(array('userType' => 'user', 'firstName' => 'Users', 'lastName' => '(Group)', 'partStat' => 'NEEDS-ACTION', 'role' => 'REQ', 'email' => 'urn:uuid:principals/intelligroups/cc74c2880f8c5c0eaacc57ea95f4d2571fb8a4b1')); $event = new Calendar_Model_Event(); Calendar_Model_Attender::emailsToAttendee($event, $emailArrayFromClient); $this->assertEquals('cc74c2880f8c5c0eaacc57ea95f4d2571fb8a4b1', $event->attendee->getFirstRecord()->user_id); $this->assertEquals('group', $event->attendee->getFirstRecord()->user_type); }
/** * @return void */ public function testEmailsToAttendeeWithGroups() { $event = $this->_getEvent(); $persistentEvent = Calendar_Controller_Event::getInstance()->create($event); $primaryGroup = Tinebase_Group::getInstance()->getGroupById(Tinebase_Core::getUser()->accountPrimaryGroup); $newAttendees = array(array('userType' => Calendar_Model_Attender::USERTYPE_USER, 'firstName' => $this->_testUser->accountFirstName, 'lastName' => $this->_testUser->accountLastName, 'partStat' => Calendar_Model_Attender::STATUS_TENTATIVE, 'role' => Calendar_Model_Attender::ROLE_REQUIRED, 'email' => $this->_testUser->accountEmailAddress), array('userType' => Calendar_Model_Attender::USERTYPE_GROUP, 'displayName' => $primaryGroup->name, 'partStat' => Calendar_Model_Attender::STATUS_NEEDSACTION, 'role' => Calendar_Model_Attender::ROLE_REQUIRED, 'email' => '*****@*****.**')); Calendar_Model_Attender::emailsToAttendee($persistentEvent, $newAttendees, TRUE); $persistentEvent = Calendar_Controller_Event::getInstance()->update($persistentEvent); $attendees = clone $persistentEvent->attendee; Calendar_Model_Attender::resolveAttendee($attendees); $newAttendees = array(); foreach ($attendees as $attendee) { $newAttendees[] = array('userType' => $attendee->user_type == 'group' ? Calendar_Model_Attender::USERTYPE_GROUP : Calendar_Model_Attender::USERTYPE_USER, 'partStat' => Calendar_Model_Attender::STATUS_TENTATIVE, 'role' => Calendar_Model_Attender::ROLE_REQUIRED, 'email' => $attendee->user_type == 'group' ? $attendee->user_id->getId() : $attendee->user_id->email, 'displayName' => $attendee->user_type == 'group' ? $attendee->user_id->name : $attendee->user_id->n_fileas); } Calendar_Model_Attender::emailsToAttendee($persistentEvent, $newAttendees, TRUE); $persistentEvent = Calendar_Controller_Event::getInstance()->update($persistentEvent); //var_dump($persistentEvent->attendee->toArray()); // there must be more than 2 attendees the user, the group + the groupmembers $this->assertGreaterThan(2, count($persistentEvent->attendee)); }
/** * (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; }
/** * parse VEVENT part of VCALENDAR * * @param Sabre_VObject_Component $_vevent the VEVENT to parse * @param Calendar_Model_Event $_event the Tine 2.0 event to update */ protected function _convertVevent(Sabre_VObject_Component $_vevent, Calendar_Model_Event $_event) { $event = $_event; $newAttendees = array(); // unset supported fields foreach ($this->_supportedFields as $field) { if ($field == 'alarms') { $event->{$field} = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm'); } else { $event->{$field} = null; } } foreach ($_vevent->children() as $property) { switch ($property->name) { case 'CREATED': case 'DTSTAMP': // do nothing break; case 'LAST-MODIFIED': $event->last_modified_time = new Tinebase_DateTime($property->value); break; case 'ATTENDEE': $newAttendees[] = $this->_getAttendee($property); break; case 'CLASS': if (in_array($property->value, array(Calendar_Model_Event::CLASS_PRIVATE, Calendar_Model_Event::CLASS_PUBLIC))) { $event->class = $property->value; } else { $event->class = Calendar_Model_Event::CLASS_PUBLIC; } break; case 'DTEND': if (isset($property['VALUE']) && strtoupper($property['VALUE']) == 'DATE') { // all day event $event->is_all_day_event = true; $dtend = $this->_convertToTinebaseDateTime($property, TRUE); // whole day events ends at 23:59:59 in Tine 2.0 but 00:00 the next day in vcalendar $dtend->subSecond(1); } else { $event->is_all_day_event = false; $dtend = $this->_convertToTinebaseDateTime($property); } $event->dtend = $dtend; break; case 'DTSTART': if (isset($property['VALUE']) && strtoupper($property['VALUE']) == 'DATE') { // all day event $event->is_all_day_event = true; $dtstart = $this->_convertToTinebaseDateTime($property, TRUE); } else { $event->is_all_day_event = false; $dtstart = $this->_convertToTinebaseDateTime($property); } $event->originator_tz = $dtstart->getTimezone()->getName(); $event->dtstart = $dtstart; break; case 'SEQUENCE': $event->seq = $property->value; break; case 'DESCRIPTION': case 'LOCATION': case 'UID': case 'SUMMARY': $key = strtolower($property->name); $event->{$key} = $property->value; break; case 'ORGANIZER': if (preg_match('/mailto:(?P<email>.*)/i', $property->value, $matches)) { // it's not possible to change the organizer by spec if (empty($event->organizer)) { $name = isset($property['CN']) ? $property['CN']->value : $matches['email']; $contact = Calendar_Model_Attender::resolveEmailToContact(array('email' => $matches['email'], 'lastName' => $name)); $event->organizer = $contact->getId(); } // Lightning attaches organizer ATTENDEE properties to ORGANIZER property and does not add an ATTENDEE for the organizer if (isset($property['PARTSTAT'])) { $newAttendees[] = $this->_getAttendee($property); } } break; case 'RECURRENCE-ID': // original start of the event $event->recurid = $this->_convertToTinebaseDateTime($property); // convert recurrence id to utc $event->recurid->setTimezone('UTC'); break; case 'RRULE': $event->rrule = $property->value; // convert date format $event->rrule = preg_replace_callback('/UNTIL=([\\dTZ]+)(?=;?)/', function ($matches) { if (strlen($matches[1]) < 10) { $dtUntil = date_create($matches[1], new DateTimeZone((string) Tinebase_Core::get(Tinebase_Core::USERTIMEZONE))); $dtUntil->setTimezone(new DateTimeZone('UTC')); } else { $dtUntil = date_create($matches[1]); } return 'UNTIL=' . $dtUntil->format(Tinebase_Record_Abstract::ISO8601LONG); }, $event->rrule); // remove additional days from BYMONTHDAY property $event->rrule = preg_replace('/(BYMONTHDAY=)([\\d]+)([,\\d]+)/', '$1$2', $event->rrule); // process exceptions if (isset($_vevent->EXDATE)) { $exdates = new Tinebase_Record_RecordSet('Calendar_Model_Event'); foreach ($_vevent->EXDATE as $exdate) { foreach ($exdate->getDateTimes() as $exception) { if (isset($exdate['VALUE']) && strtoupper($exdate['VALUE']) == 'DATE') { $recurid = new Tinebase_DateTime($exception->format(Tinebase_Record_Abstract::ISO8601LONG), (string) Tinebase_Core::get(Tinebase_Core::USERTIMEZONE)); } else { $recurid = new Tinebase_DateTime($exception->format(Tinebase_Record_Abstract::ISO8601LONG), $exception->getTimezone()); } $recurid->setTimezone(new DateTimeZone('UTC')); $eventException = new Calendar_Model_Event(array('recurid' => $recurid, 'is_deleted' => true)); $exdates->addRecord($eventException); } } $event->exdate = $exdates; } break; case 'TRANSP': if (in_array($property->value, array(Calendar_Model_Event::TRANSP_OPAQUE, Calendar_Model_Event::TRANSP_TRANSP))) { $event->transp = $property->value; } else { $event->transp = Calendar_Model_Event::TRANSP_TRANSP; } break; case 'UID': // it's not possible to change the uid by spec if (!empty($event->uid)) { continue; } $event->uid = $property->value; break; case 'VALARM': foreach ($property as $valarm) { switch (strtoupper($valarm->TRIGGER['VALUE']->value)) { # TRIGGER;VALUE=DATE-TIME:20111031T130000Z case 'DATE-TIME': //@TODO fixme $alarmTime = new Tinebase_DateTime($valarm->TRIGGER->value); $alarmTime->setTimezone('UTC'); $alarm = new Tinebase_Model_Alarm(array('alarm_time' => $alarmTime, 'minutes_before' => 'custom', 'model' => 'Calendar_Model_Event')); $event->alarms->addRecord($alarm); break; # TRIGGER;VALUE=DURATION:-PT1H15M # TRIGGER;VALUE=DURATION:-PT1H15M case 'DURATION': default: $alarmTime = $this->_convertToTinebaseDateTime($_vevent->DTSTART); $alarmTime->setTimezone('UTC'); preg_match('/(?P<invert>[+-]?)(?P<spec>P.*)/', $valarm->TRIGGER->value, $matches); $duration = new DateInterval($matches['spec']); $duration->invert = !!($matches['invert'] === '-'); $alarm = new Tinebase_Model_Alarm(array('alarm_time' => $alarmTime->add($duration), 'minutes_before' => $duration->format('%d') * 60 * 24 + $duration->format('%h') * 60 + $duration->format('%i'), 'model' => 'Calendar_Model_Event')); $event->alarms->addRecord($alarm); break; } } break; case 'CATEGORIES': // @todo handle categories break; case 'X-MOZ-LASTACK': $lastAck = $this->_convertToTinebaseDateTime($property); break; case 'X-MOZ-SNOOZE-TIME': $snoozeTime = $this->_convertToTinebaseDateTime($property); break; default: break; } } // merge old and new attendees Calendar_Model_Attender::emailsToAttendee($event, $newAttendees); if (($ownAttendee = Calendar_Model_Attender::getOwnAttender($event->attendee)) !== null) { if (isset($lastAck)) { $ownAttendee->alarm_ack_time = $lastAck; } if (isset($snoozeTime)) { $ownAttendee->alarm_snooze_time = $snoozeTime; } } if (empty($event->seq)) { $event->seq = 0; } if (empty($event->class)) { $event->class = Calendar_Model_Event::CLASS_PUBLIC; } // convert all datetime fields to UTC $event->setTimezone('UTC'); }
/** * convert contact from xml to Calendar_Model_Event * * @todo handle BusyStatus * @param SimpleXMLElement $_data * @return Calendar_Model_Event */ public function toTineModel(SimpleXMLElement $_data, $_entry = null) { if ($_entry instanceof Calendar_Model_Event) { $event = $_entry; } else { $event = new Calendar_Model_Event(array(), true); } $xmlData = $_data->children('uri:Calendar'); $airSyncBase = $_data->children('uri:AirSyncBase'); foreach ($this->_mapping as $fieldName => $value) { switch ($value) { case 'dtend': case 'dtstart': if (isset($xmlData->{$fieldName})) { $event->{$value} = new Tinebase_DateTime((string) $xmlData->{$fieldName}); } else { $event->{$value} = null; } break; default: if (isset($xmlData->{$fieldName})) { $event->{$value} = (string) $xmlData->{$fieldName}; } else { $event->{$value} = null; } break; } } // get body if (version_compare($this->_device->acsversion, '12.0', '>=') === true) { $event->description = isset($airSyncBase->Body) ? (string) $airSyncBase->Body->Data : null; } else { $event->description = isset($xmlData->Body) ? (string) $xmlData->Body : null; } // whole day events ends at 23:59:59 in Tine 2.0 but 00:00 the next day in AS if (isset($xmlData->AllDayEvent) && $xmlData->AllDayEvent == 1) { $event->dtend->subSecond(1); } if (isset($xmlData->Reminder)) { $alarm = clone $event->dtstart; $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(array('alarm_time' => $alarm->subMinute((int) $xmlData->Reminder), 'minutes_before' => (int) $xmlData->Reminder, 'model' => 'Calendar_Model_Event'))); } // decode timezone data if (isset($xmlData->Timezone)) { $timeZoneConverter = ActiveSync_TimezoneConverter::getInstance(Tinebase_Core::getLogger(), Tinebase_Core::get(Tinebase_Core::CACHE)); try { $timezone = $timeZoneConverter->getTimezone((string) $xmlData->Timezone, Tinebase_Core::get(Tinebase_Core::USERTIMEZONE)); $event->originator_tz = $timezone; } catch (ActiveSync_TimezoneNotFoundException $e) { Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ . " timezone data not found " . (string) $xmlData->Timezone); $event->originator_tz = Tinebase_Core::get(Tinebase_Core::USERTIMEZONE); } if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " timezone data " . $event->originator_tz); } } if (!$event->attendee instanceof Tinebase_Record_RecordSet) { $event->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender'); } if (isset($xmlData->Attendees)) { $newAttendees = array(); foreach ($xmlData->Attendees->Attendee as $attendee) { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " attendee email " . $attendee->Email); } if (isset($attendee->AttendeeType) && array_key_exists((int) $attendee->AttendeeType, $this->_attendeeTypeMapping)) { $role = $this->_attendeeTypeMapping[(int) $attendee->AttendeeType]; } else { $role = Calendar_Model_Attender::ROLE_REQUIRED; } // AttendeeStatus send only on repsonse if (preg_match('/(?P<firstName>\\S*) (?P<lastNameName>\\S*)/', (string) $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' => (string) $attendee->Email); } Calendar_Model_Attender::emailsToAttendee($event, $newAttendees); } // new event, add current user as participant if ($event->getId() == null) { $selfContactId = Tinebase_Core::getUser()->contact_id; $selfAttender = $event->attendee->filter('user_type', Calendar_Model_Attender::USERTYPE_USER)->filter('user_id', $selfContactId); if (count($selfAttender) == 0) { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " added current user as attender for new event "); } $newAttender = new Calendar_Model_Attender(array('user_id' => $selfContactId, 'user_type' => Calendar_Model_Attender::USERTYPE_USER, 'status' => Calendar_Model_Attender::STATUS_ACCEPTED, 'role' => Calendar_Model_Attender::ROLE_REQUIRED)); $event->attendee->addRecord($newAttender); } } if (isset($xmlData->BusyStatus) && ($ownAttendee = Calendar_Model_Attender::getOwnAttender($event->attendee)) !== null) { if (isset($this->_busyStatusMapping[(string) $xmlData->BusyStatus])) { $ownAttendee->status = $this->_busyStatusMapping[(string) $xmlData->BusyStatus]; } else { $ownAttendee->status = Calendar_Model_Attender::STATUS_NEEDSACTION; } } // handle recurrence if (isset($xmlData->Recurrence) && isset($xmlData->Recurrence->Type)) { $rrule = new Calendar_Model_Rrule(); switch ((int) $xmlData->Recurrence->Type) { case self::RECUR_TYPE_DAILY: $rrule->freq = Calendar_Model_Rrule::FREQ_DAILY; break; case self::RECUR_TYPE_WEEKLY: $rrule->freq = Calendar_Model_Rrule::FREQ_WEEKLY; $rrule->byday = $this->_convertBitMaskToDay((int) $xmlData->Recurrence->DayOfWeek); break; case self::RECUR_TYPE_MONTHLY: $rrule->freq = Calendar_Model_Rrule::FREQ_MONTHLY; $rrule->bymonthday = (int) $xmlData->Recurrence->DayOfMonth; break; case self::RECUR_TYPE_MONTHLY_DAYN: $rrule->freq = Calendar_Model_Rrule::FREQ_MONTHLY; $week = (int) $xmlData->Recurrence->WeekOfMonth; $day = (int) $xmlData->Recurrence->DayOfWeek; $byDay = $week == 5 ? -1 : $week; $byDay .= $this->_convertBitMaskToDay($day); $rrule->byday = $byDay; break; case self::RECUR_TYPE_YEARLY: $rrule->freq = Calendar_Model_Rrule::FREQ_YEARLY; $rrule->bymonth = (int) $xmlData->Recurrence->MonthOfYear; $rrule->bymonthday = (int) $xmlData->Recurrence->DayOfMonth; break; case self::RECUR_TYPE_YEARLY_DAYN: $rrule->freq = Calendar_Model_Rrule::FREQ_YEARLY; $rrule->bymonth = (int) $xmlData->Recurrence->MonthOfYear; $week = (int) $xmlData->Recurrence->WeekOfMonth; $day = (int) $xmlData->Recurrence->DayOfWeek; $byDay = $week == 5 ? -1 : $week; $byDay .= $this->_convertBitMaskToDay($day); $rrule->byday = $byDay; break; } $rrule->interval = isset($xmlData->Recurrence->Interval) ? (int) $xmlData->Recurrence->Interval : 1; if (isset($xmlData->Recurrence->Until)) { $rrule->until = new Tinebase_DateTime((string) $xmlData->Recurrence->Until); // until ends at 23:59:59 in Tine 2.0 but at 00:00:00 in Windows CE (local user time) if ($rrule->until->format('s') == '00') { $rrule->until->addHour(23)->addMinute(59)->addSecond(59); } } else { $rrule->until = null; } $event->rrule = $rrule; // handle exceptions from recurrence if (isset($xmlData->Exceptions)) { $exdates = new Tinebase_Record_RecordSet('Calendar_Model_Event'); foreach ($xmlData->Exceptions->Exception as $exception) { $eventException = new Calendar_Model_Event(array('recurid' => new Tinebase_DateTime((string) $exception->ExceptionStartTime))); if ((int) $exception->Deleted === 0) { $eventException->is_deleted = false; $this->toTineModel($exception, $eventException); } else { $eventException->is_deleted = true; } $exdates->addRecord($eventException); } $event->exdate = $exdates; } } else { $event->rrule = null; $event->exdate = null; } if (empty($event->organizer)) { $event->organizer = Tinebase_Core::getUser()->contact_id; } // 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; }
public function testEmailsToAttendeeWithMissingMail() { $contact = new Addressbook_Model_Contact(array('org_name' => 'unittestorg', 'email' => '', 'email_home' => '')); $persistentContact = Addressbook_Controller_Contact::getInstance()->create($contact, FALSE); $event = $this->_getEvent(); $event->attendee->addRecord(new Calendar_Model_Attender(array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $persistentContact->getId(), 'role' => Calendar_Model_Attender::ROLE_REQUIRED))); $persistentEvent = Calendar_Controller_Event::getInstance()->create($event); $clientAttendee = array(); foreach ($persistentEvent->attendee as $attendee) { $clientAttendee[] = array('userType' => Calendar_Model_Attender::USERTYPE_USER, 'email' => $attendee->getEmail(), 'role' => $attendee->role); } Calendar_Model_Attender::emailsToAttendee($persistentEvent, $clientAttendee, TRUE); $userIds = $persistentEvent->attendee->user_id; foreach ($userIds as $idx => $id) { if ($id instanceof Tinebase_Record_Abstract) { $userIds[$idx] = $id->getId(); } } $this->assertEquals(3, count($userIds)); $this->assertTrue(in_array($this->_testUserContact->getId(), $userIds), 'testaccount missing'); $this->assertTrue(in_array($this->_personasContacts['sclever']->getId(), $userIds), 'sclever missing'); $this->assertTrue(in_array($persistentContact->getId(), $userIds), 'unittestorg missing'); }
/** * parse VEVENT part of VCALENDAR * * @param \Sabre\VObject\Component\VEvent $vevent the VEVENT to parse * @param Calendar_Model_Event $event the Tine 2.0 event to update * @param array $options */ protected function _convertVevent(\Sabre\VObject\Component\VEvent $vevent, Calendar_Model_Event $event, $options) { if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) { Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' vevent ' . $vevent->serialize()); } $newAttendees = array(); $attachments = new Tinebase_Record_RecordSet('Tinebase_Model_Tree_Node'); $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm'); $skipFieldsIfOnlyBasicData = array('ATTENDEE', 'UID', 'ORGANIZER', 'VALARM', 'ATTACH', 'CATEGORIES'); foreach ($vevent->children() as $property) { if (isset($this->_options['onlyBasicData']) && $this->_options['onlyBasicData'] && in_array((string) $property->name, $skipFieldsIfOnlyBasicData)) { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Skipping ' . $property->name . ' (using option onlyBasicData)'); } continue; } switch ($property->name) { case 'CREATED': case 'DTSTAMP': if (!isset($options[self::OPTION_USE_SERVER_MODLOG]) || $options[self::OPTION_USE_SERVER_MODLOG] !== true) { $event->{$property->name == 'CREATED' ? 'creation_time' : 'last_modified_time'} = $this->_convertToTinebaseDateTime($property); } break; case 'LAST-MODIFIED': $event->last_modified_time = new Tinebase_DateTime($property->getValue()); break; case 'ATTENDEE': $newAttendee = $this->_getAttendee($property); if ($newAttendee) { $newAttendees[] = $newAttendee; } break; case 'CLASS': if (in_array($property->getValue(), array(Calendar_Model_Event::CLASS_PRIVATE, Calendar_Model_Event::CLASS_PUBLIC))) { $event->class = $property->getValue(); } else { $event->class = Calendar_Model_Event::CLASS_PUBLIC; } break; case 'STATUS': if (in_array($property->getValue(), array(Calendar_Model_Event::STATUS_CONFIRMED, Calendar_Model_Event::STATUS_TENTATIVE, Calendar_Model_Event::STATUS_CANCELED))) { $event->status = $property->getValue(); } else { if ($property->getValue() == 'CANCELLED') { $event->status = Calendar_Model_Event::STATUS_CANCELED; } else { $event->status = Calendar_Model_Event::STATUS_CONFIRMED; } } break; case 'DTEND': if (isset($property['VALUE']) && strtoupper($property['VALUE']) == 'DATE') { // all day event $event->is_all_day_event = true; $dtend = $this->_convertToTinebaseDateTime($property, TRUE); // whole day events ends at 23:59:59 in Tine 2.0 but 00:00 the next day in vcalendar $dtend->subSecond(1); } else { $event->is_all_day_event = false; $dtend = $this->_convertToTinebaseDateTime($property); } $event->dtend = $dtend; break; case 'DTSTART': if (isset($property['VALUE']) && strtoupper($property['VALUE']) == 'DATE') { // all day event $event->is_all_day_event = true; $dtstart = $this->_convertToTinebaseDateTime($property, TRUE); } else { $event->is_all_day_event = false; $dtstart = $this->_convertToTinebaseDateTime($property); } $event->originator_tz = $dtstart->getTimezone()->getName(); $event->dtstart = $dtstart; break; case 'SEQUENCE': if (!isset($options[self::OPTION_USE_SERVER_MODLOG]) || $options[self::OPTION_USE_SERVER_MODLOG] !== true) { $event->seq = $property->getValue(); } // iMIP only $event->external_seq = $property->getValue(); break; case 'DESCRIPTION': case 'LOCATION': case 'SUMMARY': $key = strtolower($property->name); $value = $property->getValue(); $event->{$key} = $value; break; case 'ORGANIZER': $email = null; if (!empty($property['EMAIL'])) { $email = $property['EMAIL']; } elseif (preg_match('/mailto:(?P<email>.*)/i', $property->getValue(), $matches)) { $email = $matches['email']; } if ($email !== null) { // it's not possible to change the organizer by spec if (empty($event->organizer)) { $name = isset($property['CN']) ? $property['CN']->getValue() : $email; $contact = Calendar_Model_Attender::resolveEmailToContact(array('email' => $email, 'lastName' => $name)); $event->organizer = $contact->getId(); } // Lightning attaches organizer ATTENDEE properties to ORGANIZER property and does not add an ATTENDEE for the organizer if (isset($property['PARTSTAT'])) { $newAttendees[] = $this->_getAttendee($property); } } break; case 'RECURRENCE-ID': // original start of the event $event->recurid = $this->_convertToTinebaseDateTime($property); // convert recurrence id to utc $event->recurid->setTimezone('UTC'); break; case 'RRULE': $rruleString = $property->getValue(); // convert date format $rruleString = preg_replace_callback('/UNTIL=([\\dTZ]+)(?=;?)/', function ($matches) { $dtUntil = Calendar_Convert_Event_VCalendar_Abstract::getUTCDateFromStringInUsertime($matches[1]); return 'UNTIL=' . $dtUntil->format(Tinebase_Record_Abstract::ISO8601LONG); }, $rruleString); // remove additional days from BYMONTHDAY property (BYMONTHDAY=11,15 => BYMONTHDAY=11) $rruleString = preg_replace('/(BYMONTHDAY=)([\\d]+),([,\\d]+)/', '$1$2', $rruleString); $event->rrule = $rruleString; // process exceptions if (isset($vevent->EXDATE)) { $exdates = new Tinebase_Record_RecordSet('Calendar_Model_Event'); foreach ($vevent->EXDATE as $exdate) { foreach ($exdate->getDateTimes() as $exception) { if (isset($exdate['VALUE']) && strtoupper($exdate['VALUE']) == 'DATE') { $recurid = new Tinebase_DateTime($exception->format(Tinebase_Record_Abstract::ISO8601LONG), (string) Tinebase_Core::getUserTimezone()); } else { $recurid = new Tinebase_DateTime($exception->format(Tinebase_Record_Abstract::ISO8601LONG), $exception->getTimezone()); } $recurid->setTimezone(new DateTimeZone('UTC')); $eventException = new Calendar_Model_Event(array('recurid' => $recurid, 'is_deleted' => true)); $exdates->addRecord($eventException); } } $event->exdate = $exdates; } break; case 'TRANSP': if (in_array($property->getValue(), array(Calendar_Model_Event::TRANSP_OPAQUE, Calendar_Model_Event::TRANSP_TRANSP))) { $event->transp = $property->getValue(); } else { $event->transp = Calendar_Model_Event::TRANSP_TRANSP; } break; case 'UID': // it's not possible to change the uid by spec if (!empty($event->uid)) { continue; } $event->uid = $property->getValue(); break; case 'VALARM': $this->_parseAlarm($event, $property, $vevent); break; case 'CATEGORIES': $tags = Tinebase_Model_Tag::resolveTagNameToTag($property->getParts(), 'Calendar'); if (!isset($event->tags)) { $event->tags = $tags; } else { $event->tags->merge($tags); } break; case 'ATTACH': $name = (string) $property['FILENAME']; $managedId = (string) $property['MANAGED-ID']; $value = (string) $property['VALUE']; $attachment = NULL; $readFromURL = false; $url = ''; if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' attachment found: ' . $name . ' ' . $managedId); } if ($managedId) { $attachment = $event->attachments instanceof Tinebase_Record_RecordSet ? $event->attachments->filter('hash', $property['MANAGED-ID'])->getFirstRecord() : NULL; // NOTE: we might miss a attachment here for the following reasons // 1. client reuses a managed id (we are server): // We havn't observerd this yet. iCal client reuse manged id's // from base events in exceptions but this is covered as we // initialize new exceptions with base event attachments // // When a client reuses a managed id it's not clear yet if // this managed id needs to be in the same series/calendar/server // // As we use the object hash the managed id might be used in the // same files with different names. We need to evaluate the name // (if attached) in this case as well. // // 2. server send his managed id (we are client) // * we need to download the attachment (here?) // * we need to have a mapping externalid / internalid (where?) if (!$attachment) { $readFromURL = true; $url = $property->getValue(); } else { $attachments->addRecord($attachment); } } elseif ('URI' === $value) { /* * ATTACH;VALUE=URI:https://server.com/calendars/__uids__/0AA0 3A3B-F7B6-459A-AB3E-4726E53637D0/dropbox/4971F93F-8657-412B-841A-A0FD913 9CD61.dropbox/Canada.png */ $url = $property->getValue(); $urlParts = parse_url($url); $host = $urlParts['host']; $name = pathinfo($urlParts['path'], PATHINFO_BASENAME); // iCal 10.7 places URI before uploading if (parse_url(Tinebase_Core::getHostname(), PHP_URL_HOST) != $host) { $readFromURL = true; } } else { // @TODO: implement (check if add / update / update is needed) if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) { Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' attachment found that could not be imported due to missing managed id'); } } if ($readFromURL) { if (preg_match('#^(https?://)(.*)$#', str_replace(array("\n", "\r"), '', $url), $matches)) { // we are client and found an external hosted attachment that we need to import $userCredentialCache = Tinebase_Core::getUserCredentialCache(); $url = $matches[1] . $userCredentialCache->username . ':' . $userCredentialCache->password . '@' . $matches[2]; $attachmentInfo = $matches[1] . $matches[2] . ' ' . $name . ' ' . $managedId; if (Tinebase_Helper::urlExists($url)) { if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Downloading attachment: ' . $attachmentInfo); } $stream = fopen($url, 'r'); $attachment = new Tinebase_Model_Tree_Node(array('name' => rawurldecode($name), 'type' => Tinebase_Model_Tree_Node::TYPE_FILE, 'contenttype' => (string) $property['FMTTYPE'], 'tempFile' => $stream), true); $attachments->addRecord($attachment); } else { if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) { Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Url not found (got 404): ' . $attachmentInfo . ' - Skipping attachment'); } } } else { if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) { Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Attachment found with malformed url: ' . $url); } } } break; case 'X-MOZ-LASTACK': $lastAck = $this->_convertToTinebaseDateTime($property); break; case 'X-MOZ-SNOOZE-TIME': $snoozeTime = $this->_convertToTinebaseDateTime($property); break; default: // thunderbird saves snooze time for recurring event occurrences in properties with names like this - // we just assume that the event/recur series has only one snooze time if (preg_match('/^X-MOZ-SNOOZE-TIME-[0-9]+$/', $property->name)) { $snoozeTime = $this->_convertToTinebaseDateTime($property); if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Found snooze time for recur occurrence: ' . $snoozeTime->toString()); } } break; } } // NOTE: X-CALENDARSERVER-ACCESS overwrites CLASS if (isset($vevent->{'X-CALENDARSERVER-ACCESS'})) { $event->class = $vevent->{'X-CALENDARSERVER-ACCESS'} == 'PUBLIC' ? Calendar_Model_Event::CLASS_PUBLIC : Calendar_Model_Event::CLASS_PRIVATE; } if (isset($lastAck)) { Calendar_Controller_Alarm::setAcknowledgeTime($event->alarms, $lastAck); } if (isset($snoozeTime)) { Calendar_Controller_Alarm::setSnoozeTime($event->alarms, $snoozeTime); } // merge old and new attendee Calendar_Model_Attender::emailsToAttendee($event, $newAttendees); if (empty($event->seq)) { $event->seq = 1; } if (empty($event->class)) { $event->class = Calendar_Model_Event::CLASS_PUBLIC; } $this->_manageAttachmentsFromClient($event, $attachments); if (empty($event->dtend)) { if (empty($event->dtstart)) { throw new Tinebase_Exception_UnexpectedValue("Got event without dtstart and dtend"); } // TODO find out duration (see TRIGGER DURATION) // if (isset($vevent->DURATION)) { // } if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) { Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Got event without dtend. Assuming 30 minutes duration'); } $event->dtend = clone $event->dtstart; $event->dtend->addMinute(30); } $this->_manageAttachmentsFromClient($event, $attachments); // convert all datetime fields to UTC $event->setTimezone('UTC'); }