Projects
Kolab:3.4:Updates
kolab-syncroton
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 64
View file
kolab-syncroton.spec
Changed
@@ -10,6 +10,9 @@ #!BuildIgnore: fedora-logos-httpd #!BuildIgnore: php-mysqlnd + +#!BuildIgnore: roundcubemail-skin-classic +#!BuildIgnore: roundcubemail-plugin-jqueryui-skin-classic %endif %{!?php_inidir: %global php_inidir %{_sysconfdir}/php.d} @@ -28,14 +31,14 @@ Name: kolab-syncroton Version: 2.3 -Release: 0.2.dev20140915.git6d8654fc%{?dist} +Release: 0.2.dev20141017.git92b081cf%{?dist} Summary: ActiveSync for Kolab Groupware Group: Applications/Internet License: LGPLv2 URL: http://www.syncroton.org -# From 6d8654fc43846d2274879f7837292f2102cd0582 +# From 92b081cf384a7c5158a2852f52a3c11a717f936a Source0: http://mirror.kolabsys.com/pub/releases/%{name}-%{version}.tar.gz Source1: kolab-syncroton.logrotate @@ -43,7 +46,9 @@ # Use this build requirement to make sure we are using # up to date vendorized copies of the plugins. -BuildRequires: roundcubemail-plugins-kolab >= 3.1.12 +BuildRequires: roundcubemail-plugin-kolab_auth >= 3.1.12 +BuildRequires: roundcubemail-plugin-kolab_folders >= 3.1.12 +BuildRequires: roundcubemail-plugin-libkolab >= 3.1.12 %if 0%{?suse_version} BuildRequires: roundcubemail @@ -203,7 +208,7 @@ %attr(0770,%{httpd_user},%{httpd_group}) %{_var}/log/%{name} %changelog -* Mon Sep 15 2014 Daniel Hoffend <dh@dotlan.net> - 2.3.0-2 +* Mon Sep 15 2014 Daniel Hoffend <dh@dotlan.net> - 2.3-0.2.git - New upstream version * Tue Apr 8 2014 Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> - 2.2.5-2
View file
debian.changelog
Changed
@@ -1,3 +1,9 @@ +kolab-syncroton (2.3~dev20141017-0~kolab1) unstable; urgency=low + + * New git master head snapshot + + -- Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> Fri, 17 Oct 2014 15:13:40 +0200 + kolab-syncroton (2.3-0~kolab4) unstable; urgency=low * New git master head snapshot
View file
kolab-syncroton-2.3.tar.gz/docs/SQL/mysql.initial.sql
Changed
@@ -105,6 +105,16 @@ CONSTRAINT `syncroton_modseq::device_id--syncroton_device::id` FOREIGN KEY (`device_id`) REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; +CREATE TABLE IF NOT EXISTS `syncroton_relations_state` ( + `device_id` varchar(40) NOT NULL, + `folder_id` varchar(40) NOT NULL, + `synctime` datetime NOT NULL, + `data` longblob, + PRIMARY KEY (`device_id`,`folder_id`,`synctime`), + KEY `syncroton_relations_state::device_id` (`device_id`), + CONSTRAINT `syncroton_relations_state::device_id--syncroton_device::id` FOREIGN KEY (`device_id`) REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + -- Roundcube core table should exist if we're using the same database CREATE TABLE IF NOT EXISTS `system` ( @@ -113,4 +123,4 @@ PRIMARY KEY(`name`) ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; -INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2013100800'); +INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2014101300');
View file
kolab-syncroton-2.3.tar.gz/docs/SQL/mysql/2013011200.sql
Changed
@@ -8,4 +8,4 @@ `owner_id` varchar(40) NOT NULL, `parent_id` varchar(40) DEFAULT NULL, PRIMARY KEY (`id`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; +) /*!40000 ENGINE=INNODB */;
View file
kolab-syncroton-2.3.tar.gz/docs/SQL/mysql/2013011600.sql
Changed
@@ -6,4 +6,4 @@ PRIMARY KEY (`device_id`,`folder_id`,`synctime`), KEY `syncroton_modseq::device_id` (`device_id`), CONSTRAINT `syncroton_modseq::device_id--syncroton_device::id` FOREIGN KEY (`device_id`) REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; +) /*!40000 ENGINE=INNODB */;
View file
kolab-syncroton-2.3.tar.gz/docs/SQL/mysql/2013040700.sql
Changed
@@ -6,4 +6,4 @@ `policy_key` varchar(64) NOT NULL, `json_policy` blob NOT NULL, PRIMARY KEY (`id`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; +) /*!40000 ENGINE=INNODB */;
View file
kolab-syncroton-2.3.tar.gz/docs/SQL/mysql/2014093000.sql
Added
@@ -0,0 +1,10 @@ +SET foreign_key_checks = 0; +/*!40101 ALTER TABLE `syncroton_device` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci */; +/*!40101 ALTER TABLE `syncroton_policy` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci */; +/*!40101 ALTER TABLE `syncroton_folder` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci */; +/*!40101 ALTER TABLE `syncroton_synckey` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci */; +/*!40101 ALTER TABLE `syncroton_content` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci */; +/*!40101 ALTER TABLE `syncroton_data` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci */; +/*!40101 ALTER TABLE `syncroton_data_folder` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci */; +/*!40101 ALTER TABLE `syncroton_modseq` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci */; +SET foreign_key_checks = 1;
View file
kolab-syncroton-2.3.tar.gz/docs/SQL/mysql/2014101300.sql
Added
@@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS `syncroton_relations_state` ( + `device_id` varchar(40) NOT NULL, + `folder_id` varchar(40) NOT NULL, + `synctime` datetime NOT NULL, + `data` longblob, + PRIMARY KEY (`device_id`,`folder_id`,`synctime`), + KEY `syncroton_relations_state::device_id` (`device_id`), + CONSTRAINT `syncroton_relations_state::device_id--syncroton_device::id` + FOREIGN KEY (`device_id`) REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
View file
kolab-syncroton-2.3.tar.gz/docs/SQL/oracle
Added
+(directory)
View file
kolab-syncroton-2.3.tar.gz/docs/SQL/oracle.initial.sql
Added
@@ -0,0 +1,113 @@ +CREATE TABLE "syncroton_policy" ( + "id" varchar(40) NOT NULL PRIMARY KEY, + "name" varchar(255) NOT NULL, + "description" varchar(255) DEFAULT NULL, + "policy_key" varchar(64) NOT NULL, + "json_policy" clob DEFAULT NULL +); + +CREATE TABLE "syncroton_device" ( + "id" varchar(40) NOT NULL PRIMARY KEY, + "deviceid" varchar(64) NOT NULL, + "devicetype" varchar(64) NOT NULL, + "owner_id" varchar(40) NOT NULL, + "acsversion" varchar(40) NOT NULL, + "policykey" varchar(64) DEFAULT NULL, + "policy_id" varchar(40) DEFAULT NULL, + "useragent" varchar(255) DEFAULT NULL, + "imei" varchar(255) DEFAULT NULL, + "model" varchar(255) DEFAULT NULL, + "friendlyname" varchar(255) DEFAULT NULL, + "os" varchar(255) DEFAULT NULL, + "oslanguage" varchar(255) DEFAULT NULL, + "phonenumber" varchar(255) DEFAULT NULL, + "pinglifetime" integer DEFAULT NULL, + "remotewipe" integer DEFAULT 0, + "pingfolder" clob DEFAULT NULL, + "lastsynccollection" clob DEFAULT NULL, + "lastping" timestamp DEFAULT NULL, + "contactsfilter_id" varchar(40) DEFAULT NULL, + "calendarfilter_id" varchar(40) DEFAULT NULL, + "tasksfilter_id" varchar(40) DEFAULT NULL, + "emailfilter_id" varchar(40) DEFAULT NULL +); + +CREATE UNIQUE INDEX "syncroton_device_owner_id_idx" ON "syncroton_device" ("owner_id", "deviceid"); + + +CREATE TABLE "syncroton_folder" ( + "id" varchar(40) NOT NULL PRIMARY KEY, + "device_id" varchar(40) NOT NULL + REFERENCES "syncroton_device" ("id") ON DELETE CASCADE, + "class" varchar(64) NOT NULL, + "folderid" varchar(254) NOT NULL, + "parentid" varchar(254) DEFAULT NULL, + "displayname" varchar(254) NOT NULL, + "type" integer NOT NULL, + "creation_time" timestamp NOT NULL, + "lastfiltertype" integer DEFAULT NULL, + "supportedfields" clob DEFAULT NULL +); + +CREATE UNIQUE INDEX "syncroton_folder_device_id_idx" ON "syncroton_folder" ("device_id", "class", "folderid"); + + +CREATE TABLE "syncroton_synckey" ( + "id" varchar(40) NOT NULL PRIMARY KEY, + "device_id" varchar(40) NOT NULL + REFERENCES "syncroton_device" ("id") ON DELETE CASCADE, + "type" varchar(64) DEFAULT NULL, + "counter" integer DEFAULT 0 NOT NULL, + "lastsync" timestamp DEFAULT NULL, + "pendingdata" clob +); + +CREATE UNIQUE INDEX "syncroton_synckey_device_idx" ON "syncroton_synckey" ("device_id", "type", "counter"); + +CREATE TABLE "syncroton_content" ( + "id" varchar(40) NOT NULL PRIMARY KEY, + "device_id" varchar(40) NOT NULL + REFERENCES "syncroton_device" ("id") ON DELETE CASCADE, + "folder_id" varchar(40) NOT NULL, + "contentid" varchar(128) NOT NULL, + "creation_time" timestamp DEFAULT NULL, + "creation_synckey" integer NOT NULL, + "is_deleted" smallint DEFAULT 0 +); + +CREATE UNIQUE INDEX "syncroton_content_device_idx" ON "syncroton_content" ("device_id", "folder_id", "contentid"); + +CREATE TABLE "syncroton_data" ( + "id" varchar(40) NOT NULL PRIMARY KEY, + "class" varchar(40) NOT NULL, + "folder_id" varchar(40) NOT NULL, + "data" clob +); + +CREATE TABLE "syncroton_data_folder" ( + "id" varchar(40) NOT NULL PRIMARY KEY, + "type" integer NOT NULL, + "name" varchar(255) NOT NULL, + "owner_id" varchar(40) NOT NULL, + "parent_id" varchar(40) DEFAULT NULL +); + +CREATE TABLE "syncroton_modseq" ( + "device_id" varchar(40) NOT NULL + REFERENCES "syncroton_device" ("id") ON DELETE CASCADE, + "folder_id" varchar(40) NOT NULL, + "synctime" timestamp NOT NULL, + "data" clob, + PRIMARY KEY ("device_id", "folder_id", "synctime") +); + +CREATE TABLE "syncroton_relations_state" ( + "device_id" varchar(40) NOT NULL + REFERENCES "syncroton_device" ("id") ON DELETE CASCADE, + "folder_id" varchar(40) NOT NULL, + "synctime" timestamp NOT NULL, + "data" clob, + PRIMARY KEY ("device_id", "folder_id", "synctime") +); + +INSERT INTO "system" ("name", "value") VALUES ('syncroton-version', '2014101300');
View file
kolab-syncroton-2.3.tar.gz/docs/SQL/oracle/2014101300.sql
Added
@@ -0,0 +1,8 @@ +CREATE TABLE "syncroton_relations_state" ( + "device_id" varchar(40) NOT NULL + REFERENCES "syncroton_device" ("id") ON DELETE CASCADE, + "folder_id" varchar(40) NOT NULL, + "synctime" timestamp NOT NULL, + "data" clob, + PRIMARY KEY ("device_id", "folder_id", "synctime") +);
View file
kolab-syncroton-2.3.tar.gz/lib/ext/Roundcube/rcube_db.php
Changed
@@ -50,6 +50,7 @@ ); const DEBUG_LINE_LENGTH = 4096; + const DEFAULT_QUOTE = '`'; /** * Factory, returns driver-specific instance of the class @@ -68,6 +69,8 @@ 'sybase' => 'mssql', 'dblib' => 'mssql', 'mysqli' => 'mysql', + 'oci' => 'oracle', + 'oci8' => 'oracle', ); $driver = isset($driver_map[$driver]) ? $driver_map[$driver] : $driver; @@ -128,6 +131,20 @@ return $this->dbh; } + // connect to database + if ($dbh = $this->conn_create($dsn)) { + $this->dbh = $dbh; + $this->dbhs[$mode] = $dbh; + $this->db_mode = $mode; + $this->db_connected = true; + } + } + + /** + * Create PDO connection + */ + protected function conn_create($dsn) + { // Get database specific connection options $dsn_string = $this->dsn_string($dsn); $dsn_options = $this->dsn_options($dsn); @@ -149,6 +166,8 @@ // don't throw exceptions or warnings $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); + + $this->conn_configure($dsn, $dbh); } catch (Exception $e) { $this->db_error = true; @@ -161,11 +180,7 @@ return null; } - $this->dbh = $dbh; - $this->dbhs[$mode] = $dbh; - $this->db_mode = $mode; - $this->db_connected = true; - $this->conn_configure($dsn, $dbh); + return $dbh; } /** @@ -237,8 +252,12 @@ // Read or write ? $mode = preg_match('/^(select|show|set)/i', $query) ? 'r' : 'w'; + $start = '[' . $this->options['identifier_start'] . self::DEFAULT_QUOTE . ']'; + $end = '[' . $this->options['identifier_end'] . self::DEFAULT_QUOTE . ']'; + $regex = '/(?:^|\s)(from|update|into|join)\s+'.$start.'?([a-z0-9._]+)'.$end.'?\s+/i'; + // find tables involved in this query - if (preg_match_all('/(?:^|\s)(from|update|into|join)\s+'.$this->options['identifier_start'].'?([a-z0-9._]+)'.$this->options['identifier_end'].'?\s+/i', $query, $matches, PREG_SET_ORDER)) { + if (preg_match_all($regex, $query, $matches, PREG_SET_ORDER)) { foreach ($matches as $m) { $table = $m[2]; @@ -407,6 +426,9 @@ $query = $this->set_limit($query, $numrows, $offset); } + // replace self::DEFAULT_QUOTE with driver-specific quoting + $query = $this->query_parse($query); + // Because in Roundcube we mostly use queries that are // executed only once, we will not use prepared queries $pos = 0; @@ -430,10 +452,19 @@ $query = str_replace('??', '?', $query); $query = rtrim($query, " \t\n\r\0\x0B;"); + // log query $this->debug($query); + return $this->query_execute($query); + } + + /** + * Query execution + */ + protected function query_execute($query) + { // destroy reference to previous result, required for SQLite driver (#1488874) - $this->last_result = null; + $this->last_result = null; $this->db_error_msg = null; // send query @@ -443,9 +474,52 @@ $result = $this->handle_error($query); } - $this->last_result = $result; + return $this->last_result = $result; + } - return $result; + /** + * Parse SQL query and replace identifier quoting + * + * @param string $query SQL query + * + * @return string SQL query + */ + protected function query_parse($query) + { + $start = $this->options['identifier_start']; + $end = $this->options['identifier_end']; + $quote = self::DEFAULT_QUOTE; + + if ($start == $quote) { + return $query; + } + + $pos = 0; + $in = false; + + while ($pos = strpos($query, $quote, $pos)) { + if ($query[$pos+1] == $quote) { // skip escaped quote + $pos += 2; + } + else { + if ($in) { + $q = $end; + $in = false; + } + else { + $q = $start; + $in = true; + } + + $query = substr_replace($query, $q, $pos, 1); + $pos++; + } + } + + // replace escaped quote back to normal, see self::quote() + $query = str_replace($quote.$quote, $quote, $query); + + return $query; } /** @@ -482,7 +556,9 @@ public function affected_rows($result = null) { if ($result || ($result === null && ($result = $this->last_result))) { - return $result->rowCount(); + if ($result !== true) { + return $result->rowCount(); + } } return 0; @@ -498,7 +574,7 @@ */ public function num_rows($result = null) { - if ($result || ($result === null && ($result = $this->last_result))) { + if (($result || ($result === null && ($result = $this->last_result))) && $result !== true) { // repeat query with SELECT COUNT(*) ... if (preg_match('/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/ims', $result->queryString, $m)) { $query = $this->dbh->query('SELECT COUNT(*) FROM ' . $m[1], PDO::FETCH_NUM); @@ -574,7 +650,9 @@ protected function _fetch_row($result, $mode) { if ($result || ($result === null && ($result = $this->last_result))) { - return $result->fetch($mode); + if ($result !== true) { + return $result->fetch($mode); + } } return false; @@ -644,6 +722,63 @@ } /** + * Start transaction + * + * @return bool True on success, False on failure + */ + public function startTransaction() + { + $this->db_connect('w', true); + + // check connection before proceeding + if (!$this->is_connected()) { + return $this->last_result = false; + } + + $this->debug('BEGIN TRANSACTION'); + + return $this->last_result = $this->dbh->beginTransaction(); + } + + /** + * Commit transaction + * + * @return bool True on success, False on failure + */ + public function endTransaction() + { + $this->db_connect('w', true); + + // check connection before proceeding + if (!$this->is_connected()) { + return $this->last_result = false; + } + + $this->debug('COMMIT TRANSACTION'); + + return $this->last_result = $this->dbh->commit(); + } + + /** + * Rollback transaction + * + * @return bool True on success, False on failure + */ + public function rollbackTransaction() + { + $this->db_connect('w', true); + + // check connection before proceeding + if (!$this->is_connected()) { + return $this->last_result = false; + } + + $this->debug('ROLLBACK TRANSACTION'); + + return $this->last_result = $this->dbh->rollBack(); + } + + /** * Formats input so it can be safely used in a query * * @param mixed $input Value to quote @@ -676,8 +811,13 @@ 'bool' => PDO::PARAM_BOOL, 'integer' => PDO::PARAM_INT, ); + $type = isset($map[$type]) ? $map[$type] : PDO::PARAM_STR; - return strtr($this->dbh->quote($input, $type), array('?' => '??')); // escape ? + + return strtr($this->dbh->quote($input, $type), + // escape ? and ` + array('?' => '??', self::DEFAULT_QUOTE => self::DEFAULT_QUOTE.self::DEFAULT_QUOTE) + ); } return 'NULL'; @@ -916,15 +1056,20 @@ /** * Return correct name for a specific database table * - * @param string $table Table name + * @param string $table Table name + * @param bool $quoted Quote table identifier * * @return string Translated table name */ - public function table_name($table) + public function table_name($table, $quoted = false) { // add prefix to the table name if configured if (($prefix = $this->options['table_prefix']) && strpos($table, $prefix) !== 0) { - return $prefix . $table; + $table = $prefix . $table; + } + + if ($quoted) { + $table = $this->quote_identifier($table); } return $table;
View file
kolab-syncroton-2.3.tar.gz/lib/ext/Roundcube/rcube_db_mysql.php
Changed
@@ -38,13 +38,6 @@ */ public function __construct($db_dsnw, $db_dsnr = '', $pconn = false) { - if (version_compare(PHP_VERSION, '5.3.0', '<')) { - rcube::raise_error(array('code' => 600, 'type' => 'db', - 'line' => __LINE__, 'file' => __FILE__, - 'message' => "MySQL driver requires PHP >= 5.3, current version is " . PHP_VERSION), - true, true); - } - parent::__construct($db_dsnw, $db_dsnr, $pconn); // SQL identifiers quoting
View file
kolab-syncroton-2.3.tar.gz/lib/ext/Roundcube/rcube_db_oracle.php
Added
@@ -0,0 +1,599 @@ +<?php + +/** + +-----------------------------------------------------------------------+ + | This file is part of the Roundcube Webmail client | + | Copyright (C) 2011-2014, Kolab Systems AG | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + | | + | PURPOSE: | + | Database wrapper class that implements database functions | + | for Oracle database using OCI8 extension | + +-----------------------------------------------------------------------+ + | Author: Aleksander Machniak <machniak@kolabsys.com> | + +-----------------------------------------------------------------------+ +*/ + +/** + * Database independent query interface + * + * @package Framework + * @subpackage Database + */ +class rcube_db_oracle extends rcube_db +{ + public $db_provider = 'oracle'; + + + /** + * Create connection instance + */ + protected function conn_create($dsn) + { + // Get database specific connection options + $dsn_options = $this->dsn_options($dsn); + + $function = $this->db_pconn ? 'oci_pconnect' : 'oci_connect'; + + if (!function_exists($function)) { + $this->db_error = true; + $this->db_error_msg = 'OCI8 extension not loaded. See http://php.net/manual/en/book.oci8.php'; + + rcube::raise_error(array('code' => 500, 'type' => 'db', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => $this->db_error_msg), true, false); + + return; + } + + // connect + $dbh = @$function($dsn['username'], $dsn['password'], $dsn_options['database'], $dsn_options['charset']); + + if (!$dbh) { + $error = oci_error(); + $this->db_error = true; + $this->db_error_msg = $error['message']; + + rcube::raise_error(array('code' => 500, 'type' => 'db', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => $this->db_error_msg), true, false); + + return; + } + + // configure session + $this->conn_configure($dsn, $dbh); + + return $dbh; + } + + /** + * Driver-specific configuration of database connection + * + * @param array $dsn DSN for DB connections + * @param PDO $dbh Connection handler + */ + protected function conn_configure($dsn, $dbh) + { + $init_queries = array( + "ALTER SESSION SET nls_date_format = 'YYYY-MM-DD'", + "ALTER SESSION SET nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'", + ); + + foreach ($init_queries as $query) { + $stmt = oci_parse($dbh, $query); + oci_execute($stmt); + } + } + + /** + * Connection state checker + * + * @return boolean True if in connected state + */ + public function is_connected() + { + return empty($this->dbh) ? false : $this->db_connected; + } + + /** + * Execute a SQL query with limits + * + * @param string $query SQL query to execute + * @param int $offset Offset for LIMIT statement + * @param int $numrows Number of rows for LIMIT statement + * @param array $params Values to be inserted in query + * + * @return PDOStatement|bool Query handle or False on error + */ + protected function _query($query, $offset, $numrows, $params) + { + $query = ltrim($query); + + $this->db_connect($this->dsn_select($query), true); + + // check connection before proceeding + if (!$this->is_connected()) { + return $this->last_result = false; + } + + if ($numrows || $offset) { + $query = $this->set_limit($query, $numrows, $offset); + } + + // replace self::DEFAULT_QUOTE with driver-specific quoting + $query = $this->query_parse($query); + + // Because in Roundcube we mostly use queries that are + // executed only once, we will not use prepared queries + $pos = 0; + $idx = 0; + $args = array(); + + if (count($params)) { + while ($pos = strpos($query, '?', $pos)) { + if ($query[$pos+1] == '?') { // skip escaped '?' + $pos += 2; + } + else { + $val = $this->quote($params[$idx++]); + + // long strings are not allowed inline, need to be parametrized + if (strlen($val) > 4000) { + $key = ':param' . (count($args) + 1); + $args[$key] = $params[$idx-1]; + $val = $key; + } + + unset($params[$idx-1]); + $query = substr_replace($query, $val, $pos, 1); + $pos += strlen($val); + } + } + } + + // replace escaped '?' back to normal, see self::quote() + $query = str_replace('??', '?', $query); + $query = rtrim($query, " \t\n\r\0\x0B;"); + + // log query + $this->debug($query); + + // destroy reference to previous result + $this->last_result = null; + $this->db_error_msg = null; + + // prepare query + $result = @oci_parse($this->dbh, $query); + $mode = $this->in_transaction ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS; + + if ($result) { + foreach ($args as $param => $arg) { + oci_bind_by_name($result, $param, $args[$param], -1, SQLT_LNG); + } + } + + // execute query + if (!$result || !@oci_execute($result, $mode)) { + $result = $this->handle_error($query, $result); + } + + return $this->last_result = $result; + } + + /** + * Helper method to handle DB errors. + * This by default logs the error but could be overriden by a driver implementation + * + * @param string Query that triggered the error + * @return mixed Result to be stored and returned + */ + protected function handle_error($query, $result = null) + { + $error = oci_error(is_resource($result) ? $result : $this->dbh); + + // @TODO: Find error codes for key errors + if (empty($this->options['ignore_key_errors']) || !in_array($error['code'], array('23000', '23505'))) { + $this->db_error = true; + $this->db_error_msg = sprintf('[%s] %s', $error['code'], $error['message']); + + rcube::raise_error(array('code' => 500, 'type' => 'db', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => $this->db_error_msg . " (SQL Query: $query)" + ), true, false); + } + + return false; + } + + /** + * Get last inserted record ID + * + * @param string $table Table name (to find the incremented sequence) + * + * @return mixed ID or false on failure + */ + public function insert_id($table = null) + { + if (!$this->db_connected || $this->db_mode == 'r' || empty($table)) { + return false; + } + + $sequence = $this->quote_identifier($this->sequence_name($table)); + $result = $this->query("SELECT $sequence.currval FROM dual"); + $result = $this->fetch_array($result); + + return $result[0] ?: false; + } + + /** + * Get number of affected rows for the last query + * + * @param mixed $result Optional query handle + * + * @return int Number of (matching) rows + */ + public function affected_rows($result = null) + { + if ($result || ($result === null && ($result = $this->last_result))) { + return oci_num_rows($result); + } + + return 0; + } + + /** + * Get number of rows for a SQL query + * If no query handle is specified, the last query will be taken as reference + * + * @param mixed $result Optional query handle + * @return mixed Number of rows or false on failure + * @deprecated This method shows very poor performance and should be avoided. + */ + public function num_rows($result = null) + { + // not implemented + return false; + } + + /** + * Get an associative array for one row + * If no query handle is specified, the last query will be taken as reference + * + * @param mixed $result Optional query handle + * + * @return mixed Array with col values or false on failure + */ + public function fetch_assoc($result = null) + { + return $this->_fetch_row($result, OCI_ASSOC); + } + + /** + * Get an index array for one row + * If no query handle is specified, the last query will be taken as reference + * + * @param mixed $result Optional query handle + * + * @return mixed Array with col values or false on failure + */ + public function fetch_array($result = null) + { + return $this->_fetch_row($result, OCI_NUM); + } + + /** + * Get col values for a result row + * + * @param mixed $result Optional query handle + * @param int $mode Fetch mode identifier + * + * @return mixed Array with col values or false on failure + */ + protected function _fetch_row($result, $mode) + { + if ($result || ($result === null && ($result = $this->last_result))) { + return oci_fetch_array($result, $mode + OCI_RETURN_NULLS + OCI_RETURN_LOBS); + } + + return false; + } + + /** + * Formats input so it can be safely used in a query + * PDO_OCI does not implement quote() method + * + * @param mixed $input Value to quote + * @param string $type Type of data (integer, bool, ident) + * + * @return string Quoted/converted string for use in query + */ + public function quote($input, $type = null) + { + // handle int directly for better performance + if ($type == 'integer' || $type == 'int') { + return intval($input); + } + + if (is_null($input)) { + return 'NULL'; + } + + if ($type == 'ident') { + return $this->quote_identifier($input); + } + + switch ($type) { + case 'bool': + case 'integer': + return intval($input); + default: + return "'" . strtr($input, array( + '?' => '??', + "'" => "''", + rcube_db::DEFAULT_QUOTE => rcube_db::DEFAULT_QUOTE . rcube_db::DEFAULT_QUOTE + )) . "'"; + } + } + + /** + * Return correct name for a specific database sequence + * + * @param string $table Table name + * + * @return string Translated sequence name + */ + protected function sequence_name($table) + { + // Note: we support only one sequence per table + // Note: The sequence name must be <table_name>_seq + $sequence = $table . '_seq'; + + // modify sequence name if prefix is configured + if ($prefix = $this->options['table_prefix']) { + return $prefix . $sequence; + } + + return $sequence; + } + + /** + * Return SQL statement for case insensitive LIKE + * + * @param string $column Field name + * @param string $value Search value + * + * @return string SQL statement to use in query + */ + public function ilike($column, $value) + { + return 'UPPER(' . $this->quote_identifier($column) . ') LIKE UPPER(' . $this->quote($value) . ')'; + } + + /** + * Return SQL function for current time and date + * + * @param int $interval Optional interval (in seconds) to add/subtract + * + * @return string SQL function to use in query + */ + public function now($interval = 0) + { + if ($interval) { + $interval = intval($interval); + return "current_timestamp + INTERVAL '$interval' SECOND"; + } + + return "current_timestamp"; + } + + /** + * Return SQL statement to convert a field value into a unix timestamp + * + * @param string $field Field name + * + * @return string SQL statement to use in query + * @deprecated + */ + public function unixtimestamp($field) + { + return "(($field - to_date('1970-01-01','YYYY-MM-DD')) * 60 * 60 * 24)"; + } + + /** + * Adds TOP (LIMIT,OFFSET) clause to the query + * + * @param string $query SQL query + * @param int $limit Number of rows + * @param int $offset Offset + * + * @return string SQL query + */ + protected function set_limit($query, $limit = 0, $offset = 0) + { + $limit = intval($limit); + $offset = intval($offset); + $end = $offset + $limit; + + // @TODO: Oracle 12g has better OFFSET support + + if (!$offset) { + $query = "SELECT * FROM ($query) a WHERE rownum <= $end"; + } + else { + $query = "SELECT * FROM (SELECT a.*, rownum as rn FROM ($query) a WHERE rownum <= $end) b WHERE rn > $offset"; + } + + return $query; + } + + /** + * Parse SQL file and fix table names according to table prefix + */ + protected function fix_table_names($sql) + { + if (!$this->options['table_prefix']) { + return $sql; + } + + $sql = parent::fix_table_names($sql); + + // replace sequence names, and other Oracle-specific commands + $sql = preg_replace_callback('/((SEQUENCE ["]?)([^" \r\n]+)/', + array($this, 'fix_table_names_callback'), + $sql + ); + + $sql = preg_replace_callback( + '/([ \r\n]+["]?)([^"\' \r\n\.]+)(["]?\.nextval)/', + array($this, 'fix_table_names_seq_callback'), + $sql + ); + + return $sql; + } + + /** + * Preg_replace callback for fix_table_names() + */ + protected function fix_table_names_seq_callback($matches) + { + return $matches[1] . $this->options['table_prefix'] . $matches[2] . $matches[3]; + } + + /** + * Returns connection options from DSN array + */ + protected function dsn_options($dsn) + { + $params = array(); + + if ($dsn['hostspec']) { + $host = $dsn['hostspec']; + if ($dsn['port']) { + $host .= ':' . $dsn['port']; + } + + $params['database'] = $host . '/' . $dsn['database']; + } + + $params['charset'] = 'UTF8'; + + return $params; + } + + /** + * Execute the given SQL script + * + * @param string SQL queries to execute + * + * @return boolen True on success, False on error + */ + public function exec_script($sql) + { + $sql = $this->fix_table_names($sql); + $buff = ''; + $body = false; + + foreach (explode("\n", $sql) as $line) { + $tok = strtolower(trim($line)); + if (preg_match('/^--/', $line) || $tok == '') { + continue; + } + + $buff .= $line . "\n"; + + // detect PL/SQL function bodies, don't break on semicolon + if ($body && $tok == 'end;') { + $body = false; + } + else if (!$body && $tok == 'begin') { + $body = true; + } + + if (!$body && substr($tok, -1) == ';') { + $this->query($buff); + $buff = ''; + if ($this->db_error) { + break; + } + } + } + + return !$this->db_error; + } + + /** + * Start transaction + * + * @return bool True on success, False on failure + */ + public function startTransaction() + { + $this->db_connect('w', true); + + // check connection before proceeding + if (!$this->is_connected()) { + return $this->last_result = false; + } + + $this->debug('BEGIN TRANSACTION'); + + return $this->last_result = $this->in_transaction = true; + } + + /** + * Commit transaction + * + * @return bool True on success, False on failure + */ + public function endTransaction() + { + $this->db_connect('w', true); + + // check connection before proceeding + if (!$this->is_connected()) { + return $this->last_result = false; + } + + $this->debug('COMMIT TRANSACTION'); + + if ($result = @oci_commit($this->dbh)) { + $this->in_transaction = true; + } + else { + $this->handle_error('COMMIT'); + } + + return $this->last_result = $result; + } + + /** + * Rollback transaction + * + * @return bool True on success, False on failure + */ + public function rollbackTransaction() + { + $this->db_connect('w', true); + + // check connection before proceeding + if (!$this->is_connected()) { + return $this->last_result = false; + } + + $this->debug('ROLLBACK TRANSACTION'); + + if ($result = @oci_rollback($this->dbh)) { + $this->in_transaction = false; + } + else { + $this->handle_error('ROLLBACK'); + } + + return $this->last_result = $this->dbh->rollBack(); + } +}
View file
kolab-syncroton-2.3.tar.gz/lib/kolab_sync.php
Changed
@@ -141,7 +141,7 @@ // Register Syncroton backends Syncroton_Registry::set('loggerBackend', $this->logger); - Syncroton_Registry::set(Syncroton_Registry::DATABASE, new kolab_sync_db); + Syncroton_Registry::set(Syncroton_Registry::DATABASE, $this->get_dbh()); Syncroton_Registry::set(Syncroton_Registry::TRANSACTIONMANAGER, kolab_sync_transaction_manager::getInstance()); Syncroton_Registry::set(Syncroton_Registry::DEVICEBACKEND, new kolab_sync_backend_device); Syncroton_Registry::set(Syncroton_Registry::FOLDERBACKEND, new kolab_sync_backend_folder);
View file
kolab-syncroton-2.3.tar.gz/lib/kolab_sync_backend.php
Changed
@@ -828,7 +828,7 @@ $data = json_encode($data); $db->set_option('ignore_key_errors', true); - $db->query("INSERT INTO syncroton_modseq (device_id, folder_id, synctime, data)" + $db->query("INSERT INTO `syncroton_modseq` (`device_id`, `folder_id`, `synctime`, `data`)" ." VALUES (?, ?, ?, ?)", $deviceid, $folderid, $synctime, $data); $db->set_option('ignore_key_errors', false); @@ -845,20 +845,20 @@ $rcube = rcube::get_instance(); $db = $rcube->get_dbh(); - $db->limitquery("SELECT data, synctime FROM syncroton_modseq" - ." WHERE device_id = ? AND folder_id = ? AND synctime <= ?" - ." ORDER BY synctime DESC", + $db->limitquery("SELECT `data`, `synctime` FROM `syncroton_modseq`" + ." WHERE `device_id` = ? AND `folder_id` = ? AND `synctime` <= ?" + ." ORDER BY `synctime` DESC", 0, 1, $deviceid, $folderid, $synctime); if ($row = $db->fetch_assoc()) { $synctime = $row['synctime']; // @TODO: make sure synctime from sql is in "Y-m-d H:i:s" format - $this->modseq[$folderid][$synctime] = json_decode($row['data']); + $this->modseq[$folderid][$synctime] = json_decode($row['data'], true); } // Cleanup: remove all records except the current one - $db->query("DELETE FROM syncroton_modseq" - ." WHERE device_id = ? AND folder_id = ? AND synctime <> ?", + $db->query("DELETE FROM `syncroton_modseq`" + ." WHERE `device_id` = ? AND `folder_id` = ? AND `synctime` <> ?", $deviceid, $folderid, $synctime); } @@ -866,6 +866,62 @@ } /** + * Set state of relation objects at specified point in time + */ + public function relations_state_set($deviceid, $folderid, $synctime, $relations) + { + $synctime = $synctime->format('Y-m-d H:i:s'); + $rcube = rcube::get_instance(); + $db = $rcube->get_dbh(); + $old_data = $this->relations[$folderid][$synctime]; + + if (empty($old_data)) { + $this->relations[$folderid][$synctime] = $relations; + $data = rcube_charset::clean(json_encode($relations)); + + $db->set_option('ignore_key_errors', true); + $db->query("INSERT INTO `syncroton_relations_state`" + ." (`device_id`, `folder_id`, `synctime`, `data`)" + ." VALUES (?, ?, ?, ?)", + $deviceid, $folderid, $synctime, $data); + $db->set_option('ignore_key_errors', false); + } + } + + /** + * Get state of relation objects at specified point in time + */ + public function relations_state_get($deviceid, $folderid, $synctime) + { + $synctime = $synctime->format('Y-m-d H:i:s'); + + if (empty($this->relations[$folderid][$synctime])) { + $this->relations[$folderid] = array(); + + $rcube = rcube::get_instance(); + $db = $rcube->get_dbh(); + + $db->limitquery("SELECT `data`, `synctime` FROM `syncroton_relations_state`" + ." WHERE `device_id` = ? AND `folder_id` = ? AND `synctime` <= ?" + ." ORDER BY `synctime` DESC", + 0, 1, $deviceid, $folderid, $synctime); + + if ($row = $db->fetch_assoc()) { + $synctime = $row['synctime']; + // @TODO: make sure synctime from sql is in "Y-m-d H:i:s" format + $this->relations[$folderid][$synctime] = json_decode($row['data'], true); + } + + // Cleanup: remove all records except the current one + $db->query("DELETE FROM `syncroton_relations_state`" + ." WHERE `device_id` = ? AND `folder_id` = ? AND `synctime` <> ?", + $deviceid, $folderid, $synctime); + } + + return @$this->relations[$folderid][$synctime]; + } + + /** * Compares two arrays * * @param array $array1
View file
kolab-syncroton-2.3.tar.gz/lib/kolab_sync_backend_common.php
Changed
@@ -90,17 +90,19 @@ throw new InvalidArgumentException('$object must be instanace of ' . $this->interface_name); } - $data = $this->object_to_array($object); - $insert = array(); + $data = $this->object_to_array($object); + $cols = array(); $data['id'] = $object->id = sha1(mt_rand(). microtime()); - foreach ($data as $key => $value) { - $insert[$this->db->quote_identifier($key)] = $this->db->quote($value); + foreach (array_keys($data) as $key) { + $cols[] = $this->db->quote_identifier($key); } - $this->db->query('INSERT INTO ' . $this->table_name - . ' (' . implode(', ', array_keys($insert)) . ')' . ' VALUES(' . implode(', ', $insert) . ')'); + $this->db->query('INSERT INTO `' . $this->table_name . '`' . ' (' . implode(', ', $cols) . ')' + . ' VALUES(' . implode(', ', array_fill(0, count($cols), '?')) . ')', + array_values($data) + ); if (!$this->db->insert_id($this->table_name)) { // @TODO: throw exception @@ -121,7 +123,7 @@ $id = $id instanceof $this->interface_name ? $id->id : $id; if ($id) { - $select = $this->db->query('SELECT * FROM ' . $this->table_name . ' WHERE id = ?', array($id)); + $select = $this->db->query('SELECT * FROM `' . $this->table_name . '` WHERE `id` = ?', array($id)); $data = $this->db->fetch_assoc($select); } @@ -147,7 +149,7 @@ return false; } - $result = $this->db->query('DELETE FROM ' . $this->table_name .' WHERE id = ?', array($id)); + $result = $this->db->query('DELETE FROM `' . $this->table_name .'` WHERE `id` = ?', array($id)); return (bool) $this->db->affected_rows($result); } @@ -169,12 +171,12 @@ $data = $this->object_to_array($object); $set = array(); - foreach ($data as $key => $value) { - $set[] = $this->db->quote_identifier($key) . ' = ' . $this->db->quote($value); + foreach (array_keys($data) as $key) { + $set[] = $this->db->quote_identifier($key) . ' = ?'; } - $this->db->query('UPDATE ' . $this->table_name . ' SET ' . implode(', ', $set) - . ' WHERE ' . $this->db->quote_identifier('id') . ' = ' . $this->db->quote($object->id)); + $this->db->query('UPDATE `' . $this->table_name . '` SET ' . implode(', ', $set) + . ' WHERE `id` = ' . $this->db->quote($object->id), array_values($data)); return $object; }
View file
kolab-syncroton-2.3.tar.gz/lib/kolab_sync_backend_content.php
Changed
@@ -42,7 +42,7 @@ { $id = $id instanceof Syncroton_Model_IContent ? $id->id : $id; - $result = $this->db->query('UPDATE ' . $this->table_name . ' SET is_deleted = 1 WHERE id = ?', array($id)); + $result = $this->db->query("UPDATE `{$this->table_name}` SET `is_deleted` = 1 WHERE `id` = ?", array($id)); if ($result = (bool) $this->db->affected_rows($result)) { unset($this->cache['content_folderstate']); @@ -67,7 +67,7 @@ $where[] = $this->db->quote_identifier('contentid') . ' = ' . $this->db->quote($_contentId); $where[] = $this->db->quote_identifier('is_deleted') . ' = 0'; - $select = $this->db->query('SELECT * FROM ' . $this->table_name .' WHERE ' . implode(' AND ', $where)); + $select = $this->db->query("SELECT * FROM `{$this->table_name}` WHERE " . implode(' AND ', $where)); $state = $this->db->fetch_assoc($select); if (empty($state)) { @@ -100,7 +100,7 @@ $where[] = $this->db->quote_identifier('folder_id') . ' = ' . $this->db->quote($folderId); $where[] = $this->db->quote_identifier('is_deleted') . ' = 0'; - $select = $this->db->query('SELECT contentid FROM ' . $this->table_name .' WHERE ' . implode(' AND ', $where)); + $select = $this->db->query("SELECT `contentid` FROM `{$this->table_name}` WHERE " . implode(' AND ', $where)); $result = array(); while ($state = $this->db->fetch_assoc($select)) { @@ -127,6 +127,6 @@ $where[] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($deviceId); $where[] = $this->db->quote_identifier('folder_id') . ' = ' . $this->db->quote($folderId); - $this->db->query('DELETE FROM ' . $this->table_name .' WHERE ' . implode(' AND ', $where)); + $this->db->query("DELETE FROM `{$this->table_name}` WHERE " . implode(' AND ', $where)); } }
View file
kolab-syncroton-2.3.tar.gz/lib/kolab_sync_backend_folder.php
Changed
@@ -42,7 +42,7 @@ $where[] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); - $this->db->query('DELETE FROM ' . $this->table_name .' WHERE ' . implode(' AND ', $where)); + $this->db->query('DELETE FROM `' . $this->table_name . '` WHERE ' . implode(' AND ', $where)); } /** @@ -60,7 +60,7 @@ $where[] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); $where[] = $this->db->quote_identifier('class') . ' = ' . $this->db->quote($class); - $select = $this->db->query('SELECT * FROM ' . $this->table_name .' WHERE ' . implode(' AND ', $where)); + $select = $this->db->query('SELECT * FROM `' . $this->table_name .'` WHERE ' . implode(' AND ', $where)); $result = array(); while ($folder = $this->db->fetch_assoc($select)) { @@ -85,7 +85,7 @@ $where[] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); $where[] = $this->db->quote_identifier('folderid') . ' = ' . $this->db->quote($folderid); - $select = $this->db->query('SELECT * FROM ' . $this->table_name .' WHERE ' . implode(' AND ', $where)); + $select = $this->db->query('SELECT * FROM `' . $this->table_name . '` WHERE ' . implode(' AND ', $where)); $folder = $this->db->fetch_assoc($select); if (empty($folder)) {
View file
kolab-syncroton-2.3.tar.gz/lib/kolab_sync_backend_state.php
Changed
@@ -61,7 +61,7 @@ $where[] = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($state->type); $where[] = $this->db->quote_identifier('counter') . ' <> ' . $this->db->quote($state->counter); - $this->db->query('DELETE FROM ' . $this->table_name .' WHERE ' . implode(' AND ', $where)); + $this->db->query("DELETE FROM `{$this->table_name}` WHERE " . implode(' AND ', $where)); } /** @@ -108,8 +108,8 @@ $where[] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); $where[] = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($folder_id); - $select = $this->db->limitquery('SELECT * FROM ' . $this->table_name . ' WHERE ' . implode(' AND ', $where) - .' ORDER BY counter DESC', 0, 1); + $select = $this->db->limitquery("SELECT * FROM `{$this->table_name}` WHERE " . implode(' AND ', $where) + . " ORDER BY `counter` DESC", 0, 1); $state = $this->db->fetch_assoc($select); @@ -134,7 +134,7 @@ $where[] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); $where[] = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($folder_id); - $this->db->query('DELETE FROM ' . $this->table_name .' WHERE ' . implode(' AND ', $where)); + $this->db->query("DELETE FROM `{$this->table_name}` WHERE " . implode(' AND ', $where)); } /** @@ -158,7 +158,7 @@ $where['device_id'] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); $where['type'] = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($folder_id); - $select = $this->db->query('SELECT * FROM ' . $this->table_name .' WHERE ' . implode(' AND ', $where)); + $select = $this->db->query("SELECT * FROM `{$this->table_name}` WHERE " . implode(' AND ', $where)); while ($row = $this->db->fetch_assoc($select)) { $states[$row['counter']] = $this->get_object($row); @@ -181,17 +181,17 @@ if ($next > $sync_key) { $where['synckey'] = $this->db->quote_identifier('creation_synckey') . ' = ' . $this->db->quote($state->counter); // undelete entries marked as deleted in syncroton_content table - $this->db->query('UPDATE syncroton_content SET is_deleted = 0 WHERE ' . implode(' AND ', $where)); + $this->db->query("UPDATE `syncroton_content` SET `is_deleted` = 0 WHERE " . implode(' AND ', $where)); // remove entries added during latest sync in syncroton_content table unset($where['is_deleted']); $where['synckey'] = $this->db->quote_identifier('creation_synckey') . ' > ' . $this->db->quote($state->counter); - $this->db->query('DELETE FROM syncroton_content WHERE ' . implode(' AND ', $where)); + $this->db->query("DELETE FROM `syncroton_content` WHERE " . implode(' AND ', $where)); } else { // finaly delete all entries marked for removal in syncroton_content table - $this->db->query('DELETE FROM syncroton_content WHERE ' . implode(' AND ', $where)); + $this->db->query("DELETE FROM `syncroton_content` WHERE " . implode(' AND ', $where)); } // remove all other synckeys
View file
kolab-syncroton-2.3.tar.gz/lib/kolab_sync_data.php
Changed
@@ -536,12 +536,15 @@ if (empty($filter)) { $filter = array(); } + else { + $changed_objects = $this->getChangesByRelations($folderid, $filter); + } $result = $result_type == self::RESULT_COUNT ? 0 : array(); $found = 0; - foreach ($folders as $folderid) { - $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid); + foreach ($folders as $folder_id) { + $foldername = $this->backend->folder_id2name($folder_id, $this->device->deviceid); if ($foldername === null || !($folder = $this->getFolderObject($foldername))) { continue; @@ -577,108 +580,183 @@ if ($error) { throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); } + + // handle tag modifications + if (!empty($changed_objects)) { + // build new filter + // search objects mathing current filter, + // relations may contain members of many types, we need to + // search them by UID in all requested folders to get + // only these with requested type (and that really exist + // in specified folders) + $tag_filter = array(array('uid', '=', $changed_objects)); + foreach ($filter as $f) { + if ($f[0] != 'changed') { + $tag_filter[] = $f; + } + } + + switch ($result_type) { + case self::RESULT_COUNT: + // Note: this way we're potentally counting the same objects twice + // I'm not sure if this is a problem, we most likely do not + // need a precise result here + $count = $folder->count($tag_filter); + if ($count !== null && $count !== false) { + $result += (int) $count; + } + + break; + + case self::RESULT_UID: + $uids = $folder->get_uids($tag_filter); + if (is_array($uids)) { + $result = array_unique(array_merge($result, $uids)); + } + + break; + } + } } if (!$found) { throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); } - // consider Tag object modifications if needed - if ($this->tag_categories) { - $this->searchEntriesByTagChanges($filter, $folders, $result_type, $result); - } - return $result; } /** - * Checks if any Tags have been modified in specified time - * and returns UIDs (or count) of objects assigned to them + * Detect changes of relation (tag) objects data and assigned objects + * Returns relation member identifiers */ - protected function searchEntriesByTagChanges($filter, $folders, $result_type, &$result) + protected function getChangesByRelations($folderid, $filter) { - $tag_filter = array(); - $obj_filter = array(); + if (!$this->tag_categories) { + return; + } // get period filter, create new objects filter foreach ($filter as $f) { - if ($f[0] == 'changed') { - $tag_filter[] = $f; - } - else { - $obj_filter[] = $f; + if ($f[0] == 'changed' && $f[1] == '>') { + $since = $f[2]; } } // this is not search for changes, do nothing - if (empty($tag_filter)) { + if (empty($since)) { return; } - // we're detecting changes here, let's check if any tags have been modified - // we're covering here cases when tag name or tag assignment has been changed + // get relations state from the last sync + $last_state = (array) $this->backend->relations_state_get($this->device->id, $folderid, $since); + // get current relations state $config = kolab_storage_config::get_instance(); - $members = array(); $default = true; $filter = array( array('type', '=', 'relation'), array('category', '=', 'tag') ); - $filter = array_merge($filter, $tag_filter); - $tags = $config->get_objects($filter, $default, 100); + $relations = $config->get_objects($filter, $default, 100); - // get UIDs of tag members - foreach ($tags as $tag) { - foreach ((array)$tag['members'] as $url) { - if (strpos($url, 'urn:uuid:') === 0) { - $members[] = substr($url, 9); - } + $result = array(); + $changed = false; + + // compare states, get members of changed relations + foreach ($relations as $idx => $relation) { + $rel_id = $relation['uid']; + + if ($relation['changed']) { + $relation['changed']->setTimezone(new DateTimeZone('UTC')); } - } - $members = array_unique($members); + // last state unknown... + if (empty($last_state[$rel_id])) { + // ...get all members + if (!empty($relation['members'])) { + $changed = true; + $result = array_merge($result, $relation['members']); + } + } + // last state known, changed tag name... + else if ($last_state[$rel_id]['name'] != $relation['name']) { + // ...get all (old and new) members + $members_old = explode("\n", $last_state[$rel_id]['members']); + $changed = true; + $members = array_unique(array_merge($relation['members'], $members_old)); + $result = array_merge($result, $members); + } + // last state known, any other change change... + else if ($last_state[$rel_id]['changed'] < $relation['changed']->format('U')) { + // ...find new and removed members + $members_old = explode("\n", $last_state[$rel_id]['members']); + $new = array_diff($relation['members'], $members_old); + $removed = array_diff($members_old, $relation['members']); + + if (!empty($new) || !empty($removed)) { + $changed = true; + $result = array_merge($result, $new, $removed); + } + } - // if we have UIDs in result already we can remove them here - // FIXME: if the result is an int we can end up - // with wrong result (some objects might be counted twice) - if ($result_type == self::RESULT_UID) { - $members = array_diff($members, $result); + unset($last_state[$rel_id]); } - if (empty($members)) { - return; + // get members of deleted relations + if (!empty($last_state)) { + $changed = true; + foreach ($last_state as $relation) { + $members = explode("\n", $relation['members']); + $result = array_merge($result, $members); + } } - // search objects mathing current filter, - // tags/relations may contain members of many types, we need to - // search them by UID in all requested folders to get - // only these with requested type (and that really exist) - $obj_filter[] = array('uid', '=', $members); - - foreach ($folders as $folderid) { - $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid); - - if ($foldername === null || !($folder = $this->getFolderObject($foldername))) { - continue; + // save current state + if ($changed) { + $data = array(); + foreach ($relations as $relation) { + $data[$relation['uid']] = array( + 'name' => $relation['name'], + 'changed' => $relation['changed']->format('U'), + 'members' => implode("\n", $relation['members']), + ); } - switch ($result_type) { - case self::RESULT_COUNT: - $count = $folder->count($obj_filter); - $result += (int) $count; - break; + $now = new DateTime('now', new DateTimeZone('UTC')); - case self::RESULT_UID: - $uids = $folder->get_uids($obj_filter); + $this->backend->relations_state_set($this->device->id, $folderid, $now, $data); + } - if (is_array($uids)) { - $result = array_merge($result, $uids); + // in mail mode return only message URIs + if ($this->modelName == 'mail') { + // lambda function to skip email members + $filter_func = function($value) { + return strpos($value, 'imap://') === 0; + }; + + $result = array_filter(array_unique($result), $filter_func); + } + // otherwise return only object UIDs + else { + // lambda function to skip email members + $filter_func = function($value) { + return strpos($value, 'urn:uuid:') === 0; + }; + + // lambda function to parse member URI + $member_func = function($value) { + if (strpos($value, 'urn:uuid:') === 0) { + $value = substr($value, 9); } - break; - } + return $value; + }; + + $result = array_map($member_func, array_filter(array_unique($result), $filter_func)); } + + return $result; } /**
View file
kolab-syncroton-2.3.tar.gz/lib/kolab_sync_data_email.php
Changed
@@ -805,6 +805,9 @@ } } + // get members of modified relations + $changed_msgs = $this->getChangesByRelations($folderid, $filter); + $result = $result_type == self::RESULT_COUNT ? 0 : array(); // no sorting for best performance $sort_by = null; @@ -830,6 +833,7 @@ } // We're in "get changes" mode + $modified = false; if (isset($modseq_data)) { $folder_data = $this->storage->folder_data($foldername); $got_changes = true; @@ -848,7 +852,7 @@ // We can only get folder's HIGHESTMODSEQ value and store it for the next try // Skip search if HIGHESTMODSEQ didn't change if (!$got_changes || empty($modseq) || empty($modseq[$foldername])) { - continue; + $modified = true; } $filter_str .= " MODSEQ " . ($modseq[$foldername] + 1); @@ -865,25 +869,45 @@ // $search = $this->storage->index($foldername, null, null, true, true); // else - $search = $this->storage->search_once($foldername, $filter_str); + if ($modified) { + $search = $this->storage->search_once($foldername, $filter_str); + + if (!($search instanceof rcube_result_index) || $search->is_error()) { + throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); + } - if (!($search instanceof rcube_result_index) || $search->is_error()) { - throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); + switch ($result_type) { + case self::RESULT_COUNT: + $result += (int) $search->count(); + break; + + case self::RESULT_UID: + if ($uids = $search->get()) { + foreach ($uids as $idx => $uid) { + $uids[$idx] = $this->createMessageId($folder_id, $uid); + } + $result = array_merge($result, $uids); + } + break; + } } - switch ($result_type) { - case self::RESULT_COUNT: - $result += (int) $search->count(); - break; + // handle relation changes + if (!empty($changed_msgs)) { + $uids = $this->findRelationMembersInFolder($foldername, $changed_msgs, $filter); - case self::RESULT_UID: - if ($uids = $search->get()) { + switch ($result_type) { + case self::RESULT_COUNT: + $result += (int) count($uids); + break; + + case self::RESULT_UID: foreach ($uids as $idx => $uid) { $uids[$idx] = $this->createMessageId($folder_id, $uid); } - $result = array_merge($result, $uids); + $result = array_unique(array_merge($result, $uids)); + break; } - break; } } @@ -906,12 +930,93 @@ } } - // @TODO: tag objects modification detection - return $result; } /** + * Find members (messages) in specified folder + */ + protected function findRelationMembersInFolder($foldername, $members, $filter) + { + foreach ($members as $member) { + // IMAP URI members + if ($url = kolab_storage_config::parse_member_url($member)) { + $result[$url['folder']][$url['uid']] = $url['params']; + } + } + + // convert filter into one IMAP search string + $filter_str = 'ALL UNDELETED'; + foreach ($filter as $idx => $filter_item) { + if (is_string($filter_item)) { + $filter_str .= ' ' . $filter_item; + } + } + + $rcube = rcube::get_instance(); + $storage = $rcube->get_storage(); + $found = array(); + + // first find messages by UID + if (!empty($result[$foldername])) { + $index = $storage->search_once($foldername, 'UID ' + . rcube_imap_generic::compressMessageSet(array_keys($result[$foldername]))); + $found = $index->get(); + + // remove found messages from the $result + if (!empty($found)) { + $result[$foldername] = array_diff_key($result[$foldername], array_flip($found)); + + if (empty($result[$foldername])) { + unset($result[$foldername]); + } + + // now apply the current filter to the found messages + $index = $storage->search_once($foldername, $filter_str . ' UID ' + . rcube_imap_generic::compressMessageSet($found)); + $found = $index->get(); + } + } + + // search by message parameters + if (!empty($result)) { + // @TODO: do this search in chunks (for e.g. 25 messages)? + $search = ''; + $search_count = 0; + + foreach ($result as $folder => $data) { + foreach ($data as $p) { + $search_params = array(); + $search_count++; + + 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 .= ' (' . implode(' ', $search_params) . ')'; + } + } + + $search_str .= ' ' . str_repeat(' OR', $search_count-1) . $search; + + // search messages in current folder + $search = $storage->search_once($foldername, $search_str); + $uids = $search->get(); + + if (!empty($uids)) { + // add UIDs into the result + $found = array_unique(array_merge($found, $uids)); + } + } + + return $found; + } + + /** * ActiveSync Search handler * * @param Syncroton_Model_StoreRequest $store Search query
View file
kolab-syncroton-2.3.tar.gz/lib/kolab_sync_db.php
Deleted
@@ -1,64 +0,0 @@ -<?php - -/** - +--------------------------------------------------------------------------+ - | Kolab Sync (ActiveSync for Kolab) | - | | - | Copyright (C) 2011-2012, Kolab Systems AG <contact@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/> | - +--------------------------------------------------------------------------+ - | Author: Aleksander Machniak <machniak@kolabsys.com> | - +--------------------------------------------------------------------------+ -*/ - -/** - * Database layer wrapper with transaction support - */ -class kolab_sync_db -{ - /** - * the database adapter - * - * @var rcube_db - */ - protected $db; - - - public function __construct() - { - $this->db = rcube::get_instance()->get_dbh(); - } - - public function beginTransaction() - { - $query = 'BEGIN'; - - $this->db->query($query); - } - - public function commit() - { - $query = 'COMMIT'; - - $this->db->query($query); - } - - public function rollBack() - { - $query = 'ROLLBACK'; - - $this->db->query($query); - } -}
View file
kolab-syncroton-2.3.tar.gz/lib/kolab_sync_transaction_manager.php
Changed
@@ -103,13 +103,8 @@ } if (! in_array($_transactionable, $this->_openTransactionables)) { - if ($this->_logger instanceof Zend_Log) { - $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " new transactionable. Starting transaction on this resource"); - } - - if ($_transactionable instanceof kolab_sync_db) { - //setAutocommit($_transactionable,false); - $_transactionable->beginTransaction(); + if ($_transactionable instanceof rcube_db) { + $_transactionable->startTransaction(); } else { $this->rollBack(); @@ -122,10 +117,6 @@ $transactionId = sha1(mt_rand(). microtime()); array_push($this->_openTransactions, $transactionId); - if ($this->_logger instanceof Zend_Log) { - $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " queued transaction with id $transactionId"); - } - return $transactionId; } @@ -149,19 +140,14 @@ $numOpenTransactions = count($this->_openTransactions); if ($numOpenTransactions === 0) { - if ($this->_logger instanceof Zend_Log) { - $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " no more open transactions in queue commiting all transactionables"); - } - foreach ($this->_openTransactionables as $transactionableIdx => $transactionable) { - if ($transactionable instanceof kolab_sync_db) { - $transactionable->commit(); - //setAutocommit($transactionable,true); + if ($transactionable instanceof rcube_db) { + $transactionable->endTransaction(); } } $this->_openTransactionables = array(); - $this->_openTransactions = array(); + $this->_openTransactions = array(); } else { if ($this->_logger instanceof Zend_Log) { @@ -182,13 +168,12 @@ } foreach ($this->_openTransactionables as $transactionable) { - if ($transactionable instanceof kolab_sync_db) { - $transactionable->rollBack(); - //setAutocommit($transactionable,true); + if ($transactionable instanceof rcube_db) { + $transactionable->rollbackTransaction(); } } $this->_openTransactionables = array(); - $this->_openTransactions = array(); + $this->_openTransactions = array(); } }
View file
kolab-syncroton.dsc
Changed
@@ -2,7 +2,7 @@ Source: kolab-syncroton Binary: kolab-syncroton Architecture: all -Version: 2.3~dev20140915-0~kolab4 +Version: 2.3~dev20141017-0~kolab1 Maintainer: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> Uploaders: Paul Klos <kolab@klos2day.nl> Homepage: http://www.kolab.org/
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.