diff --git a/plugins/kolab_tags/README b/plugins/kolab_tags/README new file mode 100644 --- /dev/null +++ b/plugins/kolab_tags/README @@ -0,0 +1,69 @@ +A mail tags module for Roundcube +-------------------------------------- + +This plugin currently supports a local database or a Kolab groupware +server as backends for tags storage. + + +REQUIREMENTS +------------ + +Some functions are shared with other plugins and therefore being moved to +library plugins. Thus in order to run the kolab_tags plugin, you also need the +following plugins installed: + +* kolab/libkolab [1] + + +INSTALLATION +------------ + +For a manual installation of the plugin (and its dependencies), +execute the following steps. This will set it up with the database backend +driver. + +1. Get the source from git + + $ cd /tmp + $ git clone https://git.kolab.org/diffusion/RPK/roundcubemail-plugins-kolab.git + $ cd /<path-to-roundcube>/plugins + $ cp -r /tmp/roundcubemail-plugins-kolab/plugins/kolab_tags . + $ cp -r /tmp/roundcubemail-plugins-kolab/plugins/libkolab . + +2. Create kolab_tags plugin configuration + + $ cd kolab_tags/ + $ cp config.inc.php.dist config.inc.php + $ edit config.inc.php + +3. Initialize the plugin database tables + + $ cd ../../ + $ bin/initdb.sh --dir=plugins/kolab_tags/drivers/database/SQL + +4. Build css styles for the Elastic skin (if needed) + + $ lessc --relative-urls -x plugins/libkolab/skins/elastic/libkolab.less > plugins/libkolab/skins/elastic/libkolab.min.css + +5. Enable the plugin + + $ edit config/config.inc.php + +Add 'kolab_tags' to the list of active plugins: + + $config['plugins'] = [ + (...) + 'kolab_tags', + ]; + + +IMPORTANT +--------- + +This plugin doesn't work with the Classic skin of Roundcube because no +templates are available for that skin. + +Use Roundcube `skins_allowed` option to limit skins available to the user +or remove incompatible skins from the skins folder. + +[1] https://git.kolab.org/diffusion/RPK/ diff --git a/plugins/kolab_tags/composer.json b/plugins/kolab_tags/composer.json --- a/plugins/kolab_tags/composer.json +++ b/plugins/kolab_tags/composer.json @@ -4,7 +4,7 @@ "description": "Email tags plugin", "homepage": "https://git.kolab.org/diffusion/RPK/", "license": "AGPLv3", - "version": "3.5.2", + "version": "3.5.3", "authors": [ { "name": "Aleksander Machniak", diff --git a/plugins/kolab_tags/config.inc.php.dist b/plugins/kolab_tags/config.inc.php.dist new file mode 100644 --- /dev/null +++ b/plugins/kolab_tags/config.inc.php.dist @@ -0,0 +1,4 @@ +<?php + +// Storage backend type (database, kolab) +$config['kolab_tags_driver'] = 'kolab'; diff --git a/plugins/kolab_tags/drivers/DriverInterface.php b/plugins/kolab_tags/drivers/DriverInterface.php new file mode 100644 --- /dev/null +++ b/plugins/kolab_tags/drivers/DriverInterface.php @@ -0,0 +1,73 @@ +<?php + +/** + * Kolab Tags backend driver interface + * + * @author Aleksander Machniak <machniak@kolabsys.com> + * + * Copyright (C) 2024, Apheleia IT AG <contact@apheleia-it.ch> + * + * 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/>. + */ + +namespace KolabTags\Drivers; + +interface DriverInterface +{ + /** + * Tags list + * + * @param array $filter Search filter + * + * @return array List of tags + */ + public function list_tags($filter = []); + + /** + * Create tag object + * + * @param array $tag Tag data + * + * @return false|array Tag data on success, False on failure + */ + public function create($tag); + + /** + * Update tag object + * + * @param array $tag Tag data + * + * @return false|array Tag data on success, False on failure + */ + public function update($tag); + + /** + * Remove tag object + * + * @param string $uid Object unique identifier + * + * @return bool True on success, False on failure + */ + public function remove($uid); + + /** + * Resolve members to folder/UID + * + * @param array $tag Tag object + * @param bool $force Force members list update + * + * @return array Folder/UID list + */ + public function get_tag_messages(&$tag, $force = true); +} diff --git a/plugins/kolab_tags/drivers/database/Driver.php b/plugins/kolab_tags/drivers/database/Driver.php new file mode 100644 --- /dev/null +++ b/plugins/kolab_tags/drivers/database/Driver.php @@ -0,0 +1,254 @@ +<?php + +/** + * Kolab Tags backend driver for SQL database. + * + * @author Aleksander Machniak <machniak@apheleia-it.ch> + * + * Copyright (C) 2024, Apheleia IT AG <contact@apheleia-it.ch> + * + * 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/>. + */ + +namespace KolabTags\Drivers\Database; + +use KolabTags\Drivers\DriverInterface; +use kolab_storage_cache; +use kolab_storage_config; +use rcube; + +class Driver implements DriverInterface +{ + private $members_table = 'kolab_tag_members'; + private $tags_table = 'kolab_tags'; + private $tag_cols = ['name', 'color']; + + + /** + * Tags list + * + * @param array $filter Search filter + * + * @return array List of tags + */ + public function list_tags($filter = []) + { + $rcube = rcube::get_instance(); + $db = $rcube->get_dbh(); + $user_id = $rcube->get_user_id(); + + // Parse filter, convert 'uid' into 'id' + foreach ($filter as $idx => $record) { + if ($record[0] == 'uid') { + $filter[$idx][0] = 'id'; + } + } + + // TODO: Support 'members' filter + + $where = kolab_storage_cache::sql_where($filter); + + $query = $db->query("SELECT * FROM `{$this->tags_table}` WHERE `user_id` = {$user_id}" . $where); + + $result = []; + while ($tag = $db->fetch_assoc($query)) { + $tag['uid'] = $tag['id']; // the API expects 'uid' property + $tag['members'] = []; + unset($tag['id']); + $result[$tag['uid']] = $tag; + } + + // Get the tag's members + if (!empty($result)) { + $ids = $db->array2list(array_keys($result), 'int'); + $query = $db->query("SELECT `tag_id`, `url` FROM `{$this->members_table}` WHERE `tag_id` IN ({$ids})"); + + while ($member = $db->fetch_assoc($query)) { + $result[$member['tag_id']]['members'][] = $member['url']; + } + } + + return array_values($result); + } + + /** + * Create tag object + * + * @param array $tag Tag data + * + * @return false|array Tag data on success, False on failure + */ + public function create($tag) + { + $rcube = rcube::get_instance(); + $db = $rcube->get_dbh(); + $user_id = $rcube->get_user_id(); + $insert = []; + + foreach ($this->tag_cols as $col) { + if (isset($tag[$col])) { + $insert[$db->quoteIdentifier($col)] = $db->quote($tag[$col]); + } + } + + if (empty($insert)) { + return false; + } + + $now = new \DateTime('now', new \DateTimeZone('UTC')); + + $insert['user_id'] = $user_id; + $insert['created'] = $insert['updated'] = $now->format("'Y-m-d H:i:s'"); + + $result = $db->query("INSERT INTO `{$this->tags_table}`" + . " (" . implode(', ', array_keys($insert)) . ")" + . " VALUES(" . implode(', ', array_values($insert)) . ")" + ); + + $tag['uid'] = $db->insert_id($this->tags_table); + + if (empty($tag['uid'])) { + return false; + } + + $this->save_members($tag, false); + + return $tag; + } + + /** + * Update tag object + * + * @param array $tag Tag data + * + * @return false|array Tag data on success, False on failure + */ + public function update($tag) + { + $rcube = rcube::get_instance(); + $db = $rcube->get_dbh(); + $user_id = $rcube->get_user_id(); + $update = []; + + foreach ($this->tag_cols as $col) { + if (isset($tag[$col])) { + $update[] = $db->quoteIdentifier($col) . ' = ' . $db->quote($tag[$col]); + } + } + + if (!empty($update)) { + $now = new \DateTime('now', new \DateTimeZone('UTC')); + $update[] = '`updated` = ' . $db->quote($now->format('Y-m-d H:i:s')); + + $result = $db->query("UPDATE `{$this->tags_table}` SET " . implode(', ', $update) + . " WHERE `id` = ? AND `user_id` = ?", $tag['uid'], $user_id); + + if ($result === false) { + return false; + } + } + + // Update members + $this->save_members($tag); + + return $tag; + } + + /** + * Remove tag object + * + * @param string $uid Object unique identifier + * + * @return bool True on success, False on failure + */ + public function remove($uid) + { + $rcube = rcube::get_instance(); + $db = $rcube->get_dbh(); + $user_id = $rcube->get_user_id(); + + $result = $db->query("DELETE FROM `{$this->tags_table}` WHERE `id` = ? AND `user_id` = ?", $uid, $user_id); + + return $db->affected_rows($result) > 0; + } + + /** + * Resolve members to folder/UID + * + * @param array $tag Tag object + * @param bool $force Force members list update + * + * @return array Folder/UID list + */ + public function get_tag_messages(&$tag, $force = true) + { + $result = kolab_storage_config::resolve_members($tag, $force, false); + + if ($force) { + // Update tag members + $this->save_members($tag); + } + + return $result; + } + + protected function save_members($tag, $update = true) + { + if (empty($tag['uid']) || !isset($tag['members'])) { + return; + } + + $rcube = rcube::get_instance(); + $db = $rcube->get_dbh(); + $existing = []; + + if ($update) { + $query = $db->query("SELECT `url` FROM `{$this->members_table}` WHERE `tag_id` = ?", $tag['uid']); + + while ($member = $db->fetch_assoc($query)) { + $existing[] = $member['url']; + } + } + + if (!empty($existing)) { + $insert = array_diff($tag['members'], $existing); + $delete = array_diff($existing, $tag['members']); + } else { + $insert = $tag['members']; + $delete = []; + } + + if (!empty($delete)) { + foreach (array_chunk($delete, 100) as $chunk) { + $query = $db->query("DELETE FROM `{$this->members_table}` WHERE `tag_id` = ?" + . " AND `url` IN (" . $db->array2list($chunk) . ")", + $tag['uid'] + ); + } + } + + if (!empty($insert)) { + $ts = (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y-m-d H:i:s'); + + foreach (array_chunk($insert, 100) as $chunk) { + $query = "INSERT INTO `{$this->members_table}` (`tag_id`, `url`, `created`) VALUES "; + foreach ($chunk as $idx => $url) { + $chunk[$idx] = sprintf("(%d, %s, %s)", $tag['uid'], $db->quote($url), $db->quote($ts)); + } + + $query = $db->query($query . implode(', ', $chunk)); + } + } + } +} diff --git a/plugins/kolab_tags/drivers/database/SQL/mysql.initial.sql b/plugins/kolab_tags/drivers/database/SQL/mysql.initial.sql new file mode 100644 --- /dev/null +++ b/plugins/kolab_tags/drivers/database/SQL/mysql.initial.sql @@ -0,0 +1,26 @@ + +CREATE TABLE IF NOT EXISTS `kolab_tags` ( + `id` int UNSIGNED NOT NULL AUTO_INCREMENT, + `user_id` int(10) UNSIGNED NOT NULL, + `name` varchar(255) NOT NULL, + `color` varchar(8) DEFAULT NULL, + `created` datetime DEFAULT NULL, + `updated` datetime DEFAULT NULL, + PRIMARY KEY(`id`), + UNIQUE KEY `user_id_name_idx` (`user_id`, `name`), + INDEX (`updated`), + CONSTRAINT `fk_kolab_tags_user_id` FOREIGN KEY (`user_id`) + REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE +) ROW_FORMAT=DYNAMIC ENGINE=INNODB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `kolab_tag_members` ( + `tag_id` int UNSIGNED NOT NULL, + `url` varchar(2048) BINARY NOT NULL, + `created` datetime DEFAULT NULL, + PRIMARY KEY(`tag_id`, `url`), + INDEX (`created`), + CONSTRAINT `fk_kolab_tag_members_tag_id` FOREIGN KEY (`tag_id`) + REFERENCES `kolab_tags`(`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ROW_FORMAT=DYNAMIC ENGINE=INNODB CHARACTER SET ascii; + +REPLACE INTO `system` (`name`, `value`) VALUES ('kolab-tags-database-version', '2024112000'); diff --git a/plugins/kolab_tags/lib/kolab_tags_backend.php b/plugins/kolab_tags/drivers/kolab/Driver.php rename from plugins/kolab_tags/lib/kolab_tags_backend.php rename to plugins/kolab_tags/drivers/kolab/Driver.php --- a/plugins/kolab_tags/lib/kolab_tags_backend.php +++ b/plugins/kolab_tags/drivers/kolab/Driver.php @@ -1,11 +1,11 @@ <?php /** - * Kolab Tags backend + * Kolab Tags backend driver for Kolab v3 * - * @author Aleksander Machniak <machniak@kolabsys.com> + * @author Aleksander Machniak <machniak@apheleia-it.ch> * - * Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com> + * Copyright (C) 2024, Apheleia IT AG <contact@apheleia-it.ch> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -21,7 +21,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -class kolab_tags_backend +namespace KolabTags\Drivers\Kolab; + +use KolabTags\Drivers\DriverInterface; +use kolab_storage_config; + +class Driver implements DriverInterface { private $tag_cols = ['name', 'category', 'color', 'parent', 'iconName', 'priority', 'members']; @@ -53,7 +58,7 @@ * * @param array $tag Tag data * - * @return boolean|array Tag data on success, False on failure + * @return false|array Tag data on success, False on failure */ public function create($tag) { @@ -72,7 +77,7 @@ * * @param array $tag Tag data * - * @return boolean|array Tag data on success, False on failure + * @return false|array Tag data on success, False on failure */ public function update($tag) { @@ -99,7 +104,7 @@ * * @param string $uid Object unique identifier * - * @return boolean True on success, False on failure + * @return bool True on success, False on failure */ public function remove($uid) { @@ -107,4 +112,17 @@ return $config->delete($uid); } + + /** + * Resolve members to folder/UID + * + * @param array $tag Tag object + * @param bool $force Force members list update + * + * @return array Folder/UID list + */ + public function get_tag_messages(&$tag, $force = true) + { + return kolab_storage_config::resolve_members($tag, $force); + } } diff --git a/plugins/kolab_tags/kolab_tags.js b/plugins/kolab_tags/kolab_tags.js --- a/plugins/kolab_tags/kolab_tags.js +++ b/plugins/kolab_tags/kolab_tags.js @@ -776,7 +776,6 @@ { var selection = list.get_selection(), has_tags = selection.length && rcmail.env.tags.length; - if (has_tags && !rcmail.select_all_mode) { has_tags = false; $.each(selection, function() { 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 @@ -57,12 +57,12 @@ private function engine() { if ($this->engine === null) { - // the files module can be enabled/disabled by the kolab_auth plugin + // the plugin can be enabled/disabled by the kolab_auth plugin if ($this->rc->config->get('kolab_tags_disabled') || !$this->rc->config->get('kolab_tags_enabled', true)) { return $this->engine = false; } - // $this->load_config(); + $this->load_config(); require_once $this->home . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'kolab_tags_engine.php'; 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 @@ -35,11 +35,16 @@ { $plugin->require_plugin('libkolab'); - require_once $plugin->home . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'kolab_tags_backend.php'; - - $this->backend = new kolab_tags_backend(); $this->plugin = $plugin; $this->rc = $plugin->rc; + + $driver = $this->rc->config->get('kolab_tags_driver') ?: 'database'; + $class = "\\KolabTags\\Drivers\\" . ucfirst($driver) . "\\Driver"; + + require_once "{$plugin->home}/drivers/DriverInterface.php"; + require_once "{$plugin->home}/drivers/{$driver}/Driver.php"; + + $this->backend = new $class(); } /** @@ -497,24 +502,12 @@ ]; if ($list) { - $result['uids'] = $this->get_tag_messages($tag, $force); + $result['uids'] = $this->backend->get_tag_messages($tag, $force); } return $result; } - /** - * Resolve members to folder/UID - * - * @param array $tag Tag object - * - * @return array Folder/UID list - */ - protected function get_tag_messages(&$tag, $force = true) - { - return kolab_storage_config::resolve_members($tag, $force); - } - /** * Build array of member URIs from set of messages */ diff --git a/plugins/kolab_tags/skins/elastic/templates/ui.html b/plugins/kolab_tags/skins/elastic/templates/ui.html --- a/plugins/kolab_tags/skins/elastic/templates/ui.html +++ b/plugins/kolab_tags/skins/elastic/templates/ui.html @@ -17,8 +17,8 @@ <div id="tagmessagemenu" class="popupmenu" aria-hidden="true"> <ul class="menu iconized"> <li class="separator"><label><roundcube:label name="kolab_tags.tags" /></label></li> - <roundcube:button type="link-menuitem" command="tag-add" label="kolab_tags.tagadd" classAct="tag add active" class="tag add disabled" /> - <roundcube:button type="link-menuitem" command="tag-remove" label="kolab_tags.tagremove" classAct="tag remove active" class="tag remove disabled" /> + <roundcube:button type="link-menuitem" command="tag-add" label="kolab_tags.tagadd" classAct="tag add active" class="tag add disabled" innerclass="inner" aria-haspopup="true" /> + <roundcube:button type="link-menuitem" command="tag-remove" label="kolab_tags.tagremove" classAct="tag remove active" class="tag remove disabled" innerclass="inner" aria-haspopup="true" /> <roundcube:button type="link-menuitem" command="tag-remove-all" label="kolab_tags.tagremoveall" classAct="tag remove all active" class="tag remove all disabled" /> </ul> </div> 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 @@ -777,7 +777,7 @@ $sql_query = "SELECT " . ($fetchall ? '*' : "`msguid` AS `_msguid`, `uid`") . " FROM `{$this->cache_table}` WHERE `folder_id` = ?" - . $this->_sql_where($query) + . self::sql_where($query) . (!empty($this->order_by) ? " ORDER BY " . $this->order_by : ''); $sql_result = $this->limit ? @@ -866,7 +866,7 @@ $sql_result = $this->db->query( "SELECT COUNT(*) AS `numrows` FROM `{$this->cache_table}` " . - "WHERE `folder_id` = ?" . $this->_sql_where($query), + "WHERE `folder_id` = ?" . self::sql_where($query), $this->folder_id ); @@ -946,40 +946,42 @@ /** * Helper method to compose a valid SQL query from pseudo filter triplets */ - protected function _sql_where($query) + public static function sql_where($query) { + $db = rcube::get_instance()->get_dbh(); $sql_where = ''; + foreach ((array) $query as $param) { if (is_array($param[0])) { $subq = []; foreach ($param[0] as $q) { - $subq[] = preg_replace('/^\s*AND\s+/i', '', $this->_sql_where([$q])); + $subq[] = preg_replace('/^\s*AND\s+/i', '', self::sql_where([$q])); } if (!empty($subq)) { $sql_where .= ' AND (' . implode($param[1] == 'OR' ? ' OR ' : ' AND ', $subq) . ')'; } continue; } elseif ($param[1] == '=' && is_array($param[2])) { - $qvalue = '(' . implode(',', array_map([$this->db, 'quote'], $param[2])) . ')'; + $qvalue = '(' . implode(',', array_map([$db, 'quote'], $param[2])) . ')'; $param[1] = 'IN'; } elseif ($param[1] == '~' || $param[1] == 'LIKE' || $param[1] == '!~' || $param[1] == '!LIKE') { $not = ($param[1] == '!~' || $param[1] == '!LIKE') ? 'NOT ' : ''; $param[1] = $not . 'LIKE'; - $qvalue = $this->db->quote('%' . preg_replace('/(^\^|\$$)/', ' ', $param[2]) . '%'); + $qvalue = $db->quote('%' . preg_replace('/(^\^|\$$)/', ' ', $param[2]) . '%'); } elseif ($param[1] == '~*' || $param[1] == '!~*') { $not = $param[1][1] == '!' ? 'NOT ' : ''; $param[1] = $not . 'LIKE'; - $qvalue = $this->db->quote(preg_replace('/(^\^|\$$)/', ' ', $param[2]) . '%'); + $qvalue = $db->quote(preg_replace('/(^\^|\$$)/', ' ', $param[2]) . '%'); } elseif ($param[0] == 'tags') { $param[1] = ($param[1] == '!=' ? 'NOT ' : '') . 'LIKE'; - $qvalue = $this->db->quote('% ' . $param[2] . ' %'); + $qvalue = $db->quote('% ' . $param[2] . ' %'); } else { - $qvalue = $this->db->quote($param[2]); + $qvalue = $db->quote($param[2]); } $sql_where .= sprintf( ' AND %s %s %s', - $this->db->quote_identifier($param[0]), + $db->quote_identifier($param[0]), $param[1], $qvalue ); diff --git a/plugins/libkolab/lib/kolab_storage_cache_configuration.php b/plugins/libkolab/lib/kolab_storage_cache_configuration.php --- a/plugins/libkolab/lib/kolab_storage_cache_configuration.php +++ b/plugins/libkolab/lib/kolab_storage_cache_configuration.php @@ -80,7 +80,7 @@ /** * Helper method to compose a valid SQL query from pseudo filter triplets */ - protected function _sql_where($query) + public static function sql_where($query) { if (is_array($query)) { foreach ($query as $idx => $param) { @@ -100,7 +100,7 @@ } } - return parent::_sql_where($query); + return parent::sql_where($query); } /** 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 @@ -448,7 +448,7 @@ * * @return array Folder/UIDs list */ - public static function resolve_members(&$tag, $force = true) + public static function resolve_members(&$tag, $force = true, $update = true) { $result = []; @@ -558,7 +558,9 @@ // update tag object with new members list $tag['members'] = array_unique($tag['members']); - kolab_storage_config::get_instance()->save($tag, 'relation'); + if ($update) { + kolab_storage_config::get_instance()->save($tag, 'relation'); + } } return $result; diff --git a/plugins/libkolab/lib/kolab_storage_dav_cache.php b/plugins/libkolab/lib/kolab_storage_dav_cache.php --- a/plugins/libkolab/lib/kolab_storage_dav_cache.php +++ b/plugins/libkolab/lib/kolab_storage_dav_cache.php @@ -413,7 +413,7 @@ $sql_query = "SELECT " . ($uids ? "`uid`" : '*') . " FROM `{$this->cache_table}` WHERE `folder_id` = ?" - . $this->_sql_where($query) + . self::sql_where($query) . (!empty($this->order_by) ? " ORDER BY " . $this->order_by : ''); $sql_result = $this->limit ? @@ -454,7 +454,7 @@ $sql_result = $this->db->query( "SELECT COUNT(*) AS `numrows` FROM `{$this->cache_table}` " . - "WHERE `folder_id` = ?" . $this->_sql_where($query), + "WHERE `folder_id` = ?" . self::sql_where($query), $this->folder_id );