/** * Export this event as a MS ActiveSync Message * * @param array $options Options: * - protocolversion: (float) The EAS version to support * DEFAULT: 2.5 * - bodyprefs: (array) A BODYPREFERENCE array. * DEFAULT: none (No body prefs enforced). * - truncation: (integer) Truncate event body to this length * DEFAULT: none (No truncation). * * @return Horde_ActiveSync_Message_Appointment */ public function toASAppointment(array $options = array()) { global $prefs, $registry; $message = new Horde_ActiveSync_Message_Appointment(array('logger' => $GLOBALS['injector']->getInstance('Horde_Log_Logger'), 'protocolversion' => $options['protocolversion'])); if (!$this->isPrivate()) { // Handle body/truncation if (!empty($options['bodyprefs'])) { if (Horde_String::length($this->description) > 0) { $bp = $options['bodyprefs']; $note = new Horde_ActiveSync_Message_AirSyncBaseBody(); // No HTML supported. Always use plaintext. $note->type = Horde_ActiveSync::BODYPREF_TYPE_PLAIN; if (isset($bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'])) { $truncation = $bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize']; } elseif (isset($bp[Horde_ActiveSync::BODYPREF_TYPE_HTML])) { $truncation = $bp[Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize']; $this->description = Horde_Text_Filter::filter($this->description, 'Text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO)); } else { $truncation = false; } if ($truncation && Horde_String::length($this->description) > $truncation) { $note->data = Horde_String::substr($this->desciption, 0, $truncation); $note->truncated = 1; } else { $note->data = $this->description; } $note->estimateddatasize = Horde_String::length($this->description); $message->airsyncbasebody = $note; } } else { $message->setBody($this->description); } $message->setLocation($this->location); } $message->setSubject($this->getTitle()); $message->setDatetime(array('start' => $this->start, 'end' => $this->end, 'allday' => $this->isAllDay())); $message->setTimezone($this->start); // Organizer if (count($this->attendees)) { if ($this->creator == $registry->getAuth()) { $as_ident = $prefs->getValue('activesync_identity') == 'horde' ? $prefs->getValue('default_identity') : $prefs->getValue('activesync_identity'); $name = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($this->creator)->getValue('fullname', $as_ident); $email = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($this->creator)->getValue('from_addr', $as_ident); } else { $name = Kronolith::getUserName($this->creator); $email = Kronolith::getUserEmail($this->creator); } $message->setOrganizer(array('name' => $name, 'email' => $email)); } // Privacy $message->setSensitivity($this->private ? Horde_ActiveSync_Message_Appointment::SENSITIVITY_PRIVATE : Horde_ActiveSync_Message_Appointment::SENSITIVITY_NORMAL); // Busy Status switch ($this->status) { case Kronolith::STATUS_CANCELLED: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_FREE; break; case Kronolith::STATUS_CONFIRMED: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY; break; case Kronolith::STATUS_TENTATIVE: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_TENTATIVE; case Kronolith::STATUS_FREE: case Kronolith::STATUS_NONE: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_FREE; } $message->setBusyStatus($status); // DTStamp $message->setDTStamp($_SERVER['REQUEST_TIME']); // Recurrence if ($this->recurs()) { $message->setRecurrence($this->recurrence, $GLOBALS['prefs']->getValue('week_start_monday')); /* Exceptions are tricky. Exceptions, even those that represent * deleted instances of a recurring event, must be added. To do this * we query the storage for all the events that represent exceptions * (those with the baseid == $this->uid) and then remove the * exceptionoriginaldate from the list of exceptions we know about. * Any dates left in this list when we are done, must represent * deleted instances of this recurring event.*/ if (!empty($this->recurrence) && ($exceptions = $this->recurrence->getExceptions())) { $results = $this->boundExceptions(); foreach ($results as $exception) { $e = new Horde_ActiveSync_Message_Exception(array('protocolversion' => $options['protocolversion'])); $e->setDateTime(array('start' => $exception->start, 'end' => $exception->end, 'allday' => $exception->isAllDay())); // The start time of the *original* recurring event $e->setExceptionStartTime($exception->exceptionoriginaldate); $originaldate = $exception->exceptionoriginaldate->format('Ymd'); $key = array_search($originaldate, $exceptions); if ($key !== false) { unset($exceptions[$key]); } // Remaining properties that could be different $e->setSubject($exception->getTitle()); if (!$exception->isPrivate()) { $e->setLocation($exception->location); $e->setBody($exception->description); } $e->setSensitivity($exception->private ? Horde_ActiveSync_Message_Appointment::SENSITIVITY_PRIVATE : Horde_ActiveSync_Message_Appointment::SENSITIVITY_NORMAL); $e->setReminder($exception->alarm); $e->setDTStamp($_SERVER['REQUEST_TIME']); if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWELVEONE) { switch ($exception->status) { case Kronolith::STATUS_TENTATIVE: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_TENTATIVE; break; case Kronolith::STATUS_NONE: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NORESPONSE; break; case Kronolith::STATUS_CONFIRMED: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_ACCEPTED; break; default: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NONE; } } // Tags/Categories if (!$exception->isPrivate()) { foreach ($exception->tags as $tag) { $e->addCategory($tag); } } $message->addexception($e); } // Any dates left in $exceptions must be deleted exceptions foreach ($exceptions as $deleted) { $e = new Horde_ActiveSync_Message_Exception(array('protocolversion' => $options['protocolversion'])); // Kronolith stores the date only, but some AS clients need // the datetime. list($year, $month, $mday) = sscanf($deleted, '%04d%02d%02d'); $st = clone $this->start; $st->year = $year; $st->month = $month; $st->mday = $mday; $e->setExceptionStartTime($st); $e->deleted = true; $message->addException($e); } } } // Attendees if (!$this->isPrivate() && count($this->attendees)) { $message->setMeetingStatus(Horde_ActiveSync_Message_Appointment::MEETING_IS_MEETING); foreach ($this->attendees as $email => $properties) { $attendee = new Horde_ActiveSync_Message_Attendee(array('protocolversion' => $options['protocolversion'])); $adr_obj = new Horde_Mail_Rfc822_Address($email); $attendee->name = $adr_obj->label; $attendee->email = $adr_obj->bare_address; // AS only has required or optional, and only EAS Version > 2.5 if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWOFIVE) { $attendee->type = $properties['attendance'] !== Kronolith::PART_REQUIRED ? Horde_ActiveSync_Message_Attendee::TYPE_OPTIONAL : Horde_ActiveSync_Message_Attendee::TYPE_REQUIRED; switch ($properties['response']) { case Kronolith::RESPONSE_NONE: $attendee->status = Horde_ActiveSync_Message_Attendee::STATUS_NORESPONSE; break; case Kronolith::RESPONSE_ACCEPTED: $attendee->status = Horde_ActiveSync_Message_Attendee::STATUS_ACCEPT; break; case Kronolith::RESPONSE_DECLINED: $attendee->status = Horde_ActiveSync_Message_Attendee::STATUS_DECLINE; break; case Kronolith::RESPONSE_TENTATIVE: $attendee->status = Horde_ActiveSync_Message_Attendee::STATUS_TENTATIVE; break; default: $attendee->status = Horde_ActiveSync_Message_Attendee::STATUS_UNKNOWN; } } $message->addAttendee($attendee); } } else { $message->setMeetingStatus(Horde_ActiveSync_Message_Appointment::MEETING_NOT_MEETING); } // Resources if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWOFIVE) { $r = $this->getResources(); foreach ($r as $id => $data) { $resource = Kronolith::getDriver('Resource')->getResource($id); // EAS *REQUIRES* an email field for Resources. If it is missing // a number of clients will fail, losing push. if ($resource->get('email')) { $attendee = new Horde_ActiveSync_Message_Attendee(array('protocolversion' => $options['protocolversion'])); $attendee->email = $resource->get('email'); $attendee->type = Horde_ActiveSync_Message_Attendee::TYPE_RESOURCE; $attendee->name = $data['name']; $attendee->status = $data['response']; $message->addAttendee($attendee); } } } // Reminder if ($this->alarm) { $message->setReminder($this->alarm); } // Categories (tags) if (!$this->isPrivate()) { foreach ($this->tags as $tag) { $message->addCategory($tag); } } // EAS 14 if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWELVEONE) { // We don't track the actual responses we sent to other's invitations. // Set this based on the status flag. switch ($this->status) { case Kronolith::STATUS_TENTATIVE: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_TENTATIVE; break; case Kronolith::STATUS_NONE: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NORESPONSE; break; case Kronolith::STATUS_CONFIRMED: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_ACCEPTED; break; default: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NONE; } } // 14.1 if ($options['protocolversion'] >= Horde_ActiveSync::VERSION_FOURTEENONE) { $message->onlinemeetingexternallink = $this->url; } return $message; }
/** * Export this event as a MS ActiveSync Message * * @param array $options Options: * - protocolversion: (float) The EAS version to support * DEFAULT: 2.5 * - bodyprefs: (array) A BODYPREFERENCE array. * DEFAULT: none (No body prefs enforced). * - truncation: (integer) Truncate event body to this length * DEFAULT: none (No truncation). * * @return Horde_ActiveSync_Message_Appointment */ public function toASAppointment(array $options = array()) { global $prefs, $registry; // @todo This should be a required option. if (empty($options['protocolversion'])) { $options['protocolversion'] = 2.5; } $message = new Horde_ActiveSync_Message_Appointment(array('logger' => $GLOBALS['injector']->getInstance('Horde_Log_Logger'), 'protocolversion' => $options['protocolversion'])); if (!$this->isPrivate()) { // Handle body/truncation if (!empty($options['bodyprefs'])) { if (Horde_String::length($this->description) > 0) { $bp = $options['bodyprefs']; $note = new Horde_ActiveSync_Message_AirSyncBaseBody(); // No HTML supported. Always use plaintext. $note->type = Horde_ActiveSync::BODYPREF_TYPE_PLAIN; if (isset($bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'])) { $truncation = $bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize']; } elseif (isset($bp[Horde_ActiveSync::BODYPREF_TYPE_HTML])) { $truncation = $bp[Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize']; $this->description = Horde_Text_Filter::filter($this->description, 'Text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO)); } else { $truncation = false; } if ($truncation && Horde_String::length($this->description) > $truncation) { $note->data = Horde_String::substr($this->description, 0, $truncation); $note->truncated = 1; } else { $note->data = $this->description; } $note->estimateddatasize = Horde_String::length($this->description); $message->airsyncbasebody = $note; } } else { $message->setBody($this->description); } if ($options['protocolversion'] >= Horde_ActiveSync::VERSION_SIXTEEN && !empty($this->location)) { $message->location = new Horde_ActiveSync_Message_AirSyncBaseLocation(array('logger' => $GLOBALS['injector']->getInstance('Horde_Log_Logger'), 'protocolversion' => $options['protocolversion'])); // @todo - worth it to try to get full city/country etc... // from geotagging service if available?? $message->location->displayname = $this->location; } else { $message->setLocation($this->location); } } $message->setSubject($this->getTitle()); $message->alldayevent = $this->isAllDay(); $st = clone $this->start; $et = clone $this->end; if ($this->isAllDay()) { // EAS requires all day to be from 12:00 to 12:00. if ($this->start->hour != 0 || $this->start->min != 0 || $this->start->sec != 0) { $st->hour = 0; $st->min = 0; $st->sec = 0; } // For end it's a bit trickier. If it's 11:59pm, bump it up to 12:00 // am of the next day. Otherwise, if it's not 12:00am, make it 12:00 // am of the same day. This *shouldn't* happen, but protect against // issues with EAS just in case. if ($this->end->hour != 0 || $this->end->min != 0 || $this->end->sec != 0) { if ($this->end->hour == 23 && $this->end->min == 59) { $et->mday++; } $et->hour = 0; $et->min = 0; $et->sec = 0; } } $message->starttime = $st; $message->endtime = $et; $message->setTimezone($this->start); // Organizer $attendees = $this->attendees; $skipOrganizer = null; if ($this->organizer) { $message->setOrganizer(array('email' => $this->organizer)); } elseif (count($attendees)) { if ($this->creator == $registry->getAuth()) { $as_ident = $prefs->getValue('activesync_identity') == 'horde' ? $prefs->getValue('default_identity') : $prefs->getValue('activesync_identity'); $name = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($this->creator)->getValue('fullname', $as_ident); $email = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($this->creator)->getValue('from_addr', $as_ident); } else { $name = Kronolith::getUserName($this->creator); $email = Kronolith::getUserEmail($this->creator); } $message->setOrganizer(array('name' => $name, 'email' => $email)); $skipOrganizer = $email; } // Privacy $message->setSensitivity($this->private ? Horde_ActiveSync_Message_Appointment::SENSITIVITY_PRIVATE : Horde_ActiveSync_Message_Appointment::SENSITIVITY_NORMAL); // Busy Status // This is the *busy* status of the time for this meeting. This is NOT // the Kronolith_Event::status or the attendance response for this // meeting. Kronolith does not (yet) support sepcifying the busy status // of the event time separate from the STATUS_FREE value of the // Kronolith_Event::status field, so for now we map these values the // best we can by assuming that STATUS_CONFIRMED meetings should always // show as BUSYSTATUS_BUSY etc... switch ($this->status) { case Kronolith::STATUS_CANCELLED: case Kronolith::STATUS_FREE: case Kronolith::STATUS_NONE: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_FREE; break; case Kronolith::STATUS_CONFIRMED: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY; break; case Kronolith::STATUS_TENTATIVE: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_TENTATIVE; } $message->setBusyStatus($status); // DTStamp $message->setDTStamp($_SERVER['REQUEST_TIME']); // Recurrence if ($this->recurs()) { $message->setRecurrence($this->recurrence, $GLOBALS['prefs']->getValue('week_start_monday')); /* Exceptions are tricky. Exceptions, even those that represent * deleted instances of a recurring event, must be added. To do this * we query the storage for all the events that represent exceptions * (those with the baseid == $this->uid) and then remove the * exceptionoriginaldate from the list of exceptions we know about. * Any dates left in this list when we are done, must represent * deleted instances of this recurring event.*/ if (!empty($this->recurrence) && ($exceptions = $this->recurrence->getExceptions())) { $results = $this->boundExceptions(); foreach ($results as $exception) { $e = new Horde_ActiveSync_Message_Exception(array('protocolversion' => $options['protocolversion'])); $e->setDateTime(array('start' => $exception->start, 'end' => $exception->end, 'allday' => $exception->isAllDay())); // The start time of the *original* recurring event. // EAS < 16.0 uses 'exceptionstarttime'. Otherwise it's // 'instanceid'. if ($options['protocolversion'] < Horde_ActiveSync::VERSION_SIXTEEN) { $e->setExceptionStartTime($exception->exceptionoriginaldate); } else { $e->instanceid = $exception->exceptionoriginaldate; } $originaldate = $exception->exceptionoriginaldate->format('Ymd'); $key = array_search($originaldate, $exceptions); if ($key !== false) { unset($exceptions[$key]); } // Remaining properties that could be different $e->setSubject($exception->getTitle()); if (!$exception->isPrivate()) { $e->setLocation($exception->location); $e->setBody($exception->description); } $e->setSensitivity($exception->private ? Horde_ActiveSync_Message_Appointment::SENSITIVITY_PRIVATE : Horde_ActiveSync_Message_Appointment::SENSITIVITY_NORMAL); $e->setReminder($exception->alarm); $e->setDTStamp($_SERVER['REQUEST_TIME']); if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWELVEONE) { switch ($exception->status) { case Kronolith::STATUS_TENTATIVE: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_TENTATIVE; break; case Kronolith::STATUS_NONE: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NORESPONSE; break; case Kronolith::STATUS_CONFIRMED: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_ACCEPTED; break; default: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NONE; } } // Tags/Categories if (!$exception->isPrivate()) { foreach ($exception->tags as $tag) { $e->addCategory($tag); } } $message->addexception($e); } // Any dates left in $exceptions must be deleted exceptions foreach ($exceptions as $deleted) { $e = new Horde_ActiveSync_Message_Exception(array('protocolversion' => $options['protocolversion'])); // Kronolith stores the date only, but some AS clients need // the datetime. list($year, $month, $mday) = sscanf($deleted, '%04d%02d%02d'); $st = clone $this->start; $st->year = $year; $st->month = $month; $st->mday = $mday; if ($options['protocolversion'] < Horde_ActiveSync::VERSION_SIXTEEN) { $e->setExceptionStartTime($st); } else { $e->instanceid = $st; } $e->deleted = true; $message->addException($e); } } } // Attendees if (!$this->isPrivate() && count($attendees)) { $message->setMeetingStatus($this->status == Kronolith::STATUS_CANCELLED ? Horde_ActiveSync_Message_Appointment::MEETING_CANCELLED : Horde_ActiveSync_Message_Appointment::MEETING_IS_MEETING); foreach ($attendees as $attendee) { if ($skipOrganizer && $attendee->email == $skipOrganizer) { continue; } $attendeeAS = new Horde_ActiveSync_Message_Attendee(array('protocolversion' => $options['protocolversion'])); $attendeeAS->name = $attendee->addressObject->label; $attendeeAS->email = $attendee->addressObject->bare_address; // AS only has required or optional, and only EAS Version > 2.5 if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWOFIVE) { $attendeeAS->type = $attendee->role !== Kronolith::PART_REQUIRED ? Horde_ActiveSync_Message_Attendee::TYPE_OPTIONAL : Horde_ActiveSync_Message_Attendee::TYPE_REQUIRED; switch ($attendee->response) { case Kronolith::RESPONSE_NONE: $attendeeAS->status = Horde_ActiveSync_Message_Attendee::STATUS_NORESPONSE; break; case Kronolith::RESPONSE_ACCEPTED: $attendeeAS->status = Horde_ActiveSync_Message_Attendee::STATUS_ACCEPT; break; case Kronolith::RESPONSE_DECLINED: $attendeeAS->status = Horde_ActiveSync_Message_Attendee::STATUS_DECLINE; break; case Kronolith::RESPONSE_TENTATIVE: $attendeeAS->status = Horde_ActiveSync_Message_Attendee::STATUS_TENTATIVE; break; default: $attendeeAS->status = Horde_ActiveSync_Message_Attendee::STATUS_UNKNOWN; } } $message->addAttendee($attendeeAS); } } elseif ($this->status == Kronolith::STATUS_CANCELLED) { $message->setMeetingStatus(Horde_ActiveSync_Message_Appointment::MEETING_CANCELLED); } else { $message->setMeetingStatus(Horde_ActiveSync_Message_Appointment::MEETING_NOT_MEETING); } // Resources if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWOFIVE) { $r = $this->getResources(); foreach ($r as $id => $data) { $resource = Kronolith::getDriver('Resource')->getResource($id); // EAS *REQUIRES* an email field for Resources. If it is missing // a number of clients will fail, losing push. if ($resource->get('email')) { $attendeeAS = new Horde_ActiveSync_Message_Attendee(array('protocolversion' => $options['protocolversion'])); $attendeeAS->email = $resource->get('email'); $attendeeAS->type = Horde_ActiveSync_Message_Attendee::TYPE_RESOURCE; $attendeeAS->name = $data['name']; $attendeeAS->status = $data['response']; $message->addAttendee($attendeeAS); } } } // Reminder if ($this->alarm) { $message->setReminder($this->alarm); } // Categories (tags) if (!$this->isPrivate()) { foreach ($this->tags as $tag) { $message->addCategory($tag); } } // EAS 14, and only if it is a meeting. if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWELVEONE && $message->getMeetingStatus() == Horde_ActiveSync_Message_Appointment::MEETING_IS_MEETING) { // Are we the if (empty($this->organizer) && $this->creator == $registry->getAuth()) { $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_ORGANIZER; } // We don't track the actual responses we sent to other's invitations. // Set this based on the status flag. switch ($this->status) { case Kronolith::STATUS_TENTATIVE: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_TENTATIVE; break; case Kronolith::STATUS_NONE: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NORESPONSE; break; case Kronolith::STATUS_CONFIRMED: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_ACCEPTED; break; default: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NONE; } } // 14.1 if ($options['protocolversion'] >= Horde_ActiveSync::VERSION_FOURTEENONE) { $message->onlinemeetingexternallink = $this->url; } // 16.0 if ($options['protocolversion'] >= Horde_ActiveSync::VERSION_SIXTEEN) { $files = $this->listFiles(); if (count($files)) { foreach ($files as $file) { $atc = new Horde_ActiveSync_Message_AirSyncBaseAttachment(array('logger' => $GLOBALS['injector']->getInstance('Horde_Log_Logger'), 'protocolversion' => $options['protocolversion'])); $atc->displayname = $file['name']; $atc->attname = $this->_getEASFileReference($file['name']); $atc->attmethod = Horde_ActiveSync_Message_AirSyncBaseAttachment::ATT_TYPE_NORMAL; $atc->attsize = $file['size']; $message->addAttachment($atc); } } } return $message; }