/** * Constructor. * * @param array $config Configuration key-value pairs. */ public function __construct($config = array()) { global $prefs, $registry; parent::__construct($config); $blank = new Horde_Url(); $this->addNewButton(_("_New Event"), $blank, array('id' => 'kronolithNewEvent')); $this->newExtra = $blank->link(array_merge(array('id' => 'kronolithQuickEvent'), Horde::getAccessKeyAndTitle(_("Quick _insert"), false, true))); $sidebar = $GLOBALS['injector']->createInstance('Horde_View'); /* Minical. */ $today = new Horde_Date($_SERVER['REQUEST_TIME']); $sidebar->today = $today->format('F Y'); $sidebar->weekdays = array(); for ($i = $prefs->getValue('week_start_monday'), $c = $i + 7; $i < $c; $i++) { $weekday = Horde_Nls::getLangInfo(constant('DAY_' . ($i % 7 + 1))); $sidebar->weekdays[$weekday] = Horde_String::substr($weekday, 0, 2); } /* Calendars. */ $sidebar->newShares = $registry->getAuth() && !$prefs->isLocked('default_share'); $sidebar->admin = $registry->isAdmin(); $sidebar->resourceAdmin = $registry->isAdmin() || $GLOBALS['injector']->getInstance('Horde_Core_Perms')->hasAppPermission('resource_management'); $sidebar->resources = $GLOBALS['conf']['resources']['enabled']; $sidebar->addRemote = !$prefs->isLocked('remote_cals'); $remotes = unserialize($prefs->getValue('remote_cals')); $sidebar->showRemote = !($prefs->isLocked('remote_cals') && empty($remotes)); $this->content = $sidebar->render('dynamic/sidebar'); }
/** * Output the necessary javascript code to allow display of the calendar * widget. * * @param array $params Configuration parameters for the widget: * <pre> * 'click_month' - (boolean) If true, the month is clickable. * DEFAULT: false * 'click_week' - (boolean) If true, will display a clickable week. * DEFAULT: false * 'click_year' - (boolean) If true, the year is clickable. * DEFAULT: false * 'full_weekdays' - (boolean) Add full weekday localized list to * javascript object. * DEFAULT: false * 'short_weekdays' - (boolean) Display only the first letter of * weekdays? * DEFAULT: false * </pre> */ public static function init(array $params = array()) { if (self::$_initRun) { return; } self::$_initRun = true; $params = array_merge(array('click_month' => false, 'click_week' => false, 'click_year' => false, 'full_weekdays' => false, 'short_weekdays' => false), $params); $weekdays = self::weekdays(); if ($params['short_weekdays']) { foreach ($weekdays as &$day) { $day = Horde_String::substr($day, 0, 1); } } $js = array('-Horde_Calendar.click_month' => intval($params['click_month']), '-Horde_Calendar.click_week' => intval($params['click_week']), '-Horde_Calendar.click_year' => intval($params['click_year']), '-Horde_Calendar.firstDayOfWeek' => intval($GLOBALS['prefs']->getValue('first_week_day')), 'Horde_Calendar.months' => self::months(), 'Horde_Calendar.weekdays' => $weekdays); if ($params['full_weekdays']) { $js['Horde_Calendar.fullweekdays'] = self::fullWeekdays(); } $page_output = $GLOBALS['injector']->getInstance('Horde_PageOutput'); $page_output->addScriptFile('calendar.js', 'horde'); $page_output->addInlineJsVars($js); }
/** * URL Parameters: * a: (string) Action ID. * allto: (boolean) View all To addresses? * buid: (string) Browser UID. * t: (string) Token. */ protected function _init() { global $injector, $notification, $page_output, $prefs, $session; $imp_mailbox = $this->indices->mailbox->list_ob; $imp_mailbox->setIndex($this->indices); $mailbox_url = IMP_Minimal_Mailbox::url(array('mailbox' => $this->indices->mailbox)); /* Make sure we have a valid index. */ if (!$imp_mailbox->isValidIndex()) { $mailbox_url->add('a', 'm')->redirect(); } $imp_ui = $injector->getInstance('IMP_Message_Ui'); /* Run through action handlers */ $msg_delete = false; switch ($this->vars->a) { // 'd' = delete message case 'd': $old_index = $imp_mailbox->getIndex(); try { $session->checkToken($this->vars->t); $msg_delete = (bool) $injector->getInstance('IMP_Message')->delete($this->indices, array('mailboxob' => $imp_mailbox)); } catch (Horde_Exception $e) { $notification->push($e); } break; // 'u' = undelete message // 'u' = undelete message case 'u': $old_index = $imp_mailbox->getIndex(); $injector->getInstance('IMP_Message')->undelete($this->indices); break; // 'rs' = report spam // 'ri' = report innocent // 'rs' = report spam // 'ri' = report innocent case 'rs': case 'ri': $old_index = $imp_mailbox->getIndex(); $msg_delete = $injector->getInstance('IMP_Factory_Spam')->create($this->vars->a == 'rs' ? IMP_Spam::SPAM : IMP_Spam::INNOCENT)->report($this->indices, array('mailboxob' => $imp_mailbox)) === 1; break; } if ($msg_delete && $imp_ui->moveAfterAction($this->indices->mailbox)) { $imp_mailbox->setIndex(1); } /* We may have done processing that has taken us past the end of the * message array, so we will return to the mailbox. */ if (!$imp_mailbox->isValidIndex() || $msg_delete && $prefs->getValue('mailbox_return')) { $mailbox_url->add('s', $old_index)->redirect(); } /* Now that we are done processing, get the index and array index of * the current message. */ $msg_index = $imp_mailbox[$imp_mailbox->getIndex()]; $mailbox = $msg_index['m']; $uid = $msg_index['u']; $buid = $imp_mailbox->getBuid($mailbox, $uid); /* Get envelope/flag/header information. */ try { $imp_imap = $mailbox->imp_imap; /* Need to fetch flags before HEADERTEXT, because SEEN flag might * be set before we can grab it. */ $query = new Horde_Imap_Client_Fetch_Query(); $query->flags(); $flags_ret = $imp_imap->fetch($mailbox, $query, array('ids' => $imp_imap->getIdsOb($uid))); $query = new Horde_Imap_Client_Fetch_Query(); $query->envelope(); $fetch_ret = $imp_imap->fetch($mailbox, $query, array('ids' => $imp_imap->getIdsOb($uid))); } catch (IMP_Imap_Exception $e) { $mailbox_url->add('a', 'm')->redirect(); } $envelope = $fetch_ret->first()->getEnvelope(); $flags = $flags_ret->first()->getFlags(); /* Parse the message. */ try { $imp_contents = $injector->getInstance('IMP_Factory_Contents')->create(new IMP_Indices($imp_mailbox)); $mime_headers = $imp_contents->getHeaderAndMarkAsSeen(); } catch (IMP_Exception $e) { $mailbox_url->add('a', 'm')->redirect(); } /* Get the starting index for the current message and the message * count. */ $msgindex = $imp_mailbox->getIndex(); $msgcount = count($imp_mailbox); /* Generate the mailbox link. */ $mailbox_link = $mailbox_url->add('s', $msgindex); $self_link = self::url(array('buid' => $buid, 'mailbox' => $this->indices->mailbox)); /* Create the Identity object. */ $user_identity = $injector->getInstance('IMP_Identity'); /* Develop the list of headers to display. */ $basic_headers = $imp_ui->basicHeaders(); $display_headers = $msgAddresses = array(); if ($subject = $mime_headers->getValue('subject')) { /* Filter the subject text, if requested. */ $subject = Horde_String::truncate(IMP::filterText($subject), 50); } else { $subject = _("[No Subject]"); } $display_headers['subject'] = $subject; $format_date = $imp_ui->getLocalTime($envelope->date); if (!empty($format_date)) { $display_headers['date'] = $format_date; } /* Build From address links. */ $display_headers['from'] = $imp_ui->buildAddressLinks($envelope->from, null, false); /* Build To/Cc/Bcc links. */ foreach (array('to', 'cc', 'bcc') as $val) { $msgAddresses[] = $mime_headers->getValue($val); $addr_val = $imp_ui->buildAddressLinks($envelope->{$val}, null, false); if (!empty($addr_val)) { $display_headers[$val] = $addr_val; } } /* Check for the presence of mailing list information. */ $list_info = $imp_ui->getListInformation($mime_headers); /* See if the priority has been set. */ switch ($priority = $injector->getInstance('IMP_Mime_Headers')->getPriority($mime_headers)) { case 'high': case 'low': $basic_headers['priority'] = _("Priority"); $display_headers['priority'] = Horde_String::ucfirst($priority); break; } /* Set the status information of the message. */ $status = ''; $match_identity = $identity = null; if (!empty($msgAddresses)) { $match_identity = $identity = $user_identity->getMatchingIdentity($msgAddresses); if (is_null($identity)) { $identity = $user_identity->getDefault(); } } $flag_parse = $injector->getInstance('IMP_Flags')->parse(array('flags' => $flags, 'personal' => $match_identity)); foreach ($flag_parse as $val) { if ($abbrev = $val->abbreviation) { $status .= $abbrev; } elseif ($val instanceof IMP_Flag_User) { $status .= ' *' . Horde_String::truncate($val->label, 8) . '*'; } } /* Create the body of the message. */ $inlineout = $imp_contents->getInlineOutput(array('display_mask' => IMP_Contents::RENDER_INLINE, 'no_inline_all' => true)); $msg_text = $inlineout['msgtext']; $this->view->msg = nl2br($injector->getInstance('Horde_Core_Factory_TextFilter')->filter($msg_text, 'space2html')); $menu = array(); if ($this->indices->mailbox->access_deletemsgs) { $menu[] = in_array(Horde_Imap_Client::FLAG_DELETED, $flags) ? array(_("Undelete"), $self_link->copy()->add('a', 'u')) : array(_("Delete"), $self_link->copy()->add(array('a' => 'd', 't' => $session->getToken()))); } /* Add compose actions (Reply, Reply List, Reply All, Forward, * Redirect, Edit as New). */ if (IMP_Compose::canCompose()) { $clink_ob = new IMP_Compose_Link(); $clink_ob->args['buid'] = $buid; $clink_ob->args['mailbox'] = $this->indices->mailbox; $clink = $clink_ob->link()->add(array('identity' => $identity)); $menu[] = array(_("Reply"), $clink->copy()->add(array('a' => 'r'))); if ($list_info['reply_list']) { $menu[] = array(_("Reply to List"), $clink->copy()->add(array('a' => 'rl'))); } $addr_ob = clone $envelope->to; $addr_ob->add($envelope->cc); $addr_ob->setIteratorFilter(0, $user_identity->getAllFromAddresses()); if (count($addr_ob)) { $menu[] = array(_("Reply All"), $clink->copy()->add(array('a' => 'ra'))); } $menu[] = array(_("Forward"), $clink->copy()->add(array('a' => 'f'))); $menu[] = array(_("Redirect"), $clink->copy()->add(array('a' => 'rc'))); $menu[] = array(_("Edit as New"), $clink->copy()->add(array('a' => 'en'))); } /* Generate previous/next links. */ if ($prev_msg = $imp_mailbox[$imp_mailbox->getIndex() - 1]) { $menu[] = array(_("Previous Message"), self::url(array('buid' => $imp_mailbox->getBuid($prev_msg['m'], $prev_msg['u']), 'mailbox' => $this->indices->mailbox))); } if ($next_msg = $imp_mailbox[$imp_mailbox->getIndex() + 1]) { $menu[] = array(_("Next Message"), self::url(array('buid' => $imp_mailbox->getBuid($next_msg['m'], $next_msg['u']), 'mailbox' => $this->indices->mailbox))); } $menu[] = array(sprintf(_("To %s"), $this->indices->mailbox->label), $mailbox_link); if ($mailbox->spam_show) { $menu[] = array(_("Report as Spam"), $self_link->copy()->add(array('a' => 'rs', 't' => $session->getToken()))); } if ($mailbox->innocent_show) { $menu[] = array(_("Report as Innocent"), $self_link->copy()->add(array('a' => 'ri', 't' => $session->getToken()))); } $this->view->menu = $this->getMenu('message', $menu); $hdrs = array(); foreach ($display_headers as $head => $val) { $tmp = array('label' => $basic_headers[$head]); if (Horde_String::lower($head) == 'to' && !isset($this->vars->allto) && ($pos = strpos($val, ',')) !== false) { $val = Horde_String::substr($val, 0, $pos); $tmp['all_to'] = $self_link->copy()->add('allto', 1); } $tmp['val'] = $val; $hdrs[] = $tmp; } $this->view->hdrs = $hdrs; $atc = array(); foreach ($inlineout['atc_parts'] as $key) { $summary = $imp_contents->getSummary($key, IMP_Contents::SUMMARY_BYTES | IMP_Contents::SUMMARY_SIZE | IMP_Contents::SUMMARY_DESCRIP | IMP_Contents::SUMMARY_DOWNLOAD); $tmp = array('descrip' => $summary['description_raw'], 'size' => $summary['size'], 'type' => $summary['type']); if (!empty($summary['download'])) { /* Preference: if set, only show download confirmation screen * if attachment over a certain size. */ $tmp['download'] = IMP_Minimal_Messagepart::url(array('buid' => $buid, 'mailbox' => $this->indices->mailbox))->add('atc', $key); } if ($imp_contents->canDisplay($key, IMP_Contents::RENDER_INLINE)) { $tmp['view'] = IMP_Minimal_Messagepart::url(array('buid' => $buid, 'mailbox' => $this->indices->mailbox))->add('id', $key); } $atc[] = $tmp; } $this->view->atc = $atc; $this->title = $display_headers['subject']; $this->view->title = ($status ? $status . ' ' : '') . sprintf(_("(Message %d of %d)"), $msgindex, $msgcount); $page_output->noDnsPrefetch(); $this->_pages[] = 'message'; $this->_pages[] = 'menu'; }
/** * Helper function for guessing name parts from a single name string. * * @param array $hash The attributes array. */ protected function _guessName(&$hash) { if (($pos = strpos($hash['name'], ',')) !== false) { // Assume Last, First $hash['lastname'] = Horde_String::substr($hash['name'], 0, $pos); $hash['firstname'] = trim(Horde_String::substr($hash['name'], $pos + 1)); } elseif (($pos = Horde_String::rpos($hash['name'], ' ')) !== false) { // Assume everything after last space as lastname $hash['lastname'] = trim(Horde_String::substr($hash['name'], $pos + 1)); $hash['firstname'] = Horde_String::substr($hash['name'], 0, $pos); } else { $hash['lastname'] = $hash['name']; $hash['firstname'] = ''; } }
/** * Wraps the text of a message. * * @since Horde 3.2 * * @param string $string Horde_String containing the text to wrap. * @param integer $width Wrap the string at this number of * characters. * @param string $break Character(s) to use when breaking lines. * @param boolean $cut Whether to cut inside words if a line * can't be wrapped. * @param string $charset Character set to use when breaking lines. * @param boolean $line_folding Whether to apply line folding rules per * RFC 822 or similar. The correct break * characters including leading whitespace * have to be specified too. * * @return string Horde_String containing the wrapped text. */ public function wordwrap($string, $width = 75, $break = "\n", $cut = false, $charset = null, $line_folding = false) { /* Get the user's default character set if none passed in. */ if (is_null($charset)) { $charset = self::$charset; } $charset = Horde_String::_mbstringCharset($charset); $string = Horde_String::convertCharset($string, $charset, 'utf-8'); $wrapped = ''; while (Horde_String::length($string, 'utf-8') > $width) { $line = Horde_String::substr($string, 0, $width, 'utf-8'); $string = Horde_String::substr($string, Horde_String::length($line, 'utf-8'), null, 'utf-8'); // Make sure didn't cut a word, unless we want hard breaks anyway. if (!$cut && preg_match('/^(.+?)(\\s|\\r?\\n)/u', $string, $match)) { $line .= $match[1]; $string = Horde_String::substr($string, Horde_String::length($match[1], 'utf-8'), null, 'utf-8'); } // Wrap at existing line breaks. if (preg_match('/^(.*?)(\\r?\\n)(.*)$/u', $line, $match)) { $wrapped .= $match[1] . $match[2]; $string = $match[3] . $string; continue; } // Wrap at the last colon or semicolon followed by a whitespace if // doing line folding. if ($line_folding && preg_match('/^(.*?)(;|:)(\\s+.*)$/u', $line, $match)) { $wrapped .= $match[1] . $match[2] . $break; $string = $match[3] . $string; continue; } // Wrap at the last whitespace of $line. if ($line_folding) { $sub = '(.+[^\\s])'; } else { $sub = '(.*)'; } if (preg_match('/^' . $sub . '(\\s+)(.*)$/u', $line, $match)) { $wrapped .= $match[1] . $break; $string = ($line_folding ? $match[2] : '') . $match[3] . $string; continue; } // Hard wrap if necessary. if ($cut) { $wrapped .= Horde_String::substr($line, 0, $width, 'utf-8') . $break; $string = Horde_String::substr($line, $width, null, 'utf-8') . $string; continue; } $wrapped .= $line; } return Horde_String::convertCharset($wrapped . $string, 'utf-8', $charset); }
/** * 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; }
/** * Create an AS memo from this task * * @param array $memo A memo array. * @param array $options * * @return Horde_ActiveSync_Message_Note */ public function toASNote($memo, $options = array()) { $message = new Horde_ActiveSync_Message_Note(array('protocolversion' => $options['protocolversion'])); $message->subject = $memo['desc']; $bp = $options['bodyprefs']; $body = new Horde_ActiveSync_Message_AirSyncBaseBody(); // When the note is encrypted, we won't have the passphrase so the // body will be a Mnemo_Exception. if ($memo['body'] instanceof Mnemo_Exception) { $memo['body'] = $memo['body']->getMessage(); } if (isset($bp[Horde_ActiveSync::BODYPREF_TYPE_HTML])) { $body->type = Horde_ActiveSync::BODYPREF_TYPE_HTML; $memo['body'] = Horde_Text_Filter::filter($memo['body'], 'Text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO)); if (isset($bp[Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize']) && Horde_String::length($memo['body']) > $bp[Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize']) { $body->data = Horde_String::substr($memo['body'], $bp[Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize']); $body->truncated = 1; } else { $body->data = $memo['body']; } } else { $body->type = Horde_ActiveSync::BODYPREF_TYPE_PLAIN; if (isset($bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize']) && Horde_String::length($memo['body']) > $bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize']) { $body->data = Horde_String::substr($memo['body'], 0, $bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize']); $body->truncated = 1; } else { $body->data = $memo['body']; } } $body->estimateddatasize = Horde_String::length($memo['body']); $message->body = $body; if (!empty($memo['tags'])) { $message->categories = $memo['tags']; } $history = $GLOBALS['injector']->getInstance('Horde_History'); $last = $history->getActionTimeStamp('mnemo:' . $memo['memolist_id'] . ':' . $memo['uid'], 'modify'); if (empty($last)) { $last = $history->getActionTimeStamp('mnemo:' . $memo['memolist_id'] . ':' . $memo['uid'], 'add'); } $message->lastmodified = new Horde_Date($last); return $message; }
/** * Returns a best guess at the lastname in a string. * * @param string $name String contain the full name. * * @return string String containing the last name. */ public static function guessLastname($name) { $name = trim(preg_replace('|\\s|', ' ', $name)); if (!empty($name)) { /* Assume that last names are always before any commas. */ if (is_int(strpos($name, ','))) { $name = Horde_String::substr($name, 0, strpos($name, ',')); } /* Take out anything in parentheses. */ $name = trim(preg_replace('|\\(.*\\)|', '', $name)); $namelist = explode(' ', $name); $name = $namelist[$nameindex = count($namelist) - 1]; while (!empty($name) && ($nlength = Horde_String::length($name)) < 5 && strspn($name[$nlength - 1], '.:-') && !empty($namelist[$nameindex - 1])) { $name = $namelist[--$nameindex]; } } return strlen($name) ? $name : null; }
/** * This function verifies whether a given directory is below the root. * * @param string $dir The directory to check. * * @return boolean True if the directory is below the root. */ public static function verifyDir($dir) { return Horde_String::substr(Horde_Util::realPath($dir), 0, Horde_String::length(self::$backend['root'])) == self::$backend['root']; }
/** * Regenerates body text for use in the compose screen from IMAP data. * * @param IMP_Contents $contents An IMP_Contents object. * @param array $options Additional options: * <ul> * <li>html: (boolean) Return text/html part, if available.</li> * <li>imp_msg: (integer) If non-empty, the message data was created by * IMP. Either: * <ul> * <li>self::COMPOSE</li> * <li>self::FORWARD</li> * <li>self::REPLY</li> * </ul> * </li> * <li>replylimit: (boolean) Enforce length limits?</li> * <li>toflowed: (boolean) Do flowed conversion?</li> * </ul> * * @return mixed Null if bodypart not found, or array with the following * keys: * - charset: (string) The guessed charset to use. * - flowed: (Horde_Text_Flowed) A flowed object, if the text is flowed. * Otherwise, null. * - id: (string) The MIME ID of the bodypart. * - mode: (string) Either 'text' or 'html'. * - text: (string) The body text. */ protected function _getMessageText($contents, array $options = array()) { global $conf, $injector, $notification, $prefs; $body_id = null; $mode = 'text'; $options = array_merge(array('imp_msg' => self::COMPOSE), $options); if (!empty($options['html']) && self::canHtmlCompose() && ($body_id = $contents->findBody('html')) !== null) { $mime_message = $contents->getMIMEMessage(); switch ($mime_message->getPrimaryType()) { case 'multipart': if ($body_id != '1' && $mime_message->getSubType() == 'mixed' && ($id_ob = new Horde_Mime_Id('1')) && !$id_ob->isChild($body_id)) { $body_id = null; } else { $mode = 'html'; } break; default: if (strval($body_id) != '1') { $body_id = null; } else { $mode = 'html'; } break; } } if (is_null($body_id)) { $body_id = $contents->findBody(); if (is_null($body_id)) { return null; } } if (!($part = $contents->getMimePart($body_id))) { return null; } $type = $part->getType(); $part_charset = $part->getCharset(); $msg = Horde_String::convertCharset($part->getContents(), $part_charset, 'UTF-8'); /* Enforce reply limits. */ if (!empty($options['replylimit']) && !empty($conf['compose']['reply_limit'])) { $limit = $conf['compose']['reply_limit']; if (Horde_String::length($msg) > $limit) { $msg = Horde_String::substr($msg, 0, $limit) . "\n" . _("[Truncated Text]"); } } if ($mode == 'html') { $dom = $injector->getInstance('Horde_Core_Factory_TextFilter')->filter($msg, 'Xss', array('charset' => $this->charset, 'return_dom' => true, 'strip_style_attributes' => false)); /* If we are replying to a related part, and this part refers * to local message parts, we need to move those parts into this * message (since the original message may disappear during the * compose process). */ if ($related_part = $contents->findMimeType($body_id, 'multipart/related')) { $this->_setMetadata('related_contents', $contents); $related_ob = new Horde_Mime_Related($related_part); $related_ob->cidReplace($dom, array($this, '_getMessageTextCallback'), $part_charset); $this->_setMetadata('related_contents', null); } /* Convert any Data URLs to attachments. */ $xpath = new DOMXPath($dom->dom); foreach ($xpath->query('//*[@src]') as $val) { $data_url = new Horde_Url_Data($val->getAttribute('src')); if (strlen($data_url->data)) { $data_part = new Horde_Mime_Part(); $data_part->setContents($data_url->data); $data_part->setType($data_url->type); try { $atc = $this->addAttachmentFromPart($data_part); $val->setAttribute('src', $atc->viewUrl()); $this->addRelatedAttachment($atc, $val, 'src'); } catch (IMP_Compose_Exception $e) { $notification->push($e, 'horde.warning'); } } } $msg = $dom->returnBody(); } elseif ($type == 'text/html') { $msg = $injector->getInstance('Horde_Core_Factory_TextFilter')->filter($msg, 'Html2text'); $type = 'text/plain'; } /* Always remove leading/trailing whitespace. The data in the * message body is not intended to be the exact representation of the * original message (use forward as message/rfc822 part for that). */ $msg = trim($msg); if ($type == 'text/plain') { if ($prefs->getValue('reply_strip_sig') && ($pos = strrpos($msg, "\n-- ")) !== false) { $msg = rtrim(substr($msg, 0, $pos)); } /* Remove PGP armored text. */ $pgp = $injector->getInstance('Horde_Crypt_Pgp_Parse')->parseToPart($msg); if (!is_null($pgp)) { $msg = ''; $pgp->buildMimeIds(); foreach ($pgp->partIterator() as $val) { if ($val->getPrimaryType() === 'text') { $msg .= $val->getContents(); } } } if ($part->getContentTypeParameter('format') == 'flowed') { $flowed = new Horde_Text_Flowed($msg, 'UTF-8'); if (Horde_String::lower($part->getContentTypeParameter('delsp')) == 'yes') { $flowed->setDelSp(true); } $flowed->setMaxLength(0); $msg = $flowed->toFixed(false); } else { /* If the input is *not* in flowed format, make sure there is * no padding at the end of lines. */ $msg = preg_replace("/\\s*\n/U", "\n", $msg); } if (isset($options['toflowed'])) { $flowed = new Horde_Text_Flowed($msg, 'UTF-8'); $msg = $options['toflowed'] ? $flowed->toFlowed(true) : $flowed->toFlowed(false, array('nowrap' => true)); } } if (strcasecmp($part->getCharset(), 'windows-1252') === 0) { $part_charset = 'ISO-8859-1'; } return array('charset' => $part_charset, 'flowed' => isset($flowed) ? $flowed : null, 'id' => $body_id, 'mode' => $mode, 'text' => $msg); }
/** * Limits a string to a given maximum length in a smarter way than just * using substr(). * * Namely, cut from the MIDDLE instead of from the end so that if we're * doing this on (for instance) a bunch of binder names that start off with * the same verbose description, and then are different only at the very * end, they'll still be different from one another after truncating. * * <code> * $str = 'The quick brown fox jumps over the lazy dog tomorrow morning.'; * $shortStr = $this->truncateMiddle($str, 40); * // $shortStr == 'The quick brown fox... tomorrow morning.' * </code> * * @param string $str A text to truncate. * @param integer $maxLength The maximum length of the text * @param string $joiner Replacement string for the truncated text. * * @return string The truncated text. */ public function truncateMiddle($str, $maxLength = 80, $joiner = '...') { if (Horde_String::length($str) <= $maxLength) { return $str; } $maxLength = $maxLength - Horde_String::length($joiner); if ($maxLength <= 0) { return $str; } $startPieceLength = (int) ceil($maxLength / 2); $endPieceLength = (int) floor($maxLength / 2); $trimmedString = rtrim(Horde_String::substr($str, 0, $startPieceLength)) . $joiner; if ($endPieceLength > 0) { $trimmedString .= ltrim(Horde_String::substr($str, -1 * $endPieceLength)); } return $trimmedString; }
/** * Returns an external link passed through the dereferrer to strip session * IDs from the referrer. * * @param string $url The external URL to link to. * @param boolean $tag If true, a complete <a> tag is returned, only the * url otherwise. * * @return string The link to the dereferrer script. */ public static function externalUrl($url, $tag = false) { if (!isset($_GET[session_name()]) || Horde_String::substr($url, 0, 1) == '#' || Horde_String::substr($url, 0, 7) == 'mailto:') { $ext = $url; } else { $ext = self::signQueryString($GLOBALS['registry']->getServiceLink('go', 'horde')->add('url', $url)); } if ($tag) { $ext = self::link($ext, $url, '', '_blank'); } return $ext; }
/** * Returns the widget necessary to configure this block. * * @param $app TODO * @param $block TODO * @param $param_id TODO * @param $val TODO * * @return TODO */ public function getOptionsWidget($app, $block, $param_id, $val = null) { $widget = ''; /* getParams() loads $_blocks */ $this->getParams($app, $block); $param = $this->_blocks[$app][$block]['params'][$param_id]; if (!isset($param['default'])) { $param['default'] = ''; } switch ($param['type']) { case 'boolean': case 'checkbox': $checked = !empty($val[$param_id]) ? ' checked="checked"' : ''; $widget = sprintf('<input type="checkbox" name="params[%s]"%s />', $param_id, $checked); break; case 'enum': $widget = sprintf('<select name="params[%s]">', $param_id); foreach ($param['values'] as $key => $name) { if (Horde_String::length($name) > 30) { $name = substr($name, 0, 27) . '...'; } $widget .= sprintf("<option value=\"%s\"%s>%s</option>\n", htmlspecialchars($key), isset($val[$param_id]) && $val[$param_id] == $key ? ' selected="selected"' : '', htmlspecialchars($name)); } $widget .= '</select>'; break; case 'multienum': $widget = sprintf('<select multiple="multiple" name="params[%s][]">', $param_id); foreach ($param['values'] as $key => $name) { if (Horde_String::length($name) > 30) { $name = substr($name, 0, 27) . '...'; } $widget .= sprintf("<option value=\"%s\"%s>%s</option>\n", htmlspecialchars($key), isset($val[$param_id]) && in_array($key, $val[$param_id]) ? ' selected="selected"' : '', htmlspecialchars($name)); } $widget .= '</select>'; break; case 'mlenum': // Multi-level enum. if (is_array($val) && isset($val['__' . $param_id])) { $firstval = $val['__' . $param_id]; } else { $tmp = array_keys($param['values']); $firstval = current($tmp); } $blockvalues = $param['values'][$firstval]; asort($blockvalues); $widget = sprintf('<select name="params[__%s]" onchange="document.blockform.action.value=\'save-resume\';document.blockform.submit()">', $param_id) . "\n"; foreach (array_keys($param['values']) as $key) { $name = Horde_String::length($key) > 30 ? Horde_String::substr($key, 0, 27) . '...' : $key; $widget .= sprintf("<option value=\"%s\"%s>%s</option>\n", htmlspecialchars($key), $key == $firstval ? ' selected="selected"' : '', htmlspecialchars($name)); } $widget .= "</select><br />\n"; $widget .= sprintf("<select name=\"params[%s]\">\n", $param_id); foreach ($blockvalues as $key => $name) { $name = Horde_String::length($name) > 30 ? Horde_String::substr($name, 0, 27) . '...' : $name; $widget .= sprintf("<option value=\"%s\"%s>%s</option>\n", htmlspecialchars($key), $val[$param_id] == $key ? ' selected="selected"' : '', htmlspecialchars($name)); } $widget .= "</select><br />\n"; break; case 'int': case 'text': $widget = sprintf('<input type="text" name="params[%s]" value="%s" />', $param_id, !isset($val[$param_id]) ? $param['default'] : $val[$param_id]); break; case 'password': $widget = sprintf('<input type="password" name="params[%s]" value="%s" />', $param_id, !isset($val[$param_id]) ? $param['default'] : $val[$param_id]); break; case 'error': $widget = '<span class="form-error">' . $param['default'] . '</span>'; break; } return $widget; }
/** * Returns the main text body of the message suitable for sending over * EAS response. * * @param array $options An options array containgin: * - bodyprefs: (array) Bodypref settings * DEFAULT: none (No bodyprefs used). * - mimesupport: (integer) Indicates if MIME is supported or not. * Possible values: 0 - Not supported 1 - Only S/MIME or * 2 - All MIME. * DEFAULT: 0 (No MIME support) * - protocolversion: (float) The EAS protocol we are supporting. * DEFAULT 2.5 * * @return array An array of one or both of 'plain' and 'html' content. * * @throws Horde_ActiveSync_Exception, Horde_Exception_NotFound */ public function getMessageBodyData(array $options = array()) { $version = empty($options['protocolversion']) ? Horde_ActiveSync::VERSION_TWOFIVE : $options['protocolversion']; // Look for the parts we need. We try to detect and fetch only the parts // we need, while ensuring we have something to return. So, e.g., if we // don't have BODYPREF_TYPE_HTML, we only request plain text, but if we // can't find plain text but we have a html body, fetch that anyway. $text_id = $this->_message->findBody('plain'); $html_id = $this->_message->findBody('html'); // Deduce which part(s) we need to request. $want_html_text = $version >= Horde_ActiveSync::VERSION_TWELVE && (!empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]) || !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME])); $want_plain_text = $version == Horde_ActiveSync::VERSION_TWOFIVE || empty($options['bodyprefs']) || !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]) || !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_RTF]) || !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME]) || $want_html_text && empty($html_id); $want_html_as_plain = false; if (!empty($text_id) && $want_plain_text) { $text_body_part = $this->_message->getPart($text_id); $charset = $text_body_part->getCharset(); } elseif ($want_plain_text && !empty($html_id) && empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME])) { $want_html_text = true; $want_html_as_plain = true; } if (!empty($html_id) && $want_html_text) { $html_body_part = $this->_message->getPart($html_id); $html_charset = $html_body_part->getCharset(); } // Sanity check the truncation stuff if (empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]) && !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]) && $want_plain_text && $want_html_text) { // We only have HTML truncation data, requested HTML body but only // have plaintext. $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN] = $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]; } $query = new Horde_Imap_Client_Fetch_Query(); $query_opts = array('decode' => true, 'peek' => true); // Get body information if ($version >= Horde_ActiveSync::VERSION_TWELVE) { if (!empty($html_id)) { $query->bodyPartSize($html_id); $query->bodyPart($html_id, $query_opts); } if (!empty($text_id)) { $query->bodyPart($text_id, $query_opts); $query->bodyPartSize($text_id); } } else { // EAS 2.5 Plaintext body $query->bodyPart($text_id, $query_opts); $query->bodyPartSize($text_id); } try { $fetch_ret = $this->_imap->fetch($this->_mbox, $query, array('ids' => new Horde_Imap_Client_Ids(array($this->_uid)))); } catch (Horde_Imap_Client_Exception $e) { throw new Horde_ActiveSync_Exception($e); } if (!($data = $fetch_ret->first())) { throw new Horde_Exception_NotFound(sprintf('Could not load message %s from server.', $this->_uid)); } $return = array(); if (!empty($text_id) && $want_plain_text) { $text = $data->getBodyPart($text_id); if (!$data->getBodyPartDecode($text_id)) { $text_body_part->setContents($text); $text = $text_body_part->getContents(); } $text_size = !is_null($data->getBodyPartSize($text_id)) ? $data->getBodyPartSize($text_id) : Horde_String::length($text); if (!empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'])) { // EAS >= 12.0 truncation $text = Horde_String::substr($text, 0, $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'], $charset); } $truncated = $text_size > Horde_String::length($text); if ($version >= Horde_ActiveSync::VERSION_TWELVE && $truncated && !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['allornone'])) { $text = ''; } $return['plain'] = array('charset' => $charset, 'body' => $text, 'truncated' => $truncated, 'size' => $text_size); } if (!empty($html_id) && $want_html_text) { $html = $data->getBodyPart($html_id); if (!$data->getBodyPartDecode($html_id)) { $html_body_part->setContents($html); $html = $html_body_part->getContents(); } // Size of the original HTML part. $html_size = !is_null($data->getBodyPartSize($html_id)) ? $data->getBodyPartSize($html_id) : Horde_String::length($html); if (!empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize'])) { $html = Horde_String::substr($html, 0, $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize'], $html_charset); } elseif ($want_html_as_plain) { $html = Horde_Text_Filter::filter($html, 'Html2text', array('charset' => $html_charset)); // Get the new size, since it probably changed. $html_size = Horde_String::length($html); if (!empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'])) { // EAS >= 12.0 truncation $html = Horde_String::substr($html, 0, $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'], $html_charset); } } // Was the part truncated? $truncated = $html_size > Horde_String::length($html); if ($want_html_as_plain) { $return['plain'] = array('charset' => $html_charset, 'body' => $html, 'truncated' => $truncated, 'size' => $html_size); } if ($version >= Horde_ActiveSync::VERSION_TWELVE && !($truncated && !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]['allornone']))) { $return['html'] = array('charset' => $html_charset, 'body' => $html, 'estimated_size' => $html_size, 'truncated' => $truncated); } } return $return; }
?> </a> </div> <div data-role="content" id="kronolith-minical" class="kronolith-minical"> <table> <thead> <tr> <?php for ($i = $GLOBALS['prefs']->getValue('week_start_monday'), $c = $i + 7; $i < $c; $i++) { ?> <th title="<?php echo Horde_Nls::getLangInfo(constant('DAY_' . ($i % 7 + 1))); ?> "><?php echo Horde_String::substr(Horde_Nls::getLangInfo(constant('DAY_' . ($i % 7 + 1))), 0, 1); ?> </th> <?php } ?> </tr> </thead> <tbody><tr><td></td></tr></tbody> </table> </div> <div id="kronolithDayDetailHeader" data-role="header"> <h3></h3> </div>
/** * Helper method to build a view object for a tweet. * * @param stdClass $tweet The tweet object. * * @return Horde_View The view object, populated with tweet data. */ protected function _buildTweet($tweet) { global $injector, $registry; $view = new Horde_View(array('templatePath' => HORDE_TEMPLATES . '/block')); $view->addHelper('Tag'); $view->ajax_uri = $registry->getServiceLink('ajax', $registry->getApp()); $filter = $injector->getInstance('Horde_Core_Factory_TextFilter'); $instance = $this->vars->i; // Links and media $map = $previews = array(); foreach ($tweet->entities->urls as $link) { $replace = '<a target="_blank" href="' . $link->url . '" title="' . $link->expanded_url . '">' . htmlspecialchars($link->display_url) . '</a>'; $map[$link->indices[0]] = array($link->indices[1], $replace); } if (!empty($tweet->entities->media)) { foreach ($tweet->entities->media as $picture) { $replace = '<a target="_blank" href="' . $picture->url . '" title="' . $picture->expanded_url . '">' . htmlentities($picture->display_url, ENT_COMPAT, 'UTF-8') . '</a>'; $map[$picture->indices[0]] = array($picture->indices[1], $replace); $previews[] = ' <a href="#" onclick="return Horde[\'twitter' . $instance . '\'].showPreview(\'' . $picture->media_url . ':small\');"><img src="' . Horde_Themes::img('mime/image.png') . '" /></a>'; } } if (!empty($tweet->entities->user_mentions)) { foreach ($tweet->entities->user_mentions as $user) { $replace = ' <a target="_blank" title="' . $user->name . '" href="http://twitter.com/' . $user->screen_name . '">@' . htmlentities($user->screen_name, ENT_COMPAT, 'UTF-8') . '</a>'; $map[$user->indices[0]] = array($user->indices[1], $replace); } } if (!empty($tweet->entities->hashtags)) { foreach ($tweet->entities->hashtags as $hashtag) { $replace = ' <a target="_blank" href="http://twitter.com/search?q=#' . urlencode($hashtag->text) . '">#' . htmlentities($hashtag->text, ENT_COMPAT, 'UTF-8') . '</a>'; $map[$hashtag->indices[0]] = array($hashtag->indices[1], $replace); } } $body = ''; $pos = 0; while ($pos <= Horde_String::length($tweet->text) - 1) { if (!empty($map[$pos])) { $entity = $map[$pos]; $body .= $entity[1]; $pos = $entity[0]; } else { $body .= Horde_String::substr($tweet->text, $pos, 1); ++$pos; } } foreach ($previews as $preview) { $body .= $preview; } $view->body = $body; /* If this is a retweet, use the original author's profile info */ if (!empty($tweet->retweeted_status)) { $tweetObj = $tweet->retweeted_status; } else { $tweetObj = $tweet; } /* These are all referencing the *original* tweet */ $view->profileLink = Horde::externalUrl('http://twitter.com/' . htmlspecialchars($tweetObj->user->screen_name), true); $view->profileImg = $GLOBALS['browser']->usingSSLConnection() ? $tweetObj->user->profile_image_url_https : $tweetObj->user->profile_image_url; $view->authorName = '@' . htmlspecialchars($tweetObj->user->screen_name); $view->authorFullname = htmlspecialchars($tweetObj->user->name); $view->createdAt = $tweetObj->created_at; $view->clientText = $filter->filter($tweet->source, 'xss'); $view->tweet = $tweet; $view->instanceid = $instance; return $view; }
/** * Create an AS message from this task * * @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_Task */ public function toASTask(array $options = array()) { $message = new Horde_ActiveSync_Message_Task(array('protocolversion' => $options['protocolversion'])); /* Notes and Title */ if ($options['protocolversion'] >= Horde_ActiveSync::VERSION_TWELVE) { $bp = $options['bodyprefs']; $body = new Horde_ActiveSync_Message_AirSyncBaseBody(); $body->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->desc = Horde_Text_Filter::filter($this->desc, 'Text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO)); } else { $truncation = false; } if ($truncation && Horde_String::length($this->desc) > $truncation) { $body->data = Horde_String::substr($this->desc, 0, $truncation); $body->truncated = 1; } else { $body->data = $this->desc; } $body->estimateddatasize = Horde_String::length($this->desc); $message->airsyncbasebody = $body; } else { $message->body = $this->desc; } $message->subject = $this->name; /* Completion */ if ($this->completed) { $message->datecompleted = new Horde_Date($this->completed_date); $message->complete = Horde_ActiveSync_Message_Task::TASK_COMPLETE_TRUE; } else { $message->complete = Horde_ActiveSync_Message_Task::TASK_COMPLETE_FALSE; } /* Due Date */ if (!empty($this->due)) { $message->utcduedate = new Horde_Date($this->due); $message->duedate = new Horde_Date($this->due, 'UTC'); } /* Start Date */ if (!empty($this->start)) { $message->utcstartdate = new Horde_Date($this->start); $message->startdate = new Horde_Date($this->start, 'UTC'); } /* Priority */ switch ($this->priority) { case 5: $priority = Horde_ActiveSync_Message_Task::IMPORTANCE_LOW; break; case 4: case 3: case 2: $priority = Horde_ActiveSync_Message_Task::IMPORTANCE_NORMAL; break; case 1: $priority = Horde_ActiveSync_Message_Task::IMPORTANCE_HIGH; break; default: $priority = Horde_ActiveSync_Message_Task::IMPORTANCE_NORMAL; } $message->setImportance($priority); /* Reminders */ if ($this->due && $this->alarm) { $message->setReminder(new Horde_Date($this->due - $this->alarm * 60)); } /* Recurrence */ if ($this->recurs()) { $message->setRecurrence($this->recurrence); } /* Categories */ $message->categories = $this->tags; return $message; }
/** * Replace the memo identified by UID with the content represented in * the specified contentType. * * @param string $uid Idenfity the memo to replace. * @param string $content The content of the memo. * @param string $contentType What format is the data in? Currently supports: * text/plain * text/x-vnote * activesync * @throws Mnemo_Exception * @throws Horde_Exception_PermissionDenied */ public function replace($uid, $content, $contentType) { $storage = $GLOBALS['injector']->getInstance('Mnemo_Factory_Driver')->create(); $memo = $storage->getByUID($uid); if (!array_key_exists($memo['memolist_id'], Mnemo::listNotepads(false, Horde_Perms::EDIT))) { throw new Horde_Exception_PermissionDenied(); } switch ($contentType) { case 'text/plain': $storage->modify($memo['memo_id'], $storage->getMemoDescription($content), $content, null); break; case 'text/x-vnote': if (!$content instanceof Horde_Icalendar_Vnote) { $iCal = new Horde_Icalendar(); if (!$iCal->parsevCalendar($content)) { throw new Mnemo_Exception(_("There was an error importing the iCalendar data.")); } $components = $iCal->getComponents(); switch (count($components)) { case 0: throw new Mnemo_Exception(_("No iCalendar data was found.")); case 1: $content = $components[0]; break; default: throw new Mnemo_Exception(_("Multiple iCalendar components found; only one vNote is supported.")); } } $note = $storage->fromiCalendar($content); $storage->modify($memo['memo_id'], $note['desc'], $note['body'], !empty($note['tags']) ? $note['tags'] : ''); break; case 'activesync': // We only support plaintext if ($content->body->type == Horde_ActiveSync::BODYPREF_TYPE_HTML) { $body = Horde_Text_Filter::filter($content->body->data, 'Html2text'); } else { $body = $content->body->data; } $storage->modify($memo['memo_id'], Horde_String::substr($content->subject, 0, 255), $body, $content->categories); break; default: throw new Mnemo_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType)); } }
public function getCostObjectType($vars) { global $registry; $clients = $vars->get('clients'); if (count($clients) == 0) { $clients = array(''); } $costobjects = array(); foreach ($clients as $client) { $criteria = array('user' => $GLOBALS['registry']->getAuth(), 'active' => true, 'client_id' => $client); foreach ($registry->listApps() as $app) { if ($registry->hasMethod('listCostObjects', $app)) { try { $res = $registry->callByPackage($app, 'listCostObjects', array($criteria)); } catch (Horde_Exception $e) { $GLOBALS['notification']->push(sprintf(_("Error retrieving cost objects from \"%s\": %s"), $registry->get('name', $app), $res->getMessage()), 'horde.error'); continue; } foreach (array_keys($res) as $catkey) { foreach (array_keys($res[$catkey]['objects']) as $okey) { $res[$catkey]['objects'][$okey]['id'] = $app . ':' . $res[$catkey]['objects'][$okey]['id']; } } $costobjects = array_merge($costobjects, $res); } } } $elts = array(); $counter = 0; foreach ($costobjects as $category) { Horde_Array::arraySort($category['objects'], 'name'); foreach ($category['objects'] as $object) { $name = $object['name']; if (Horde_String::length($name) > 80) { $name = Horde_String::substr($name, 0, 76) . ' ...'; } $elts[$object['id']] = $name; } } return $elts; }
/** * Extract a set of encapsulated MAPI properties. Normally either embedded * in an attachment structure, or an idMessageProperty structure. * * @param string $data The data string. * @param array &$attachment_data TODO */ protected function _extractMapiAttributes($data) { // Number of attributes. $number = $this->_geti($data, 32); $this->_logger->debug(sprintf('TNEF: Extracting %d MAPI attributes.', $number)); while (strlen($data) > 0 && $number--) { $have_mval = false; $num_mval = 1; $value = null; $attr_type = $this->_geti($data, 16); $attr_name = $this->_geti($data, 16); $namespace = false; // Multivalue attributes. if (($attr_type & self::MAPI_MV_FLAG) != 0) { $have_mval = true; $attr_type = $attr_type & ~self::MAPI_MV_FLAG; $this->_logger->debug(sprintf('TNEF: Multivalue attribute of type: 0x%04X', $attr_type)); } // Named attributes. if ($attr_name >= 0x8000 && $attr_name < 0xfffe) { $namespace = $this->_toNamespaceGUID($this->_getx($data, 16)); // The type of named property, an ID or STRING. $named_type = $this->_geti($data, 32); switch ($named_type) { case self::MAPI_NAMED_TYPE_ID: $pid = $attr_name; $attr_name = $this->_geti($data, 32); $msg = sprintf('TNEF: pid: 0x%X type: 0x%X Named Id: %s 0x%04X', $pid, $attr_type, $namespace, $attr_name); $this->_logger->debug($msg); break; case self::MAPI_NAMED_TYPE_STRING: // @todo. We haven't needed data from any string named id // yet, but might be able to just assign the name to // $attr_name and pass it down to _currentObject for now. // For H6, look at using some lightweight object to transport // the name/value to the various objects. $attr_name = 0x9999; $id_len = $this->_geti($data, 32); $data_len = $id_len + (4 - $id_len % 4) % 4; $name = Horde_String::substr($this->_getx($data, $data_len), 0, $id_len); $name = trim(Horde_String::convertCharset($name, 'UTF-16LE', 'UTF-8')); $this->_logger->debug(sprintf('TNEF: Named String Id: %s', $name)); break; case self::MAPI_NAMED_TYPE_NONE: continue 2; break; default: $msg = sprintf('TNEF: Unknown NAMED type: pid: 0x%X type: 0x%X Named TYPE: %s 0x%04X', $pid, $attr_type, $namespace, $named_type); $this->_logger->notice($msg); continue 2; } } if ($have_mval) { $num_mval = $this->_geti($data, 32); $this->_logger->debug(sprintf('TNEF: Number of multivalues: %s', $num_mval)); } switch ($attr_type) { case self::MAPI_NULL: case self::MAPI_TYPE_UNSPECIFIED: break; case self::MAPI_SHORT: $value = $this->_geti($data, 16); // Padding. See MS-OXTNEF 2.1.3.4 // Must always pad to a multiple of 4 bytes. $this->_geti($data, 16); break; case self::MAPI_INT: case self::MAPI_BOOLEAN: for ($i = 0; $i < $num_mval; $i++) { $value = $this->_geti($data, 32); } break; case self::MAPI_FLOAT: case self::MAPI_ERROR: $value = $this->_getx($data, 4); break; case self::MAPI_DOUBLE: case self::MAPI_APPTIME: case self::MAPI_CURRENCY: case self::MAPI_INT8BYTE: case self::MAPI_SYSTIME: $value = $this->_getx($data, 8); break; case self::MAPI_CLSID: $this->_logger->debug('TNEF: CLSID??'); $this->_getx($data, 16); break; case self::MAPI_STRING: case self::MAPI_UNICODE_STRING: case self::MAPI_BINARY: case self::MAPI_OBJECT: $num_vals = $have_mval ? $num_mval : $this->_geti($data, 32); for ($i = 0; $i < $num_vals; $i++) { $length = $this->_geti($data, 32); /* Pad to next 4 byte boundary. */ $datalen = $length + (4 - $length % 4) % 4; /* Read and truncate to length. */ $value = substr($this->_getx($data, $datalen), 0, $length); } switch ($attr_type) { case self::MAPI_UNICODE_STRING: // MAPI Unicode is UTF-16LE; convert to UTF-8 $value = Horde_String::convertCharset($value, 'UTF-16LE', 'UTF-8'); break; } switch ($attr_type) { case self::MAPI_STRING: case self::MAPI_UNICODE_STRING: // Strings are null-terminated. $value = substr($value, 0, -1); break; } break; default: $msg = sprintf('TNEF: Unknown attribute type, "0x%X"', $attr_type); throw new Horde_Compress_Exception($msg); $this->_logger->notice($msg); } // @todo Utility method to make this log more readable. $this->_logger->debug(sprintf('TNEF: Attribute: 0x%X Type: 0x%X', $attr_name, $attr_type)); switch ($attr_name) { case self::MAPI_TAG_RTF_COMPRESSED: $this->_logger->debug('TNEF: Found compressed RTF text.'); $rtf = new Horde_Compress_Tnef_Rtf($this->_logger, $value); $this->_files[] = $rtf; // Give the currentObject a chance to do something with the RTF // body. This is used, e.g., in meeting requests to populate // the description field. if ($this->_currentObject) { try { $this->_currentObject->setMapiAttribute($attr_type, $attr_name, $rtf->toPlain()); } catch (Horde_Compress_Exception $e) { $this->_logger->err(sprintf('TNEF: Unable to set attribute: %s', $e->getMessage())); } } break; case self::MAPI_ATTACH_DATA: $this->_logger->debug('TNEF: Found nested MAPI object. Parsing.'); $this->_getx($value, 16); $att = new Horde_Compress_Tnef($this->_logger); $att->setCurrentObject($this->_currentObject); $att->decompress($value); $this->_attachments[] = $att; $this->_logger->debug('TNEF: Completed nested attachment parsing.'); break; default: try { $this->_msgInfo->setMapiAttribute($attr_type, $attr_name, $value); if ($this->_currentObject) { $this->_currentObject->setMapiAttribute($attr_type, $attr_name, $value, $namespace); } } catch (Horde_Compress_Exception $e) { $this->_logger->err(sprintf('TNEF: Unable to set attribute: %s', $e->getMessage())); } } } }
/** * Build the data needed for the BodyPart part. * * @param Horde_Imap_Client_Data_Fetch $data The FETCH results. * @param Horde_Mime_Part $mime The plaintext MIME part. * @param boolean $to_html If true, $id is assumed to be a text/plain * part and is converted to html. * * @return array The BodyPart data. * - charset: (string) The charset of the text. * - body: (string) The body text. * - truncated: (boolean) True if text was truncated. * - size: (integer) The original part size, in bytes. */ protected function _getBodyPart(Horde_Imap_Client_Data_Fetch $data, Horde_Mime_Part $mime, $to_html) { $id = $mime->getMimeId(); $text = $data->getBodyPart($id); if (!$data->getBodyPartDecode($id)) { $mime->setContents($text); $text = $mime->getContents(); } if ($to_html) { $text = Horde_Text_Filter::filter($text, 'Text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO, 'charset' => $mime->getCharset())); $size = strlen($text); } else { $size = !is_null($data->getBodyPartSize($id)) ? $data->getBodyPartSize($id) : strlen($text); } if (!empty($this->_options['bodypartprefs']['truncationsize'])) { $text = Horde_String::substr($text, 0, $this->_options['bodypartprefs']['truncationsize'], $mime->getCharset()); } return array('charset' => $mime->getCharset(), 'body' => $text, 'truncated' => $size > strlen($text), 'size' => $size); }
/** * List code points for a given input. * * @param string $input * * @return array Multi-dimension array with basic, non-basic and * aggregated code points. */ protected function _codePoints($input) { $codePoints = array('all' => array(), 'basic' => array(), 'nonBasic' => array()); $len = Horde_String::length($input, 'UTF-8'); for ($i = 0; $i < $len; ++$i) { $char = Horde_String::substr($input, $i, 1, 'UTF-8'); $code = $this->_charToCodePoint($char); if ($code < 128) { $codePoints['all'][] = $codePoints['basic'][] = $code; } else { $codePoints['all'][] = $codePoints['nonBasic'][] = $code; } } return $codePoints; }
/** * Returns a hash with all information necessary to reply to a message. * * @param mixed $message The ID of the parent message to reply to, or arry of its data. * * @return array A hash with all relevant information. * @throws Horde_Exception_NotFound * @throws Agora_Exception */ public function replyMessage($message) { if (!is_array($message)) { $message = $this->getMessage($message); } /* Set up the form subject with the parent subject. */ if (Horde_String::lower(Horde_String::substr($message['message_subject'], 0, 3)) != 're:') { $message['message_subject'] = 'Re: ' . $message['message_subject']; } /* Prepare the message quite body . */ $message['body'] = sprintf(_("Posted by %s on %s"), htmlspecialchars($message['message_author']), strftime($GLOBALS['prefs']->getValue('date_format'), $message['message_timestamp'])) . "\n-------------------------------------------------------\n" . $message['body']; $message['body'] = "\n> " . Horde_String::wrap($message['body'], 60, "\n> "); return $message; }
/** * Reformats the input string, where the string is 'format=flowed' plain * text as described in RFC 2646. * * @param boolean $toflowed Convert to flowed? * @param boolean $quote Add level of quoting to each line? * @param boolean $wrap Wrap unquoted lines? */ protected function _reformat($toflowed, $quote, $wrap = true) { $format_type = implode('|', array($toflowed, $quote)); if ($format_type == $this->_formattype) { return; } $this->_output = array(); $this->_formattype = $format_type; /* Set variables used in regexps. */ $delsp = $toflowed && $this->_delsp ? 1 : 0; $opt = $this->_optlength - 1 - $delsp; /* Process message line by line. */ $text = preg_split("/\r?\n/", $this->_text); $text_count = count($text) - 1; $skip = 0; reset($text); while (list($no, $line) = each($text)) { if ($skip) { --$skip; continue; } /* Per RFC 2646 [4.3], the 'Usenet Signature Convention' line * (DASH DASH SP) is not considered flowed. Watch for this when * dealing with potentially flowed lines. */ /* The next three steps come from RFC 2646 [4.2]. */ /* STEP 1: Determine quote level for line. */ if ($num_quotes = $this->_numquotes($line)) { $line = substr($line, $num_quotes); } /* Only combine lines if we are converting to flowed or if the * current line is quoted. */ if (!$toflowed || $num_quotes) { /* STEP 2: Remove space stuffing from line. */ $line = $this->_unstuff($line); /* STEP 3: Should we interpret this line as flowed? * While line is flowed (not empty and there is a space * at the end of the line), and there is a next line, and the * next line has the same quote depth, add to the current * line. A line is not flowed if it is a signature line. */ if ($line != '-- ') { while (!empty($line) && substr($line, -1) == ' ' && $text_count != $no && $this->_numquotes($text[$no + 1]) == $num_quotes) { /* If DelSp is yes and this is flowed input, we need to * remove the trailing space. */ if (!$toflowed && $this->_delsp) { $line = substr($line, 0, -1); } $line .= $this->_unstuff(substr($text[++$no], $num_quotes)); ++$skip; } } } /* Ensure line is fixed, since we already joined all flowed * lines. Remove all trailing ' ' from the line. */ if ($line != '-- ') { $line = rtrim($line); } /* Increment quote depth if we're quoting. */ if ($quote) { $num_quotes++; } /* The quote prefix for the line. */ $quotestr = str_repeat('>', $num_quotes); if (empty($line)) { /* Line is empty. */ $this->_output[] = array('text' => $quotestr, 'level' => $num_quotes); } elseif (!$wrap && !$num_quotes || empty($this->_maxlength) || Horde_String::length($line, $this->_charset) + $num_quotes <= $this->_maxlength) { /* Line does not require rewrapping. */ $this->_output[] = array('text' => $quotestr . $this->_stuff($line, $num_quotes, $toflowed), 'level' => $num_quotes); } else { $min = $num_quotes + 1; /* Rewrap this paragraph. */ while ($line) { /* Stuff and re-quote the line. */ $line = $quotestr . $this->_stuff($line, $num_quotes, $toflowed); $line_length = Horde_String::length($line, $this->_charset); if ($line_length <= $this->_optlength) { /* Remaining section of line is short enough. */ $this->_output[] = array('text' => $line, 'level' => $num_quotes); break; } else { $regex = array(); if ($min <= $opt) { $regex[] = '^(.{' . $min . ',' . $opt . '}) (.*)'; } if ($min <= $this->_maxlength) { $regex[] = '^(.{' . $min . ',' . $this->_maxlength . '}) (.*)'; } $regex[] = '^(.{' . $min . ',})? (.*)'; if ($m = Horde_String::regexMatch($line, $regex, $this->_charset)) { /* We need to wrap text at a certain number of * *characters*, not a certain number of *bytes*; * thus the need for a multibyte capable regex. * If a multibyte regex isn't available, we are * stuck with preg_match() (the function will * still work - are just left with shorter rows * than expected if multibyte characters exist in * the row). * * 1. Try to find a string as long as _optlength. * 2. Try to find a string as long as _maxlength. * 3. Take the first word. */ if (empty($m[1])) { $m[1] = $m[2]; $m[2] = ''; } $this->_output[] = array('text' => $m[1] . ' ' . ($delsp ? ' ' : ''), 'level' => $num_quotes); $line = $m[2]; } elseif ($line_length > 998) { /* One excessively long word left on line. Be * absolutely sure it does not exceed 998 * characters in length or else we must * truncate. */ $this->_output[] = array('text' => Horde_String::substr($line, 0, 998, $this->_charset), 'level' => $num_quotes); $line = Horde_String::substr($line, 998, null, $this->_charset); } else { $this->_output[] = array('text' => $line, 'level' => $num_quotes); break; } } } } } }
} echo '</tbody>'; } /* Display all of the files in this directory */ $readmes = array(); if ($fileList) { echo '<tbody>'; foreach ($fileList as $currFile) { if ($conf['hide_restricted'] && Chora::isRestricted($currFile->getFileName())) { continue; } $lg = $currFile->getLastLog(); $realname = $currFile->getFileName(); $mimeType = Horde_Mime_Magic::filenameToMIME($realname); $currFile->mimeType = $mimeType; if (Horde_String::lower(Horde_String::substr($realname, 0, 6)) == 'readme') { $readmes[] = $currFile; } $icon = $injector->getInstance('Horde_Core_Factory_MimeViewer')->getIcon($mimeType); $author = Chora::showAuthorName($lg->getAuthor()); $filerev = $lg->getRevision(); $date = $lg->getDate(); $log = $lg->getMessage(); $attic = $currFile->isDeleted(); $fileName = $where . ($attic ? '/' . 'Attic' : '') . '/' . $realname; $name = $injector->getInstance('Horde_Core_Factory_TextFilter')->filter($realname, 'space2html', array('encode' => true, 'encode_all' => true)); $url = Chora::url('browsefile', $fileName, $branchArgs); $readableDate = Chora::readableTime($date); if ($log) { $shortLog = Horde_String::truncate(str_replace("\n", ' ', trim($log)), $conf['options']['shortLogLength']); }
/** * 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; }