diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -1746,11 +1746,11 @@ // convert link URIs references into structs if (array_key_exists('links', $event)) { - foreach ((array)$event['links'] as $i => $link) { - if (strpos($link, 'imap://') === 0 && ($msgref = $this->driver->get_message_reference($link))) { - $event['links'][$i] = $msgref; + foreach ((array) $event['links'] as $i => $link) { + if (strpos($link, 'imap://') === 0 && ($msgref = $this->driver->get_message_reference($link))) { + $event['links'][$i] = $msgref; + } } - } } // check for organizer in attendees list diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php --- a/plugins/calendar/drivers/kolab/kolab_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_calendar.php @@ -188,29 +188,35 @@ */ 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] && ($record = $this->storage->get_object($id))) - $this->events[$id] = $this->_to_driver_event($record, true); + if (!$this->events[$id] && $master_id == $id && ($record = $this->storage->get_object($id))) { + $this->events[$id] = $this->_to_driver_event($record, true); + } - // event not found, maybe a recurring instance is requested - if (!$this->events[$id]) { - $master_id = preg_replace('/-\d+(T\d{6})?$/', '', $id); + // maybe a recurring instance is requested + if (!$this->events[$id] && $master_id != $id) { $instance_id = substr($id, strlen($master_id) + 1); - if ($master_id != $id && ($record = $this->storage->get_object($master_id))) { - $master = $this->_to_driver_event($record); + if ($record = $this->storage->get_object($master_id)) { + $master = $this->_to_driver_event($record, true); + $this->events[$master_id] = $master; } - // check for match in top-level exceptions (aka loose single occurrences) - if ($master && $master['_formatobj'] && ($instance = $master['_formatobj']->get_instance($instance_id))) { - $this->events[$id] = $this->_to_driver_event($instance); - } - // check for match on the first instance already - else if ($master['_instance'] && $master['_instance'] == $instance_id) { - $this->events[$id] = $master; - } - else if ($master && is_array($master['recurrence'])) { - $this->get_recurring_events($record, $master['start'], null, $id); + 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); + } + // 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); + } } } @@ -298,13 +304,13 @@ $events = array(); foreach ($this->storage->select($query) as $record) { - $event = $this->_to_driver_event($record, !$virtual); + $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) { @@ -347,7 +353,7 @@ // 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); + $component = $this->_to_driver_event($ex, false, false); if ($component['start'] <= $end && $component['end'] >= $start) { $events[] = $component; } @@ -381,6 +387,10 @@ 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; @@ -451,8 +461,8 @@ //generate new event from RC input $object = $this->_from_driver_event($event); - $saved = $this->storage->save($object, 'event'); - + $saved = $this->storage->save($object, 'event'); + if (!$saved) { rcube::raise_error(array( 'code' => 600, 'type' => 'php', @@ -463,11 +473,13 @@ } else { // save links in configuration.relation object - $this->save_links($event['uid'], $links); + if ($this->save_links($event['uid'], $links)) { + $object['links'] = $links; + } $this->events = array($event['uid'] => $this->_to_driver_event($object, true)); } - + return $saved; } @@ -490,7 +502,7 @@ unset($event['links']); $object = $this->_from_driver_event($event, $old); - $saved = $this->storage->save($object, 'event', $old['uid']); + $saved = $this->storage->save($object, 'event', $old['uid']); if (!$saved) { rcube::raise_error(array( @@ -501,7 +513,9 @@ } else { // save links in configuration.relation object - $this->save_links($event['uid'], $links); + if ($this->save_links($event['uid'], $links)) { + $object['links'] = $links; + } $updated = true; $this->events = array($event['uid'] => $this->_to_driver_event($object, true)); @@ -572,14 +586,8 @@ */ protected function save_links($uid, $links) { - // make sure we have a valid array - if (empty($links)) { - $links = array(); - } - $storage = kolab_storage_config::get_instance(); - $remove = array_diff($storage->get_object_links($uid), $links); - return $storage->save_object_links($uid, $links, $remove); + return $storage->save_object_links($uid, (array) $links); } /** @@ -628,7 +636,7 @@ if (!$exception['_instance']) $exception['_instance'] = libcalendaring::recurrence_instance_identifier($exception); - $rec_event = $this->_to_driver_event($exception); + $rec_event = $this->_to_driver_event($exception, false, false); $rec_event['id'] = $event['uid'] . '-' . $exception['_instance']; $rec_event['isexception'] = 1; @@ -677,7 +685,7 @@ // add to output if in range $rec_id = $event['uid'] . '-' . $instance_id; if (($next_event['start'] <= $end && $next_event['end'] >= $start) || ($event_id && $rec_id == $event_id)) { - $rec_event = $this->_to_driver_event($next_event); + $rec_event = $this->_to_driver_event($next_event, false, false); $rec_event['_instance'] = $instance_id; $rec_event['_count'] = $i + 1; @@ -709,10 +717,13 @@ /** * Convert from Kolab_Format to internal representation */ - private function _to_driver_event($record, $noinst = false) + private function _to_driver_event($record, $noinst = false, $links = true) { $record['calendar'] = $this->id; - $record['links'] = $this->get_links($record['uid']); + + 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'; diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -57,12 +57,11 @@ require_once(dirname(__FILE__) . '/kolab_invitation_calendar.php'); $this->cal = $cal; - $this->rc = $cal->rc; - $this->_read_calendars(); - + $this->rc = $cal->rc; + $this->cal->register_action('push-freebusy', array($this, 'push_freebusy')); $this->cal->register_action('calendar-acl', array($this, 'calendar_acl')); - + $this->freebusy_trigger = $this->rc->config->get('calendar_freebusy_trigger', false); if (kolab_storage::$version == '2.0') { @@ -89,11 +88,11 @@ // get all folders that have "event" type, sorted by namespace/name $folders = kolab_storage::sort_folders(kolab_storage::get_folders('event') + kolab_storage::get_user_folders('event', true)); - $this->calendars = array(); + $this->calendars = array(); foreach ($folders as $folder) { if ($folder instanceof kolab_storage_folder_user) { - $calendar = new kolab_user_calendar($folder->name, $this->cal); + $calendar = new kolab_user_calendar($folder, $this->cal); $calendar->subscriptions = count($folder->children) > 0; } else { @@ -120,10 +119,12 @@ */ public function list_calendars($filter = 0, &$tree = null) { + $this->_read_calendars(); + // attempt to create a default calendar for this user if (!$this->has_writeable) { if ($this->create_calendar(array('name' => 'Calendar', 'color' => 'cc0000'))) { - unset($this->calendars); + unset($this->calendars); $this->_read_calendars(); } } @@ -162,8 +163,8 @@ // special handling for user or virtual folders if ($cal instanceof kolab_storage_folder_user) { $calendars[$cal->id] = array( - 'id' => $cal->id, - 'name' => kolab_storage::object_name($fullname), + 'id' => $cal->id, + 'name' => $fullname, 'listname' => $listname, 'editname' => $cal->get_foldername(), 'color' => $cal->get_color(), @@ -287,6 +288,8 @@ */ protected function filter_calendars($filter) { + $this->_read_calendars(); + $calendars = array(); $plugin = $this->rc->plugins->exec_hook('calendar_list_filter', array( @@ -340,14 +343,19 @@ */ public function get_calendar($id) { + $this->_read_calendars(); + // create calendar object if necesary - if (!$this->calendars[$id] && in_array($id, array(self::INVITATIONS_CALENDAR_PENDING, self::INVITATIONS_CALENDAR_DECLINED))) { - $this->calendars[$id] = new kolab_invitation_calendar($id, $this->cal); - } - else if (!$this->calendars[$id] && $id !== self::BIRTHDAY_CALENDAR_ID) { - $calendar = kolab_calendar::factory($id, $this->cal); - if ($calendar->ready) - $this->calendars[$calendar->id] = $calendar; + if (!$this->calendars[$id]) { + if (in_array($id, array(self::INVITATIONS_CALENDAR_PENDING, self::INVITATIONS_CALENDAR_DECLINED))) { + $this->calendars[$id] = new kolab_invitation_calendar($id, $this->cal); + } + else if ($id !== self::BIRTHDAY_CALENDAR_ID) { + $calendar = kolab_calendar::factory($id, $this->cal); + if ($calendar->ready) { + $this->calendars[$calendar->id] = $calendar; + } + } } return $this->calendars[$id]; @@ -592,8 +600,12 @@ $event = self::from_rcube_event($event); - $cid = $event['calendar'] ? $event['calendar'] : reset(array_keys($this->calendars)); - if ($storage = $this->get_calendar($cid)) { + if (!$event['calendar']) { + $this->_read_calendars(); + $event['calendar'] = reset(array_keys($this->calendars)); + } + + if ($storage = $this->get_calendar($event['calendar'])) { // if this is a recurrence instance, append as exception to an already existing object for this UID if (!empty($event['recurrence_date']) && ($master = $storage->get_event($event['uid']))) { self::add_exception($master, $event); @@ -1012,7 +1024,7 @@ // copy attachment metadata to new event $event = self::from_rcube_event($event, $master); - + // remove recurrence exceptions on re-scheduling if ($reschedule) { unset($event['recurrence']['EXCEPTIONS'], $event['exceptions'], $master['recurrence']['EXDATE']); @@ -1436,8 +1448,10 @@ { if ($calendars && is_string($calendars)) $calendars = explode(',', $calendars); - else if (!$calendars) + else if (!$calendars) { + $this->_read_calendars(); $calendars = array_keys($this->calendars); + } $query = array(); if ($modifiedsince) @@ -1482,8 +1496,10 @@ if ($calendars && is_string($calendars)) $calendars = explode(',', $calendars); - else if (!$calendars) + else if (!$calendars) { + $this->_read_calendars(); $calendars = array_keys($this->calendars); + } foreach ($calendars as $cid) { if ($storage = $this->get_calendar($cid)) { @@ -1521,6 +1537,9 @@ $candidates = array(); $query = array(array('tags', '=', 'x-has-alarms')); + + $this->_read_calendars(); + foreach ($this->calendars as $cid => $calendar) { // skip calendars with alarms disabled if (!$calendar->alarms || ($calendars && !in_array($cid, $calendars))) @@ -2317,6 +2336,8 @@ return parent::calendar_form($action, $calendar, $formfields); } + $this->_read_calendars(); + if ($calendar['id'] && ($cal = $this->calendars[$calendar['id']])) { $folder = $cal->get_realname(); // UTF7 $color = $cal->get_color(); diff --git a/plugins/calendar/drivers/kolab/kolab_invitation_calendar.php b/plugins/calendar/drivers/kolab/kolab_invitation_calendar.php --- a/plugins/calendar/drivers/kolab/kolab_invitation_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_invitation_calendar.php @@ -34,6 +34,7 @@ public $categories = array(); public $name = 'Invitations'; + /** * Default constructor */ @@ -62,7 +63,6 @@ $this->alarms = $prefs[$this->id]['showalarms']; } - /** * Getter for a nice and human readable name for this calendar * @@ -73,7 +73,6 @@ return $this->name; } - /** * Getter for the IMAP folder owner * @@ -84,7 +83,6 @@ return $this->cal->rc->get_user_name(); } - /** * */ @@ -93,7 +91,6 @@ return $this->get_name(); } - /** * Getter for the name of the namespace to which the IMAP folder belongs * @@ -104,7 +101,6 @@ return 'x-special'; } - /** * Getter for the top-end calendar folder name (not the entire path) * @@ -171,7 +167,6 @@ return $prop['id']; } - /** * Getter for a single event object */ @@ -202,7 +197,7 @@ else { $cal = null; foreach (kolab_storage::list_folders('', '*', 'event', null) as $foldername) { - $cal = new kolab_calendar($foldername, $this->cal); + $cal = $this->_get_calendar($foldername); if ($cal->ready && $cal->storage && $cal->get_event($event['id'])) { break; } @@ -216,7 +211,6 @@ return false; } - /** * @param integer Event's new start (unix timestamp) * @param integer Event's new end (unix timestamp) @@ -239,7 +233,7 @@ // aggregate events from all calendar folders $events = array(); foreach (kolab_storage::list_folders('', '*', 'event', null) as $foldername) { - $cal = new kolab_calendar($foldername, $this->cal); + $cal = $this->_get_calendar($foldername); if ($cal->get_namespace() == 'other') continue; @@ -293,7 +287,7 @@ // aggregate counts from all calendar folders $count = 0; foreach (kolab_storage::list_folders('', '*', 'event', null) as $foldername) { - $cal = new kolab_calendar($foldername, $this->cal); + $cal = $this->_get_calendar($foldername); if ($cal->get_namespace() == 'other') continue; @@ -304,6 +298,15 @@ } /** + * Get calendar object instance (that maybe already initialized) + */ + private function _get_calendar($folder_name) + { + $id = kolab_storage::folder_id($folder_name, true); + return $this->cal->driver->get_calendar($id); + } + + /** * Helper method to modify some event properties */ private function _mod_event($event) @@ -318,7 +321,6 @@ return $event; } - /** * Create a new event record * @@ -337,7 +339,6 @@ * @see calendar_driver::new_event() * @return boolean True on success, False on error */ - public function update_event($event, $exception_id = null) { // forward call to the actual storage folder @@ -372,6 +373,4 @@ { return false; } - - } diff --git a/plugins/calendar/drivers/kolab/kolab_user_calendar.php b/plugins/calendar/drivers/kolab/kolab_user_calendar.php --- a/plugins/calendar/drivers/kolab/kolab_user_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_user_calendar.php @@ -5,7 +5,7 @@ * * @author Thomas Bruederli * - * Copyright (C) 2014-2015, Kolab Systems AG + * 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 @@ -45,8 +45,12 @@ $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->storage = new kolab_storage_folder_user($user_or_folder); $this->userdata = $this->storage->ldaprec; } @@ -57,7 +61,7 @@ // 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->get_name(); + $this->name = $this->storage->name; $this->parent = ''; // user calendars are top level // user-specific alarms settings win @@ -67,7 +71,6 @@ } } - /** * Getter for a nice and human readable name for this calendar * @@ -78,7 +81,6 @@ return $this->userdata['displayname'] ?: ($this->userdata['name'] ?: $this->userdata['mail']); } - /** * Getter for the IMAP folder owner * @@ -89,7 +91,6 @@ return $this->userdata['mail']; } - /** * */ @@ -98,7 +99,6 @@ return trim($this->userdata['displayname'] . '; ' . $this->userdata['mail'], '; '); } - /** * Getter for the name of the namespace to which the IMAP folder belongs * @@ -109,7 +109,6 @@ return 'other user'; } - /** * Getter for the top-end calendar folder name (not the entire path) * @@ -164,7 +163,6 @@ return $prop['id']; } - /** * Getter for a single event object */ @@ -370,7 +368,6 @@ return sprintf('%s/%s', $event['start']->format('U'), is_object($event['end']->format('U')) ?: '0'); } - /** * Create a new event record * @@ -389,7 +386,6 @@ * @see calendar_driver::new_event() * @return boolean True on success, False on error */ - public function update_event($event, $exception_id = null) { return false; @@ -417,7 +413,6 @@ return false; } - /** * Convert from Kolab_Format to internal representation */ @@ -428,5 +423,4 @@ return kolab_driver::to_rcube_event($record); } - } diff --git a/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php --- a/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php +++ b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php @@ -166,7 +166,6 @@ $this->action = rcube::get_instance()->action; } - /** * Getter for the address book name to be displayed * @@ -174,8 +173,7 @@ */ public function get_name() { - $folder = kolab_storage::object_name($this->imap_folder, $this->namespace); - return $folder; + return $this->storagefolder->get_name(); } /** @@ -186,7 +184,6 @@ return $this->storagefolder->get_foldername(); } - /** * Getter for the IMAP folder name * @@ -197,7 +194,6 @@ return $this->imap_folder; } - /** * Getter for the name of the namespace to which the IMAP folder belongs * diff --git a/plugins/kolab_notes/kolab_notes.php b/plugins/kolab_notes/kolab_notes.php --- a/plugins/kolab_notes/kolab_notes.php +++ b/plugins/kolab_notes/kolab_notes.php @@ -239,7 +239,7 @@ else if ($folder->virtual) { $lists[$list_id] = array( 'id' => $list_id, - 'name' => kolab_storage::object_name($fullname), + 'name' => $fullname, 'listname' => $listname, 'virtual' => true, 'editable' => false, @@ -441,6 +441,7 @@ { $config = kolab_storage_config::get_instance(); $tags = $config->apply_tags($records); + $config->apply_links($records); foreach ($records as $i => $rec) { unset($records[$i]['description']); @@ -557,6 +558,8 @@ if ($result) { // get note tags $result['tags'] = $this->get_tags($result['uid']); + // get note links + $result['links'] = $this->get_links($result['uid']); } return $result; @@ -565,7 +568,7 @@ /** * Helper method to encode the given note record for use in the client */ - private function _client_encode(&$note, $resolve = false) + private function _client_encode(&$note) { foreach ($note as $key => $prop) { if ($key[0] == '_' || $key == 'x-custom') { @@ -585,10 +588,14 @@ $note['html'] = $this->_wash_html($note['description']); } - // resolve message links - $note['links'] = array_map(function($link) { - return kolab_storage_config::get_message_reference($link, 'note') ?: array('uri' => $link); - }, $this->get_links($note['uid'])); + // convert link URIs references into structs + if (array_key_exists('links', $note)) { + foreach ((array)$note['links'] as $i => $link) { + if (strpos($link, 'imap://') === 0 && ($msgref = kolab_storage_config::get_message_reference($link, 'note'))) { + $note['links'][$i] = $msgref; + } + } + } return $note; } @@ -1045,7 +1052,7 @@ $newfolder = kolab_storage::folder_update($list); if ($newfolder === false) { - $save_error = $this->gettext(kolab_storage::$last_error); + $save_error = $this->gettext(kolab_storage::$last_error); } else { $success = true; @@ -1056,7 +1063,7 @@ // compose the new display name $delim = $this->rc->get_storage()->get_hierarchy_delimiter(); $path_imap = explode($delim, $newfolder); - $list['name'] = kolab_storage::object_name($newfolder); + $list['name'] = kolab_storage::object_name($newfolder); $list['editname'] = rcube_charset::convert(array_pop($path_imap), 'UTF7-IMAP'); $list['listname'] = str_repeat('   ', count($path_imap)) . '» ' . $list['editname']; } @@ -1247,12 +1254,8 @@ private function save_links($uid, $links) { - if (empty($links)) { - $links = array(); - } $config = kolab_storage_config::get_instance(); - $remove = array_diff($config->get_object_links($uid), $links); - return $config->save_object_links($uid, $links, $remove); + return $config->save_object_links($uid, (array) $links); } /** diff --git a/plugins/kolab_tags/kolab_tags.php b/plugins/kolab_tags/kolab_tags.php --- a/plugins/kolab_tags/kolab_tags.php +++ b/plugins/kolab_tags/kolab_tags.php @@ -120,6 +120,10 @@ return $args; } + if ($this->rc->action == 'print') { + return; + } + $this->mail_headers_done = true; if ($engine = $this->engine()) { diff --git a/plugins/kolab_tags/lib/kolab_tags_engine.php b/plugins/kolab_tags/lib/kolab_tags_engine.php --- a/plugins/kolab_tags/lib/kolab_tags_engine.php +++ b/plugins/kolab_tags/lib/kolab_tags_engine.php @@ -278,6 +278,12 @@ public function taglist($attrib) { $taglist = $this->backend->list_tags(); + + // Performance: Save the list for later + if ($this->rc->action == 'show' || $this->rc->action == 'preview') { + $this->taglist = $taglist; + } + $taglist = array_map(array($this, 'parse_tag'), $taglist); $this->rc->output->set_env('tags', $taglist); @@ -329,7 +335,7 @@ */ public function message_headers_handler($args) { - $taglist = $this->backend->list_tags(); + $taglist = $this->taglist ?: $this->backend->list_tags(); $uid = $args['uid']; $folder = $args['folder']; $tags = array(); diff --git a/plugins/libkolab/lib/kolab_storage.php b/plugins/libkolab/lib/kolab_storage.php --- a/plugins/libkolab/lib/kolab_storage.php +++ b/plugins/libkolab/lib/kolab_storage.php @@ -25,7 +25,7 @@ class kolab_storage { - const CTYPE_KEY = '/shared/vendor/kolab/folder-type'; + const CTYPE_KEY = '/shared/vendor/kolab/folder-type'; const CTYPE_KEY_PRIVATE = '/private/vendor/kolab/folder-type'; const COLOR_KEY_SHARED = '/shared/vendor/kolab/color'; const COLOR_KEY_PRIVATE = '/private/vendor/kolab/color'; @@ -233,20 +233,23 @@ * * @param array Pseudo-SQL query as list of filter parameter triplets * @param string Object type (contact,event,task,journal,file,note,configuration) + * @param int Expected number of records or limit (for performance reasons) + * * @return array List of Kolab data objects (each represented as hash array) * @see kolab_storage_format::select() */ - public static function select($query, $type) + public static function select($query, $type, $limit = null) { self::setup(); $folder = null; $result = array(); foreach ((array)self::list_folders('', '*', $type, null, $folderdata) as $foldername) { - if (!$folder) - $folder = new kolab_storage_folder($foldername, $type, $folderdata[$foldername]); - else - $folder->set_folder($foldername, $type, $folderdata[$foldername]); + $folder = new kolab_storage_folder($foldername, $type, $folderdata[$foldername]); + + if ($limit) { + $folder->set_order_and_limit(null, $limit); + } foreach ($folder->select($query, '*') as $object) { $result[] = $object; @@ -551,6 +554,7 @@ /** * Getter for human-readable name of Kolab object (folder) + * with kolab_custom_display_names support. * See http://wiki.kolab.org/UI-Concepts/Folder-Listing for reference * * @param string $folder IMAP folder name (UTF7-IMAP) @@ -560,13 +564,47 @@ */ public static function object_name($folder, &$folder_ns=null) { - self::setup(); - // find custom display name in folder METADATA if ($name = self::custom_displayname($folder)) { return $name; } + return self::object_prettyname($folder, $folder_ns); + } + + /** + * Get custom display name (saved in metadata) for the given folder + */ + public static function custom_displayname($folder) + { + // find custom display name in folder METADATA + if (self::$config->get('kolab_custom_display_names', true) && self::setup()) { + // For performance reasons ask for all folders, it will be cached as one cache entry + $metadata = self::$imap->get_metadata("*", array(self::NAME_KEY_PRIVATE, self::NAME_KEY_SHARED)); + + if ($data = $metadata[$folder]) { + if (($name = $data[self::NAME_KEY_PRIVATE]) || ($name = $data[self::NAME_KEY_SHARED])) { + return $name; + } + } + } + + return false; + } + + /** + * Getter for human-readable name of Kolab object (folder) + * See http://wiki.kolab.org/UI-Concepts/Folder-Listing for reference + * + * @param string $folder IMAP folder name (UTF7-IMAP) + * @param string $folder_ns Will be set to namespace name of the folder + * + * @return string Name of the folder-object + */ + public static function object_prettyname($folder, &$folder_ns=null) + { + self::setup(); + $found = false; $namespace = self::$imap->get_namespace(); @@ -582,6 +620,7 @@ } } } + if (!$found && !empty($namespace['other'])) { foreach ($namespace['other'] as $ns) { if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) { @@ -605,6 +644,7 @@ } } } + if (!$found && !empty($namespace['personal'])) { foreach ($namespace['personal'] as $ns) { if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) { @@ -635,22 +675,6 @@ } /** - * Get custom display name (saved in metadata) for the given folder - */ - public static function custom_displayname($folder) - { - // find custom display name in folder METADATA - if (self::$config->get('kolab_custom_display_names', true)) { - $metadata = self::$imap->get_metadata($folder, array(self::NAME_KEY_PRIVATE, self::NAME_KEY_SHARED)); - if (($name = $metadata[$folder][self::NAME_KEY_PRIVATE]) || ($name = $metadata[$folder][self::NAME_KEY_SHARED])) { - return $name; - } - } - - return false; - } - - /** * Helper method to generate a truncated folder name to display. * Note: $origname is a string returned by self::object_name() */ @@ -745,7 +769,7 @@ } } - $names[$name] = self::object_name($name); + $names[$name] = $c_folder->get_name(); } // Build SELECT field of parent folder @@ -958,16 +982,16 @@ $nsnames = array('personal' => array(), 'shared' => array(), 'other' => array()); foreach ($folders as $folder) { - $folders[$folder->name] = $folder; + $_folders[$folder->name] = $folder; $ns = $folder->get_namespace(); - $nsnames[$ns][$folder->name] = strtolower(html_entity_decode(self::object_name($folder->name, $ns), ENT_COMPAT, RCUBE_CHARSET)) . $pad; // decode » + $nsnames[$ns][$folder->name] = strtolower(html_entity_decode($folder->get_name(), ENT_COMPAT, RCUBE_CHARSET)) . $pad; // decode » } // $folders is a result of get_folders() we can assume folders were already sorted foreach (array_keys($nsnames) as $ns) { asort($nsnames[$ns], SORT_LOCALE_STRING); foreach (array_keys($nsnames[$ns]) as $utf7name) { - $out[] = $folders[$utf7name]; + $out[] = $_folders[$utf7name]; } } @@ -1008,6 +1032,7 @@ while (count($path) >= $depth && ($parent = join($delim, $path))) { array_pop($path); $parent_parent = join($delim, $path); + if (!$refs[$parent]) { if ($folder->type && self::folder_type($parent) == $folder->type) { $refs[$parent] = new kolab_storage_folder($parent, $folder->type, $folder->type); @@ -1017,7 +1042,7 @@ $refs[$parent] = new kolab_storage_folder_user($parent, $parent_parent); } else { - $name = kolab_storage::object_name($parent, $folder->get_namespace()); + $name = kolab_storage::object_name($parent); $refs[$parent] = new kolab_storage_folder_virtual($parent, $name, $folder->get_namespace(), $parent_parent); } $parents[] = $refs[$parent]; @@ -1599,5 +1624,31 @@ $db = rcmail::get_instance()->get_dbh(); $prefix = 'imap://' . urlencode($args['username']) . '@' . $args['host'] . '/%'; $db->query("DELETE FROM " . $db->table_name('kolab_folders', true) . " WHERE `resource` LIKE ?", $prefix); + + } + + /** + * Get folder METADATA for all supported keys + * Do this in one go for better caching performance + */ + public static function folder_metadata($folder) + { + if (self::setup()) { + $keys = array( + // For better performance we skip displayname here, see (self::custom_displayname()) + // self::NAME_KEY_PRIVATE, + // self::NAME_KEY_SHARED, + self::CTYPE_KEY, + self::CTYPE_KEY_PRIVATE, + self::COLOR_KEY_PRIVATE, + self::COLOR_KEY_SHARED, + self::UID_KEY_SHARED, + self::UID_KEY_CYRUS, + ); + + $metadata = self::$imap->get_metadata($folder, $keys); + + return $metadata[$folder]; + } } } diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php --- a/plugins/libkolab/lib/kolab_storage_cache.php +++ b/plugins/libkolab/lib/kolab_storage_cache.php @@ -25,6 +25,7 @@ class kolab_storage_cache { const DB_DATE_FORMAT = 'Y-m-d H:i:s'; + const MAX_RECORDS = 500; public $sync_complete = false; @@ -428,18 +429,23 @@ /** * Move an existing cache entry to a new resource * - * @param string Entry's IMAP message UID - * @param string Entry's Object UID - * @param object kolab_storage_folder Target storage folder instance + * @param string Entry's IMAP message UID + * @param string Entry's Object UID + * @param kolab_storage_folder Target storage folder instance + * @param string Target entry's IMAP message UID */ - public function move($msguid, $uid, $target) + public function move($msguid, $uid, $target, $new_msguid = null) { - if ($this->ready) { + if ($this->ready && $target) { // clear cached uid mapping and force new lookup unset($target->cache->uid2msg[$uid]); // resolve new message UID in target folder - if ($new_msguid = $target->cache->uid2msguid($uid)) { + if (!$new_msguid) { + $new_msguid = $target->cache->uid2msguid($uid); + } + + if ($new_msguid) { $this->_read_folder_data(); $this->db->query( @@ -528,7 +534,7 @@ $this->_read_folder_data(); // fetch full object data on one query if a small result set is expected - $fetchall = !$uids && ($this->limit ? $this->limit[0] : ($count = $this->count($query))) < 500; + $fetchall = !$uids && ($this->limit ? $this->limit[0] : ($count = $this->count($query))) < self::MAX_RECORDS; // skip SELECT if we know it will return nothing if ($count === 0) { @@ -665,7 +671,12 @@ public function set_order_by($sortcols) { if (!empty($sortcols)) { - $this->order_by = '`' . join('`, `', (array)$sortcols) . '`'; + $sortcols = array_map(function($v) { + list($column, $order) = explode(' ', $v, 2); + return "`$column`" . ($order ? " $order" : ''); + }, (array) $sortcols); + + $this->order_by = join(', ', $sortcols); } else { $this->order_by = null; diff --git a/plugins/libkolab/lib/kolab_storage_config.php b/plugins/libkolab/lib/kolab_storage_config.php --- a/plugins/libkolab/lib/kolab_storage_config.php +++ b/plugins/libkolab/lib/kolab_storage_config.php @@ -25,8 +25,8 @@ class kolab_storage_config { - const FOLDER_TYPE = 'configuration'; - + const FOLDER_TYPE = 'configuration'; + const MAX_RELATIONS = 499; // should be less than kolab_storage_cache::MAX_RECORDS /** * Singleton instace of kolab_storage_config @@ -58,8 +58,12 @@ /** * Private constructor (finds default configuration folder as a config source) */ - private function __construct() + private function _init() { + if ($this->enabled !== null) { + return $this->enabled; + } + // get all configuration folders $this->folders = kolab_storage::get_folders(self::FOLDER_TYPE, false); @@ -86,9 +90,7 @@ } // check if configuration folder exist - if ($this->default && $this->default->name) { - $this->enabled = true; - } + return $this->enabled = $this->default && $this->default->name; } /** @@ -98,7 +100,7 @@ */ public function is_enabled() { - return $this->enabled; + return $this->_init(); } /** @@ -114,6 +116,10 @@ { $list = array(); + if (!$this->is_enabled()) { + return $list; + } + foreach ($this->folders as $folder) { // we only want to read from default folder if ($default && !$folder->default) { @@ -144,6 +150,10 @@ */ public function get_object($uid, $default = false) { + if (!$this->is_enabled()) { + return; + } + foreach ($this->folders as $folder) { // we only want to read from default folder if ($default && !$folder->default) { @@ -166,7 +176,7 @@ */ public function save(&$object, $type) { - if (!$this->enabled) { + if (!$this->is_enabled()) { return false; } @@ -201,25 +211,27 @@ /** * Remove configuration object * - * @param string $uid Object UID + * @param string|array $object Object array or its UID * * @return bool True on success, False on failure */ - public function delete($uid) + public function delete($object) { - if (!$this->enabled) { + if (!$this->is_enabled()) { return false; } // fetch the object to find folder - $object = $this->get_object($uid); + if (!is_array($object)) { + $object = $this->get_object($object); + } if (!$object) { return false; } $folder = $this->find_folder($object); - $status = $folder->delete($uid); + $status = $folder->delete($object); // on success, update cached tags list if ($status && is_array($this->tags)) { @@ -239,6 +251,10 @@ */ public function find_folder($object = array()) { + if (!$this->is_enabled()) { + return; + } + // find folder object if ($object['_mailbox']) { foreach ($this->folders as $folder) { @@ -551,12 +567,17 @@ /** * Assign tags to kolab objects * - * @param array $records List of kolab objects + * @param array $records List of kolab objects + * @param bool $no_return Don't return anything * * @return array List of tags */ - public function apply_tags(&$records) + public function apply_tags(&$records, $no_return = false) { + if (empty($records) && $no_return) { + return; + } + // first convert categories into tags foreach ($records as $i => $rec) { if (!empty($rec['categories'])) { @@ -587,12 +608,84 @@ $tags[] = $tag['name']; } - $tags = array_unique($tags); + $tags = $no_return ? null : array_unique($tags); return $tags; } /** + * Assign links (relations) to kolab objects + * + * @param array $records List of kolab objects + */ + public function apply_links(&$records) + { + $links = array(); + $uids = array(); + $ids = array(); + $limit = 25; + + // get list of object UIDs and UIRs map + foreach ($records as $i => $rec) { + $uids[] = $rec['uid']; + // there can be many objects with the same uid (recurring events) + $ids[self::build_member_url($rec['uid'])][] = $i; + $records[$i]['links'] = array(); + } + + if (!empty($uids)) { + $uids = array_unique($uids); + } + + // The whole story here is to not do SELECT for every object. + // We'll build one SELECT for many (limit above) objects at once + + while (!empty($uids)) { + $chunk = array_splice($uids, 0, $limit); + $chunk = array_map(function($v) { return array('member', '=', $v); }, $chunk); + + $filter = array( + array('type', '=', 'relation'), + array('category', '=', 'generic'), + array($chunk, 'OR'), + ); + + $relations = $this->get_objects($filter, true, self::MAX_RELATIONS); + + foreach ($relations as $relation) { + $links[$relation['uid']] = $relation; + } + } + + if (empty($links)) { + return; + } + + // assign links of related messages + foreach ($links as $relation) { + // make relation members up-to-date + kolab_storage_config::resolve_members($relation); + + $members = array(); + foreach ((array) $relation['members'] as $member) { + if (strpos($member, 'imap://') === 0) { + $members[$member] = $member; + } + } + $members = array_values($members); + + // assign links to objects + foreach ((array) $relation['members'] as $member) { + if (($id = $ids[$member]) !== null) { + foreach ($id as $i) { + $records[$i]['links'] = array_unique(array_merge($records[$i]['links'], $members)); + } + } + } + } + } + + /** * Update object tags * * @param string $uid Kolab object UID @@ -650,12 +743,10 @@ * Get tags (all or referring to specified object) * * @param string $member Optional object UID or mail message-id - * @param int $limit Max. number of records (per-folder) - * Used when searching by member * * @return array List of Relation objects */ - public function get_tags($member = '*', $limit = 0) + public function get_tags($member = '*') { if (!isset($this->tags)) { $default = true; @@ -667,10 +758,10 @@ // use faster method if ($member && $member != '*') { $filter[] = array('member', '=', $member); - $tags = $this->get_objects($filter, $default, $limit); + $tags = $this->get_objects($filter, $default, self::MAX_RELATIONS); } else { - $this->tags = $tags = $this->get_objects($filter, $default); + $this->tags = $tags = $this->get_objects($filter, $default, self::MAX_RELATIONS); } } else { @@ -711,7 +802,8 @@ * Find objects linked with the given groupware object through a relation * * @param string Object UUID - * @param array List of related URIs + * + * @return array List of related URIs */ public function get_object_links($uid) { @@ -735,30 +827,33 @@ } /** + * Save relations of an object. + * Note, that we already support only one-to-one relations. + * So, all relations to the object that are not provided in $links + * argument will be removed. + * + * @param string $uid Object UUID + * @param array $links List of related-object URIs * + * @return bool True on success, False on failure */ - public function save_object_links($uid, $links, $remove = array()) + public function save_object_links($uid, $links) { $object_uri = self::build_member_url($uid); - $relations = $this->get_relations_for_member($uid); - $done = false; + $relations = $this->get_relations_for_member($uid); + $done = false; foreach ($relations as $relation) { // make relation members up-to-date kolab_storage_config::resolve_members($relation); // remove and add links - $members = array_diff($relation['members'], (array)$remove); + $members = array($object_uri); $members = array_unique(array_merge($members, $links)); - // make sure the object_uri is still a member - if (!in_array($object_uri, $members)) { - $members[$object_uri]; - } - // remove relation if no other members remain if (count($members) <= 1) { - $done = $this->delete($relation['uid']); + $done = $this->delete($relation); } // update relation object if members changed else if (count(array_diff($members, $relation['members'])) || count(array_diff($relation['members'], $members))) { @@ -768,7 +863,7 @@ } // no changes, we're happy else { - $done = true; + $done = true; $links = array(); } } @@ -780,10 +875,10 @@ 'category' => 'generic', ); - $ret = $this->save($relation, 'relation'); + $done = $this->save($relation, 'relation'); } - return $ret; + return $done; } /** @@ -798,7 +893,7 @@ array('member', '=', $uid), ); - return $this->get_objects($filter, $default, 100); + return $this->get_objects($filter, $default, self::MAX_RELATIONS); } /** @@ -862,7 +957,7 @@ // get kolab objects of specified type if (!empty($uids)) { $query = array(array('uid', '=', array_unique($uids))); - $result = kolab_storage::select($query, $type); + $result = kolab_storage::select($query, $type, count($uids)); } return $result; @@ -893,7 +988,7 @@ /** * Resolve the email message reference from the given URI */ - public function get_message_reference($uri, $rel = null) + public static function get_message_reference($uri, $rel = null) { if ($linkref = self::parse_member_url($uri)) { $linkref['subject'] = $linkref['params']['subject']; diff --git a/plugins/libkolab/lib/kolab_storage_folder.php b/plugins/libkolab/lib/kolab_storage_folder.php --- a/plugins/libkolab/lib/kolab_storage_folder.php +++ b/plugins/libkolab/lib/kolab_storage_folder.php @@ -64,15 +64,16 @@ */ public function set_folder($name, $type = null, $type_annotation = null) { + $this->name = $name; + if (empty($type_annotation)) { - $type_annotation = kolab_storage::folder_type($name); + $type_annotation = $this->get_type(); } $oldtype = $this->type; list($this->type, $suffix) = explode('.', $type_annotation); $this->default = $suffix == 'default'; $this->subtype = $this->default ? '' : $suffix; - $this->name = $name; $this->id = kolab_storage::folder_id($name); $this->valid = !empty($this->type) && $this->type != 'mail' && (!$type || $this->type == $type); @@ -155,7 +156,7 @@ { // UID is defined in folder METADATA $metakeys = array(kolab_storage::UID_KEY_SHARED, kolab_storage::UID_KEY_CYRUS); - $metadata = $this->get_metadata($metakeys); + $metadata = $this->get_metadata(); if ($metadata !== null) { foreach ($metakeys as $key) { @@ -347,6 +348,12 @@ if ($length !== null) { $this->cache->set_limit($length, $offset); } + + $this->order_and_limit = array( + 'cols' => $sortcols, + 'limit' => $length, + 'offset' => $offset, + ); } /** @@ -375,7 +382,6 @@ return $query; } - /** * Getter for a single Kolab object, identified by its UID * @@ -387,17 +393,35 @@ */ public function get_object($uid, $type = null) { - if (!$this->valid) { + if (!$this->valid || !$uid) { return false; } + $query = array(array('uid', '=', $uid)); + + if ($type) { + $query[] = array('type', '=', $type); + } + // synchronize caches $this->cache->synchronize(); - $msguid = $this->cache->uid2msguid($uid); + // we don't use cache->get() here because we don't have msguid + // yet, using select() is faster - if ($msguid && ($object = $this->cache->get($msguid, $type))) { - return $object; + // set order to make sure we get most recent object version + // set limit to skip count query + $this->cache->set_order_by('msguid DESC'); + $this->cache->set_limit(1); + + $list = $this->cache->select($this->_prepare_query($query)); + + // set the order/limit back to defined value + $this->cache->set_order_by($this->order_and_limit['order']); + $this->cache->set_limit($this->order_and_limit['limit'], $this->order_and_limit['offset']); + + if (!empty($list) && !empty($list[0])) { + return $list[0]; } return false; @@ -906,7 +930,8 @@ $this->cache->bypass(false); if ($result) { - $this->cache->move($msguid, $uid, $target_folder); + $new_uid = ($copyuid = $this->imap->conn->data['COPYUID']) ? $copyuid[1] : null; + $this->cache->move($msguid, $uid, $target_folder, $new_uid); return true; } else { @@ -1168,6 +1193,4 @@ return true; } - } - diff --git a/plugins/libkolab/lib/kolab_storage_folder_api.php b/plugins/libkolab/lib/kolab_storage_folder_api.php --- a/plugins/libkolab/lib/kolab_storage_folder_api.php +++ b/plugins/libkolab/lib/kolab_storage_folder_api.php @@ -57,7 +57,7 @@ * @var array */ public $children = array(); - + /** * Name of the parent folder * @var string @@ -69,6 +69,7 @@ protected $info; protected $idata; protected $namespace; + protected $metadata; /** @@ -135,10 +136,10 @@ { if (!isset($this->namespace)) $this->namespace = $this->imap->folder_namespace($this->name); + return $this->namespace; } - /** * Get the display name value of this folder * @@ -146,10 +147,9 @@ */ public function get_name() { - return kolab_storage::object_name($this->name, $this->get_namespace()); + return kolab_storage::object_name($this->name); } - /** * Getter for the top-end folder name (not the entire path) * @@ -221,7 +221,7 @@ public function get_color($default = null) { // color is defined in folder METADATA - $metadata = $this->get_metadata(array(kolab_storage::COLOR_KEY_PRIVATE, kolab_storage::COLOR_KEY_SHARED)); + $metadata = $this->get_metadata(); if (($color = $metadata[kolab_storage::COLOR_KEY_PRIVATE]) || ($color = $metadata[kolab_storage::COLOR_KEY_SHARED])) { return $color; } @@ -229,19 +229,20 @@ return $default; } - /** * Returns IMAP metadata/annotations (GETMETADATA/GETANNOTATION) + * supported by kolab_storage * - * @param array List of metadata keys to read * @return array Metadata entry-value hash array on success, NULL on error */ - public function get_metadata($keys) + public function get_metadata() { - $metadata = rcube::get_instance()->get_storage()->get_metadata($this->name, (array)$keys); - return $metadata[$this->name]; - } + if ($this->metadata === null) { + $this->metadata = kolab_storage::folder_metadata($this->name); + } + return $this->metadata; + } /** * Sets IMAP metadata/annotations (SETMETADATA/SETANNOTATION) @@ -251,10 +252,10 @@ */ public function set_metadata($entries) { + $this->metadata = null; return $this->imap->set_metadata($this->name, $entries); } - /** * */ @@ -277,6 +278,21 @@ return $this->idata; } + /** + * Returns (full) type of IMAP folder + * + * @return string Folder type + */ + public function get_type() + { + $metadata = $this->get_metadata(); + + if (!empty($metadata)) { + return kolab_storage::folder_select_metadata($metadata); + } + + return $this->type; + } /** * Get IMAP ACL information for this folder @@ -358,4 +374,3 @@ return $this->name; } } - diff --git a/plugins/libkolab/lib/kolab_storage_folder_user.php b/plugins/libkolab/lib/kolab_storage_folder_user.php --- a/plugins/libkolab/lib/kolab_storage_folder_user.php +++ b/plugins/libkolab/lib/kolab_storage_folder_user.php @@ -33,7 +33,7 @@ */ public function __construct($name, $parent = '', $ldaprec = null) { - parent::__construct($name, $name, 'other', $parent); + parent::__construct($name, kolab_storage::object_prettyname($name), 'other', $parent); if (!empty($ldaprec)) { self::$ldapcache[$name] = $this->ldaprec = $ldaprec; @@ -72,7 +72,7 @@ */ public function get_title() { - return trim($this->ldaprec['displayname'] . '; ' . $this->ldaprec['mail'], '; '); + return trim($this->ldaprec['displayname'] . '; ' . $this->ldaprec['mail'], '; '); } /** @@ -131,5 +131,4 @@ return $success; } - -} \ No newline at end of file +} diff --git a/plugins/libkolab/lib/kolab_storage_folder_virtual.php b/plugins/libkolab/lib/kolab_storage_folder_virtual.php --- a/plugins/libkolab/lib/kolab_storage_folder_virtual.php +++ b/plugins/libkolab/lib/kolab_storage_folder_virtual.php @@ -56,4 +56,4 @@ { return $default; } -} \ No newline at end of file +} diff --git a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php --- a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php +++ b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php @@ -46,7 +46,7 @@ */ public function __construct($plugin) { - $this->rc = $plugin->rc; + $this->rc = $plugin->rc; $this->plugin = $plugin; if (kolab_storage::$version == '2.0') { @@ -59,8 +59,6 @@ // get configuration for the Bonnie API $this->bonnie_api = libkolab::get_bonnie_api(); - $this->_read_lists(); - $this->plugin->register_action('folder-acl', array($this, 'folder_acl')); } @@ -70,8 +68,9 @@ private function _read_lists($force = false) { // already read sources - if (isset($this->lists) && !$force) + if (isset($this->lists) && !$force) { return $this->lists; + } // get all folders that have type "task" $folders = kolab_storage::sort_folders(kolab_storage::get_folders('task')); @@ -102,6 +101,8 @@ $this->folders[$tasklist['id']] = $folder; $this->folders[$folder->name] = $folder; } + + return $this->lists; } /** @@ -165,6 +166,8 @@ */ public function get_lists(&$tree = null) { + $this->_read_lists(); + // attempt to create a default list for this user if (empty($this->lists) && !isset($this->search_more_results)) { $prop = array('name' => 'Tasks', 'color' => '0000CC', 'default' => true); @@ -211,7 +214,7 @@ if ($folder instanceof kolab_storage_folder_user) { $lists[$list_id] = array( 'id' => $list_id, - 'name' => $folder->get_name(), + 'name' => $fullname, 'listname' => $listname, 'title' => $folder->get_title(), 'virtual' => true, @@ -225,7 +228,7 @@ else if ($folder->virtual) { $lists[$list_id] = array( 'id' => $list_id, - 'name' => kolab_storage::object_name($fullname), + 'name' => $fullname, 'listname' => $listname, 'virtual' => true, 'editable' => false, @@ -256,6 +259,8 @@ */ protected function get_folder($id) { + $this->_read_lists(); + // create list and folder instance if necesary if (!$this->lists[$id]) { $folder = kolab_storage::get_folder(kolab_storage::id_decode($id)); @@ -385,8 +390,10 @@ ($prop['active'] ? kolab_storage::folder_activate($subfolder) : kolab_storage::folder_deactivate($subfolder)); } } + return $ret; } + return false; } @@ -400,10 +407,11 @@ public function delete_list($prop) { if ($prop['id'] && ($folder = $this->get_folder($prop['id']))) { - if (kolab_storage::folder_delete($folder->name)) - return true; - else - $this->last_error = kolab_storage::$last_error; + if (kolab_storage::folder_delete($folder->name)) { + return true; + } + + $this->last_error = kolab_storage::$last_error; } return false; @@ -486,21 +494,26 @@ */ public function count_tasks($lists = null) { - if (empty($lists)) - $lists = array_keys($this->lists); - else if (is_string($lists)) + if (empty($lists)) { + $lists = $this->_read_lists(); + $lists = array_keys($lists); + } + else if (is_string($lists)) { $lists = explode(',', $lists); + } - $today_date = new DateTime('now', $this->plugin->timezone); - $today = $today_date->format('Y-m-d'); + $today_date = new DateTime('now', $this->plugin->timezone); + $today = $today_date->format('Y-m-d'); $tomorrow_date = new DateTime('now + 1 day', $this->plugin->timezone); - $tomorrow = $tomorrow_date->format('Y-m-d'); + $tomorrow = $tomorrow_date->format('Y-m-d'); $counts = array('all' => 0, 'flagged' => 0, 'today' => 0, 'tomorrow' => 0, 'overdue' => 0, 'nodate' => 0, 'mytasks' => 0); + foreach ($lists as $list_id) { if (!$folder = $this->get_folder($list_id)) { continue; } + foreach ($folder->select(array(array('tags','!~','x-complete'))) as $record) { $rec = $this->_to_rcube_task($record, $list_id, false); @@ -542,11 +555,15 @@ */ public function list_tasks($filter, $lists = null) { - if (empty($lists)) - $lists = array_keys($this->lists); - else if (is_string($lists)) + if (empty($lists)) { + $lists = $this->_read_lists(); + $lists = array_keys($lists); + } + else if (is_string($lists)) { $lists = explode(',', $lists); + } + $config = kolab_storage_config::get_instance(); $results = array(); // query Kolab storage @@ -568,23 +585,25 @@ $query[] = array('changed', '>=', $filter['since']); } - // load all tags into memory first - kolab_storage_config::get_instance()->get_tags(); - foreach ($lists as $list_id) { if (!$folder = $this->get_folder($list_id)) { continue; } - foreach ($folder->select($query) as $record) { - $this->load_tags($record); - $task = $this->_to_rcube_task($record, $list_id); + foreach ($folder->select($query) as $record) { // TODO: post-filter tasks returned from storage - - $results[] = $task; + $record['list_id'] = $list_id; + $results[] = $record; } } + $config->apply_tags($results, true); + $config->apply_links($results); + + foreach (array_keys($results) as $idx) { + $results[$idx] = $this->_to_rcube_task($results[$idx], $results[$idx]['list_id']); + } + // avoid session race conditions that will loose temporary subscriptions $this->plugin->rc->session->nowrite = true; @@ -599,7 +618,9 @@ */ public function get_task($prop) { + $this->_read_lists(); $this->_parse_id($prop); + $id = $prop['uid']; $list_id = $prop['list']; $folders = $list_id ? array($list_id => $this->get_folder($list_id)) : $this->folders; @@ -745,7 +766,7 @@ $list_id = $prop['list']; list($uid, $mailbox, $msguid) = $this->_resolve_task_identity($prop); - $folder = $this->get_folder($list_id); + $folder = $this->get_folder($list_id); $success = false; if ($folder && ($raw_msg = $this->bonnie_api->rawdata('task', $uid, $rev, $mailbox))) { @@ -908,7 +929,6 @@ return array($uid, $mailbox, $msguid); } - /** * Get a list of pending alarms to be displayed to the user * @@ -938,7 +958,13 @@ $time = $slot + $interval; $candidates = array(); - $query = array(array('tags', '=', 'x-has-alarms'), array('tags', '!=', 'x-complete')); + $query = array( + array('tags', '=', 'x-has-alarms'), + array('tags', '!=', 'x-complete') + ); + + $this->_read_lists(); + foreach ($this->lists as $lid => $list) { // skip lists with alarms disabled if (!$list['showalarms'] || ($lists && !in_array($lid, $lists))) @@ -1091,14 +1117,8 @@ */ private function save_links($uid, $links) { - // make sure we have a valid array - if (empty($links)) { - $links = array(); - } - $config = kolab_storage_config::get_instance(); - $remove = array_diff($config->get_object_links($uid), $links); - return $config->save_object_links($uid, $links, $remove); + return $config->save_object_links($uid, (array) $links); } /** @@ -1171,10 +1191,11 @@ 'sequence' => $record['sequence'], 'tags' => $record['tags'], 'list' => $list_id, + 'links' => $record['links'], ); // we can sometimes skip this expensive operation - if ($all) { + if ($all && !array_key_exists('links', $task)) { $task['links'] = $this->get_links($task['uid']); } @@ -1238,7 +1259,7 @@ */ private function _from_rcube_task($task, $old = array()) { - $object = $task; + $object = $task; $id_prefix = $task['list'] . ':'; if (!empty($task['date'])) { @@ -1372,7 +1393,7 @@ // email links and tags are stored separately $links = $task['links']; - $tags = $task['tags']; + $tags = $task['tags']; unset($task['tags'], $task['links']); // moved from another folder @@ -1603,6 +1624,8 @@ */ public function tasklist_edit_form($action, $list, $fieldprop) { + $this->_read_lists(); + if ($list['id'] && ($list = $this->lists[$list['id']])) { $folder_name = $this->get_folder($list['id'])->name; // UTF7 } diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php --- a/plugins/tasklist/tasklist.php +++ b/plugins/tasklist/tasklist.php @@ -1256,8 +1256,8 @@ // convert link URIs references into structs if (array_key_exists('links', $rec)) { - foreach ((array)$rec['links'] as $i => $link) { - if (strpos($link, 'imap://') === 0 && ($msgref = $this->driver->get_message_reference($link))) { + foreach ((array) $rec['links'] as $i => $link) { + if (strpos($link, 'imap://') === 0 && ($msgref = $this->driver->get_message_reference($link, 'task'))) { $rec['links'][$i] = $msgref; } }