Changeset View
Changeset View
Standalone View
Standalone View
lib/kolab_sync_data_calendar.php
Show First 20 Lines • Show All 174 Lines • ▼ Show 20 Lines | // 'CHAIR' => self::ATTENDEE_TYPE_RESOURCE, | ||||
* @param string $serverId Local entry identifier | * @param string $serverId Local entry identifier | ||||
* @param boolean $as_array Return entry as array | * @param boolean $as_array Return entry as array | ||||
* | * | ||||
* @return array|Syncroton_Model_Event|array Event object | * @return array|Syncroton_Model_Event|array Event object | ||||
*/ | */ | ||||
public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId, $as_array = false) | public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId, $as_array = false) | ||||
{ | { | ||||
$event = is_array($serverId) ? $serverId : $this->getObject($collection->collectionId, $serverId); | $event = is_array($serverId) ? $serverId : $this->getObject($collection->collectionId, $serverId); | ||||
// If there is no such event, return NULL (otherwise this function | |||||
// returns a new Syncroton_Model_Event, that is further treated as if | |||||
// it were a valid, existing entry. | |||||
if (!$event) { | |||||
return NULL; | |||||
machniak: I think we should rather throw an exception as we do e.g. in kolab_sync_data_contacts::getEntry… | |||||
vanmeeuwenAuthorUnsubmitted Not Done Inline ActionsThe NULL will result in an exception when such exception is appropriate. vanmeeuwen: The NULL will result in an exception when such exception is appropriate. | |||||
} | |||||
$config = $this->getFolderConfig($event['_mailbox']); | $config = $this->getFolderConfig($event['_mailbox']); | ||||
$result = array(); | $result = array(); | ||||
// Timezone | // Timezone | ||||
// Kolab Format 3.0 and xCal does support timezone per-date, but ActiveSync allows | // Kolab Format 3.0 and xCal does support timezone per-date, but ActiveSync allows | ||||
// only one timezone per-event. We'll use timezone of the start date | // only one timezone per-event. We'll use timezone of the start date | ||||
if ($event['start'] instanceof DateTime) { | if ($event['start'] instanceof DateTime) { | ||||
$timezone = $event['start']->getTimezone(); | $timezone = $event['start']->getTimezone(); | ||||
▲ Show 20 Lines • Show All 77 Lines • ▼ Show 20 Lines | public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId, $as_array = false) | ||||
// Organizer | // Organizer | ||||
if (!empty($event['attendees'])) { | if (!empty($event['attendees'])) { | ||||
foreach ($event['attendees'] as $idx => $attendee) { | foreach ($event['attendees'] as $idx => $attendee) { | ||||
if ($attendee['role'] == 'ORGANIZER') { | if ($attendee['role'] == 'ORGANIZER') { | ||||
if ($name = $attendee['name']) { | if ($name = $attendee['name']) { | ||||
$result['organizerName'] = $name; | $result['organizerName'] = $name; | ||||
} | } | ||||
if ($email = $attendee['email']) { | if ($email = $attendee['email']) { | ||||
$result['organizerEmail'] = $email; | $result['organizerEmail'] = $email; | ||||
} | } | ||||
unset($event['attendees'][$idx]); | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
// Attendees | // Attendees | ||||
if (!empty($event['attendees'])) { | if (!empty($event['attendees'])) { | ||||
foreach ($event['attendees'] as $idx => $attendee) { | foreach ($event['attendees'] as $idx => $attendee) { | ||||
$att = array(); | if ($attendee['role'] == 'ORGANIZER') | ||||
continue; | |||||
$att = Array(); | |||||
if ($name = $attendee['name']) { | if ($name = $attendee['name']) { | ||||
$att['name'] = $name; | $att['name'] = $name; | ||||
} | } | ||||
if ($email = $attendee['email']) { | if ($email = $attendee['email']) { | ||||
$att['email'] = $email; | $att['email'] = $email; | ||||
} | } | ||||
if ($this->asversion >= 12) { | if ($this->asversion >= 12) { | ||||
$type = isset($attendee['role']) ? $this->attendeeTypeMap[$attendee['role']] : null; | $type = isset($attendee['role']) ? $this->attendeeTypeMap[$attendee['role']] : null; | ||||
$status = isset($attendee['status']) ? $this->attendeeStatusMap[$attendee['status']] : null; | $status = isset($attendee['status']) ? $this->attendeeStatusMap[$attendee['status']] : null; | ||||
▲ Show 20 Lines • Show All 143 Lines • ▼ Show 20 Lines | public function toKolab(Syncroton_Model_IEntry $data, $folderid, $entry = null, $timezone = null) | ||||
} | } | ||||
} | } | ||||
// Organizer | // Organizer | ||||
if (!$is_exception) { | if (!$is_exception) { | ||||
$name = $data->organizerName; | $name = $data->organizerName; | ||||
$email = $data->organizerEmail; | $email = $data->organizerEmail; | ||||
if ($name || $email) { | if ($name || $email) { | ||||
$event['attendees'][] = array( | $_attendee = array( | ||||
'role' => 'ORGANIZER', | 'role' => 'ORGANIZER', | ||||
'name' => $name, | 'name' => $name, | ||||
'email' => $email, | 'email' => $email, | ||||
); | ); | ||||
if ($this->asversion >= 12) { | |||||
machniakUnsubmitted Not Done Inline ActionsI don't think we need this version check here. MeetingStatus is supported in older versions for top-level data. Do I miss something? machniak: I don't think we need this version check here. MeetingStatus is supported in older versions for… | |||||
vanmeeuwenAuthorUnsubmitted Not Done Inline ActionsI don't know what this means:
vanmeeuwen: I don't know what this means:
> MeetingStatus is supported in older versions for top-level… | |||||
$_attendee['status'] = $data->meetingStatus == 1 ? 'ACCEPTED' : 'DECLINED'; | |||||
machniakUnsubmitted Not Done Inline ActionsThis does not make much sense to me. This will set the organizer status to DECLINED for events where the user is an attendee. I'd rather set the organizer status always to ACCEPTED. machniak: This does not make much sense to me. This will set the organizer status to DECLINED for events… | |||||
vanmeeuwenAuthorUnsubmitted Not Done Inline ActionsNo, it'll only set the attendee's status to DECLINED if the overall meeting status is CANCELLED, and the attendee in question is also the organizer. vanmeeuwen: No, it'll only set the attendee's status to DECLINED if the overall meeting status is CANCELLED… | |||||
$_attendee['rsvp'] = FALSE; | |||||
} | |||||
$event['attendees'][] = $_attendee; | |||||
} | } | ||||
} | } | ||||
// Attendees | // Attendees | ||||
if (isset($data->attendees)) { | if (isset($data->attendees)) { | ||||
foreach ($data->attendees as $attendee) { | foreach ($data->attendees as $attendee) { | ||||
if ($attendee->email == $data->organizerEmail) | |||||
continue; | |||||
$role = false; | $role = false; | ||||
if (isset($attendee->attendeeType)) { | if (isset($attendee->attendeeType)) { | ||||
$role = array_search($attendee->attendeeType, $this->attendeeTypeMap); | $role = array_search( | ||||
$attendee->attendeeType, | |||||
$this->attendeeTypeMap | |||||
); | |||||
} | } | ||||
if ($role === false) { | if ($role === false) { | ||||
$role = array_search(self::ATTENDEE_TYPE_REQUIRED, $this->attendeeTypeMap); | $role = array_search( | ||||
self::ATTENDEE_TYPE_REQUIRED, | |||||
$this->attendeeTypeMap | |||||
); | |||||
} | } | ||||
// AttendeeStatus send only on repsonse (?) | // AttendeeStatus send only on response (?) | ||||
$_attendee = array( | |||||
$event['attendees'][] = array( | |||||
'role' => $role, | 'role' => $role, | ||||
'name' => $attendee->name, | 'name' => $attendee->name, | ||||
'email' => $attendee->email, | 'email' => $attendee->email, | ||||
); | ); | ||||
if ($this->asversion >= 12) { | |||||
if ($status = $attendee->attendeeStatus) { | |||||
$_attendee['status'] = array_search( | |||||
$status, | |||||
$this->attendeeStatusMap | |||||
); | |||||
if ($status == self::ATTENDEE_STATUS_UNKNOWN) { | |||||
$_attendee['rsvp'] = TRUE; | |||||
} | |||||
} | |||||
} | |||||
$event['attendees'][] = $_attendee; | |||||
} | } | ||||
} | } | ||||
// recurrence (and exceptions) | // recurrence (and exceptions) | ||||
if (!$is_exception) { | if (!$is_exception) { | ||||
$event['recurrence'] = $this->recurrence_to_kolab($data, $folderid, $timezone); | $event['recurrence'] = $this->recurrence_to_kolab($data, $folderid, $timezone); | ||||
} | } | ||||
Show All 14 Lines | // 'CHAIR' => self::ATTENDEE_TYPE_RESOURCE, | ||||
* Set attendee status for meeting | * Set attendee status for meeting | ||||
* | * | ||||
* @param Syncroton_Model_MeetingResponse $request The meeting response | * @param Syncroton_Model_MeetingResponse $request The meeting response | ||||
* | * | ||||
* @return string ID of new calendar entry | * @return string ID of new calendar entry | ||||
*/ | */ | ||||
public function setAttendeeStatus(Syncroton_Model_MeetingResponse $request) | public function setAttendeeStatus(Syncroton_Model_MeetingResponse $request) | ||||
{ | { | ||||
/* | |||||
* The request actually involves an iTip request. | |||||
* | |||||
* - Get to the iTip, | |||||
* - Extract the event UID, | |||||
* - If not already an event in the user's calendar(s), create the event, | |||||
* - If already an event, update that event. | |||||
*/ | |||||
$object = $this->parseMessageId($request->requestId); | |||||
$message = new rcube_message($object['uid'], $object['foldername']); | |||||
// Parse the message | |||||
$libcal = libcalendaring::get_instance(); | |||||
$libcal->mail_message_load(array('object' => $message)); | |||||
$ical_objects = $libcal->get_mail_ical_objects(); | |||||
$itip = $ical_objects->objects[0]; | |||||
$uid = $itip['uid']; | |||||
// Search the user's (event) folders | |||||
$folders = $this->listFolders(); | |||||
$serverIds = Array(); | |||||
foreach ($folders as $folder => $attrs) { | |||||
$serverIds[] = $this->serverId($uid, $attrs['imap_name']); | |||||
} | |||||
// Go search for a match | |||||
$existing_event = NULL; | |||||
foreach ($serverIds as $serverId) { | |||||
foreach ($folders as $_folderid => $attrs) { | |||||
$collection = new Syncroton_Model_SyncCollection( | |||||
Array( | |||||
'collectionId' => $_folderid | |||||
) | |||||
); | |||||
$existing_event = $this->getEntry( | |||||
$collection, | |||||
$serverId | |||||
); | |||||
if ($existing_event) { | |||||
$folderId = $_folderid; | |||||
break; | |||||
} | |||||
} | |||||
if ($existing_event) { | |||||
break; | |||||
} | |||||
} | |||||
/* | |||||
Consider; | |||||
- $existing_event->sensitivity (private, confidential, public) | |||||
- $existing_event->attendees[$x]->{role,rsvp,status} | |||||
- $request->userresponse | |||||
*/ | |||||
if ($existing_event) { | |||||
// This is an existing event that needs updating | |||||
$user_emails = kolab_sync::get_instance()->user->list_emails(); | |||||
$user_emails = array_map( | |||||
function($v) { return $v['email']; }, | |||||
$user_emails | |||||
); | |||||
foreach ($existing_event->attendees as $idx => $attendee) { | |||||
if (in_array_nocase($attendee->email, $user_emails)) { | |||||
$this->log->debug("found my record, updating status"); | |||||
switch ($request->userResponse) { | |||||
case Syncroton_Model_MeetingResponse::RESPONSE_ACCEPTED: | |||||
$existing_event->attendees[$idx]->attendeeStatus = self::ATTENDEE_STATUS_ACCEPTED; | |||||
break; | |||||
case Syncroton_Model_MeetingResponse::RESPONSE_DECLINED: | |||||
$existing_event->attendees[$idx]->attendeeStatus = self::ATTENDEE_STATUS_DECLINED; | |||||
break; | |||||
case Syncroton_Model_MeetingResponse::RESPONSE_TENTATIVE: | |||||
$existing_event->attendees[$idx]->attendeeStatus = self::ATTENDEE_STATUS_TENTATIVE; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
$this->updateEntry($folderId, $serverId, $existing_event); | |||||
} else { | |||||
// This is a new event | |||||
$this->log->debug("This is a new event"); | |||||
$folderId = $this->getDefaultFolder()['realid']; | |||||
$user_emails = kolab_sync::get_instance()->user->list_emails(); | |||||
$user_emails = array_map( | |||||
function($v) { return $v['email']; }, | |||||
$user_emails | |||||
); | |||||
$attendees = Array(); | |||||
foreach($itip['attendees'] as $idx => $attendee) { | |||||
$att = $attendee; | |||||
if (in_array_nocase($att['email'], $user_emails)) { | |||||
switch ($request->userResponse) { | |||||
case Syncroton_Model_MeetingResponse::RESPONSE_ACCEPTED: | |||||
$att['status'] = 'ACCEPTED'; | |||||
$att['rsvp'] = FALSE; | |||||
break; | |||||
case Syncroton_Model_MeetingResponse::RESPONSE_DECLINED: | |||||
$att['status'] = 'DECLINED'; | |||||
$att['rsvp'] = FALSE; | |||||
break; | |||||
case Syncroton_Model_MeetingResponse::RESPONSE_TENTATIVE: | |||||
$att['status'] = 'TENTATIVE'; | |||||
break; | |||||
} | |||||
} | |||||
$attendees[] = $att; | |||||
} | |||||
$itip['attendees'] = $attendees; | |||||
$data = $this->createObject($folderId, $itip); | |||||
} | |||||
// @TODO: not implemented | // @TODO: not implemented | ||||
throw new Syncroton_Exception_Status_MeetingResponse(Syncroton_Exception_Status_MeetingResponse::MEETING_ERROR); | throw new Syncroton_Exception_Status_MeetingResponse(1); | ||||
} | } | ||||
/** | /** | ||||
* Returns filter query array according to specified ActiveSync FilterType | * Returns filter query array according to specified ActiveSync FilterType | ||||
* | * | ||||
* @param int $filter_type Filter type | * @param int $filter_type Filter type | ||||
* | * | ||||
* @param array Filter query | * @param array Filter query | ||||
Show All 38 Lines | protected function meeting_status_from_kolab($collection, $event, &$result) | ||||
// 7 - The meeting has been canceled. The user was not the meeting organizer. | // 7 - The meeting has been canceled. The user was not the meeting organizer. | ||||
$status = 0; | $status = 0; | ||||
if (!empty($event['attendees'])) { | if (!empty($event['attendees'])) { | ||||
// Find out if the user is an organizer | // Find out if the user is an organizer | ||||
// TODO: Delegation/aliases support | // TODO: Delegation/aliases support | ||||
$user_emails = kolab_sync::get_instance()->user->list_emails(); | $user_emails = kolab_sync::get_instance()->user->list_emails(); | ||||
$user_emails = array_map(function($v) { return $v['email']; }, $user_emails); | $user_emails = array_map(function($v) { return $v['email']; }, $user_emails); | ||||
$is_organizer = true; | $is_organizer = false; | ||||
foreach ($event['attendees'] as $attendee) { | foreach ($event['attendees'] as $attendee) { | ||||
if ($attendee['role'] != 'ORGANIZER') { | |||||
// Irrelevant, skip | |||||
continue; | |||||
} | |||||
if (in_array_nocase($attendee['email'], $user_emails)) { | if (in_array_nocase($attendee['email'], $user_emails)) { | ||||
$is_organizer = false; | $is_organizer = true; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
if ($event['status'] == 'CANCELLED') { | if ($event['status'] == 'CANCELLED') { | ||||
$status = !empty($is_organizer) ? 5 : 7; | $status = $is_organizer ? 5 : 7; | ||||
} | } | ||||
else { | else { | ||||
$status = !empty($is_organizer) ? 1 : 3; | $status = $is_organizer ? 1 : 3; | ||||
} | } | ||||
} | } | ||||
$result['meetingStatus'] = $status; | $result['meetingStatus'] = $status; | ||||
} | } | ||||
/** | /** | ||||
* Converts libkolab alarms spec. into a number of minutes | * Converts libkolab alarms spec. into a number of minutes | ||||
▲ Show 20 Lines • Show All 106 Lines • ▼ Show 20 Lines | protected function eventCheck(Syncroton_Model_IEntry &$entry) | ||||
if ($entry->startTime < $now) { | if ($entry->startTime < $now) { | ||||
throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::INVALID_ITEM); | throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::INVALID_ITEM); | ||||
} | } | ||||
$rounded->add(new DateInterval($entry->allDayEvent ? 'P1D' : 'PT30M')); | $rounded->add(new DateInterval($entry->allDayEvent ? 'P1D' : 'PT30M')); | ||||
$entry->endTime = $rounded; | $entry->endTime = $rounded; | ||||
} | } | ||||
} | } | ||||
private function parseMessageId($entryid) | |||||
{ | |||||
// replyEmail/forwardEmail | |||||
if (is_array($entryid)) { | |||||
$entryid = $entryid['itemId']; | |||||
} | |||||
list($folderid, $uid) = explode('::', $entryid); | |||||
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid); | |||||
if ($foldername === null || $foldername === false) { | |||||
// @TODO exception? | |||||
return null; | |||||
} | |||||
return array( | |||||
'uid' => $uid, | |||||
'folderid' => $folderid, | |||||
'foldername' => $foldername, | |||||
); | |||||
} | |||||
} | } |
I think we should rather throw an exception as we do e.g. in kolab_sync_data_contacts::getEntry(), Syncroton code can't handle NULL properly.