Branch 'roundcubemail-plugins-kolab-3.1' - 3 commits - plugins/libkolab

Aleksander Machniak machniak at kolabsys.com
Fri Oct 17 15:01:16 CEST 2014


 plugins/libkolab/lib/kolab_format.php                      |    2 
 plugins/libkolab/lib/kolab_format_configuration.php        |   59 
 plugins/libkolab/lib/kolab_storage_cache.php               |    7 
 plugins/libkolab/lib/kolab_storage_cache_configuration.php |   50 
 plugins/libkolab/lib/kolab_storage_config.php              |  840 +++++++++++++
 5 files changed, 952 insertions(+), 6 deletions(-)

New commits:
commit 353d36956b56914accb78bd0f00fabc1ab05d38f
Author: Aleksander Machniak <machniak at kolabsys.com>
Date:   Fri Oct 17 08:59:27 2014 -0400

    Add support for configuration./file_driver objects (#3775)
    
    Conflicts:
    
    	plugins/libkolab/lib/kolab_format_configuration.php
    
    Added kolab_storage_config class

diff --git a/plugins/libkolab/lib/kolab_format.php b/plugins/libkolab/lib/kolab_format.php
index ef6ba7a..fa85eec 100644
--- a/plugins/libkolab/lib/kolab_format.php
+++ b/plugins/libkolab/lib/kolab_format.php
@@ -174,7 +174,7 @@ abstract class kolab_format
         if (!self::supports($version))
             return PEAR::raiseError("No support for Kolab format version " . $version);
 
-        $type = preg_replace('/configuration\.[a-z.]+$/', 'configuration', $type);
+        $type = preg_replace('/configuration\.[a-z._]+$/', 'configuration', $type);
         $suffix = preg_replace('/[^a-z]+/', '', $type);
         $classname = 'kolab_format_' . $suffix;
         if (class_exists($classname))
diff --git a/plugins/libkolab/lib/kolab_format_configuration.php b/plugins/libkolab/lib/kolab_format_configuration.php
index 174ab37..21dc587 100644
--- a/plugins/libkolab/lib/kolab_format_configuration.php
+++ b/plugins/libkolab/lib/kolab_format_configuration.php
@@ -32,10 +32,12 @@ class kolab_format_configuration extends kolab_format
     protected $write_func = 'writeConfiguration';
 
     private $type_map = array(
-        'dictionary' => Configuration::TypeDictionary,
-        'category' => Configuration::TypeCategoryColor,
+        'category'    => Configuration::TypeCategoryColor,
+        'dictionary'  => Configuration::TypeDictionary,
+        'file_driver' => Configuration::TypeFileDriver,
     );
 
+    private $driver_settings_fields = array('host', 'port', 'username', 'password');
 
     /**
      * Set properties to the kolabformat object
@@ -60,6 +62,22 @@ class kolab_format_configuration extends kolab_format
             $categories = new vectorcategorycolor;
             $this->obj = new Configuration($categories);
             break;
+
+        case 'file_driver':
+            $driver = new FileDriver($object['driver'], $object['title']);
+
+            $driver->setEnabled((bool) $object['enabled']);
+
+            foreach ($this->driver_settings_fields as $field) {
+                $value = $object[$field];
+                if ($value !== null) {
+                    $driver->{'set' . ucfirst($field)}($value);
+                }
+            }
+
+            $this->obj = new Configuration($driver);
+            break;
+
         default:
             return false;
         }
@@ -111,6 +129,19 @@ class kolab_format_configuration extends kolab_format
         case 'category':
             // TODO: implement this
             break;
+
+        case 'file_driver':
+            $driver = $this->obj->file_driver();
+
+            $object['driver']  = $driver->driver();
+            $object['title']   = $driver->title();
+            $object['enabled'] = $driver->enabled();
+
+            foreach ($this->driver_settings_fields as $field) {
+                $object[$field] = $driver->{$field}();
+            }
+
+            break;
         }
 
         // adjust content-type string
diff --git a/plugins/libkolab/lib/kolab_storage_cache_configuration.php b/plugins/libkolab/lib/kolab_storage_cache_configuration.php
index 8382852..c3c7ac4 100644
--- a/plugins/libkolab/lib/kolab_storage_cache_configuration.php
+++ b/plugins/libkolab/lib/kolab_storage_cache_configuration.php
@@ -37,4 +37,52 @@ class kolab_storage_cache_configuration extends kolab_storage_cache
 
         return $sql_data;
     }
+
+    /**
+     * Select Kolab objects filtered by the given query
+     *
+     * @param array Pseudo-SQL query as list of filter parameter triplets
+     * @param boolean Set true to only return UIDs instead of complete objects
+     * @return array List of Kolab data objects (each represented as hash array) or UIDs
+     */
+    public function select($query = array(), $uids = false)
+    {
+        // modify query for IMAP search: query param 'type' is actually a subtype
+        if (!$this->ready) {
+            foreach ($query as $i => $tuple) {
+                if ($tuple[0] == 'type') {
+                    $tuple[2] = 'configuration.' . $tuple[2];
+                    $query[$i] = $tuple;
+                }
+            }
+        }
+
+        return parent::select($query, $uids);
+    }
+
+    /**
+     * Helper method to compose a valid SQL query from pseudo filter triplets
+     */
+    protected function _sql_where($query)
+    {
+        if (is_array($query)) {
+            foreach ($query as $idx => $param) {
+                // convert category filter
+                if ($param[0] == 'category') {
+                    $param[2] = array_map(function($n) { return 'category:' . $n; }, (array) $param[2]);
+
+                    $query[$idx][0] = 'tags';
+                    $query[$idx][2] = count($param[2]) > 1 ? $param[2] : $param[2][0];
+                }
+                // convert member filter (we support only = operator with single value)
+                else if ($param[0] == 'member') {
+                    $query[$idx][0] = 'words';
+                    $query[$idx][1] = '~';
+                    $query[$idx][2] = '^' . $param[2] . '$';
+                }
+            }
+        }
+
+        return parent::_sql_where($query);
+    }
 }
diff --git a/plugins/libkolab/lib/kolab_storage_config.php b/plugins/libkolab/lib/kolab_storage_config.php
new file mode 100644
index 0000000..d58e3c0
--- /dev/null
+++ b/plugins/libkolab/lib/kolab_storage_config.php
@@ -0,0 +1,840 @@
+<?php
+
+/**
+ * Kolab storage class providing access to configuration objects on a Kolab server.
+ *
+ * @version @package_version@
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
+ * @author Aleksander Machniak <machniak at kolabsys.com>
+ *
+ * Copyright (C) 2012-2014, Kolab Systems AG <contact at kolabsys.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+class kolab_storage_config
+{
+    const FOLDER_TYPE = 'configuration';
+
+
+    /**
+     * Singleton instace of kolab_storage_config
+     *
+     * @var kolab_storage_config
+     */
+    static protected $instance;
+
+    private $folders;
+    private $default;
+    private $enabled;
+
+
+    /**
+     * This implements the 'singleton' design pattern
+     *
+     * @return kolab_storage_config The one and only instance
+     */
+    static function get_instance()
+    {
+        if (!self::$instance) {
+            self::$instance = new kolab_storage_config();
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * Private constructor (finds default configuration folder as a config source)
+     */
+    private function __construct()
+    {
+        // get all configuration folders
+        $this->folders = kolab_storage::get_folders(self::FOLDER_TYPE, false);
+
+        foreach ($this->folders as $folder) {
+            if ($folder->default) {
+                $this->default = $folder;
+                break;
+            }
+        }
+
+        // if no folder is set as default, choose the first one
+        if (!$this->default) {
+            $this->default = reset($this->folders);
+        }
+
+        // attempt to create a default folder if it does not exist
+        if (!$this->default) {
+            $folder_name = 'Configuration';
+            $folder_type = self::FOLDER_TYPE . '.default';
+
+            if (kolab_storage::folder_create($folder_name, $folder_type, true)) {
+                $this->default = new kolab_storage_folder($folder_name, $folder_type);
+            }
+        }
+
+        // check if configuration folder exist
+        if ($this->default && $this->default->name) {
+            $this->enabled = true;
+        }
+    }
+
+    /**
+     * Check wether any configuration storage (folder) exists
+     *
+     * @return bool
+     */
+    public function is_enabled()
+    {
+        return $this->enabled;
+    }
+
+    /**
+     * Get configuration objects
+     *
+     * @param array $filter  Search filter
+     * @param bool  $default Enable to get objects only from default folder
+     * @param int   $limit   Max. number of records (per-folder)
+     *
+     * @return array List of objects
+     */
+    public function get_objects($filter = array(), $default = false, $limit = 0)
+    {
+        $list = array();
+
+        foreach ($this->folders as $folder) {
+            // we only want to read from default folder
+            if ($default && !$folder->default) {
+                continue;
+            }
+
+            // for better performance it's good to assume max. number of records
+            if ($limit) {
+                $folder->set_order_and_limit(null, $limit);
+            }
+
+            foreach ($folder->select($filter) as $object) {
+                unset($object['_formatobj']);
+                $list[] = $object;
+            }
+        }
+
+        return $list;
+    }
+
+    /**
+     * Get configuration object
+     *
+     * @param string $uid     Object UID
+     * @param bool   $default Enable to get objects only from default folder
+     *
+     * @return array Object data
+     */
+    public function get_object($uid, $default = false)
+    {
+        foreach ($this->folders as $folder) {
+            // we only want to read from default folder
+            if ($default && !$folder->default) {
+                continue;
+            }
+
+            if ($object = $folder->get_object($uid)) {
+                return $object;
+            }
+        }
+    }
+
+    /**
+     * Create/update configuration object
+     *
+     * @param array  $object Object data
+     * @param string $type   Object type
+     *
+     * @return bool True on success, False on failure
+     */
+    public function save(&$object, $type)
+    {
+        if (!$this->enabled) {
+            return false;
+        }
+
+        $folder = $this->find_folder($object);
+
+        if ($type) {
+            $object['type'] = $type;
+        }
+
+        return $folder->save($object, self::FOLDER_TYPE . '.' . $object['type'], $object['uid']);
+    }
+
+    /**
+     * Remove configuration object
+     *
+     * @param string $uid Object UID
+     *
+     * @return bool True on success, False on failure
+     */
+    public function delete($uid)
+    {
+        if (!$this->enabled) {
+            return false;
+        }
+
+        // fetch the object to find folder
+        $object = $this->get_object($uid);
+
+        if (!$object) {
+            return false;
+        }
+
+        $folder = $this->find_folder($object);
+
+        return $folder->delete($uid);
+    }
+
+    /**
+     * Find folder
+     */
+    public function find_folder($object = array())
+    {
+        // find folder object
+        if ($object['_mailbox']) {
+            foreach ($this->folders as $folder) {
+                if ($folder->name == $object['_mailbox']) {
+                    break;
+                }
+            }
+        }
+        else {
+            $folder = $this->default;
+        }
+
+        return $folder;
+    }
+
+    /**
+     * Builds relation member URI
+     *
+     * @param string|array Object UUID or Message folder, UID, Search headers (Message-Id, Date)
+     *
+     * @return string $url Member URI
+     */
+    public static function build_member_url($params)
+    {
+        // param is object UUID
+        if (is_string($params) && !empty($params)) {
+            return 'urn:uuid:' . $params;
+        }
+
+        if (empty($params) || !strlen($params['folder'])) {
+            return null;
+        }
+
+        $rcube   = rcube::get_instance();
+        $storage = $rcube->get_storage();
+
+        // modify folder spec. according to namespace
+        $folder = $params['folder'];
+        $ns     = $storage->folder_namespace($folder);
+
+        if ($ns == 'shared') {
+            // Note: this assumes there's only one shared namespace root
+            if ($ns = $storage->get_namespace('shared')) {
+                if ($prefix = $ns[0][0]) {
+                    $folder = 'shared' . substr($folder, strlen($prefix));
+                }
+            }
+        }
+        else {
+            if ($ns == 'other') {
+                // Note: this assumes there's only one other users namespace root
+                if ($ns = $storage->get_namespace('shared')) {
+                    if ($prefix = $ns[0][0]) {
+                        $folder = 'user' . substr($folder, strlen($prefix));
+                    }
+                }
+            }
+            else {
+                $folder = 'user' . '/' . $rcube->get_user_name() . '/' . $folder;
+            }
+        }
+
+        $folder = implode('/', array_map('rawurlencode', explode('/', $folder)));
+
+        // build URI
+        $url = 'imap:///' . $folder;
+
+        // UID is optional here because sometimes we want
+        // to build just a member uri prefix
+        if ($params['uid']) {
+            $url .= '/' . $params['uid'];
+        }
+
+        unset($params['folder']);
+        unset($params['uid']);
+
+        if (!empty($params)) {
+            $url .= '?' . http_build_query($params, '', '&');
+        }
+
+        return $url;
+    }
+
+    /**
+     * Parses relation member string
+     *
+     * @param string $url Member URI
+     *
+     * @return array Message folder, UID, Search headers (Message-Id, Date)
+     */
+    public static function parse_member_url($url)
+    {
+        // Look for IMAP URI:
+        // imap:///(user/username@domain|shared)/<folder>/<UID>?<search_params>
+        if (strpos($url, 'imap:///') === 0) {
+            $rcube   = rcube::get_instance();
+            $storage = $rcube->get_storage();
+
+            // parse_url does not work with imap:/// prefix
+            $url   = parse_url(substr($url, 8));
+            $path  = explode('/', $url['path']);
+            parse_str($url['query'], $params);
+
+            $uid  = array_pop($path);
+            $ns   = array_shift($path);
+            $path = array_map('rawurldecode', $path);
+
+            // resolve folder name
+            if ($ns == 'shared') {
+                $folder = implode('/', $path);
+                // Note: this assumes there's only one shared namespace root
+                if ($ns = $storage->get_namespace('shared')) {
+                    if ($prefix = $ns[0][0]) {
+                        $folder = $prefix . '/' . $folder;
+                    }
+                }
+            }
+            else if ($ns == 'user') {
+                $username = array_shift($path);
+                $folder   = implode('/', $path);
+
+                if ($username != $rcube->get_user_name()) {
+                    // Note: this assumes there's only one other users namespace root
+                    if ($ns = $storage->get_namespace('other')) {
+                        if ($prefix = $ns[0][0]) {
+                            $folder = $prefix . '/' . $username . '/' . $folder;
+                        }
+                    }
+                }
+                else if (!strlen($folder)) {
+                    $folder = 'INBOX';
+                }
+            }
+            else {
+                return;
+            }
+
+            return array(
+                'folder' => $folder,
+                'uid'    => $uid,
+                'params' => $params,
+            );
+        }
+
+        return false;
+    }
+
+    /**
+     * Build array of member URIs from set of messages
+     *
+     * @param string $folder   Folder name
+     * @param array  $messages Array of rcube_message objects
+     *
+     * @return array List of members (IMAP URIs)
+     */
+    public static function build_members($folder, $messages)
+    {
+        $members = array();
+
+        foreach ((array) $messages as $msg) {
+            $params = array(
+                'folder' => $folder,
+                'uid'    => $msg->uid,
+            );
+
+            // add search parameters:
+            // we don't want to build "invalid" searches e.g. that
+            // will return false positives (more or wrong messages)
+            if (($messageid = $msg->get('message-id', false)) && ($date = $msg->get('date', false))) {
+                $params['message-id'] = $messageid;
+                $params['date']       = $date;
+
+                if ($subject = $msg->get('subject', false)) {
+                    $params['subject'] = substr($subject, 0, 256);
+                }
+            }
+
+            $members[] = self::build_member_url($params);
+        }
+
+        return $members;
+    }
+
+    /**
+     * Resolve/validate/update members (which are IMAP URIs) of relation object.
+     *
+     * @param array $tag   Tag object
+     * @param bool  $force Force members list update
+     *
+     * @return array Folder/UIDs list
+     */
+    public static function resolve_members(&$tag, $force = true)
+    {
+        $result = array();
+
+        foreach ((array) $tag['members'] as $member) {
+            // IMAP URI members
+            if ($url = self::parse_member_url($member)) {
+                $folder = $url['folder'];
+
+                if (!$force) {
+                    $result[$folder][] = $url['uid'];
+                }
+                else {
+                    $result[$folder]['uid'][]    = $url['uid'];
+                    $result[$folder]['params'][] = $url['params'];
+                    $result[$folder]['member'][] = $member;
+                }
+            }
+        }
+
+        if (empty($result) || !$force) {
+            return $result;
+        }
+
+        $rcube   = rcube::get_instance();
+        $storage = $rcube->get_storage();
+        $search  = array();
+        $missing = array();
+
+        // first we search messages by Folder+UID
+        foreach ($result as $folder => $data) {
+            // @FIXME: maybe better use index() which is cached?
+            // @TODO: consider skip_deleted option
+            $index = $storage->search_once($folder, 'UID ' . rcube_imap_generic::compressMessageSet($data['uid']));
+            $uids  = $index->get();
+
+            // messages that were not found need to be searched by search parameters
+            $not_found = array_diff($data['uid'], $uids);
+            if (!empty($not_found)) {
+                foreach ($not_found as $uid) {
+                    $idx = array_search($uid, $data['uid']);
+
+                    if ($p = $data['params'][$idx]) {
+                        $search[] = $p;
+                    }
+
+                    $missing[] = $result[$folder]['member'][$idx];
+
+                    unset($result[$folder]['uid'][$idx]);
+                    unset($result[$folder]['params'][$idx]);
+                    unset($result[$folder]['member'][$idx]);
+                }
+            }
+
+            $result[$folder] = $uids;
+        }
+
+        // search in all subscribed mail folders using search parameters
+        if (!empty($search)) {
+            // remove not found members from the members list
+            $tag['members'] = array_diff($tag['members'], $missing);
+
+            // get subscribed folders
+            $folders = $storage->list_folders_subscribed('', '*', 'mail', null, true);
+
+            // @TODO: do this search in chunks (for e.g. 10 messages)?
+            $search_str = '';
+
+            foreach ($search as $p) {
+                $search_params = array();
+                foreach ($p as $key => $val) {
+                    $key = strtoupper($key);
+                    // don't search by subject, we don't want false-positives
+                    if ($key != 'SUBJECT') {
+                        $search_params[] = 'HEADER ' . $key . ' ' . rcube_imap_generic::escape($val);
+                    }
+                }
+
+                $search_str .= ' (' . implode(' ', $search_params) . ')';
+            }
+
+            $search_str = trim(str_repeat(' OR', count($search)-1) . $search_str);
+
+            // search
+            $search = $storage->search_once($folders, $search_str);
+
+            // handle search result
+            $folders = (array) $search->get_parameters('MAILBOX');
+
+            foreach ($folders as $folder) {
+                $set  = $search->get_set($folder);
+                $uids = $set->get();
+
+                if (!empty($uids)) {
+                    $msgs    = $storage->fetch_headers($folder, $uids, false);
+                    $members = self::build_members($folder, $msgs);
+
+                    // merge new members into the tag members list
+                    $tag['members'] = array_merge($tag['members'], $members);
+
+                    // add UIDs into the result
+                    $result[$folder] = array_unique(array_merge((array)$result[$folder], $uids));
+                }
+            }
+
+            // update tag object with new members list
+            $tag['members'] = array_unique($tag['members']);
+            kolab_storage_config::get_instance()->save($tag, 'relation', false);
+        }
+
+        return $result;
+    }
+
+    /**
+     * Assign tags to kolab objects
+     *
+     * @param array $records List of kolab objects
+     *
+     * @return array List of tags
+     */
+    public function apply_tags(&$records)
+    {
+        // first convert categories into tags
+        foreach ($records as $i => $rec) {
+            if (!empty($rec['categories'])) {
+                $folder = new kolab_storage_folder($rec['_mailbox']);
+                if ($object = $folder->get_object($rec['uid'])) {
+                    $tags = $rec['categories'];
+
+                    unset($object['categories']);
+                    unset($records[$i]['categories']);
+
+                    $this->save_tags($rec['uid'], $tags);
+                    $folder->save($object, $rec['_type'], $rec['uid']);
+                }
+            }
+        }
+
+        $tags = array();
+
+        // assign tags to objects
+        foreach ($this->get_tags() as $tag) {
+            foreach ($records as $idx => $rec) {
+                $uid = self::build_member_url($rec['uid']);
+                if (in_array($uid, (array) $tag['members'])) {
+                    $records[$idx]['tags'][] = $tag['name'];
+                }
+            }
+
+            $tags[] = $tag['name'];
+        }
+
+        $tags = array_unique($tags);
+
+        return $tags;
+    }
+
+    /**
+     * Update object tags
+     *
+     * @param string $uid  Kolab object UID
+     * @param array  $tags List of tag names
+     */
+    public function save_tags($uid, $tags)
+    {
+        $url       = self::build_member_url($uid);
+        $relations = $this->get_tags();
+
+        foreach ($relations as $idx => $relation) {
+            $selected = !empty($tags) && in_array($relation['name'], $tags);
+            $found    = !empty($relation['members']) && in_array($url, $relation['members']);
+            $update   = false;
+
+            // remove member from the relation
+            if ($found && !$selected) {
+                $relation['members'] = array_diff($relation['members'], (array) $url);
+                $update = true;
+            }
+            // add member to the relation
+            else if (!$found && $selected) {
+                $relation['members'][] = $url;
+                $update = true;
+            }
+
+            if ($update) {
+                if ($this->save($relation, 'relation')) {
+                    $this->tags[$idx] = $relation; // update in-memory cache
+                }
+            }
+
+            if ($selected) {
+                $tags = array_diff($tags, (array)$relation['name']);
+            }
+        }
+
+        // create new relations
+        if (!empty($tags)) {
+            foreach ($tags as $tag) {
+                $relation = array(
+                    'name'     => $tag,
+                    'members'  => (array) $url,
+                    'category' => 'tag',
+                );
+
+                if ($this->save($relation, 'relation')) {
+                    $this->tags[] = $relation; // update in-memory cache
+                }
+            }
+        }
+    }
+
+    /**
+     * Get tags (all or referring to specified object)
+     *
+     * @param string $uid Optional object UID
+     *
+     * @return array List of Relation objects
+     */
+    public function get_tags($uid = '*')
+    {
+        if (!isset($this->tags)) {
+            $default = true;
+            $filter  = array(
+                array('type', '=', 'relation'),
+                array('category', '=', 'tag')
+            );
+
+            // use faster method
+            if ($uid && $uid != '*') {
+                $filter[] = array('member', '=', $uid);
+                $tags = $this->get_objects($filter, $default);
+            }
+            else {
+                $this->tags = $tags = $this->get_objects($filter, $default);
+            }
+        }
+        else {
+            $tags = $this->tags;
+        }
+
+        if ($uid === '*') {
+            return $tags;
+        }
+
+        $result = array();
+        $search = self::build_member_url($uid);
+
+        foreach ($tags as $tag) {
+            if (in_array($search, (array) $tag['members'])) {
+                $result[] = $tag;
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Find objects linked with the given groupware object through a relation
+     *
+     * @param string Object UUID
+     * @param array List of related URIs
+     */
+    public function get_object_links($uid)
+    {
+        $links = array();
+        $object_uri = self::build_member_url($uid);
+
+        foreach ($this->get_relations_for_member($uid) as $relation) {
+            if (in_array($object_uri, (array) $relation['members'])) {
+                // make relation members up-to-date
+                kolab_storage_config::resolve_members($relation);
+
+                foreach ($relation['members'] as $member) {
+                    if ($member != $object_uri) {
+                        $links[] = $member;
+                    }
+                }
+            }
+        }
+
+        return array_unique($links);
+    }
+
+    /**
+     *
+     */
+    public function save_object_links($uid, $links, $remove = array())
+    {
+        $object_uri = self::build_member_url($uid);
+        $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_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']);
+            }
+            // update relation object if members changed
+            else if (count(array_diff($members, $relation['members'])) || count(array_diff($relation['members'], $members))) {
+                $relation['members'] = $members;
+                $done = $this->save($relation, 'relation');
+                $links = array();
+            }
+            // no changes, we're happy
+            else {
+                $done = true;
+                $links = array();
+            }
+        }
+
+        // create a new relation
+        if (!$done && !empty($links)) {
+            $relation = array(
+                'members'  => array_merge($links, array($object_uri)),
+                'category' => 'generic',
+            );
+
+            $ret = $this->save($relation, 'relation');
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Find relation objects referring to specified note
+     */
+    public function get_relations_for_member($uid, $reltype = 'generic')
+    {
+        $default = true;
+        $filter  = array(
+            array('type', '=', 'relation'),
+            array('category', '=', $reltype),
+            array('member', '=', $uid),
+        );
+
+        return $this->get_objects($filter, $default, 100);
+    }
+
+    /**
+     * Find kolab objects assigned to specified e-mail message
+     *
+     * @param rcube_message $message E-mail message
+     * @param string        $folder  Folder name
+     * @param string        $type    Result objects type
+     *
+     * @return array List of kolab objects
+     */
+    public function get_message_relations($message, $folder, $type)
+    {
+        static $_cache = array();
+
+        $result  = array();
+        $uids    = array();
+        $default = true;
+        $uri     = self::get_message_uri($message, $folder);
+        $filter  = array(
+            array('type', '=', 'relation'),
+            array('category', '=', 'generic'),
+        );
+
+        // query by message-id
+        $member_id = $message->get('message-id', false);
+        if (empty($member_id)) {
+            // derive message identifier from URI
+            $member_id = md5($uri);
+        }
+        $filter[] = array('member', '=', $member_id);
+
+        if (!isset($_cache[$uri])) {
+            // get UIDs of related groupware objects
+            foreach ($this->get_objects($filter, $default) as $relation) {
+                // we don't need to update members if the URI is found
+                if (!in_array($uri, $relation['members'])) {
+                    // update members...
+                    $messages = kolab_storage_config::resolve_members($relation);
+                    // ...and check again
+                    if (empty($messages[$folder]) || !in_array($message->uid, $messages[$folder])) {
+                        continue;
+                    }
+                }
+
+                // find groupware object UID(s)
+                foreach ($relation['members'] as $member) {
+                    if (strpos($member, 'urn:uuid:') === 0) {
+                        $uids[] = substr($member, 9);
+                    }
+                }
+            }
+
+            // remember this lookup
+            $_cache[$uri] = $uids;
+        }
+        else {
+            $uids = $_cache[$uri];
+        }
+
+        // get kolab objects of specified type
+        if (!empty($uids)) {
+            $query  = array(array('uid', '=', array_unique($uids)));
+            $result = kolab_storage::select($query, $type);
+        }
+
+        return $result;
+    }
+
+    /**
+     * Build a URI representing the given message reference
+     */
+    public static function get_message_uri($headers, $folder)
+    {
+        $params = array(
+            'folder' => $headers->folder ?: $folder,
+            'uid'    => $headers->uid,
+        );
+
+        if (($messageid = $headers->get('message-id', false)) && ($date = $headers->get('date', false))) {
+            $params['message-id'] = $messageid;
+            $params['date']       = $date;
+
+            if ($subject = $headers->get('subject')) {
+                $params['subject'] = $subject;
+            }
+        }
+
+        return self::build_member_url($params);
+    }
+}


commit f3332f9ffb376c7eec51f6b4a9819f5e5d8199b1
Author: Aleksander Machniak <machniak at kolabsys.com>
Date:   Fri Oct 17 08:53:35 2014 -0400

    Cache relation members for better performance (#3452)
    
    Conflicts:
    
    	plugins/kolab_notes/kolab_notes.php
    	plugins/libkolab/lib/kolab_format_configuration.php
    	plugins/libkolab/lib/kolab_storage_cache_configuration.php
    	plugins/libkolab/lib/kolab_storage_config.php

diff --git a/plugins/libkolab/lib/kolab_format_configuration.php b/plugins/libkolab/lib/kolab_format_configuration.php
index 5a8d3ff..174ab37 100644
--- a/plugins/libkolab/lib/kolab_format_configuration.php
+++ b/plugins/libkolab/lib/kolab_format_configuration.php
@@ -136,4 +136,28 @@ class kolab_format_configuration extends kolab_format
         return $tags;
     }
 
+    /**
+     * Callback for kolab_storage_cache to get words to index for fulltext search
+     *
+     * @return array List of words to save in cache
+     */
+    public function get_words()
+    {
+        $words = array();
+
+        foreach ((array)$this->data['members'] as $url) {
+            $member = kolab_storage_config::parse_member_url($url);
+
+            if (empty($member)) {
+                if (strpos($url, 'urn:uuid:') === 0) {
+                    $words[] = substr($url, 9);
+                }
+            }
+            else if (!empty($member['params']['message-id'])) {
+                $words[] = $member['params']['message-id'];
+            }
+        }
+
+        return $words;
+    }
 }
diff --git a/plugins/libkolab/lib/kolab_storage_cache_configuration.php b/plugins/libkolab/lib/kolab_storage_cache_configuration.php
index 8380aa8..8382852 100644
--- a/plugins/libkolab/lib/kolab_storage_cache_configuration.php
+++ b/plugins/libkolab/lib/kolab_storage_cache_configuration.php
@@ -37,4 +37,4 @@ class kolab_storage_cache_configuration extends kolab_storage_cache
 
         return $sql_data;
     }
-}
\ No newline at end of file
+}


commit 061def18a1460e9646d3a5a42ff3bf062964dce8
Author: Aleksander Machniak <machniak at kolabsys.com>
Date:   Wed Jul 30 10:53:30 2014 -0400

    Fix caching configuration objects

diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php
index 4a1746b..4fa3d09 100644
--- a/plugins/libkolab/lib/kolab_storage_cache.php
+++ b/plugins/libkolab/lib/kolab_storage_cache.php
@@ -755,12 +755,15 @@ class kolab_storage_cache
             }
         }
 
+        $object_type = $sql_arr['type'] ?: $this->folder->type;
+        $format_type = $this->folder->type == 'configuration' ? 'configuration' : $object_type;
+
         // add meta data
-        $object['_type']      = $sql_arr['type'] ?: $this->folder->type;
+        $object['_type']      = $object_type;
         $object['_msguid']    = $sql_arr['msguid'];
         $object['_mailbox']   = $this->folder->name;
         $object['_size']      = strlen($sql_arr['xml']);
-        $object['_formatobj'] = kolab_format::factory($object['_type'], 3.0, $sql_arr['xml']);
+        $object['_formatobj'] = kolab_format::factory($format_type, 3.0, $sql_arr['xml']);
 
         return $object;
     }




More information about the commits mailing list