diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php index dcb7ac99..1cbeab02 100644 --- a/plugins/calendar/drivers/calendar_driver.php +++ b/plugins/calendar/drivers/calendar_driver.php @@ -1,830 +1,831 @@ * @author Thomas Bruederli * * Copyright (C) 2010, Lazlo Westerhof * Copyright (C) 2012-2015, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ /** * Struct of an internal event object how it is passed from/to the driver classes: * * $event = array( * 'id' => 'Event ID used for editing', * 'uid' => 'Unique identifier of this event', * 'calendar' => 'Calendar identifier to add event to or where the event is stored', * 'start' => DateTime, // Event start date/time as DateTime object * 'end' => DateTime, // Event end date/time as DateTime object * 'allday' => true|false, // Boolean flag if this is an all-day event * 'changed' => DateTime, // Last modification date of event * 'title' => 'Event title/summary', * 'location' => 'Location string', * 'description' => 'Event description', * 'url' => 'URL to more information', * 'recurrence' => array( // Recurrence definition according to iCalendar (RFC 2445) specification as list of key-value pairs * 'FREQ' => 'DAILY|WEEKLY|MONTHLY|YEARLY', * 'INTERVAL' => 1...n, * 'UNTIL' => DateTime, * 'COUNT' => 1..n, // number of times * // + more properties (see http://www.kanzaki.com/docs/ical/recur.html) * 'EXDATE' => array(), // list of DateTime objects of exception Dates/Times * 'EXCEPTIONS' => array(), list of event objects which denote exceptions in the recurrence chain * ), * 'recurrence_id' => 'ID of the recurrence group', // usually the ID of the starting event * '_instance' => 'ID of the recurring instance', // identifies an instance within a recurrence chain * 'categories' => 'Event category', * 'free_busy' => 'free|busy|outofoffice|tentative', // Show time as * 'status' => 'TENTATIVE|CONFIRMED|CANCELLED', // event status according to RFC 2445 * 'priority' => 0-9, // Event priority (0=undefined, 1=highest, 9=lowest) * 'sensitivity' => 'public|private|confidential', // Event sensitivity * 'alarms' => '-15M:DISPLAY', // DEPRECATED Reminder settings inspired by valarm definition (e.g. display alert 15 minutes before event) * 'valarms' => array( // List of reminders (new format), each represented as a hash array: * array( * 'trigger' => '-PT90M', // ISO 8601 period string prefixed with '+' or '-', or DateTime object * 'action' => 'DISPLAY|EMAIL|AUDIO', * 'duration' => 'PT15M', // ISO 8601 period string * 'repeat' => 0, // number of repetitions * 'description' => '', // text to display for DISPLAY actions * 'summary' => '', // message text for EMAIL actions * 'attendees' => array(), // list of email addresses to receive alarm messages * ), * ), * 'attachments' => array( // List of attachments * 'name' => 'File name', * 'mimetype' => 'Content type', * 'size' => 1..n, // in bytes * 'id' => 'Attachment identifier' * ), * 'deleted_attachments' => array(), // array of attachment identifiers to delete when event is updated * 'attendees' => array( // List of event participants * 'name' => 'Participant name', * 'email' => 'Participant e-mail address', // used as identifier * 'role' => 'ORGANIZER|REQ-PARTICIPANT|OPT-PARTICIPANT|CHAIR', * 'status' => 'NEEDS-ACTION|UNKNOWN|ACCEPTED|TENTATIVE|DECLINED' * 'rsvp' => true|false, * ), * * '_savemode' => 'all|future|current|new', // How changes on recurring event should be handled * '_notify' => true|false, // whether to notify event attendees about changes * '_fromcalendar' => 'Calendar identifier where the event was stored before', * ); */ /** * Interface definition for calendar driver classes */ abstract class calendar_driver { const FILTER_ALL = 0; const FILTER_WRITEABLE = 1; const FILTER_INSERTABLE = 2; const FILTER_ACTIVE = 4; const FILTER_PERSONAL = 8; const FILTER_PRIVATE = 16; const FILTER_CONFIDENTIAL = 32; const FILTER_SHARED = 64; const BIRTHDAY_CALENDAR_ID = '__bdays__'; // features supported by backend public $alarms = false; public $attendees = false; public $freebusy = false; public $attachments = false; public $undelete = false; public $history = false; public $categoriesimmutable = false; public $alarm_types = array('DISPLAY'); public $alarm_absolute = true; public $last_error; protected $default_categories = array( 'Personal' => 'c0c0c0', 'Work' => 'ff0000', 'Family' => '00ff00', 'Holiday' => 'ff6600', ); /** * Get a list of available calendars from this source * * @param integer Bitmask defining filter criterias. * See FILTER_* constants for possible values. * @return array List of calendars */ abstract function list_calendars($filter = 0); /** * Create a new calendar assigned to the current user * * @param array Hash array with calendar properties * name: Calendar name * color: The color of the calendar * showalarms: True if alarms are enabled * @return mixed ID of the calendar on success, False on error */ abstract function create_calendar($prop); /** * Update properties of an existing calendar * * @param array Hash array with calendar properties * id: Calendar Identifier * name: Calendar name * color: The color of the calendar * showalarms: True if alarms are enabled (if supported) * @return boolean True on success, Fales on failure */ abstract function edit_calendar($prop); /** * Set active/subscribed state of a calendar * * @param array Hash array with calendar properties * id: Calendar Identifier * active: True if calendar is active, false if not * @return boolean True on success, Fales on failure */ abstract function subscribe_calendar($prop); /** * Delete the given calendar with all its contents * * @param array Hash array with calendar properties * id: Calendar Identifier * @return boolean True on success, Fales on failure */ abstract function delete_calendar($prop); /** * Search for shared or otherwise not listed calendars the user has access * * @param string Search string * @param string Section/source to search * @return array List of calendars */ abstract function search_calendars($query, $source); /** * Add a single event to the database * * @param array Hash array with event properties (see header of this file) * @return mixed New event ID on success, False on error */ abstract function new_event($event); /** * Update an event entry with the given data * * @param array Hash array with event properties (see header of this file) * @return boolean True on success, False on error */ abstract function edit_event($event); /** * Extended event editing with possible changes to the argument * * @param array Hash array with event properties * @param string New participant status * @param array List of hash arrays with updated attendees * @return boolean True on success, False on error */ public function edit_rsvp(&$event, $status, $attendees) { return $this->edit_event($event); } /** * Update the participant status for the given attendee * * @param array Hash array with event properties * @param array List of hash arrays each represeting an updated attendee * @return boolean True on success, False on error */ public function update_attendees(&$event, $attendees) { return $this->edit_event($event); } /** * Move a single event * * @param array Hash array with event properties: * id: Event identifier * start: Event start date/time as DateTime object * end: Event end date/time as DateTime object * allday: Boolean flag if this is an all-day event * @return boolean True on success, False on error */ abstract function move_event($event); /** * Resize a single event * * @param array Hash array with event properties: * id: Event identifier * start: Event start date/time as DateTime object with timezone * end: Event end date/time as DateTime object with timezone * @return boolean True on success, False on error */ abstract function resize_event($event); /** * Remove a single event from the database * * @param array Hash array with event properties: * id: Event identifier * @param boolean Remove event irreversible (mark as deleted otherwise, * if supported by the backend) * * @return boolean True on success, False on error */ abstract function remove_event($event, $force = true); /** * Restores a single deleted event (if supported) * * @param array Hash array with event properties: * id: Event identifier * * @return boolean True on success, False on error */ public function restore_event($event) { return false; } /** * Return data of a single event * * @param mixed UID string or hash array with event properties: * id: Event identifier * uid: Event UID * _instance: Instance identifier in combination with uid (optional) * calendar: Calendar identifier (optional) * @param integer Bitmask defining the scope to search events in. * See FILTER_* constants for possible values. * @param boolean If true, recurrence exceptions shall be added * * @return array Event object as hash array */ abstract function get_event($event, $scope = 0, $full = false); /** * Get events from source. * * @param integer Date range start (unix timestamp) * @param integer Date range end (unix timestamp) * @param string Search query (optional) * @param mixed List of calendar IDs to load events from (either as array or comma-separated string) * @param boolean Include virtual/recurring events (optional) * @param integer Only list events modified since this time (unix timestamp) * @return array A list of event objects (see header of this file for struct of an event) */ abstract function load_events($start, $end, $query = null, $calendars = null, $virtual = 1, $modifiedsince = null); /** * Get number of events in the given calendar * * @param mixed List of calendar IDs to count events (either as array or comma-separated string) * @param integer Date range start (unix timestamp) * @param integer Date range end (unix timestamp) * @return array Hash array with counts grouped by calendar ID */ abstract function count_events($calendars, $start, $end = null); /** * Get a list of pending alarms to be displayed to the user * * @param integer Current time (unix timestamp) * @param mixed List of calendar IDs to show alarms for (either as array or comma-separated string) * @return array A list of alarms, each encoded as hash array: * id: Event identifier * uid: Unique identifier of this event * start: Event start date/time as DateTime object * end: Event end date/time as DateTime object * allday: Boolean flag if this is an all-day event * title: Event title/summary * location: Location string */ abstract function pending_alarms($time, $calendars = null); /** * (User) feedback after showing an alarm notification * This should mark the alarm as 'shown' or snooze it for the given amount of time * * @param string Event identifier * @param integer Suspend the alarm for this number of seconds */ abstract function dismiss_alarm($event_id, $snooze = 0); /** * Check the given event object for validity * * @param array Event object as hash array * @return boolean True if valid, false if not */ public function validate($event) { $valid = true; if (!is_object($event['start']) || !is_a($event['start'], 'DateTime')) $valid = false; if (!is_object($event['end']) || !is_a($event['end'], 'DateTime')) $valid = false; return $valid; } /** * Get list of event's attachments. * Drivers can return list of attachments as event property. * If they will do not do this list_attachments() method will be used. * * @param array $event Hash array with event properties: * id: Event identifier * calendar: Calendar identifier * * @return array List of attachments, each as hash array: * id: Attachment identifier * name: Attachment name * mimetype: MIME content type of the attachment * size: Attachment size */ public function list_attachments($event) { } /** * Get attachment properties * * @param string $id Attachment identifier * @param array $event Hash array with event properties: * id: Event identifier * calendar: Calendar identifier * * @return array Hash array with attachment properties: * id: Attachment identifier * name: Attachment name * mimetype: MIME content type of the attachment * size: Attachment size */ public function get_attachment($id, $event) { } /** * Get attachment body * * @param string $id Attachment identifier * @param array $event Hash array with event properties: * id: Event identifier * calendar: Calendar identifier * * @return string Attachment body */ public function get_attachment_body($id, $event) { } /** * Build a struct representing the given message reference * * @param object|string $uri_or_headers rcube_message_header instance holding the message headers * or an URI from a stored link referencing a mail message. * @param string $folder IMAP folder the message resides in * * @return array An struct referencing the given IMAP message */ public function get_message_reference($uri_or_headers, $folder = null) { // to be implemented by the derived classes return false; } /** * List availabale categories * The default implementation reads them from config/user prefs */ public function list_categories() { $rcmail = rcube::get_instance(); return $rcmail->config->get('calendar_categories', $this->default_categories); } /** * Create a new category */ public function add_category($name, $color) { } /** * Remove the given category */ public function remove_category($name) { } /** * Update/replace a category */ public function replace_category($oldname, $name, $color) { } /** * Fetch free/busy information from a person within the given range * * @param string E-mail address of attendee * @param integer Requested period start date/time as unix timestamp * @param integer Requested period end date/time as unix timestamp * * @return array List of busy timeslots within the requested range */ public function get_freebusy_list($email, $start, $end) { return false; } /** * Create instances of a recurring event * * @param array Hash array with event properties * @param object DateTime Start date of the recurrence window * @param object DateTime End date of the recurrence window * @return array List of recurring event instances */ public function get_recurring_events($event, $start, $end = null) { $events = array(); if ($event['recurrence']) { // include library class require_once(dirname(__FILE__) . '/../lib/calendar_recurrence.php'); $rcmail = rcmail::get_instance(); $recurrence = new calendar_recurrence($rcmail->plugins->get_plugin('calendar'), $event); $recurrence_id_format = libcalendaring::recurrence_id_format($event); // determine a reasonable end date if none given if (!$end) { switch ($event['recurrence']['FREQ']) { case 'YEARLY': $intvl = 'P100Y'; break; case 'MONTHLY': $intvl = 'P20Y'; break; default: $intvl = 'P10Y'; break; } $end = clone $event['start']; $end->add(new DateInterval($intvl)); } $i = 0; while ($next_event = $recurrence->next_instance()) { // add to output if in range if (($next_event['start'] <= $end && $next_event['end'] >= $start)) { $next_event['_instance'] = $next_event['start']->format($recurrence_id_format); $next_event['id'] = $next_event['uid'] . '-' . $exception['_instance']; $next_event['recurrence_id'] = $event['uid']; $events[] = $next_event; } else if ($next_event['start'] > $end) { // stop loop if out of range break; } // avoid endless recursion loops if (++$i > 1000) { break; } } } return $events; } /** * Provide a list of revisions for the given event * * @param array $event Hash array with event properties: * id: Event identifier * calendar: Calendar identifier * * @return array List of changes, each as a hash array: * rev: Revision number * type: Type of the change (create, update, move, delete) * date: Change date * user: The user who executed the change * ip: Client IP * destination: Destination calendar for 'move' type */ public function get_event_changelog($event) { return false; } /** * Get a list of property changes beteen two revisions of an event * - * @param array $event Hash array with event properties: + * @param array $event Hash array with event properties: * id: Event identifier * calendar: Calendar identifier - * @param mixed $rev Revisions: "from:to" + * @param mixed $rev1 Old Revision + * @param mixed $rev2 New Revision * * @return array List of property changes, each as a hash array: * property: Revision number * old: Old property value * new: Updated property value */ - public function get_event_diff($event, $rev) + public function get_event_diff($event, $rev1, $rev2) { return false; } /** * Return full data of a specific revision of an event * * @param mixed UID string or hash array with event properties: * id: Event identifier * calendar: Calendar identifier * @param mixed $rev Revision number * * @return array Event object as hash array * @see self::get_event() */ public function get_event_revison($event, $rev) { return false; } /** * Command the backend to restore a certain revision of an event. * This shall replace the current event with an older version. * * @param mixed UID string or hash array with event properties: * id: Event identifier * calendar: Calendar identifier * @param mixed $rev Revision number * * @return boolean True on success, False on failure */ public function restore_event_revision($event, $rev) { return false; } /** * Callback function to produce driver-specific calendar create/edit form * * @param string Request action 'form-edit|form-new' * @param array Calendar properties (e.g. id, color) * @param array Edit form fields * * @return string HTML content of the form */ public function calendar_form($action, $calendar, $formfields) { $html = ''; foreach ($formfields as $field) { $html .= html::div('form-section', html::label($field['id'], $field['label']) . $field['value']); } return $html; } /** * Compose a list of birthday events from the contact records in the user's address books. * * This is a default implementation using Roundcube's address book API. * It can be overriden with a more optimized version by the individual drivers. * * @param integer Event's new start (unix timestamp) * @param integer Event's new end (unix timestamp) * @param string Search query (optional) * @param integer Only list events modified since this time (unix timestamp) * @return array A list of event records */ public function load_birthday_events($start, $end, $search = null, $modifiedsince = null) { // ignore update requests for simplicity reasons if (!empty($modifiedsince)) { return array(); } // convert to DateTime for comparisons $start = new DateTime('@'.$start); $end = new DateTime('@'.$end); // extract the current year $year = $start->format('Y'); $year2 = $end->format('Y'); $events = array(); $search = mb_strtolower($search); $rcmail = rcmail::get_instance(); $cache = $rcmail->get_cache('calendar.birthdays', 'db', 3600); $cache->expunge(); $alarm_type = $rcmail->config->get('calendar_birthdays_alarm_type', ''); $alarm_offset = $rcmail->config->get('calendar_birthdays_alarm_offset', '-1D'); $alarms = $alarm_type ? $alarm_offset . ':' . $alarm_type : null; // let the user select the address books to consider in prefs $selected_sources = $rcmail->config->get('calendar_birthday_adressbooks'); $sources = $selected_sources ?: array_keys($rcmail->get_address_sources(false, true)); foreach ($sources as $source) { $abook = $rcmail->get_address_book($source); // skip LDAP address books unless selected by the user if (!$abook || ($abook instanceof rcube_ldap && empty($selected_sources))) { continue; } $abook->set_pagesize(10000); // check for cached results $cache_records = array(); $cached = $cache->get($source); // iterate over (cached) contacts foreach (($cached ?: $abook->search('*', '', 2, true, true, array('birthday'))) as $contact) { $event = self::parse_contact($contact, $source); if (empty($event)) { continue; } // add stripped record to cache if (empty($cached)) { $cache_records[] = array( 'ID' => $contact['ID'], 'name' => $event['_displayname'], 'birthday' => $event['start']->format('Y-m-d'), ); } // filter by search term (only name is involved here) if (!empty($search) && strpos(mb_strtolower($event['title']), $search) === false) { continue; } $bday = clone $event['start']; $byear = $bday->format('Y'); // quick-and-dirty recurrence computation: just replace the year $bday->setDate($year, $bday->format('n'), $bday->format('j')); $bday->setTime(12, 0, 0); $this_year = $year; // date range reaches over multiple years: use end year if not in range if (($bday > $end || $bday < $start) && $year2 != $year) { $bday->setDate($year2, $bday->format('n'), $bday->format('j')); $this_year = $year2; } // birthday is within requested range if ($bday <= $end && $bday >= $start) { unset($event['_displayname']); $event['alarms'] = $alarms; // if this is not the first occurence modify event details // but not when this is "all birthdays feed" request if ($year2 - $year < 10 && ($age = ($this_year - $byear))) { $event['description'] = $rcmail->gettext(array('name' => 'birthdayage', 'vars' => array('age' => $age)), 'calendar'); $event['start'] = $bday; $event['end'] = clone $bday; unset($event['recurrence']); } // add the main instance $events[] = $event; } } // store collected contacts in cache if (empty($cached)) { $cache->write($source, $cache_records); } } return $events; } /** * Get a single birthday calendar event */ public function get_birthday_event($id) { // decode $id list(,$source,$contact_id,$year) = explode(':', rcube_ldap::dn_decode($id)); $rcmail = rcmail::get_instance(); if ($source && $contact_id && ($abook = $rcmail->get_address_book($source))) { if ($contact = $abook->get_record($contact_id, true)) { return self::parse_contact($contact, $source); } } } /** * Parse contact and create an event for its birthday * * @param array $contact Contact data * @param string $source Addressbook source ID * * @return array Birthday event data */ public static function parse_contact($contact, $source) { if (!is_array($contact)) { return; } if (is_array($contact['birthday'])) { $contact['birthday'] = reset($contact['birthday']); } if (empty($contact['birthday'])) { return; } try { $bday = $contact['birthday']; if (!$bday instanceof DateTime) { $bday = new DateTime($bday, new DateTimezone('UTC')); } $bday->_dateonly = true; } catch (Exception $e) { rcube::raise_error(array( 'code' => 600, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => 'BIRTHDAY PARSE ERROR: ' . $e->getMessage()), true, false); return; } $rcmail = rcmail::get_instance(); $birthyear = $bday->format('Y'); $display_name = rcube_addressbook::compose_display_name($contact); $label = array('name' => 'birthdayeventtitle', 'vars' => array('name' => $display_name)); $event_title = $rcmail->gettext($label, 'calendar'); $uid = rcube_ldap::dn_encode('bday:' . $source . ':' . $contact['ID'] . ':' . $birthyear); $event = array( 'id' => $uid, 'uid' => $uid, 'calendar' => self::BIRTHDAY_CALENDAR_ID, 'title' => $event_title, 'description' => '', 'allday' => true, 'start' => $bday, 'end' => clone $bday, 'recurrence' => array('FREQ' => 'YEARLY', 'INTERVAL' => 1), 'free_busy' => 'free', '_displayname' => $display_name, ); return $event; } /** * Store alarm dismissal for birtual birthay events * * @param string Event identifier * @param integer Suspend the alarm for this number of seconds */ public function dismiss_birthday_alarm($event_id, $snooze = 0) { $rcmail = rcmail::get_instance(); $cache = $rcmail->get_cache('calendar.birthdayalarms', 'db', 86400 * 30); $cache->remove($event_id); // compute new notification time or disable if not snoozed $notifyat = $snooze > 0 ? time() + $snooze : null; $cache->set($event_id, array('snooze' => $snooze, 'notifyat' => $notifyat)); return true; } /** * Handler for user_delete plugin hook * * @param array Hash array with hook arguments * @return array Return arguments for plugin hooks */ public function user_delete($args) { // TO BE OVERRIDDEN return $args; } } diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php index f8733d52..11ec1f41 100644 --- a/plugins/calendar/drivers/kolab/kolab_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_calendar.php @@ -1,869 +1,869 @@ * @author Aleksander Machniak * * Copyright (C) 2012-2015, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ class kolab_calendar extends kolab_storage_folder_api { public $ready = false; public $rights = 'lrs'; public $editable = false; public $attachments = true; public $alarms = false; public $history = false; public $subscriptions = true; public $categories = array(); public $storage; public $type = 'event'; protected $cal; protected $events = array(); protected $search_fields = array('title', 'description', 'location', 'attendees'); /** * Factory method to instantiate a kolab_calendar object * * @param string Calendar ID (encoded IMAP folder name) * @param object calendar plugin object * @return object kolab_calendar instance */ public static function factory($id, $calendar) { $imap = $calendar->rc->get_storage(); $imap_folder = kolab_storage::id_decode($id); $info = $imap->folder_info($imap_folder, true); if (empty($info) || $info['noselect'] || strpos(kolab_storage::folder_type($imap_folder), 'event') !== 0) { return new kolab_user_calendar($imap_folder, $calendar); } else { return new kolab_calendar($imap_folder, $calendar); } } /** * Default constructor */ public function __construct($imap_folder, $calendar) { $this->cal = $calendar; $this->imap = $calendar->rc->get_storage(); $this->name = $imap_folder; // ID is derrived from folder name $this->id = kolab_storage::folder_id($this->name, true); $old_id = kolab_storage::folder_id($this->name, false); // fetch objects from the given IMAP folder $this->storage = kolab_storage::get_folder($this->name); $this->ready = $this->storage && $this->storage->valid; // Set writeable and alarms flags according to folder permissions if ($this->ready) { if ($this->storage->get_namespace() == 'personal') { $this->editable = true; $this->rights = 'lrswikxteav'; $this->alarms = true; } else { $rights = $this->storage->get_myrights(); if ($rights && !PEAR::isError($rights)) { $this->rights = $rights; if (strpos($rights, 't') !== false || strpos($rights, 'd') !== false) $this->editable = strpos($rights, 'i');; } } // user-specific alarms settings win $prefs = $this->cal->rc->config->get('kolab_calendars', array()); if (isset($prefs[$this->id]['showalarms'])) $this->alarms = $prefs[$this->id]['showalarms']; else if (isset($prefs[$old_id]['showalarms'])) $this->alarms = $prefs[$old_id]['showalarms']; } $this->default = $this->storage->default; $this->subtype = $this->storage->subtype; } /** * Getter for the IMAP folder name * * @return string Name of the IMAP folder */ public function get_realname() { return $this->name; } /** * */ public function get_title() { return null; } /** * Return color to display this calendar */ - public function get_color() + public function get_color($default = null) { // color is defined in folder METADATA if ($color = $this->storage->get_color()) { return $color; } // calendar color is stored in user prefs (temporary solution) $prefs = $this->cal->rc->config->get('kolab_calendars', array()); if (!empty($prefs[$this->id]) && !empty($prefs[$this->id]['color'])) return $prefs[$this->id]['color']; - return 'cc0000'; + return $default ?: 'cc0000'; } /** * Compose an URL for CalDAV access to this calendar (if configured) */ public function get_caldav_url() { if ($template = $this->cal->rc->config->get('calendar_caldav_url', null)) { return strtr($template, array( '%h' => $_SERVER['HTTP_HOST'], '%u' => urlencode($this->cal->rc->get_user_name()), '%i' => urlencode($this->storage->get_uid()), '%n' => urlencode($this->name), )); } return false; } /** * Update properties of this calendar folder * * @see calendar_driver::edit_calendar() */ public function update(&$prop) { $prop['oldname'] = $this->get_realname(); $newfolder = kolab_storage::folder_update($prop); if ($newfolder === false) { $this->cal->last_error = $this->cal->gettext(kolab_storage::$last_error); return false; } // create ID return kolab_storage::folder_id($newfolder); } /** * Getter for a single event object */ public function get_event($id) { // remove our occurrence identifier if it's there $master_id = preg_replace('/-\d{8}(T\d{6})?$/', '', $id); // directly access storage object if (!$this->events[$id] && $master_id == $id && ($record = $this->storage->get_object($id))) { $this->events[$id] = $this->_to_driver_event($record, true); } // maybe a recurring instance is requested if (!$this->events[$id] && $master_id != $id) { $instance_id = substr($id, strlen($master_id) + 1); if ($record = $this->storage->get_object($master_id)) { $master = $this->_to_driver_event($record); } if ($master) { // check for match in top-level exceptions (aka loose single occurrences) if ($master['_formatobj'] && ($instance = $master['_formatobj']->get_instance($instance_id))) { $this->events[$id] = $this->_to_driver_event($instance, false, true, $master); } // check for match on the first instance already else if ($master['_instance'] && $master['_instance'] == $instance_id) { $this->events[$id] = $master; } else if (is_array($master['recurrence'])) { $this->get_recurring_events($record, $master['start'], null, $id); } } } return $this->events[$id]; } /** * Get attachment body * @see calendar_driver::get_attachment_body() */ public function get_attachment_body($id, $event) { if (!$this->ready) return false; $data = $this->storage->get_attachment($event['id'], $id); if ($data == null) { // try again with master UID $uid = preg_replace('/-\d+(T\d{6})?$/', '', $event['id']); if ($uid != $event['id']) { $data = $this->storage->get_attachment($uid, $id); } } return $data; } /** * @param integer Event's new start (unix timestamp) * @param integer Event's new end (unix timestamp) * @param string Search query (optional) * @param boolean Include virtual events (optional) * @param array Additional parameters to query storage * @param array Additional query to filter events * @return array A list of event records */ public function list_events($start, $end, $search = null, $virtual = 1, $query = array(), $filter_query = null) { // convert to DateTime for comparisons // #5190: make the range a little bit wider // to workaround possible timezone differences try { $start = new DateTime('@' . ($start - 12 * 3600)); } catch (Exception $e) { $start = new DateTime('@0'); } try { $end = new DateTime('@' . ($end + 12 * 3600)); } catch (Exception $e) { $end = new DateTime('today +10 years'); } // get email addresses of the current user $user_emails = $this->cal->get_user_emails(); // query Kolab storage $query[] = array('dtstart', '<=', $end); $query[] = array('dtend', '>=', $start); if (is_array($filter_query)) { $query = array_merge($query, $filter_query); } if (!empty($search)) { $search = mb_strtolower($search); $words = rcube_utils::tokenize_string($search, 1); foreach (rcube_utils::normalize_string($search, true) as $word) { $query[] = array('words', 'LIKE', $word); } } else { $words = array(); } // set partstat filter to skip pending and declined invitations if (empty($filter_query) && $this->cal->rc->config->get('kolab_invitation_calendars') && $this->get_namespace() != 'other' ) { $partstat_exclude = array('NEEDS-ACTION','DECLINED'); } else { $partstat_exclude = array(); } $events = array(); foreach ($this->storage->select($query) as $record) { $event = $this->_to_driver_event($record, !$virtual, false); // remember seen categories if ($event['categories']) { $cat = is_array($event['categories']) ? $event['categories'][0] : $event['categories']; $this->categories[$cat]++; } // list events in requested time window if ($event['start'] <= $end && $event['end'] >= $start) { unset($event['_attendees']); $add = true; // skip the first instance of a recurring event if listed in exdate if ($virtual && !empty($event['recurrence']['EXDATE'])) { $event_date = $event['start']->format('Ymd'); $exdates = (array)$event['recurrence']['EXDATE']; foreach ($exdates as $exdate) { if ($exdate->format('Ymd') == $event_date) { $add = false; break; } } } // find and merge exception for the first instance if ($virtual && !empty($event['recurrence']) && is_array($event['recurrence']['EXCEPTIONS'])) { foreach ($event['recurrence']['EXCEPTIONS'] as $exception) { if ($event['_instance'] == $exception['_instance']) { // clone date objects from main event before adjusting them with exception data if (is_object($event['start'])) $event['start'] = clone $record['start']; if (is_object($event['end'])) $event['end'] = clone $record['end']; kolab_driver::merge_exception_data($event, $exception); } } } if ($add) $events[] = $event; } // resolve recurring events if ($record['recurrence'] && $virtual == 1) { $events = array_merge($events, $this->get_recurring_events($record, $start, $end)); } // add top-level exceptions (aka loose single occurrences) else if (is_array($record['exceptions'])) { foreach ($record['exceptions'] as $ex) { $component = $this->_to_driver_event($ex, false, false, $record); if ($component['start'] <= $end && $component['end'] >= $start) { $events[] = $component; } } } } // post-filter all events by fulltext search and partstat values $me = $this; $events = array_filter($events, function($event) use ($words, $partstat_exclude, $user_emails, $me) { // fulltext search if (count($words)) { $hits = 0; foreach ($words as $word) { $hits += $me->fulltext_match($event, $word, false); } if ($hits < count($words)) { return false; } } // partstat filter if (count($partstat_exclude) && is_array($event['attendees'])) { foreach ($event['attendees'] as $attendee) { if (in_array($attendee['email'], $user_emails) && in_array($attendee['status'], $partstat_exclude)) { return false; } } } return true; }); // Apply event-to-mail relations $config = kolab_storage_config::get_instance(); $config->apply_links($events); // avoid session race conditions that will loose temporary subscriptions $this->cal->rc->session->nowrite = true; return $events; } /** * * @param integer Date range start (unix timestamp) * @param integer Date range end (unix timestamp) * @param array Additional query to filter events * @return integer Count */ public function count_events($start, $end = null, $filter_query = null) { // convert to DateTime for comparisons try { $start = new DateTime('@'.$start); } catch (Exception $e) { $start = new DateTime('@0'); } if ($end) { try { $end = new DateTime('@'.$end); } catch (Exception $e) { $end = null; } } // query Kolab storage $query[] = array('dtend', '>=', $start); if ($end) $query[] = array('dtstart', '<=', $end); // add query to exclude pending/declined invitations if (empty($filter_query)) { foreach ($this->cal->get_user_emails() as $email) { $query[] = array('tags', '!=', 'x-partstat:' . $email . ':needs-action'); $query[] = array('tags', '!=', 'x-partstat:' . $email . ':declined'); } } else if (is_array($filter_query)) { $query = array_merge($query, $filter_query); } // we rely the Kolab storage query (no post-filtering) return $this->storage->count($query); } /** * Create a new event record * * @see calendar_driver::new_event() * * @return mixed The created record ID on success, False on error */ public function insert_event($event) { if (!is_array($event)) return false; // email links are stored separately $links = $event['links']; unset($event['links']); //generate new event from RC input $object = $this->_from_driver_event($event); $saved = $this->storage->save($object, 'event'); if (!$saved) { rcube::raise_error(array( 'code' => 600, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Error saving event object to Kolab server"), true, false); $saved = false; } else { // save links in configuration.relation object if ($this->save_links($event['uid'], $links)) { $object['links'] = $links; } $this->events = array($event['uid'] => $this->_to_driver_event($object, true)); } return $saved; } /** * Update a specific event record * * @see calendar_driver::new_event() * @return boolean True on success, False on error */ public function update_event($event, $exception_id = null) { $updated = false; $old = $this->storage->get_object($event['uid'] ?: $event['id']); if (!$old || PEAR::isError($old)) return false; // email links are stored separately $links = $event['links']; unset($event['links']); $object = $this->_from_driver_event($event, $old); $saved = $this->storage->save($object, 'event', $old['uid']); if (!$saved) { rcube::raise_error(array( 'code' => 600, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Error saving event object to Kolab server"), true, false); } else { // save links in configuration.relation object if ($this->save_links($event['uid'], $links)) { $object['links'] = $links; } $updated = true; $this->events = array($event['uid'] => $this->_to_driver_event($object, true)); // refresh local cache with recurring instances if ($exception_id) { $this->get_recurring_events($object, $event['start'], $event['end'], $exception_id); } } return $updated; } /** * Delete an event record * * @see calendar_driver::remove_event() * @return boolean True on success, False on error */ public function delete_event($event, $force = true) { $deleted = $this->storage->delete($event['uid'] ?: $event['id'], $force); if (!$deleted) { rcube::raise_error(array( 'code' => 600, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => sprintf("Error deleting event object '%s' from Kolab server", $event['id'])), true, false); } return $deleted; } /** * Restore deleted event record * * @see calendar_driver::undelete_event() * @return boolean True on success, False on error */ public function restore_event($event) { if ($this->storage->undelete($event['id'])) { return true; } else { rcube::raise_error(array( 'code' => 600, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Error undeleting the event object $event[id] from the Kolab server"), true, false); } return false; } /** * Find messages linked with an event */ protected function get_links($uid) { $storage = kolab_storage_config::get_instance(); return $storage->get_object_links($uid); } /** * */ protected function save_links($uid, $links) { $storage = kolab_storage_config::get_instance(); return $storage->save_object_links($uid, (array) $links); } /** * Create instances of a recurring event * * @param array Hash array with event properties * @param object DateTime Start date of the recurrence window * @param object DateTime End date of the recurrence window * @param string ID of a specific recurring event instance * @return array List of recurring event instances */ public function get_recurring_events($event, $start, $end = null, $event_id = null) { $object = $event['_formatobj']; if (!$object) { $rec = $this->storage->get_object($event['id']); $object = $rec['_formatobj']; } if (!is_object($object)) return array(); // determine a reasonable end date if none given if (!$end) { $end = clone $event['start']; $end->add(new DateInterval('P100Y')); } // copy the recurrence rule from the master event (to be used in the UI) $recurrence_rule = $event['recurrence']; unset($recurrence_rule['EXCEPTIONS'], $recurrence_rule['EXDATE']); // read recurrence exceptions first $events = array(); $exdata = array(); $futuredata = array(); $recurrence_id_format = libcalendaring::recurrence_id_format($event); if (is_array($event['recurrence']['EXCEPTIONS'])) { foreach ($event['recurrence']['EXCEPTIONS'] as $exception) { if (!$exception['_instance']) $exception['_instance'] = libcalendaring::recurrence_instance_identifier($exception, $event['allday']); $rec_event = $this->_to_driver_event($exception, false, false, $event); $rec_event['id'] = $event['uid'] . '-' . $exception['_instance']; $rec_event['isexception'] = 1; // found the specifically requested instance: register exception (single occurrence wins) if ($rec_event['id'] == $event_id && (!$this->events[$event_id] || $this->events[$event_id]['thisandfuture'])) { $rec_event['recurrence'] = $recurrence_rule; $rec_event['recurrence_id'] = $event['uid']; $this->events[$rec_event['id']] = $rec_event; } // remember this exception's date $exdate = substr($exception['_instance'], 0, 8); if (!$exdata[$exdate] || $exdata[$exdate]['thisandfuture']) { $exdata[$exdate] = $rec_event; } if ($rec_event['thisandfuture']) { $futuredata[$exdate] = $rec_event; } } } // found the specifically requested instance, exiting... if ($event_id && !empty($this->events[$event_id])) { return array($this->events[$event_id]); } // use libkolab to compute recurring events if (class_exists('kolabcalendaring')) { $recurrence = new kolab_date_recurrence($object); } else { // fallback to local recurrence implementation require_once($this->cal->home . '/lib/calendar_recurrence.php'); $recurrence = new calendar_recurrence($this->cal, $event); } $i = 0; while ($next_event = $recurrence->next_instance()) { $datestr = $next_event['start']->format('Ymd'); $instance_id = $next_event['start']->format($recurrence_id_format); // use this event data for future recurring instances if ($futuredata[$datestr]) $overlay_data = $futuredata[$datestr]; $rec_id = $event['uid'] . '-' . $instance_id; $exception = $exdata[$datestr] ?: $overlay_data; $event_start = $next_event['start']; $event_end = $next_event['end']; // copy some event from exception to get proper start/end dates if ($exception) { $event_copy = $next_event; kolab_driver::merge_exception_dates($event_copy, $exception); $event_start = $event_copy['start']; $event_end = $event_copy['end']; } // add to output if in range if (($event_start <= $end && $event_end >= $start) || ($event_id && $rec_id == $event_id)) { $rec_event = $this->_to_driver_event($next_event, false, false, $event); $rec_event['_instance'] = $instance_id; $rec_event['_count'] = $i + 1; if ($exception) // copy data from exception kolab_driver::merge_exception_data($rec_event, $exception); $rec_event['id'] = $rec_id; $rec_event['recurrence_id'] = $event['uid']; $rec_event['recurrence'] = $recurrence_rule; unset($rec_event['_attendees']); $events[] = $rec_event; if ($rec_id == $event_id) { $this->events[$rec_id] = $rec_event; break; } } else if ($next_event['start'] > $end) // stop loop if out of range break; // avoid endless recursion loops if (++$i > 100000) break; } return $events; } /** * Convert from Kolab_Format to internal representation */ private function _to_driver_event($record, $noinst = false, $links = true, $master_event = null) { $record['calendar'] = $this->id; if ($links && !array_key_exists('links', $record)) { $record['links'] = $this->get_links($record['uid']); } if ($this->get_namespace() == 'other') { $record['className'] = 'fc-event-ns-other'; $record = kolab_driver::add_partstat_class($record, array('NEEDS-ACTION','DECLINED'), $this->get_owner()); } // add instance identifier to first occurrence (master event) $recurrence_id_format = libcalendaring::recurrence_id_format($master_event ? $master_event : $record); if (!$noinst && $record['recurrence'] && !$record['recurrence_id'] && !$record['_instance']) { $record['_instance'] = $record['start']->format($recurrence_id_format); } else if (is_a($record['recurrence_date'], 'DateTime')) { $record['_instance'] = $record['recurrence_date']->format($recurrence_id_format); } // clean up exception data if ($record['recurrence'] && is_array($record['recurrence']['EXCEPTIONS'])) { array_walk($record['recurrence']['EXCEPTIONS'], function(&$exception) { unset($exception['_mailbox'], $exception['_msguid'], $exception['_formatobj'], $exception['_attachments']); }); } return $record; } /** * Convert the given event record into a data structure that can be passed to Kolab_Storage backend for saving * (opposite of self::_to_driver_event()) */ private function _from_driver_event($event, $old = array()) { // set current user as ORGANIZER if ($identity = $this->cal->rc->user->list_emails(true)) { $event['attendees'] = (array) $event['attendees']; $found = false; // there can be only resources on attendees list (T1484) // let's check the existence of an organizer foreach ($event['attendees'] as $attendee) { if ($attendee['role'] == 'ORGANIZER') { $found = true; break; } } if (!$found) { $event['attendees'][] = array('role' => 'ORGANIZER', 'name' => $identity['name'], 'email' => $identity['email']); } $event['_owner'] = $identity['email']; } // remove EXDATE values if RDATE is given if (!empty($event['recurrence']['RDATE'])) { $event['recurrence']['EXDATE'] = array(); } // remove recurrence information (e.g. EXDATES and EXCEPTIONS) entirely if ($event['recurrence'] && empty($event['recurrence']['FREQ']) && empty($event['recurrence']['RDATE'])) { $event['recurrence'] = array(); } // keep 'comment' from initial itip invitation if (!empty($old['comment'])) { $event['comment'] = $old['comment']; } // clean up exception data if (is_array($event['exceptions'])) { array_walk($event['exceptions'], function(&$exception) { unset($exception['_mailbox'], $exception['_msguid'], $exception['_formatobj'], $exception['_attachments'], $event['attachments'], $event['deleted_attachments'], $event['recurrence_id']); }); } // remove some internal properties which should not be saved unset($event['_savemode'], $event['_fromcalendar'], $event['_identity'], $event['_folder_id'], $event['recurrence_id'], $event['attachments'], $event['deleted_attachments'], $event['className']); // copy meta data (starting with _) from old object foreach ((array)$old as $key => $val) { if (!isset($event[$key]) && $key[0] == '_') $event[$key] = $val; } return $event; } /** * Match the given word in the event contents */ public function fulltext_match($event, $word, $recursive = true) { $hits = 0; foreach ($this->search_fields as $col) { $sval = is_array($event[$col]) ? self::_complex2string($event[$col]) : $event[$col]; if (empty($sval)) continue; // do a simple substring matching (to be improved) $val = mb_strtolower($sval); if (strpos($val, $word) !== false) { $hits++; break; } } return $hits; } /** * Convert a complex event attribute to a string value */ private static function _complex2string($prop) { static $ignorekeys = array('role','status','rsvp'); $out = ''; if (is_array($prop)) { foreach ($prop as $key => $val) { if (is_numeric($key)) { $out .= self::_complex2string($val); } else if (!in_array($key, $ignorekeys)) { $out .= $val . ' '; } } } else if (is_string($prop) || is_numeric($prop)) { $out .= $prop . ' '; } return rtrim($out); } } diff --git a/plugins/calendar/drivers/kolab/kolab_user_calendar.php b/plugins/calendar/drivers/kolab/kolab_user_calendar.php index 777f0515..fd9280dd 100644 --- a/plugins/calendar/drivers/kolab/kolab_user_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_user_calendar.php @@ -1,416 +1,421 @@ * * Copyright (C) 2014-2016, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ class kolab_user_calendar extends kolab_calendar { public $id = 'unknown'; public $ready = false; public $editable = false; public $attachments = false; public $subscriptions = false; protected $userdata = array(); protected $timeindex = array(); /** * Default constructor */ public function __construct($user_or_folder, $calendar) { $this->cal = $calendar; // full user record is provided if (is_array($user_or_folder)) { $this->userdata = $user_or_folder; $this->storage = new kolab_storage_folder_user($this->userdata['kolabtargetfolder'], '', $this->userdata); } else if ($user_or_folder instanceof kolab_storage_folder_user) { $this->storage = $user_or_folder; $this->userdata = $this->storage->ldaprec; } else { // get user record from LDAP $this->storage = new kolab_storage_folder_user($user_or_folder); $this->userdata = $this->storage->ldaprec; } $this->ready = !empty($this->userdata['kolabtargetfolder']); $this->storage->type = 'event'; if ($this->ready) { // ID is derrived from the user's kolabtargetfolder attribute $this->id = kolab_storage::folder_id($this->userdata['kolabtargetfolder'], true); $this->imap_folder = $this->userdata['kolabtargetfolder']; $this->name = $this->storage->name; $this->parent = ''; // user calendars are top level // user-specific alarms settings win $prefs = $this->cal->rc->config->get('kolab_calendars', array()); if (isset($prefs[$this->id]['showalarms'])) $this->alarms = $prefs[$this->id]['showalarms']; } } /** * Getter for a nice and human readable name for this calendar * * @return string Name of this calendar */ public function get_name() { return $this->userdata['displayname'] ?: ($this->userdata['name'] ?: $this->userdata['mail']); } /** * Getter for the IMAP folder owner * + * @param bool Return a fully qualified owner name (unused) + * * @return string Name of the folder owner */ - public function get_owner() + public function get_owner($fully_qualified = false) { return $this->userdata['mail']; } /** * */ public function get_title() { return trim($this->userdata['displayname'] . '; ' . $this->userdata['mail'], '; '); } /** * Getter for the name of the namespace to which the IMAP folder belongs * * @return string Name of the namespace (personal, other, shared) */ public function get_namespace() { return 'other user'; } /** * Getter for the top-end calendar folder name (not the entire path) * * @return string Name of this calendar */ public function get_foldername() { return $this->get_name(); } /** * Return color to display this calendar */ public function get_color() { // calendar color is stored in local user prefs $prefs = $this->cal->rc->config->get('kolab_calendars', array()); if (!empty($prefs[$this->id]) && !empty($prefs[$this->id]['color'])) return $prefs[$this->id]['color']; return 'cc0000'; } /** * Compose an URL for CalDAV access to this calendar (if configured) */ public function get_caldav_url() { return false; } /** * Check subscription status of this folder * * @return boolean True if subscribed, false if not */ public function is_subscribed() { return $this->storage->is_subscribed(); } /** * Update properties of this calendar folder * * @see calendar_driver::edit_calendar() */ public function update(&$prop) { // don't change anything. // let kolab_driver save props in local prefs return $prop['id']; } /** * Getter for a single event object */ public function get_event($id) { // TODO: implement this return $this->events[$id]; } /** * Get attachment body * @see calendar_driver::get_attachment_body() */ public function get_attachment_body($id, $event) { if (!$event['calendar'] && ($ev = $this->get_event($event['id']))) { $event['calendar'] = $ev['calendar']; } if ($event['calendar'] && ($cal = $this->cal->get_calendar($event['calendar']))) { return $cal->get_attachment_body($id, $event); } return false; } /** * @param integer Event's new start (unix timestamp) * @param integer Event's new end (unix timestamp) * @param string Search query (optional) * @param boolean Include virtual events (optional) * @param array Additional parameters to query storage + * @param array Additional query to filter events + * * @return array A list of event records */ - public function list_events($start, $end, $search = null, $virtual = 1, $query = array()) + public function list_events($start, $end, $search = null, $virtual = 1, $query = array(), $filter_query = null) { // convert to DateTime for comparisons try { $start_dt = new DateTime('@'.$start); } catch (Exception $e) { $start_dt = new DateTime('@0'); } try { $end_dt = new DateTime('@'.$end); } catch (Exception $e) { $end_dt = new DateTime('today +10 years'); } $limit_changed = null; if (!empty($query)) { foreach ($query as $q) { if ($q[0] == 'changed' && $q[1] == '>=') { try { $limit_changed = new DateTime('@'.$q[2]); } catch (Exception $e) { /* ignore */ } } } } // aggregate all calendar folders the user shares (but are not activated) foreach (kolab_storage::list_user_folders($this->userdata, 'event', 2) as $foldername) { $cal = new kolab_calendar($foldername, $this->cal); foreach ($cal->list_events($start, $end, $search, 1) as $event) { $uid = $event['id'] ?: $event['uid']; $this->events[$uid] = $event; $this->timeindex[$this->time_key($event)] = $uid; } } // get events from the user's free/busy feed (for quickview only) $fbview = $this->cal->rc->config->get('calendar_include_freebusy_data', 1); if ($fbview && ($fbview == 1 || !empty($_REQUEST['_quickview'])) && empty($search)) { $this->fetch_freebusy($limit_changed); } $events = array(); foreach ($this->events as $event) { // list events in requested time window if ($event['start'] <= $end_dt && $event['end'] >= $start_dt && (!$limit_changed || !$event['changed'] || $event['changed'] >= $limit_changed)) { $events[] = $event; } } // avoid session race conditions that will loose temporary subscriptions $this->cal->rc->session->nowrite = true; return $events; } /** * * @param integer Date range start (unix timestamp) * @param integer Date range end (unix timestamp) + * @param array Additional query to filter events * @return integer Count */ - public function count_events($start, $end = null) + public function count_events($start, $end = null, $filter_query = null) { // not implemented return 0; } /** * Helper method to fetch free/busy data for the user and turn it into calendar data */ private function fetch_freebusy($limit_changed = null) { // ask kolab server first try { $request_config = array( 'store_body' => true, 'follow_redirects' => true, ); $request = libkolab::http_request(kolab_storage::get_freebusy_url($this->userdata['mail']), 'GET', $request_config); $response = $request->send(); // authentication required if ($response->getStatus() == 401) { $request->setAuth($this->cal->rc->user->get_username(), $this->cal->rc->decrypt($_SESSION['password'])); $response = $request->send(); } if ($response->getStatus() == 200) $fbdata = $response->getBody(); unset($request, $response); } catch (Exception $e) { rcube::raise_error(array( 'code' => 900, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Error fetching free/busy information: " . $e->getMessage()), true, false); return false; } $statusmap = array( 'FREE' => 'free', 'BUSY' => 'busy', 'BUSY-TENTATIVE' => 'tentative', 'X-OUT-OF-OFFICE' => 'outofoffice', 'OOF' => 'outofoffice', ); $titlemap = array( 'FREE' => $this->cal->gettext('availfree'), 'BUSY' => $this->cal->gettext('availbusy'), 'BUSY-TENTATIVE' => $this->cal->gettext('availtentative'), 'X-OUT-OF-OFFICE' => $this->cal->gettext('availoutofoffice'), ); // rcube::console('_fetch_freebusy', kolab_storage::get_freebusy_url($this->userdata['mail']), $fbdata); // parse free-busy information $count = 0; if ($fbdata) { $ical = $this->cal->get_ical(); $ical->import($fbdata); if ($fb = $ical->freebusy) { // consider 'changed >= X' queries if ($limit_changed && $fb['created'] && $fb['created'] < $limit_changed) { return 0; } foreach ($fb['periods'] as $tuple) { list($from, $to, $type) = $tuple; $event = array( 'uid' => md5($this->id . $from->format('U') . '/' . $to->format('U')), 'calendar' => $this->id, 'changed' => $fb['created'] ?: new DateTime(), 'title' => $this->get_name() . ' ' . ($titlemap[$type] ?: $type), 'start' => $from, 'end' => $to, 'free_busy' => $statusmap[$type] ?: 'busy', 'className' => 'fc-type-freebusy', 'organizer' => array( 'email' => $this->userdata['mail'], 'name' => $this->userdata['displayname'], ), ); // avoid duplicate entries $key = $this->time_key($event); if (!$this->timeindex[$key]) { $this->events[$event['uid']] = $event; $this->timeindex[$key] = $event['uid']; $count++; } } } } return $count; } /** * Helper to build a key for the absolute time slot the given event convers */ private function time_key($event) { return sprintf('%s/%s', $event['start']->format('U'), is_object($event['end']) ? $event['end']->format('U') : '0'); } /** * Create a new event record * * @see calendar_driver::new_event() * * @return mixed The created record ID on success, False on error */ public function insert_event($event) { return false; } /** * Update a specific event record * * @see calendar_driver::new_event() * @return boolean True on success, False on error */ public function update_event($event, $exception_id = null) { return false; } /** * Delete an event record * * @see calendar_driver::remove_event() * @return boolean True on success, False on error */ public function delete_event($event, $force = true) { return false; } /** * Restore deleted event record * * @see calendar_driver::undelete_event() * @return boolean True on success, False on error */ public function restore_event($event) { return false; } }