Page MenuHomePhorge

No OneTemporary

Size
556 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/docs/syncroton.sql b/docs/syncroton.sql
index bcd97b7..5e140d9 100644
--- a/docs/syncroton.sql
+++ b/docs/syncroton.sql
@@ -1,57 +1,125 @@
+CREATE TABLE IF NOT EXISTS `syncroton_policy` (
+ `id` varchar(40) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `description` varchar(255) DEFAULT NULL,
+ `policy_key` varchar(64) NOT NULL,
+ `allow_bluetooth` int(11) DEFAULT NULL,
+ `allow_browser` int(11) DEFAULT NULL,
+ `allow_camera` int(11) DEFAULT NULL,
+ `allow_consumer_email` int(11) DEFAULT NULL,
+ `allow_desktop_sync` int(11) DEFAULT NULL,
+ `allow_h_t_m_l_email` int(11) DEFAULT NULL,
+ `allow_internet_sharing` int(11) DEFAULT NULL,
+ `allow_ir_d_a` int(11) DEFAULT NULL,
+ `allow_p_o_p_i_m_a_p_email` int(11) DEFAULT NULL,
+ `allow_remote_desktop` int(11) DEFAULT NULL,
+ `allow_simple_device_password` int(11) DEFAULT NULL,
+ `allow_s_m_i_m_e_encryption_algorithm_negotiation` int(11) DEFAULT NULL,
+ `allow_s_m_i_m_e_soft_certs` int(11) DEFAULT NULL,
+ `allow_storage_card` int(11) DEFAULT NULL,
+ `allow_text_messaging` int(11) DEFAULT NULL,
+ `allow_unsigned_applications` int(11) DEFAULT NULL,
+ `allow_unsigned_installation_packages` int(11) DEFAULT NULL,
+ `allow_wifi` int(11) DEFAULT NULL,
+ `alphanumeric_device_password_required` int(11) DEFAULT NULL,
+ `approved_application_list` varchar(255) DEFAULT NULL,
+ `attachments_enabled` int(11) DEFAULT NULL,
+ `device_password_enabled` int(11) DEFAULT NULL,
+ `device_password_expiration` int(11) DEFAULT NULL,
+ `device_password_history` int(11) DEFAULT NULL,
+ `max_attachment_size` int(11) DEFAULT NULL,
+ `max_calendar_age_filter` int(11) DEFAULT NULL,
+ `max_device_password_failed_attempts` int(11) DEFAULT NULL,
+ `max_email_age_filter` int(11) DEFAULT NULL,
+ `max_email_body_truncation_size` int(11) DEFAULT NULL,
+ `max_email_h_t_m_l_body_truncation_size` int(11) DEFAULT NULL,
+ `max_inactivity_time_device_lock` int(11) DEFAULT NULL,
+ `min_device_password_complex_characters` int(11) DEFAULT NULL,
+ `min_device_password_length` int(11) DEFAULT NULL,
+ `password_recovery_enabled` int(11) DEFAULT NULL,
+ `require_device_encryption` int(11) DEFAULT NULL,
+ `require_encrypted_s_m_i_m_e_messages` int(11) DEFAULT NULL,
+ `require_encryption_s_m_i_m_e_algorithm` int(11) DEFAULT NULL,
+ `require_manual_sync_when_roaming` int(11) DEFAULT NULL,
+ `require_signed_s_m_i_m_e_algorithm` int(11) DEFAULT NULL,
+ `require_signed_s_m_i_m_e_messages` int(11) DEFAULT NULL,
+ `require_storage_card_encryption` int(11) DEFAULT NULL,
+ `unapproved_in_r_o_m_application_list` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB;
+
CREATE TABLE IF NOT EXISTS `syncroton_device` (
`id` varchar(40) NOT NULL,
`deviceid` varchar(64) NOT NULL,
`devicetype` varchar(64) NOT NULL,
- `policykey` varchar(64) DEFAULT NULL,
- `policy_id` varchar(40) NOT NULL,
`owner_id` varchar(40) NOT NULL,
- `useragent` varchar(255) 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` int(11) DEFAULT NULL,
`remotewipe` int(11) DEFAULT '0',
`pingfolder` longblob,
- PRIMARY KEY (`id`)
+ `contactsfilter_id` varchar(40) DEFAULT NULL,
+ `calendarfilter_id` varchar(40) DEFAULT NULL,
+ `tasksfilter_id` varchar(40) DEFAULT NULL,
+ `emailfilter_id` varchar(40) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `owner_id--deviceid` (`owner_id`, `deviceid`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `syncroton_folder` (
- `id` varchar(40) NOT NULL,
- `device_id` varchar(40) NOT NULL,
- `class` varchar(64) NOT NULL,
- `folderid` varchar(254) NOT NULL,
- `parentid` varchar(254) DEFAULT NULL,
- `displayname` varchar(254) NOT NULL,
- `type` int(11) NOT NULL,
- `creation_time` datetime NOT NULL,
- `lastfiltertype` int(11) DEFAULT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `device_id--class--folderid` (`device_id`,`class`,`folderid`),
- CONSTRAINT `syncroton_folder::device_id--syncroton_device::id` FOREIGN KEY (`device_id`)
- REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+ `id` varchar(40) NOT NULL,
+ `device_id` varchar(40) NOT NULL,
+ `class` varchar(64) NOT NULL,
+ `folderid` varchar(254) NOT NULL,
+ `parentid` varchar(254) DEFAULT NULL,
+ `displayname` varchar(254) NOT NULL,
+ `type` int(11) NOT NULL,
+ `creation_time` datetime NOT NULL,
+ `lastfiltertype` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `device_id--class--folderid` (`device_id`(40),`class`(40),`folderid`(40)),
+ KEY `folderstates::device_id--devices::id` (`device_id`),
+ CONSTRAINT `folderstates::device_id--devices::id` FOREIGN KEY (`device_id`) REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
-CREATE TABLE `syncroton_synckey` (
- `id` varchar(40) NOT NULL,
- `device_id` varchar(40) NOT NULL DEFAULT '',
- `type` varchar(64) NOT NULL DEFAULT '',
- `counter` int(11) unsigned NOT NULL DEFAULT '0',
- `lastsync` datetime DEFAULT NULL,
- `pendingdata` longblob,
- PRIMARY KEY (`id`),
- UNIQUE KEY `device_id--type--counter` (`device_id`,`type`,`counter`),
- CONSTRAINT `syncroton_synckey::device_id--syncroton_device::id` FOREIGN KEY (`device_id`)
- REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+CREATE TABLE IF NOT EXISTS `syncroton_synckey` (
+ `id` varchar(40) NOT NULL,
+ `device_id` varchar(40) NOT NULL DEFAULT '',
+ `type` varchar(64) NOT NULL DEFAULT '',
+ `counter` int(11) NOT NULL DEFAULT '0',
+ `lastsync` datetime DEFAULT NULL,
+ `pendingdata` longblob,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `device_id--type--counter` (`device_id`,`type`,`counter`),
+ CONSTRAINT `syncroton_synckey::device_id--syncroton_device::id` FOREIGN KEY (`device_id`) REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
-CREATE TABLE `syncroton_content` (
- `id` varchar(40) NOT NULL,
- `device_id` varchar(40) DEFAULT NULL,
- `folder_id` varchar(40) DEFAULT NULL,
- `contentid` varchar(64) DEFAULT NULL,
- `creation_time` datetime DEFAULT NULL,
- `creation_synckey` int(11) NOT NULL,
- `is_deleted` tinyint(1) unsigned DEFAULT '0',
- PRIMARY KEY (`id`),
- UNIQUE KEY `device_id--folder_id--contentid` (`device_id`,`folder_id`,`contentid`),
- CONSTRAINT `syncroton_content::device_id--syncroton_device::id` FOREIGN KEY (`device_id`)
- REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+CREATE TABLE IF NOT EXISTS `syncroton_content` (
+ `id` varchar(40) NOT NULL,
+ `device_id` varchar(40) DEFAULT NULL,
+ `folder_id` varchar(40) DEFAULT NULL,
+ `contentid` varchar(64) DEFAULT NULL,
+ `creation_time` datetime DEFAULT NULL,
+ `creation_synckey` int(11) NOT NULL,
+ `is_deleted` tinyint(1) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `device_id--folder_id--contentid` (`device_id`(40),`folder_id`(40),`contentid`(40)),
+ KEY `syncroton_contents::device_id` (`device_id`),
+ CONSTRAINT `syncroton_contents::device_id--syncroton_device::id` FOREIGN KEY (`device_id`) REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB;
+
+CREATE TABLE IF NOT EXISTS `syncroton_data` (
+ `id` varchar(40) NOT NULL,
+ `type` varchar(40) NOT NULL,
+ `folder_id` varchar(40) NOT NULL,
+ `data` longblob,
+ PRIMARY KEY (`id`)
) ENGINE=InnoDB;
diff --git a/lib/ext/Roundcube/rcube_mdb2.php b/lib/ext/Roundcube/rcube_mdb2.php
deleted file mode 100644
index 721963b..0000000
--- a/lib/ext/Roundcube/rcube_mdb2.php
+++ /dev/null
@@ -1,935 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_mdb2.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2009, The Roundcube Dev Team |
- | |
- | 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: |
- | PEAR:DB wrapper class that implements PEAR MDB2 functions |
- | See http://pear.php.net/package/MDB2 |
- | |
- +-----------------------------------------------------------------------+
- | Author: Lukas Kahwe Smith <smith@pooteeweet.org> |
- +-----------------------------------------------------------------------+
-*/
-
-
-/**
- * Database independent query interface
- *
- * This is a wrapper for the PEAR::MDB2 class
- *
- * @package Database
- * @author David Saez Padros <david@ols.es>
- * @author Thomas Bruederli <roundcube@gmail.com>
- * @author Lukas Kahwe Smith <smith@pooteeweet.org>
- * @version 1.18
- * @link http://pear.php.net/package/MDB2
- */
-class rcube_mdb2
-{
- public $db_dsnw; // DSN for write operations
- public $db_dsnr; // DSN for read operations
- public $db_connected = false; // Already connected ?
- public $db_mode = ''; // Connection mode
- public $db_handle = 0; // Connection handle
- public $db_error = false;
- public $db_error_msg = '';
-
- private $debug_mode = false;
- private $conn_failure = false;
- private $a_query_results = array('dummy');
- private $last_res_id = 0;
- private $tables;
- private $variables;
-
-
- /**
- * Object constructor
- *
- * @param string $db_dsnw DSN for read/write operations
- * @param string $db_dsnr Optional DSN for read only operations
- */
- public function __construct($db_dsnw, $db_dsnr='', $pconn=false)
- {
- if (empty($db_dsnr)) {
- $db_dsnr = $db_dsnw;
- }
-
- $this->db_dsnw = $db_dsnw;
- $this->db_dsnr = $db_dsnr;
- $this->db_pconn = $pconn;
-
- $dsn_array = MDB2::parseDSN($db_dsnw);
- $this->db_provider = $dsn_array['phptype'];
- }
-
-
- /**
- * Connect to specific database
- *
- * @param string $dsn DSN for DB connections
- * @return MDB2 PEAR database handle
- * @access private
- */
- private function dsn_connect($dsn)
- {
- // Use persistent connections if available
- $db_options = array(
- 'persistent' => $this->db_pconn,
- 'emulate_prepared' => $this->debug_mode,
- 'debug' => $this->debug_mode,
- 'debug_handler' => array($this, 'debug_handler'),
- 'portability' => MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_EMPTY_TO_NULL,
- );
-
- if ($this->db_provider == 'pgsql') {
- $db_options['disable_smart_seqname'] = true;
- $db_options['seqname_format'] = '%s';
- }
- $this->db_error = false;
- $this->db_error_msg = null;
-
- $dbh = MDB2::connect($dsn, $db_options);
-
- if (MDB2::isError($dbh)) {
- $this->db_error = true;
- $this->db_error_msg = $dbh->getMessage();
-
- rcube::raise_error(array('code' => 500, 'type' => 'db',
- 'line' => __LINE__, 'file' => __FILE__,
- 'message' => $dbh->getUserInfo()), true, false);
- }
- else if ($this->db_provider == 'sqlite') {
- $dsn_array = MDB2::parseDSN($dsn);
- if (!filesize($dsn_array['database']) && !empty($this->sqlite_initials)) {
- $this->sqlite_create_database($dbh, $this->sqlite_initials);
- }
- }
- else if ($this->db_provider != 'mssql' && $this->db_provider != 'sqlsrv') {
- $dbh->setCharset('utf8');
- }
-
- return $dbh;
- }
-
-
- /**
- * Connect to appropiate database depending on the operation
- *
- * @param string $mode Connection mode (r|w)
- */
- public function db_connect($mode)
- {
- // previous connection failed, don't attempt to connect again
- if ($this->conn_failure) {
- return;
- }
-
- // no replication
- if ($this->db_dsnw == $this->db_dsnr) {
- $mode = 'w';
- }
-
- // Already connected
- if ($this->db_connected) {
- // connected to db with the same or "higher" mode
- if ($this->db_mode == 'w' || $this->db_mode == $mode) {
- return;
- }
- }
-
- $dsn = ($mode == 'r') ? $this->db_dsnr : $this->db_dsnw;
-
- $this->db_handle = $this->dsn_connect($dsn);
- $this->db_connected = !PEAR::isError($this->db_handle);
-
- // use write-master when read-only fails
- if (!$this->db_connected && $mode == 'r') {
- $mode = 'w';
- $this->db_handle = $this->dsn_connect($this->db_dsnw);
- $this->db_connected = !PEAR::isError($this->db_handle);
- }
-
- if ($this->db_connected) {
- $this->db_mode = $mode;
- }
- else {
- $this->conn_failure = true;
- }
- }
-
-
- /**
- * Activate/deactivate debug mode
- *
- * @param boolean $dbg True if SQL queries should be logged
- */
- public function set_debug($dbg = true)
- {
- $this->debug_mode = $dbg;
- if ($this->db_connected) {
- $this->db_handle->setOption('debug', $dbg);
- $this->db_handle->setOption('emulate_prepared', $dbg);
- }
- }
-
-
- /**
- * Getter for error state
- *
- * @param boolean True on error
- */
- public function is_error()
- {
- return $this->db_error ? $this->db_error_msg : false;
- }
-
-
- /**
- * Connection state checker
- *
- * @param boolean True if in connected state
- */
- public function is_connected()
- {
- return PEAR::isError($this->db_handle) ? false : $this->db_connected;
- }
-
-
- /**
- * Is database replication configured?
- * This returns true if dsnw != dsnr
- */
- public function is_replicated()
- {
- return !empty($this->db_dsnr) && $this->db_dsnw != $this->db_dsnr;
- }
-
-
- /**
- * Get database runtime variables
- *
- * @param string Variable name
- * @param mixed Default value if var is not set
- * @return mixed Variable value or default
- */
- public function get_variable($varname, $default = null)
- {
- if (!isset($this->variables)) {
- $this->variables = array();
-
- // only mysql and postgres are know to support this
- if ($this->db_provider == 'pgsql' || $this->db_provider == 'mysql' || $this->db_provider == 'mysqli') {
- $this->db_connect('r');
- $query = $this->db_provider == 'pgsql' ? 'SHOW ALL' : 'SHOW VARIABLES';
- foreach ((array)$this->db_handle->queryAll($query) as $row)
- $this->variables[$row[0]] = $row[1];
- }
- }
-
- return isset($this->variables[$varname]) ? $this->variables[$varname] : $default;
- }
-
-
- /**
- * Execute a SQL query
- *
- * @param string SQL query to execute
- * @param mixed Values to be inserted in query
- *
- * @return number Query handle identifier
- */
- public function query()
- {
- $params = func_get_args();
- $query = array_shift($params);
-
- // Support one argument of type array, instead of n arguments
- if (count($params) == 1 && is_array($params[0])) {
- $params = $params[0];
- }
-
- return $this->_query($query, 0, 0, $params);
- }
-
-
- /**
- * Execute a SQL query with limits
- *
- * @param string SQL query to execute
- * @param number Offset for LIMIT statement
- * @param number Number of rows for LIMIT statement
- * @param mixed Values to be inserted in query
- *
- * @return number Query handle identifier
- */
- public function limitquery()
- {
- $params = func_get_args();
- $query = array_shift($params);
- $offset = array_shift($params);
- $numrows = array_shift($params);
-
- return $this->_query($query, $offset, $numrows, $params);
- }
-
-
- /**
- * Execute a SQL query with limits
- *
- * @param string $query SQL query to execute
- * @param number $offset Offset for LIMIT statement
- * @param number $numrows Number of rows for LIMIT statement
- * @param array $params Values to be inserted in query
- * @return number Query handle identifier
- * @access private
- */
- private function _query($query, $offset, $numrows, $params)
- {
- // Read or write ?
- $mode = (strtolower(substr(trim($query),0,6)) == 'select') ? 'r' : 'w';
-
- $this->db_connect($mode);
-
- // check connection before proceeding
- if (!$this->is_connected()) {
- return null;
- }
-
- if ($this->db_provider == 'sqlite') {
- $this->sqlite_prepare();
- }
-
- if ($numrows || $offset) {
- $result = $this->db_handle->setLimit($numrows,$offset);
- }
-
- if (empty($params)) {
- $result = $mode == 'r' ? $this->db_handle->query($query) : $this->db_handle->exec($query);
- }
- else {
- $params = (array)$params;
- $q = $this->db_handle->prepare($query, null, $mode=='w' ? MDB2_PREPARE_MANIP : null);
- if ($this->db_handle->isError($q)) {
- $this->db_error = true;
- $this->db_error_msg = $q->userinfo;
-
- rcube::raise_error(array('code' => 500, 'type' => 'db',
- 'line' => __LINE__, 'file' => __FILE__,
- 'message' => $this->db_error_msg), true, false);
-
- $result = false;
- }
- else {
- $result = $q->execute($params);
- $q->free();
- }
- }
-
- // add result, even if it's an error
- return $this->_add_result($result);
- }
-
-
- /**
- * Get number of rows for a SQL query
- * If no query handle is specified, the last query will be taken as reference
- *
- * @param number $res_id Optional query handle identifier
- * @return mixed Number of rows or false on failure
- */
- public function num_rows($res_id=null)
- {
- if (!$this->db_connected) {
- return false;
- }
-
- if ($result = $this->_get_result($res_id)) {
- return $result->numRows();
- }
-
- return false;
- }
-
-
- /**
- * Get number of affected rows for the last query
- *
- * @param number $res_id Optional query handle identifier
- * @return mixed Number of rows or false on failure
- */
- public function affected_rows($res_id = null)
- {
- if (!$this->db_connected) {
- return false;
- }
-
- return $this->_get_result($res_id);
- }
-
-
- /**
- * Get last inserted record ID
- * For Postgres databases, a sequence name is required
- *
- * @param string $table Table name (to find the incremented sequence)
- *
- * @return mixed ID or false on failure
- */
- public function insert_id($table = '')
- {
- if (!$this->db_connected || $this->db_mode == 'r') {
- return false;
- }
-
- if ($table) {
- if ($this->db_provider == 'pgsql') {
- // find sequence name
- $table = $this->sequence_name($table);
- }
- else {
- // resolve table name
- $table = $this->table_name($table);
- }
- }
-
- $id = $this->db_handle->lastInsertID($table);
-
- return $this->db_handle->isError($id) ? null : $id;
- }
-
-
- /**
- * Get an associative array for one row
- * If no query handle is specified, the last query will be taken as reference
- *
- * @param number $res_id Optional query handle identifier
- *
- * @return mixed Array with col values or false on failure
- */
- public function fetch_assoc($res_id = null)
- {
- $result = $this->_get_result($res_id);
- return $this->_fetch_row($result, MDB2_FETCHMODE_ASSOC);
- }
-
-
- /**
- * Get an index array for one row
- * If no query handle is specified, the last query will be taken as reference
- *
- * @param number $res_id Optional query handle identifier
- *
- * @return mixed Array with col values or false on failure
- */
- public function fetch_array($res_id = null)
- {
- $result = $this->_get_result($res_id);
- return $this->_fetch_row($result, MDB2_FETCHMODE_ORDERED);
- }
-
-
- /**
- * Get col values for a result row
- *
- * @param MDB2_Result_Common Query $result result handle
- * @param number $mode Fetch mode identifier
- *
- * @return mixed Array with col values or false on failure
- */
- private function _fetch_row($result, $mode)
- {
- if ($result === false || PEAR::isError($result) || !$this->is_connected()) {
- return false;
- }
-
- return $result->fetchRow($mode);
- }
-
-
- /**
- * Wrapper for the SHOW TABLES command
- *
- * @return array List of all tables of the current database
- * @since 0.4-beta
- */
- public function list_tables()
- {
- // get tables if not cached
- if (!$this->tables) {
- $this->db_handle->loadModule('Manager');
- if (!PEAR::isError($result = $this->db_handle->listTables())) {
- $this->tables = $result;
- }
- else {
- $this->tables = array();
- }
- }
-
- return $this->tables;
- }
-
-
- /**
- * Wrapper for SHOW COLUMNS command
- *
- * @param string Table name
- *
- * @return array List of table cols
- */
- public function list_cols($table)
- {
- $this->db_handle->loadModule('Manager');
- if (!PEAR::isError($result = $this->db_handle->listTableFields($table))) {
- return $result;
- }
-
- return null;
- }
-
-
- /**
- * Formats input so it can be safely used in a query
- *
- * @param mixed $input Value to quote
- * @param string $type Type of data
- *
- * @return string Quoted/converted string for use in query
- */
- public function quote($input, $type = null)
- {
- // handle int directly for better performance
- if ($type == 'integer') {
- return intval($input);
- }
-
- // create DB handle if not available
- if (!$this->db_handle) {
- $this->db_connect('r');
- }
-
- return $this->db_connected ? $this->db_handle->quote($input, $type) : addslashes($input);
- }
-
-
- /**
- * Quotes a string so it can be safely used as a table or column name
- *
- * @param string $str Value to quote
- *
- * @return string Quoted string for use in query
- * @deprecated Replaced by rcube_MDB2::quote_identifier
- * @see rcube_mdb2::quote_identifier
- */
- public function quoteIdentifier($str)
- {
- return $this->quote_identifier($str);
- }
-
-
- /**
- * Quotes a string so it can be safely used as a table or column name
- *
- * @param string $str Value to quote
- *
- * @return string Quoted string for use in query
- */
- public function quote_identifier($str)
- {
- if (!$this->db_handle) {
- $this->db_connect('r');
- }
-
- return $this->db_connected ? $this->db_handle->quoteIdentifier($str) : $str;
- }
-
-
- /**
- * Escapes a string
- *
- * @param string $str The string to be escaped
- *
- * @return string The escaped string
- * @since 0.1.1
- */
- public function escapeSimple($str)
- {
- if (!$this->db_handle) {
- $this->db_connect('r');
- }
-
- return $this->db_handle->escape($str);
- }
-
-
- /**
- * Return SQL function for current time and date
- *
- * @return string SQL function to use in query
- */
- public function now()
- {
- switch ($this->db_provider) {
- case 'mssql':
- case 'sqlsrv':
- return "getdate()";
-
- default:
- return "now()";
- }
- }
-
-
- /**
- * Return list of elements for use with SQL's IN clause
- *
- * @param array $arr Input array
- * @param string $type Type of data
- *
- * @return string Comma-separated list of quoted values for use in query
- */
- public function array2list($arr, $type = null)
- {
- if (!is_array($arr)) {
- return $this->quote($arr, $type);
- }
-
- foreach ($arr as $idx => $item) {
- $arr[$idx] = $this->quote($item, $type);
- }
-
- return implode(',', $arr);
- }
-
-
- /**
- * Return SQL statement to convert a field value into a unix timestamp
- *
- * This method is deprecated and should not be used anymore due to limitations
- * of timestamp functions in Mysql (year 2038 problem)
- *
- * @param string $field Field name
- *
- * @return string SQL statement to use in query
- * @deprecated
- */
- public function unixtimestamp($field)
- {
- switch($this->db_provider) {
- case 'pgsql':
- return "EXTRACT (EPOCH FROM $field)";
-
- case 'mssql':
- case 'sqlsrv':
- return "DATEDIFF(second, '19700101', $field) + DATEDIFF(second, GETDATE(), GETUTCDATE())";
-
- default:
- return "UNIX_TIMESTAMP($field)";
- }
- }
-
-
- /**
- * Return SQL statement to convert from a unix timestamp
- *
- * @param string $timestamp Field name
- *
- * @return string SQL statement to use in query
- */
- public function fromunixtime($timestamp)
- {
- return date("'Y-m-d H:i:s'", $timestamp);
- }
-
-
- /**
- * 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)
- {
- // TODO: use MDB2's matchPattern() function
- switch ($this->db_provider) {
- case 'pgsql':
- return $this->quote_identifier($column).' ILIKE '.$this->quote($value);
- default:
- return $this->quote_identifier($column).' LIKE '.$this->quote($value);
- }
- }
-
-
- /**
- * Abstract SQL statement for value concatenation
- *
- * @return string SQL statement to be used in query
- */
- public function concat(/* col1, col2, ... */)
- {
- $func = '';
- $args = func_get_args();
- if (is_array($args[0]))
- $args = $args[0];
-
- switch ($this->db_provider) {
- case 'mysql':
- case 'mysqli':
- $func = 'CONCAT';
- $delim = ', ';
- break;
- case 'mssql':
- case 'sqlsrv':
- $delim = ' + ';
- // Modify arguments, because + operator requires them to be of type varchar (#1488505)
- // with SQL Server 2012 we can use just CONCAT(), but we need to support older versions
- foreach ($args as $idx => $arg) {
- $args[$idx] = "CAST($arg AS varchar)";
- }
- break;
- default:
- $delim = ' || ';
- }
-
- return $func . '(' . join($delim, $args) . ')';
- }
-
-
- /**
- * Encodes non-UTF-8 characters in string/array/object (recursive)
- *
- * @param mixed $input Data to fix
- *
- * @return mixed Properly UTF-8 encoded data
- */
- public static function encode($input)
- {
- if (is_object($input)) {
- foreach (get_object_vars($input) as $idx => $value) {
- $input->$idx = self::encode($value);
- }
- return $input;
- }
- else if (is_array($input)) {
- foreach ($input as $idx => $value) {
- $input[$idx] = self::encode($value);
- }
- return $input;
- }
-
- return utf8_encode($input);
- }
-
-
- /**
- * Decodes encoded UTF-8 string/object/array (recursive)
- *
- * @param mixed $input Input data
- *
- * @return mixed Decoded data
- */
- public static function decode($input)
- {
- if (is_object($input)) {
- foreach (get_object_vars($input) as $idx => $value) {
- $input->$idx = self::decode($value);
- }
- return $input;
- }
- else if (is_array($input)) {
- foreach ($input as $idx => $value) {
- $input[$idx] = self::decode($value);
- }
- return $input;
- }
-
- return utf8_decode($input);
- }
-
-
- /**
- * Adds a query result and returns a handle ID
- *
- * @param object $res Query handle
- *
- * @return mixed Handle ID
- */
- private function _add_result($res)
- {
- // sql error occured
- if (PEAR::isError($res)) {
- $this->db_error = true;
- $this->db_error_msg = $res->getMessage();
- rcube::raise_error(array('code' => 500, 'type' => 'db',
- 'line' => __LINE__, 'file' => __FILE__,
- 'message' => $res->getMessage() . " Query: "
- . substr(preg_replace('/[\r\n]+\s*/', ' ', $res->userinfo), 0, 512)),
- true, false);
- }
-
- $res_id = sizeof($this->a_query_results);
- $this->last_res_id = $res_id;
- $this->a_query_results[$res_id] = $res;
- return $res_id;
- }
-
-
- /**
- * Resolves a given handle ID and returns the according query handle
- * If no ID is specified, the last resource handle will be returned
- *
- * @param number $res_id Handle ID
- *
- * @return mixed Resource handle or false on failure
- */
- private function _get_result($res_id = null)
- {
- if ($res_id == null) {
- $res_id = $this->last_res_id;
- }
-
- if (isset($this->a_query_results[$res_id])) {
- if (!PEAR::isError($this->a_query_results[$res_id])) {
- return $this->a_query_results[$res_id];
- }
- }
-
- return false;
- }
-
-
- /**
- * Create a sqlite database from a file
- *
- * @param MDB2 $dbh SQLite database handle
- * @param string $file_name File path to use for DB creation
- */
- private function sqlite_create_database($dbh, $file_name)
- {
- if (empty($file_name) || !is_string($file_name)) {
- return;
- }
-
- $data = file_get_contents($file_name);
-
- if (strlen($data)) {
- if (!sqlite_exec($dbh->connection, $data, $error) || MDB2::isError($dbh)) {
- rcube::raise_error(array('code' => 500, 'type' => 'db',
- 'line' => __LINE__, 'file' => __FILE__,
- 'message' => $error), true, false);
- }
- }
- }
-
-
- /**
- * Add some proprietary database functions to the current SQLite handle
- * in order to make it MySQL compatible
- */
- private function sqlite_prepare()
- {
- // we emulate via callback some missing MySQL functions
- sqlite_create_function($this->db_handle->connection,
- 'unix_timestamp', array('rcube_mdb2', 'sqlite_unix_timestamp'));
- sqlite_create_function($this->db_handle->connection,
- 'now', array('rcube_mdb2', 'sqlite_now'));
- }
-
-
- /**
- * Debug handler for the MDB2
- */
- function debug_handler(&$db, $scope, $message, $context = array())
- {
- if ($scope != 'prepare') {
- $debug_output = sprintf('%s(%d): %s;',
- $scope, $db->db_index, rtrim($message, ';'));
- rcube::write_log('sql', $debug_output);
- }
- }
-
-
- /**
- * Return correct name for a specific database table
- *
- * @param string $table Table name
- *
- * @return string Translated table name
- */
- public function table_name($table)
- {
- $rcube = rcube::get_instance();
-
- // return table name if configured
- $config_key = 'db_table_'.$table;
-
- if ($name = $rcube->config->get($config_key)) {
- return $name;
- }
-
- return $table;
- }
-
-
- /**
- * Return correct name for a specific database sequence
- * (used for Postgres only)
- *
- * @param string $sequence Secuence name
- *
- * @return string Translated sequence name
- */
- public function sequence_name($sequence)
- {
- $rcube = rcube::get_instance();
-
- // return sequence name if configured
- $config_key = 'db_sequence_'.$sequence;
-
- if ($name = $rcube->config->get($config_key)) {
- return $name;
- }
-
- return $sequence;
- }
-
-
- /**
- * Callback for sqlite: unix_timestamp()
- */
- public static function sqlite_unix_timestamp($timestamp = '')
- {
- $timestamp = trim($timestamp);
- if (!$timestamp) {
- $ret = time();
- }
- else if (!preg_match('/^[0-9]+$/s', $timestamp)) {
- $ret = strtotime($timestamp);
- }
- else {
- $ret = $timestamp;
- }
-
- return $ret;
- }
-
-
- /**
- * Callback for sqlite: now()
- */
- public static function sqlite_now()
- {
- return date("Y-m-d H:i:s");
- }
-
-}
diff --git a/lib/ext/Syncroton/Backend/ABackend.php b/lib/ext/Syncroton/Backend/ABackend.php
new file mode 100644
index 0000000..d345d6d
--- /dev/null
+++ b/lib/ext/Syncroton/Backend/ABackend.php
@@ -0,0 +1,156 @@
+<?php
+/**
+ * Syncroton
+ *
+ * @package Command
+ * @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
+ * @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author Lars Kneschke <l.kneschke@metaways.de>
+ */
+
+/**
+ * class to handle ActiveSync Sync command
+ *
+ * @package Backend
+ */
+
+abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
+{
+ /**
+ * the database adapter
+ *
+ * @var Zend_Db_Adapter_Abstract
+ */
+ protected $_db;
+
+ protected $_tablePrefix;
+
+ protected $_tableName;
+
+ protected $_modelClassName;
+
+ protected $_modelInterfaceName;
+
+ public function __construct(Zend_Db_Adapter_Abstract $_db, $_tablePrefix = 'Syncroton_')
+ {
+ $this->_db = $_db;
+ $this->_tablePrefix = $_tablePrefix;
+ }
+
+ /**
+ * create new device
+ *
+ * @param Syncroton_Model_IDevice $_device
+ * @return Syncroton_Model_IDevice
+ */
+ public function create($model)
+ {
+ if (! $model instanceof $this->_modelInterfaceName) {
+ throw new InvalidArgumentException('$model must be instanace of ' . $this->_modelInterfaceName);
+ }
+
+ $data = $this->_convertModelToArray($model);
+
+ $data['id'] = sha1(mt_rand(). microtime());
+
+ $this->_db->insert($this->_tablePrefix . $this->_tableName, $data);
+
+ return $this->get($data['id']);
+ }
+
+ protected function _convertModelToArray($model)
+ {
+ $data = array();
+
+ foreach ($model as $key => $value) {
+ if ($value instanceof DateTime) {
+ $value = $value->format('Y-m-d H:i:s');
+ } elseif (is_object($value) && isset($value->id)) {
+ $value = $value->id;
+ }
+
+ $data[$this->_fromCamelCase($key)] = $value;
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param string $_id
+ * @throws Syncroton_Exception_NotFound
+ * @return Syncroton_Model_IDevice
+ */
+ public function get($id)
+ {
+ $id = $id instanceof $this->_modelInterfaceName ? $id->id : $id;
+
+ $select = $this->_db->select()
+ ->from($this->_tablePrefix . $this->_tableName)
+ ->where('id = ?', $id);
+
+ $stmt = $this->_db->query($select);
+ $data = $stmt->fetch();
+ $stmt = null; # see https://bugs.php.net/bug.php?id=44081
+
+ if ($data === false) {
+ throw new Syncroton_Exception_NotFound('id not found');
+ }
+
+ return $this->_getObject($data);
+ }
+
+ protected function _getObject($data)
+ {
+ foreach ($data as $key => $value) {
+ unset($data[$key]);
+
+ if (!empty($value) && preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $value)) { # 2012-08-12 07:43:26
+ $value = new DateTime($value, new DateTimeZone('utc'));
+ }
+
+ $data[$this->_toCamelCase($key, false)] = $value;
+ }
+
+ return new $this->_modelClassName($data);
+ }
+
+ public function delete($id)
+ {
+ $id = $id instanceof $this->_modelInterfaceName ? $id->id : $id;
+
+ $result = $this->_db->delete($this->_tablePrefix . $this->_tableName, array('id = ?' => $id));
+
+ return (bool) $result;
+ }
+
+ public function update($model)
+ {
+ if (! $model instanceof $this->_modelInterfaceName) {
+ throw new InvalidArgumentException('$model must be instanace of ' . $this->_modelInterfaceName);
+ }
+
+ $data = $this->_convertModelToArray($model);
+
+ $this->_db->update($this->_tablePrefix . $this->_tableName, $data, array(
+ 'id = ?' => $model->id
+ ));
+
+ return $this->get($model->id);
+ }
+
+ protected function _fromCamelCase($string)
+ {
+ $string = lcfirst($string);
+
+ return preg_replace_callback('/([A-Z])/', function ($string) {return '_' . strtolower($string[0]);}, $string);
+ }
+
+ protected function _toCamelCase($string, $ucFirst = true)
+ {
+ if ($ucFirst === true) {
+ $string = ucfirst($string);
+ }
+
+ return preg_replace_callback('/_([a-z])/', function ($string) {return strtoupper($string[1]);}, $string);
+ }
+}
diff --git a/lib/ext/Syncroton/Backend/Content.php b/lib/ext/Syncroton/Backend/Content.php
index 2500a17..b186b00 100644
--- a/lib/ext/Syncroton/Backend/Content.php
+++ b/lib/ext/Syncroton/Backend/Content.php
@@ -1,179 +1,116 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Backend
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @author Lars Kneschke <l.kneschke@metaways.de>
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
*
*/
/**
* sql backend class for the folder state
*
* @package Syncroton
* @subpackage Backend
*/
-class Syncroton_Backend_Content implements Syncroton_Backend_IContent
+class Syncroton_Backend_Content extends Syncroton_Backend_ABackend implements Syncroton_Backend_IContent
{
- /**
- * the database adapter
- *
- * @var Zend_Db_Adapter_Abstract
- */
- protected $_db;
-
- protected $_tablePrefix;
-
- public function __construct(Zend_Db_Adapter_Abstract $_db, $_tablePrefix = 'Syncroton_')
- {
- $this->_db = $_db;
- $this->_tablePrefix = $_tablePrefix;
- }
-
- /**
- * create new content state
- *
- * @param Syncroton_Model_IContent $_state
- * @return Syncroton_Model_IContent
- */
- public function create(Syncroton_Model_IContent $_state)
- {
- $id = sha1(mt_rand(). microtime());
-
- $deviceId = $_state->device_id instanceof Syncroton_Model_IDevice ? $_state->device_id->id : $_state->device_id;
- $folderId = $_state->folder_id instanceof Syncroton_Model_IFolder ? $_state->folder_id->id : $_state->folder_id;
-
- $this->_db->insert($this->_tablePrefix . 'content', array(
- 'id' => $id,
- 'device_id' => $deviceId,
- 'folder_id' => $folderId,
- 'contentid' => $_state->contentid,
- 'creation_time' => $_state->creation_time->format('Y-m-d H:i:s'),
- 'creation_synckey' => $_state->creation_synckey,
- 'is_deleted' => isset($_state->is_deleted) ? (int)!!$_state->is_deleted : 0
- ));
-
- return $this->get($id);
- }
+ protected $_tableName = 'content';
+
+ protected $_modelClassName = 'Syncroton_Model_Content';
+
+ protected $_modelInterfaceName = 'Syncroton_Model_IContent';
/**
* mark state as deleted. The state gets removed finally,
* when the synckey gets validated during next sync.
*
* @param Syncroton_Model_IContent|string $_id
*/
- public function delete($_id)
+ public function delete($id)
{
- $id = $_id instanceof Syncroton_Model_IContent ? $_id->id : $_id;
+ $id = $id instanceof $this->_modelInterfaceName ? $id->id : $id;
$this->_db->update($this->_tablePrefix . 'content', array(
'is_deleted' => 1
), array(
'id = ?' => $id
));
}
/**
- * @param string $_id
- * @throws Syncroton_Exception_NotFound
- * @return Syncroton_Model_IContent
- */
- public function get($_id)
- {
- $select = $this->_db->select()
- ->from($this->_tablePrefix . 'content')
- ->where('id = ?', $_id);
-
- $stmt = $this->_db->query($select);
- $state = $stmt->fetchObject('Syncroton_Model_Content');
-
- if (! $state instanceof Syncroton_Model_IContent) {
- throw new Syncroton_Exception_NotFound('id not found');
- }
-
- if (!empty($state->creation_time)) {
- $state->creation_time = new DateTime($state->creation_time, new DateTimeZone('utc'));
- }
-
- return $state;
- }
-
- /**
- * @param Syncroton_Model_IDevice|string $_deviceId
- * @param Syncroton_Model_IFolder|string $_folderId
+ * @param Syncroton_Model_IDevice|string $deviceId
+ * @param Syncroton_Model_IFolder|string $folderId
* @param string $_contentId
* @return Syncroton_Model_IContent
*/
- public function getContentState($_deviceId, $_folderId, $_contentId)
+ public function getContentState($deviceId, $folderId, $contentId)
{
- $deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
- $folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->id : $_folderId;
+ $deviceId = $deviceId instanceof Syncroton_Model_IDevice ? $deviceId->id : $deviceId;
+ $folderId = $folderId instanceof Syncroton_Model_IFolder ? $folderId->id : $folderId;
$select = $this->_db->select()
->from($this->_tablePrefix . 'content')
->where($this->_db->quoteIdentifier('device_id') . ' = ?', $deviceId)
->where($this->_db->quoteIdentifier('folder_id') . ' = ?', $folderId)
- ->where($this->_db->quoteIdentifier('contentid') . ' = ?', $_contentId)
+ ->where($this->_db->quoteIdentifier('contentid') . ' = ?', $contentId)
->where($this->_db->quoteIdentifier('is_deleted') . ' = ?', 0);
- $stmt = $this->_db->query($select);
- $state = $stmt->fetchObject('Syncroton_Model_Content');
-
- if (! $state instanceof Syncroton_Model_IContent) {
- throw new Syncroton_Exception_NotFound('id not found');
- }
-
- if (!empty($state->creation_time)) {
- $state->creation_time = new DateTime($state->creation_time, new DateTimeZone('utc'));
- }
-
- return $state;
+ $stmt = $this->_db->query($select);
+ $data = $stmt->fetch();
+ $stmt = null; # see https://bugs.php.net/bug.php?id=44081
+
+ if ($data === false) {
+ throw new Syncroton_Exception_NotFound('id not found');
+ }
+
+ return $this->_getObject($data);
}
/**
* get array of ids which got send to the client for a given class
*
- * @param Syncroton_Model_IDevice|string $_deviceId
- * @param Syncroton_Model_IFolder|string $_folderId
+ * @param Syncroton_Model_IDevice|string $deviceId
+ * @param Syncroton_Model_IFolder|string $folderId
* @return array
*/
- public function getFolderState($_deviceId, $_folderId)
+ public function getFolderState($deviceId, $folderId)
{
- $deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
- $folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->id : $_folderId;
+ $deviceId = $deviceId instanceof Syncroton_Model_IDevice ? $deviceId->id : $deviceId;
+ $folderId = $folderId instanceof Syncroton_Model_IFolder ? $folderId->id : $folderId;
$select = $this->_db->select()
->from($this->_tablePrefix . 'content', 'contentid')
->where($this->_db->quoteIdentifier('device_id') . ' = ?', $deviceId)
->where($this->_db->quoteIdentifier('folder_id') . ' = ?', $folderId)
->where($this->_db->quoteIdentifier('is_deleted') . ' = ?', 0);
$stmt = $this->_db->query($select);
$result = $stmt->fetchAll(Zend_Db::FETCH_COLUMN);
return $result;
}
/**
* reset list of stored id
*
- * @param Syncroton_Model_IDevice|string $_deviceId
- * @param Syncroton_Model_IFolder|string $_folderId
+ * @param Syncroton_Model_IDevice|string $deviceId
+ * @param Syncroton_Model_IFolder|string $folderId
*/
- public function resetState($_deviceId, $_folderId)
+ public function resetState($deviceId, $folderId)
{
- $deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
- $folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->id : $_folderId;
+ $deviceId = $deviceId instanceof Syncroton_Model_IDevice ? $deviceId->id : $deviceId;
+ $folderId = $folderId instanceof Syncroton_Model_IFolder ? $folderId->id : $folderId;
$where = array(
$this->_db->quoteInto($this->_db->quoteIdentifier('device_id') . ' = ?', $deviceId),
$this->_db->quoteInto($this->_db->quoteIdentifier('folder_id') . ' = ?', $folderId)
);
$this->_db->delete($this->_tablePrefix . 'content', $where);
}
}
diff --git a/lib/ext/Syncroton/Backend/Device.php b/lib/ext/Syncroton/Backend/Device.php
index 8176a02..f7646b9 100644
--- a/lib/ext/Syncroton/Backend/Device.php
+++ b/lib/ext/Syncroton/Backend/Device.php
@@ -1,129 +1,56 @@
<?php
/**
* Syncroton
*
* @package Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sync command
*
* @package Backend
*/
-class Syncroton_Backend_Device implements Syncroton_Backend_IDevice
+class Syncroton_Backend_Device extends Syncroton_Backend_ABackend implements Syncroton_Backend_IDevice
{
- /**
- * the database adapter
- *
- * @var Zend_Db_Adapter_Abstract
- */
- protected $_db;
+ protected $_tableName = 'device';
- protected $_tablePrefix;
+ protected $_modelClassName = 'Syncroton_Model_Device';
- public function __construct(Zend_Db_Adapter_Abstract $_db, $_tablePrefix = 'Syncroton_')
- {
- $this->_db = $_db;
- $this->_tablePrefix = $_tablePrefix;
- }
-
- /**
- * create new device
- *
- * @param Syncroton_Model_IDevice $_device
- * @return Syncroton_Model_IDevice
- */
- public function create(Syncroton_Model_IDevice $_device)
- {
- $id = sha1(mt_rand(). microtime());
-
- $this->_db->insert($this->_tablePrefix . 'device', array(
- 'id' => $id,
- 'deviceid' => $_device->deviceid,
- 'devicetype' => $_device->devicetype,
- 'owner_id' => $_device->owner_id,
- 'policy_id' => isset($_device->policy_id) ? $_device->policy_id : 1,
- 'policykey' => isset($_device->policykey) ? $_device->policykey : null,
- 'useragent' => isset($_device->useragent) ? $_device->useragent : '',
- 'acsversion' => isset($_device->acsversion) ? $_device->acsversion : '',
- 'remotewipe' => isset($_device->remotewipe) ? $_device->remotewipe : null
- ));
-
- return $this->get($id);
- }
-
- /**
- * @param string $_id
- * @throws Syncroton_Exception_NotFound
- * @return Syncroton_Model_IDevice
- */
- public function get($_id)
- {
- $select = $this->_db->select()
- ->from($this->_tablePrefix . 'device')
- ->where('id = ?', $_id);
-
- $stmt = $this->_db->query($select);
- $device = $stmt->fetchObject('Syncroton_Model_Device');
-
- if (! $device instanceof Syncroton_Model_IDevice) {
- throw new Syncroton_Exception_NotFound('id not found');
- }
-
- return $device;
- }
+ protected $_modelInterfaceName = 'Syncroton_Model_IDevice';
/**
* return device for this user
*
* @param string $userId
* @param string $deviceId
* @throws Syncroton_Exception_NotFound
* @return Syncroton_Model_Device
*/
public function getUserDevice($ownerId, $deviceId)
{
$select = $this->_db->select()
- ->from($this->_tablePrefix . 'device')
+ ->from($this->_tablePrefix . $this->_tableName)
->where('owner_id = ?', $ownerId)
->where('deviceid = ?', $deviceId);
$stmt = $this->_db->query($select);
- $device = $stmt->fetchObject('Syncroton_Model_Device');
+ $data = $stmt->fetch();
- if (! $device instanceof Syncroton_Model_IDevice) {
- throw new Syncroton_Exception_NotFound('device not found');
+ if ($data === false) {
+ throw new Syncroton_Exception_NotFound('id not found');
+ }
+
+ foreach ($data as $key => $value) {
+ unset($data[$key]);
+ $data[$this->_toCamelCase($key, false)] = $value;
}
- return $device;
- }
-
- public function delete($_id)
- {
- $id = $_id instanceof Syncroton_Model_IDevice ? $_id->id : $_id;
-
- $result = $this->_db->delete($this->_tablePrefix . 'device', array('id = ?' => $id));
-
- return (bool) $result;
- }
-
- public function update(Syncroton_Model_IDevice $_device)
- {
- $this->_db->update($this->_tablePrefix . 'device', array(
- 'acsversion' => $_device->acsversion,
- 'policykey' => $_device->policykey,
- 'pingfolder' => $_device->pingfolder,
- 'pinglifetime' => $_device->pinglifetime,
- 'remotewipe' => $_device->remotewipe
- ), array(
- 'id = ?' => $_device->id
- ));
-
- return $this->get($_device->id);
+ $model = new $this->_modelClassName($data);
+ return $model;
}
}
diff --git a/lib/ext/Syncroton/Backend/Folder.php b/lib/ext/Syncroton/Backend/Folder.php
index 98fd4f4..f42eb08 100644
--- a/lib/ext/Syncroton/Backend/Folder.php
+++ b/lib/ext/Syncroton/Backend/Folder.php
@@ -1,184 +1,136 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Backend
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @author Lars Kneschke <l.kneschke@metaways.de>
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
*
*/
/**
* sql backend class for the folder state
*
* @package Syncroton
* @subpackage Backend
*/
-class Syncroton_Backend_Folder implements Syncroton_Backend_IFolder
+class Syncroton_Backend_Folder extends Syncroton_Backend_ABackend implements Syncroton_Backend_IFolder
{
- /**
- * the database adapter
- *
- * @var Zend_Db_Adapter_Abstract
- */
- protected $_db;
+ protected $_tableName = 'folder';
- protected $_tablePrefix;
+ protected $_modelClassName = 'Syncroton_Model_Folder';
- public function __construct(Zend_Db_Adapter_Abstract $_db, $_tablePrefix = 'Syncroton_')
- {
- $this->_db = $_db;
-
- $this->_tablePrefix = $_tablePrefix;
- }
+ protected $_modelInterfaceName = 'Syncroton_Model_IFolder';
/**
- * create new folder state
- *
- * @param Syncroton_Model_IFolder $_folder
- * @return Syncroton_Model_IFolder
+ * (non-PHPdoc)
+ * @see Syncroton_Backend_IFolder::getFolder()
*/
- public function create(Syncroton_Model_IFolder $_folder)
+ public function getFolder($deviceId, $folderId)
{
- $id = sha1(mt_rand(). microtime());
- $deviceId = $_folder->device_id instanceof Syncroton_Model_IDevice ? $_folder->device_id->id : $_folder->device_id;
-
- $this->_db->insert($this->_tablePrefix . 'folder', array(
- 'id' => $id,
- 'device_id' => $deviceId,
- 'class' => $_folder->class,
- 'folderid' => $_folder->folderid instanceof Syncroton_Model_IFolder ? $_folder->folderid->id : $_folder->folderid,
- 'parentid' => $_folder->parentid,
- 'displayname' => $_folder->displayname,
- 'type' => $_folder->type,
- 'creation_time' => $_folder->creation_time->format('Y-m-d H:i:s'),
- 'lastfiltertype' => $_folder->lastfiltertype
- ));
+ $deviceId = $deviceId instanceof Syncroton_Model_IDevice ? $deviceId->id : $deviceId;
- return $this->get($id);
- }
-
- public function delete($_id)
- {
- $id = $_id instanceof Syncroton_Model_IFolder ? $_id->id : $_id;
-
- $result = $this->_db->delete($this->_tablePrefix . 'folder', array('id = ?' => $id));
-
- return (bool) $result;
+ $select = $this->_db->select()
+ ->from($this->_tablePrefix . $this->_tableName)
+ ->where($this->_db->quoteIdentifier('device_id') . ' = ?', $deviceId)
+ ->where($this->_db->quoteIdentifier('folderid') . ' = ?', $folderId);
+
+ $stmt = $this->_db->query($select);
+ $data = $stmt->fetch();
+
+ if ($data === false) {
+ throw new Syncroton_Exception_NotFound('id not found');
+ }
+
+ return $this->_getObject($data);
}
/**
- * @param string $_id
- * @throws Syncroton_Exception_NotFound
- * @return Syncroton_Model_IFolder
+ * (non-PHPdoc)
+ * @see Syncroton_Backend_IFolder::getFolderState()
*/
- public function get($_id)
+ public function getFolderState($deviceId, $class)
{
+ $deviceId = $deviceId instanceof Syncroton_Model_IDevice ? $deviceId->id : $deviceId;
+
$select = $this->_db->select()
- ->from($this->_tablePrefix . 'folder')
- ->where('id = ?', $_id);
-
- $stmt = $this->_db->query($select);
- $state = $stmt->fetchObject('Syncroton_Model_Folder');
+ ->from($this->_tablePrefix . $this->_tableName)
+ ->where($this->_db->quoteIdentifier('device_id') . ' = ?', $deviceId)
+ ->where($this->_db->quoteIdentifier('class') . ' = ?', $class);
- if (! $state instanceof Syncroton_Model_IFolder) {
- throw new Syncroton_Exception_NotFound('id not found');
- }
+ $result = array();
- if (!empty($state->creation_time)) {
- $state->creation_time = new DateTime($state->creation_time, new DateTimeZone('utc'));
+ $stmt = $this->_db->query($select);
+ while ($data = $stmt->fetch()) {
+ $result[$data['folderid']] = $this->_getObject($data);
}
-
- return $state;
+
+ return $result;
}
/**
- * delete all stored folderId's for given device
- *
- * @param Syncroton_Model_Device|string $_deviceId
- * @param string $_class
+ * (non-PHPdoc)
+ * @see Syncroton_Backend_IFolder::resetState()
*/
- public function resetState($_deviceId)
+ public function resetState($deviceId)
{
- $deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
+ $deviceId = $deviceId instanceof Syncroton_Model_IDevice ? $deviceId->id : $deviceId;
$where = array(
$this->_db->quoteInto($this->_db->quoteIdentifier('device_id') . ' = ?', $deviceId)
);
- $this->_db->delete($this->_tablePrefix . 'folder', $where);
- }
-
- public function update(Syncroton_Model_IFolder $_folder)
- {
- $deviceId = $_folder->device_id instanceof Syncroton_Model_IDevice ? $_folder->device_id->id : $_folder->device_id;
-
- $this->_db->update($this->_tablePrefix . 'folder', array(
- 'lastfiltertype' => $_folder->lastfiltertype,
- 'displayname' => $_folder->displayname,
- 'parentid' => $_folder->parentid
- ), array(
- 'id = ?' => $_folder->id
- ));
-
- return $this->get($_folder->id);
+ $this->_db->delete($this->_tablePrefix . $this->_tableName, $where);
}
/**
- * get array of ids which got send to the client for a given class
- *
- * @param Syncroton_Model_Device|string $_deviceId
- * @param string $_class
- * @return array
+ * (non-PHPdoc)
+ * @see Syncroton_Backend_ABackend::_fromCamelCase()
*/
- public function getFolderState($_deviceId, $_class)
+ protected function _fromCamelCase($string)
{
- $deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
-
- $select = $this->_db->select()
- ->from($this->_tablePrefix . 'folder')
- ->where($this->_db->quoteIdentifier('device_id') . ' = ?', $deviceId)
- ->where($this->_db->quoteIdentifier('class') . ' = ?', $_class);
-
- $result = array();
-
- $stmt = $this->_db->query($select);
- while ($row = $stmt->fetchObject("Syncroton_Model_Folder")) {
- $result[$row->folderid] = $row;
- }
-
- return $result;
- }
+ switch ($string) {
+ case 'displayName':
+ case 'parentId':
+ return strtolower($string);
+ break;
+
+ case 'serverId':
+ return 'folderid';
+ break;
+
+ default:
+ return parent::_fromCamelCase($string);
+
+ break;
+ }
+ }
/**
- * get folder indentified by $_folderId
- *
- * @param Syncroton_Model_Device|string $_deviceId
- * @param string $_folderId
- * @return Syncroton_Model_IFolder
- */
- public function getFolder($_deviceId, $_folderId)
+ * (non-PHPdoc)
+ * @see Syncroton_Backend_ABackend::_toCamelCase()
+ */
+ protected function _toCamelCase($string, $ucFirst = true)
{
- $deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
-
- $select = $this->_db->select()
- ->from($this->_tablePrefix . 'folder')
- ->where($this->_db->quoteIdentifier('device_id') . ' = ?', $deviceId)
- ->where($this->_db->quoteIdentifier('folderid') . ' = ?', $_folderId);
-
- $stmt = $this->_db->query($select);
- $folder = $stmt->fetchObject('Syncroton_Model_Folder');
-
- if (! $folder instanceof Syncroton_Model_IFolder) {
- throw new Syncroton_Exception_NotFound('folder not found');
- }
-
- if (!empty($folder->creation_time)) {
- $folder->creation_time = new DateTime($folder->creation_time, new DateTimeZone('utc'));
- }
-
- return $folder;
- }
+ switch ($string) {
+ case 'displayname':
+ return 'displayName';
+ break;
+
+ case 'parentid':
+ return 'parentId';
+ break;
+
+ case 'folderid':
+ return 'serverId';
+ break;
+
+ default:
+ return parent::_toCamelCase($string, $ucFirst);
+
+ break;
+ }
+ }
}
diff --git a/lib/ext/Syncroton/Backend/IDevice.php b/lib/ext/Syncroton/Backend/IBackend.php
similarity index 62%
copy from lib/ext/Syncroton/Backend/IDevice.php
copy to lib/ext/Syncroton/Backend/IBackend.php
index d6de9fa..56979fb 100644
--- a/lib/ext/Syncroton/Backend/IDevice.php
+++ b/lib/ext/Syncroton/Backend/IBackend.php
@@ -1,57 +1,50 @@
<?php
/**
* Syncroton
*
* @package Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sync command
*
* @package Backend
*/
-interface Syncroton_Backend_IDevice
+interface Syncroton_Backend_IBackend
{
/**
- * Create a new device
- *
- * @param Syncroton_Model_IDevice $_device
- * @return Syncroton_Model_IDevice
- */
- public function create(Syncroton_Model_IDevice $_device);
+ * Create a new device
+ *
+ * @param Syncroton_Model_IDevice $device
+ * @return Syncroton_Model_IDevice
+ */
+ public function create($model);
/**
* Deletes one or more existing devices
*
* @param string|array $_id
* @return void
*/
- public function delete($_id);
+ public function delete($id);
/**
* Return a single device
*
* @param string $_id
* @return Syncroton_Model_IDevice
*/
- public function get($_id);
-
- /**
- * @param unknown_type $userId
- * @param unknown_type $deviceId
- * @return Syncroton_Model_IDevice
- */
- public function getUserDevice($userId, $deviceId);
+ public function get($id);
/**
* Upates an existing persistent record
*
* @param Syncroton_Model_IDevice $_device
* @return Syncroton_Model_IDevice
*/
- public function update(Syncroton_Model_IDevice $_device);
+ public function update($model);
}
diff --git a/lib/ext/Syncroton/Backend/IContent.php b/lib/ext/Syncroton/Backend/IContent.php
index 32d217a..8974e49 100644
--- a/lib/ext/Syncroton/Backend/IContent.php
+++ b/lib/ext/Syncroton/Backend/IContent.php
@@ -1,61 +1,45 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Backend
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @author Lars Kneschke <l.kneschke@metaways.de>
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
*
*/
/**
* sql backend class for the folder state
*
* @package Syncroton
* @subpackage Backend
*/
-interface Syncroton_Backend_IContent
+interface Syncroton_Backend_IContent extends Syncroton_Backend_IBackend
{
- /**
- * create new content state
- *
- * @param Syncroton_Model_IContent $_contentState
- * @return Syncroton_Model_IContent
- */
- public function create(Syncroton_Model_IContent $_contentState);
-
- /**
- * mark state as deleted. The state gets removed finally,
- * when the synckey gets validated during next sync.
- *
- * @param Syncroton_Model_IContent|string $_id
- */
- public function delete($_id);
-
/**
* @param Syncroton_Model_IDevice|string $_deviceId
* @param Syncroton_Model_IFolder|string $_folderId
* @param string $_contentId
* @return Syncroton_Model_IContent
*/
public function getContentState($_deviceId, $_folderId, $_contentId);
/**
* get array of ids which got send to the client for a given class
*
* @param Syncroton_Model_IDevice|string $_deviceId
* @param Syncroton_Model_IFolder|string $_folderId
* @return array
*/
public function getFolderState($_deviceId, $_folderId);
/**
* reset list of stored id
*
* @param Syncroton_Model_IDevice|string $_deviceId
* @param Syncroton_Model_IFolder|string $_folderId
*/
public function resetState($_deviceId, $_folderId);
}
diff --git a/lib/ext/Syncroton/Backend/IDevice.php b/lib/ext/Syncroton/Backend/IDevice.php
index d6de9fa..27a3a55 100644
--- a/lib/ext/Syncroton/Backend/IDevice.php
+++ b/lib/ext/Syncroton/Backend/IDevice.php
@@ -1,57 +1,25 @@
<?php
/**
* Syncroton
*
* @package Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sync command
*
* @package Backend
*/
-interface Syncroton_Backend_IDevice
+interface Syncroton_Backend_IDevice extends Syncroton_Backend_IBackend
{
- /**
- * Create a new device
- *
- * @param Syncroton_Model_IDevice $_device
- * @return Syncroton_Model_IDevice
- */
- public function create(Syncroton_Model_IDevice $_device);
-
- /**
- * Deletes one or more existing devices
- *
- * @param string|array $_id
- * @return void
- */
- public function delete($_id);
-
- /**
- * Return a single device
- *
- * @param string $_id
- * @return Syncroton_Model_IDevice
- */
- public function get($_id);
-
/**
* @param unknown_type $userId
* @param unknown_type $deviceId
* @return Syncroton_Model_IDevice
*/
public function getUserDevice($userId, $deviceId);
-
- /**
- * Upates an existing persistent record
- *
- * @param Syncroton_Model_IDevice $_device
- * @return Syncroton_Model_IDevice
- */
- public function update(Syncroton_Model_IDevice $_device);
}
diff --git a/lib/ext/Syncroton/Backend/IFolder.php b/lib/ext/Syncroton/Backend/IFolder.php
index 039c5ba..fc1d078 100755
--- a/lib/ext/Syncroton/Backend/IFolder.php
+++ b/lib/ext/Syncroton/Backend/IFolder.php
@@ -1,58 +1,45 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Backend
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @author Lars Kneschke <l.kneschke@metaways.de>
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
*
*/
/**
* sql backend class for the folder state
*
* @package Syncroton
* @subpackage Backend
*/
-interface Syncroton_Backend_IFolder
+interface Syncroton_Backend_IFolder extends Syncroton_Backend_IBackend
{
/**
- * @param Syncroton_Model_IFolder $_folder
- * @return Syncroton_Model_IFolder
- */
- public function create(Syncroton_Model_IFolder $_folder);
-
- public function delete($_id);
-
- public function get($_id);
-
- /**
- * get folder indentified by $_folderId
+ * get folder indentified by $folderId
*
- * @param Syncroton_Model_Device|string $_deviceId
- * @param string $_folderId
+ * @param Syncroton_Model_Device|string $deviceId
+ * @param string $folderId
* @return Syncroton_Model_IFolder
*/
- public function getFolder($_deviceId, $_folderId);
+ public function getFolder($deviceId, $folderId);
/**
* get array of ids which got send to the client for a given class
*
- * @param Syncroton_Model_Device $_deviceId
- * @param string $_class
+ * @param Syncroton_Model_Device|string $deviceId
+ * @param string $class
* @return array
*/
- public function getFolderState($_deviceId, $_class);
+ public function getFolderState($deviceId, $class);
/**
* delete all stored folderId's for given device
*
- * @param Syncroton_Model_Device $_deviceId
- * @param string $_class
+ * @param Syncroton_Model_Device|string $deviceId
*/
- public function resetState($_deviceId);
-
- public function update(Syncroton_Model_IFolder $_folder);
+ public function resetState($deviceId);
}
diff --git a/lib/ext/Syncroton/Backend/ISyncState.php b/lib/ext/Syncroton/Backend/ISyncState.php
index d453665..144f61e 100644
--- a/lib/ext/Syncroton/Backend/ISyncState.php
+++ b/lib/ext/Syncroton/Backend/ISyncState.php
@@ -1,50 +1,48 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Backend
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @author Lars Kneschke <l.kneschke@metaways.de>
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
*
*/
/**
* sql backend class for the folder state
*
* @package Syncroton
* @subpackage Backend
*/
-interface Syncroton_Backend_ISyncState
+interface Syncroton_Backend_ISyncState extends Syncroton_Backend_IBackend
{
/**
* create new sync state
- *
- * @param Syncroton_Model_ISyncState $_syncState
- * @return Syncroton_Model_ISyncState
+ *
+ * @param Syncroton_Model_IDevice $model
+ * @param boolean $keepPreviousSyncState
*/
- public function create(Syncroton_Model_ISyncState $_syncState);
+ #public function create($model, $keepPreviousSyncState = true);
/**
* always returns the latest syncstate
*
- * @param Syncroton_Model_IDevice|string $_deviceId
- * @param Syncroton_Model_IFolder|string $_folderId
+ * @param Syncroton_Model_IDevice|string $deviceId
+ * @param Syncroton_Model_IFolder|string $folderId
* @return Syncroton_Model_SyncState
*/
- public function getSyncState($_deviceId, $_folderId);
+ public function getSyncState($deviceId, $folderId);
public function resetState($_deviceId, $_type);
- public function update(Syncroton_Model_ISyncState $_syncState);
-
/**
* get array of ids which got send to the client for a given class
*
* @param Syncroton_Model_Device $_deviceId
* @param string $_class
* @return array
*/
public function validate($_deviceId, $_syncKey, $_type);
}
diff --git a/lib/ext/Syncroton/Backend/Policy.php b/lib/ext/Syncroton/Backend/Policy.php
new file mode 100644
index 0000000..5163359
--- /dev/null
+++ b/lib/ext/Syncroton/Backend/Policy.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Syncroton
+ *
+ * @package Command
+ * @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
+ * @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author Lars Kneschke <l.kneschke@metaways.de>
+ */
+
+/**
+ * class to handle ActiveSync Sync command
+ *
+ * @package Backend
+ */
+
+class Syncroton_Backend_Policy extends Syncroton_Backend_ABackend #implements Syncroton_Backend_IDevice
+{
+ protected $_tableName = 'policy';
+
+ protected $_modelClassName = 'Syncroton_Model_Policy';
+
+ protected $_modelInterfaceName = 'Syncroton_Model_IPolicy';
+}
diff --git a/lib/ext/Syncroton/Backend/SyncState.php b/lib/ext/Syncroton/Backend/SyncState.php
index 078aacc..87dc39c 100644
--- a/lib/ext/Syncroton/Backend/SyncState.php
+++ b/lib/ext/Syncroton/Backend/SyncState.php
@@ -1,254 +1,209 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Backend
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @author Lars Kneschke <l.kneschke@metaways.de>
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
*
*/
/**
* sql backend class for the folder state
*
* @package Syncroton
* @subpackage Backend
*/
-class Syncroton_Backend_SyncState implements Syncroton_Backend_ISyncState
+class Syncroton_Backend_SyncState extends Syncroton_Backend_ABackend implements Syncroton_Backend_ISyncState
{
- /**
- * the database adapter
- *
- * @var Zend_Db_Adapter_Abstract
- */
- protected $_db;
-
- protected $_tablePrefix;
-
- public function __construct(Zend_Db_Adapter_Abstract $_db, $_tablePrefix = 'Syncroton_')
- {
- $this->_db = $_db;
- $this->_tablePrefix = $_tablePrefix;
- }
+ protected $_tableName = 'synckey';
+
+ protected $_modelClassName = 'Syncroton_Model_SyncState';
+
+ protected $_modelInterfaceName = 'Syncroton_Model_ISyncState';
/**
- * create new sync state
- *
- * @param Syncroton_Model_ISyncState $_syncState
- * @return Syncroton_Model_SyncState
+ * (non-PHPdoc)
+ * @see Syncroton_Backend_ISyncState::create()
*/
- public function create(Syncroton_Model_ISyncState $_syncState, $_keepPreviousSyncState = true)
+ public function create($model, $keepPreviousSyncState = true)
{
- $id = sha1(mt_rand(). microtime());
- $deviceId = $_syncState->device_id instanceof Syncroton_Model_IDevice ? $_syncState->device_id->id : $_syncState->device_id;
-
- $this->_db->insert($this->_tablePrefix . 'synckey', array(
- 'id' => $id,
- 'device_id' => $deviceId,
- 'type' => $_syncState->type instanceof Syncroton_Model_IFolder ? $_syncState->type->id : $_syncState->type,
- 'counter' => $_syncState->counter,
- 'lastsync' => $_syncState->lastsync->format('Y-m-d H:i:s'),
- 'pendingdata' => isset($_syncState->pendingdata) && is_array($_syncState->pendingdata) ? Zend_Json::encode($_syncState->pendingdata) : null
- ));
-
- $state = $this->get($id);
+ $state = parent::create($model);
- if ($_keepPreviousSyncState !== true) {
+ if ($keepPreviousSyncState !== true) {
// remove all other synckeys
$this->_deleteOtherStates($state);
}
return $state;
}
- protected function _deleteOtherStates(Syncroton_Model_ISyncState $_state)
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Backend_ABackend::_convertModelToArray()
+ */
+ protected function _convertModelToArray($model)
+ {
+ $model = parent::_convertModelToArray($model);
+
+ $model['pendingdata'] = isset($model['pendingdata']) && is_array($model['pendingdata']) ? Zend_Json::encode($model['pendingdata']) : null;
+
+ return $model;
+ }
+
+ /**
+ *
+ * @param Syncroton_Model_ISyncState $state
+ */
+ protected function _deleteOtherStates(Syncroton_Model_ISyncState $state)
{
// remove all other synckeys
$where = array(
- 'device_id = ?' => $_state->device_id,
- 'type = ?' => $_state->type,
- 'counter != ?' => $_state->counter
+ 'device_id = ?' => $state->deviceId,
+ 'type = ?' => $state->type,
+ 'counter != ?' => $state->counter
);
- $this->_db->delete($this->_tablePrefix . 'synckey', $where);
+ $this->_db->delete($this->_tablePrefix . $this->_tableName, $where);
return true;
}
/**
- * @param string $_id
- * @throws Syncroton_Exception_NotFound
- * @return Syncroton_Model_SyncState
+ * (non-PHPdoc)
+ * @see Syncroton_Backend_ABackend::_getObject()
*/
- public function get($_id)
+ protected function _getObject($data)
{
- $select = $this->_db->select()
- ->from($this->_tablePrefix . 'synckey')
- ->where('id = ?', $_id);
-
- $stmt = $this->_db->query($select);
- $state = $stmt->fetchObject('Syncroton_Model_SyncState');
- $stmt = null; # see https://bugs.php.net/bug.php?id=44081
+ $model = parent::_getObject($data);
- if (! $state instanceof Syncroton_Model_ISyncState) {
- throw new Syncroton_Exception_NotFound('id not found');
- }
-
- $this->_convertFields($state);
-
- return $state;
- }
-
- protected function _convertFields(Syncroton_Model_SyncState $state)
- {
- if (!empty($state->lastsync)) {
- $state->lastsync = new DateTime($state->lastsync, new DateTimeZone('utc'));
- }
- if ($state->pendingdata !== NULL) {
- $state->pendingdata = Zend_Json::decode($state->pendingdata);
+ if ($model->pendingdata !== NULL) {
+ $model->pendingdata = Zend_Json::decode($model->pendingdata);
}
+
+ return $model;
}
/**
- * always returns the latest syncstate
- *
- * @param Syncroton_Model_IDevice|string $_deviceId
- * @param Syncroton_Model_IFolder|string $_folderId
- * @return Syncroton_Model_SyncState
+ * (non-PHPdoc)
+ * @see Syncroton_Backend_ISyncState::getSyncState()
*/
- public function getSyncState($_deviceId, $_folderId)
+ public function getSyncState($deviceId, $folderId)
{
- $deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
- $folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->id : $_folderId;
+ $deviceId = $deviceId instanceof Syncroton_Model_IDevice ? $deviceId->id : $deviceId;
+ $folderId = $folderId instanceof Syncroton_Model_IFolder ? $folderId->id : $folderId;
$select = $this->_db->select()
- ->from($this->_tablePrefix . 'synckey')
+ ->from($this->_tablePrefix . $this->_tableName)
->where($this->_db->quoteIdentifier('device_id') . ' = ?', $deviceId)
->where($this->_db->quoteIdentifier('type') . ' = ?', $folderId)
->order('counter DESC')
->limit(1);
- $stmt = $this->_db->query($select);
- $state = $stmt->fetchObject('Syncroton_Model_SyncState');
- $stmt = null; # see https://bugs.php.net/bug.php?id=44081
-
- if (! $state instanceof Syncroton_Model_ISyncState) {
- throw new Syncroton_Exception_NotFound('id not found');
- }
-
- $this->_convertFields($state);
-
- return $state;
+ $stmt = $this->_db->query($select);
+ $data = $stmt->fetch();
+ $stmt = null; # see https://bugs.php.net/bug.php?id=44081
+
+ if ($data === false) {
+ throw new Syncroton_Exception_NotFound('id not found');
+ }
+
+ return $this->_getObject($data);
}
/**
* delete all stored synckeys for given type
*
- * @param Syncroton_Model_IDevice|string $_deviceId
- * @param Syncroton_Model_IFolder|string $_folderId
+ * @param Syncroton_Model_IDevice|string $deviceId
+ * @param Syncroton_Model_IFolder|string $folderId
*/
- public function resetState($_deviceId, $_folderId)
+ public function resetState($deviceId, $folderId)
{
- $deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
- $folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->id : $_folderId;
+ $deviceId = $deviceId instanceof Syncroton_Model_IDevice ? $deviceId->id : $deviceId;
+ $folderId = $folderId instanceof Syncroton_Model_IFolder ? $folderId->id : $folderId;
$where = array(
$this->_db->quoteInto($this->_db->quoteIdentifier('device_id') . ' = ?', $deviceId),
$this->_db->quoteInto($this->_db->quoteIdentifier('type') . ' = ?', $folderId)
);
- $this->_db->delete($this->_tablePrefix . 'synckey', $where);
- }
-
- public function update(Syncroton_Model_ISyncState $_syncState)
- {
- $deviceId = $_syncState->device_id instanceof Syncroton_Model_IDevice ? $_syncState->device_id->id : $_syncState->device_id;
-
- $this->_db->update($this->_tablePrefix . 'synckey', array(
- 'counter' => $_syncState->counter,
- 'lastsync' => $_syncState->lastsync->format('Y-m-d H:i:s'),
- 'pendingdata' => isset($_syncState->pendingdata) && is_array($_syncState->pendingdata) ? Zend_Json::encode($_syncState->pendingdata) : null
- ), array(
- 'id = ?' => $_syncState->id
- ));
-
- return $this->get($_syncState->id);
+ $this->_db->delete($this->_tablePrefix . $this->_tableName, $where);
}
/**
* get array of ids which got send to the client for a given class
*
- * @param Syncroton_Model_IDevice|string $_deviceId
- * @param Syncroton_Model_IFolder|string $_folderId
+ * @param Syncroton_Model_IDevice|string $deviceId
+ * @param Syncroton_Model_IFolder|string $folderId
* @return Syncroton_Model_SyncState
*/
- public function validate($_deviceId, $_folderId, $_syncKey)
+ public function validate($deviceId, $folderId, $syncKey)
{
- $deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
- $folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->id : $_folderId;
+ $deviceId = $deviceId instanceof Syncroton_Model_IDevice ? $deviceId->id : $deviceId;
+ $folderId = $folderId instanceof Syncroton_Model_IFolder ? $folderId->id : $folderId;
$select = $this->_db->select()
- ->from($this->_tablePrefix . 'synckey')
+ ->from($this->_tablePrefix . $this->_tableName)
->where($this->_db->quoteIdentifier('device_id') . ' = ?', $deviceId)
- ->where($this->_db->quoteIdentifier('counter') . ' = ?', $_syncKey)
+ ->where($this->_db->quoteIdentifier('counter') . ' = ?', $syncKey)
->where($this->_db->quoteIdentifier('type') . ' = ?', $folderId);
$stmt = $this->_db->query($select);
- $state = $stmt->fetchObject('Syncroton_Model_SyncState');
+ $data = $stmt->fetch();
$stmt = null; # see https://bugs.php.net/bug.php?id=44081
- if (! $state instanceof Syncroton_Model_ISyncState) {
+ if ($data === false) {
return false;
}
- $this->_convertFields($state);
+ $state = $this->_getObject($data);
// check if this was the latest syncKey
$select = $this->_db->select()
- ->from($this->_tablePrefix . 'synckey')
+ ->from($this->_tablePrefix . $this->_tableName)
->where($this->_db->quoteIdentifier('device_id') . ' = ?', $deviceId)
- ->where($this->_db->quoteIdentifier('counter') . ' = ?', $_syncKey + 1)
- ->where($this->_db->quoteIdentifier('type') . ' = ?', $folderId);
+ ->where($this->_db->quoteIdentifier('counter') . ' = ?', $syncKey + 1)
+ ->where($this->_db->quoteIdentifier('type') . ' = ?', $folderId);
$stmt = $this->_db->query($select);
- $moreRecentState = $stmt->fetchObject('Syncroton_Model_SyncState');
+ $moreRecentStateData = $stmt->fetch();
$stmt = null; # see https://bugs.php.net/bug.php?id=44081
// found more recent synckey => the last sync repsone got not received by the client
- if ($moreRecentState instanceof Syncroton_Model_ISyncState) {
+ if ($moreRecentStateData !== false) {
// undelete entries marked as deleted in Syncroton_content table
$this->_db->update($this->_tablePrefix . 'content', array(
'is_deleted' => 0,
), array(
'device_id = ?' => $deviceId,
'folder_id = ?' => $folderId,
'creation_synckey = ?' => $state->counter,
'is_deleted = ?' => 1
));
// remove entries added during latest sync in Syncroton_content table
$this->_db->delete($this->_tablePrefix . 'content', array(
'device_id = ?' => $deviceId,
'folder_id = ?' => $folderId,
'creation_synckey > ?' => $state->counter,
));
} else {
// finaly delete all entries marked for removal in Syncroton_content table
$this->_db->delete($this->_tablePrefix . 'content', array(
'device_id = ?' => $deviceId,
'folder_id = ?' => $folderId,
'is_deleted = ?' => 1
));
}
// remove all other synckeys
$this->_deleteOtherStates($state);
return $state;
}
}
diff --git a/lib/ext/Syncroton/Command/FolderCreate.php b/lib/ext/Syncroton/Command/FolderCreate.php
index 47aefcb..1932848 100644
--- a/lib/ext/Syncroton/Command/FolderCreate.php
+++ b/lib/ext/Syncroton/Command/FolderCreate.php
@@ -1,120 +1,120 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync FolderSync command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_FolderCreate extends Syncroton_Command_Wbxml
{
protected $_defaultNameSpace = 'uri:FolderHierarchy';
protected $_documentElement = 'FolderCreate';
protected $_classes = array(
Syncroton_Data_Factory::CLASS_CALENDAR,
Syncroton_Data_Factory::CLASS_CONTACTS,
Syncroton_Data_Factory::CLASS_EMAIL,
Syncroton_Data_Factory::CLASS_TASKS
);
/**
- * synckey sent from client
- *
- * @var string
+ *
+ * @var Syncroton_Model_Folder
*/
- protected $_syncKey;
- protected $_parentId;
- protected $_displayName;
- protected $_type;
+ protected $_folder;
/**
* parse FolderCreate request
*
*/
public function handle()
{
$xml = simplexml_import_dom($this->_requestBody);
- $this->_syncKey = (int)$xml->SyncKey;
- $this->_parentId = (string)$xml->ParentId;
- $this->_displayName = (string)$xml->DisplayName;
- $this->_type = (int)$xml->Type;
-
+ $syncKey = (int)$xml->SyncKey;
+
if ($this->_logger instanceof Zend_Log)
- $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " synckey is $this->_syncKey");
+ $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " synckey is $syncKey");
+
+ if (!($this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $syncKey)) instanceof Syncroton_Model_SyncState) {
+
+ $this->_syncStateBackend->resetState($this->_device, 'FolderSync');
+
+ return;
+ }
- switch((int)$xml->Type) {
+ $folder = new Syncroton_Model_Folder($xml);
+
+ if ($this->_logger instanceof Zend_Log)
+ $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " parentId: {$folder->parentId} displayName: {$folder->displayName}");
+
+ switch($folder->type) {
case Syncroton_Command_FolderSync::FOLDERTYPE_CALENDAR_USER_CREATED:
- $this->_class = Syncroton_Data_Factory::CLASS_CALENDAR;
+ $folder->class = Syncroton_Data_Factory::CLASS_CALENDAR;
break;
case Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED:
- $this->_class = Syncroton_Data_Factory::CLASS_CONTACTS;
+ $folder->class = Syncroton_Data_Factory::CLASS_CONTACTS;
break;
case Syncroton_Command_FolderSync::FOLDERTYPE_MAIL_USER_CREATED:
- $this->_class = Syncroton_Data_Factory::CLASS_EMAIL;
+ $folder->class = Syncroton_Data_Factory::CLASS_EMAIL;
break;
case Syncroton_Command_FolderSync::FOLDERTYPE_TASK_USER_CREATED:
- $this->_class = Syncroton_Data_Factory::CLASS_TASKS;
+ $folder->class = Syncroton_Data_Factory::CLASS_TASKS;
break;
default:
throw new Syncroton_Exception_UnexpectedValue('invalid type defined');
break;
}
- $this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $this->_syncKey);
+ $folder->deviceId = $this->_device;
+ $folder->creationTime = $this->_syncTimeStamp;
+
+ $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
+
+ $this->_folder = $dataController->createFolder($folder);
+
+ $this->_folderBackend->create($this->_folder);
}
/**
* generate FolderCreate response
*/
public function getResponse()
{
$folderCreate = $this->_outputDom->documentElement;
- if($this->_syncState == false) {
+ if (!$this->_syncState instanceof Syncroton_Model_SyncState) {
if ($this->_logger instanceof Zend_Log)
- $this->_logger->info(__METHOD__ . '::' . __LINE__ . " INVALID synckey");
+ $this->_logger->info(__METHOD__ . '::' . __LINE__ . " invalid synckey provided. FolderSync 0 needed.");
$folderCreate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncroton_Command_FolderSync::STATUS_INVALID_SYNC_KEY));
+
} else {
$this->_syncState->counter++;
+ $this->_syncState->lastsync = $this->_syncTimeStamp;
- $dataController = Syncroton_Data_Factory::factory($this->_class, $this->_device, $this->_syncTimeStamp);
-
- $folder = $dataController->createFolder(new Syncroton_Model_Folder(array(
- 'device_id' => $this->_device,
- 'class' => $this->_class,
- 'parentid' => $this->_parentId,
- 'displayname' => $this->_displayName,
- 'type' => $this->_type,
- 'creation_time' => $this->_syncTimeStamp,
- 'lastfiltertype' => null
- )));
+ // store folder in state backend
+ $this->_syncStateBackend->update($this->_syncState);
// create xml output
$folderCreate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncroton_Command_FolderSync::STATUS_SUCCESS));
$folderCreate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'SyncKey', $this->_syncState->counter));
- $folderCreate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ServerId', $folder->folderid));
-
- // store folder in state backend
- $this->_folderBackend->create($folder);
-
- $this->_syncStateBackend->update($this->_syncState);
+ $folderCreate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ServerId', $this->_folder->serverId));
}
return $this->_outputDom;
}
}
diff --git a/lib/ext/Syncroton/Command/FolderDelete.php b/lib/ext/Syncroton/Command/FolderDelete.php
index e390105..4259ea8 100644
--- a/lib/ext/Syncroton/Command/FolderDelete.php
+++ b/lib/ext/Syncroton/Command/FolderDelete.php
@@ -1,98 +1,108 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync FolderDelete command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_FolderDelete extends Syncroton_Command_Wbxml
{
protected $_defaultNameSpace = 'uri:FolderHierarchy';
protected $_documentElement = 'FolderDelete';
protected $_classes = array(
Syncroton_Data_Factory::CLASS_CALENDAR,
Syncroton_Data_Factory::CLASS_CONTACTS,
Syncroton_Data_Factory::CLASS_EMAIL,
Syncroton_Data_Factory::CLASS_TASKS
);
protected $_serverId;
/**
* @var Syncroton_Model_ISyncState
*/
protected $_syncState;
/**
* parse FolderDelete request
*
*/
public function handle()
{
$xml = simplexml_import_dom($this->_requestBody);
- $syncKey = (int)$xml->SyncKey;
- $folderId = (string)$xml->ServerId;
+ $syncKey = (int)$xml->SyncKey;
if ($this->_logger instanceof Zend_Log)
- $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " synckey is $syncKey");
+ $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " synckey is $syncKey");
- $this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $syncKey);
+ if (!($this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $syncKey)) instanceof Syncroton_Model_SyncState) {
+
+ $this->_syncStateBackend->resetState($this->_device, 'FolderSync');
+
+ return;
+ }
+
+ $serverId = (string)$xml->ServerId;
try {
- $this->_folder = $this->_folderBackend->getFolder($this->_device, $folderId);
+ $this->_folder = $this->_folderBackend->getFolder($this->_device, $serverId);
$dataController = Syncroton_Data_Factory::factory($this->_folder->class, $this->_device, $this->_syncTimeStamp);
$dataController->deleteFolder($this->_folder);
$this->_folderBackend->delete($this->_folder);
} catch (Syncroton_Exception_NotFound $senf) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $senf->getMessage());
}
}
/**
* generate FolderDelete response
*
* @todo currently we support only the main folder which contains all contacts/tasks/events/notes per class
*/
public function getResponse()
{
$folderDelete = $this->_outputDom->documentElement;
- if($this->_syncState == false) {
+ if (!$this->_syncState instanceof Syncroton_Model_SyncState) {
if ($this->_logger instanceof Zend_Log)
- $this->_logger->info(__METHOD__ . '::' . __LINE__ . " INVALID synckey");
+ $this->_logger->info(__METHOD__ . '::' . __LINE__ . " invalid synckey provided. FolderSync 0 needed.");
$folderDelete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncroton_Command_FolderSync::STATUS_INVALID_SYNC_KEY));
+
} else {
if ($this->_folder instanceof Syncroton_Model_IFolder) {
$this->_syncState->counter++;
+ $this->_syncState->lastsync = $this->_syncTimeStamp;
+
+ // store folder in state backend
+ $this->_syncStateBackend->update($this->_syncState);
// create xml output
$folderDelete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncroton_Command_FolderSync::STATUS_SUCCESS));
$folderDelete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'SyncKey', $this->_syncState->counter));
-
- $this->_syncStateBackend->update($this->_syncState);
+
} else {
// create xml output
$folderDelete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncroton_Command_FolderSync::STATUS_FOLDER_NOT_FOUND));
$folderDelete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'SyncKey', $this->_syncState->counter));
}
}
return $this->_outputDom;
}
}
diff --git a/lib/ext/Syncroton/Command/FolderSync.php b/lib/ext/Syncroton/Command/FolderSync.php
index df82ca4..9017798 100644
--- a/lib/ext/Syncroton/Command/FolderSync.php
+++ b/lib/ext/Syncroton/Command/FolderSync.php
@@ -1,235 +1,211 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync FolderSync command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
{
const STATUS_SUCCESS = 1;
const STATUS_FOLDER_EXISTS = 2;
const STATUS_IS_SPECIAL_FOLDER = 3;
const STATUS_FOLDER_NOT_FOUND = 4;
const STATUS_PARENT_FOLDER_NOT_FOUND = 5;
const STATUS_SERVER_ERROR = 6;
const STATUS_ACCESS_DENIED = 7;
const STATUS_REQUEST_TIMED_OUT = 8;
const STATUS_INVALID_SYNC_KEY = 9;
const STATUS_MISFORMATTED = 10;
const STATUS_UNKNOWN_ERROR = 11;
/**
* some usefull constants for working with the xml files
*
*/
const FOLDERTYPE_GENERIC_USER_CREATED = 1;
const FOLDERTYPE_INBOX = 2;
const FOLDERTYPE_DRAFTS = 3;
const FOLDERTYPE_DELETEDITEMS = 4;
const FOLDERTYPE_SENTMAIL = 5;
const FOLDERTYPE_OUTBOX = 6;
const FOLDERTYPE_TASK = 7;
const FOLDERTYPE_CALENDAR = 8;
const FOLDERTYPE_CONTACT = 9;
const FOLDERTYPE_NOTE = 10;
const FOLDERTYPE_JOURNAL = 11;
const FOLDERTYPE_MAIL_USER_CREATED = 12;
const FOLDERTYPE_CALENDAR_USER_CREATED = 13;
const FOLDERTYPE_CONTACT_USER_CREATED = 14;
const FOLDERTYPE_TASK_USER_CREATED = 15;
const FOLDERTYPE_JOURNAL_USER_CREATED = 16;
const FOLDERTYPE_NOTES_USER_CREATED = 17;
const FOLDERTYPE_UNKOWN = 18;
protected $_defaultNameSpace = 'uri:FolderHierarchy';
protected $_documentElement = 'FolderSync';
protected $_classes = array(
Syncroton_Data_Factory::CLASS_CALENDAR,
Syncroton_Data_Factory::CLASS_CONTACTS,
Syncroton_Data_Factory::CLASS_EMAIL,
Syncroton_Data_Factory::CLASS_TASKS
);
/**
* @var string
*/
protected $_syncKey;
/**
* parse FolderSync request
*
*/
public function handle()
{
- #if ($this->_statusProvisioning = $this->_checkProvisioningNeeded() !== false) {
- # if (version_compare($this->_device->acsversion, '14.0', '<')) {
- # throw new Syncroton_Exception_ProvisioningNeeded();
- # } else {
- # return;
- # }
- #}
-
$xml = simplexml_import_dom($this->_requestBody);
$syncKey = (int)$xml->SyncKey;
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " synckey is $syncKey");
- if ($syncKey == 0) {
+ if ($syncKey === 0) {
$this->_syncState = new Syncroton_Model_SyncState(array(
'device_id' => $this->_device,
'counter' => 0,
'type' => 'FolderSync',
'lastsync' => $this->_syncTimeStamp
));
// reset state of foldersync
$this->_syncStateBackend->resetState($this->_device, 'FolderSync');
- } else {
- if (($this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $syncKey)) instanceof Syncroton_Model_SyncState) {
- $this->_syncState->lastsync = $this->_syncTimeStamp;
- } else {
- $this->_syncStateBackend->resetState($this->_device, 'FolderSync');
- }
+
+ return;
+ }
+
+ if (!($this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $syncKey)) instanceof Syncroton_Model_SyncState) {
+ $this->_syncStateBackend->resetState($this->_device, 'FolderSync');
}
}
/**
* generate FolderSync response
*
* @todo changes are missing in response (folder got renamed for example)
*/
public function getResponse()
{
$folderSync = $this->_outputDom->documentElement;
- // provioning needed?
- #if ($this->_statusProvisioning !== false) {
- # if ($this->_logger instanceof Zend_Log)
- # $this->_logger->info(__METHOD__ . '::' . __LINE__ . " provisiong needed or remote wipe requested");
- # $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', $this->_statusProvisioning));
- #
- # return $this->_outputDom;
- #}
-
// invalid synckey provided
- if($this->_syncState === false) {
+ if (!$this->_syncState instanceof Syncroton_Model_SyncState) {
if ($this->_logger instanceof Zend_Log)
- $this->_logger->info(__METHOD__ . '::' . __LINE__ . " INVALID synckey provided");
+ $this->_logger->info(__METHOD__ . '::' . __LINE__ . " invalid synckey provided. FolderSync 0 needed.");
$folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', self::STATUS_INVALID_SYNC_KEY));
return $this->_outputDom;
}
$folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', self::STATUS_SUCCESS));
$adds = array();
$deletes = array();
foreach($this->_classes as $class) {
try {
$dataController = Syncroton_Data_Factory::factory($class, $this->_device, $this->_syncTimeStamp);
- } catch (Zend_Exception $ze) {
+ } catch (Exception $e) {
// backend not defined
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " no data backend defined for class: " . $class);
continue;
}
// retrieve all folders available in data backend
$serverFolders = $dataController->getAllFolders();
+
// retrieve all folders sent to client
$clientFolders = $this->_folderBackend->getFolderState($this->_device, $class);
$serverFoldersIds = array_keys($serverFolders);
// is this the first sync?
if($this->_syncState->counter == 0) {
$clientFoldersIds = array();
} else {
$clientFoldersIds = array_keys($clientFolders);
}
-
+
// calculate added entries
$serverDiff = array_diff($serverFoldersIds, $clientFoldersIds);
foreach($serverDiff as $serverFolderId) {
+ // have we created a folderObject in syncroton_folder before?
if (isset($clientFolders[$serverFolderId])) {
- $adds[] = $clientFolders[$serverFolderId];
+ $add = $clientFolders[$serverFolderId];
} else {
- $adds[] = new Syncroton_Model_Folder(array(
- 'device_id' => $this->_device,
- 'class' => $class,
- 'folderid' => $serverFolders[$serverFolderId]->folderid,
- 'parentid' => $serverFolders[$serverFolderId]->parentid,
- 'displayname' => $serverFolders[$serverFolderId]->displayname,
- 'type' => $serverFolders[$serverFolderId]->type,
- 'creation_time' => $this->_syncTimeStamp,
- 'lastfiltertype' => null
- ));
+ $add = $serverFolders[$serverFolderId];
+ $add->creationTime = $this->_syncTimeStamp;
+ $add->deviceId = $this->_device;
+ unset($add->id);
}
+ $add->class = $class;
+
+ $adds[] = $add;
}
// calculate deleted entries
$serverDiff = array_diff($clientFoldersIds, $serverFoldersIds);
foreach($serverDiff as $serverFolderId) {
$deletes[] = $clientFolders[$serverFolderId];
}
}
$count = count($adds) + /*count($changes) + */count($deletes);
if($count > 0) {
$this->_syncState->counter++;
+ $this->_syncState->lastsync = $this->_syncTimeStamp;
}
// create xml output
$folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'SyncKey', $this->_syncState->counter));
$changes = $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Changes'));
$changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Count', $count));
foreach($adds as $folder) {
-
$add = $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Add'));
- $add->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ServerId', $folder->folderid));
- $add->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ParentId', $folder->parentid));
-
- $displayName = $this->_outputDom->createElementNS('uri:FolderHierarchy', 'DisplayName');
- $displayName->appendChild($this->_outputDom->createTextNode($folder->displayname));
- $add->appendChild($displayName);
-
- $add->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Type', $folder->type));
+ $folder->appendXML($add);
+
// store folder in backend
if (empty($folder->id)) {
$this->_folderBackend->create($folder);
}
}
foreach($deletes as $folder) {
$delete = $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Delete'));
- $delete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ServerId', $folder->folderid));
+ $delete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ServerId', $folder->serverId));
$this->_folderBackend->delete($folder);
}
if (empty($this->_syncState->id)) {
$this->_syncStateBackend->create($this->_syncState);
} else {
$this->_syncStateBackend->update($this->_syncState);
}
return $this->_outputDom;
}
}
diff --git a/lib/ext/Syncroton/Command/FolderUpdate.php b/lib/ext/Syncroton/Command/FolderUpdate.php
index 5089bd7..d219ede 100644
--- a/lib/ext/Syncroton/Command/FolderUpdate.php
+++ b/lib/ext/Syncroton/Command/FolderUpdate.php
@@ -1,93 +1,101 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync FolderUpdate command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_FolderUpdate extends Syncroton_Command_Wbxml
{
protected $_defaultNameSpace = 'uri:FolderHierarchy';
protected $_documentElement = 'FolderUpdate';
- protected $_classes = array('Contacts', 'Tasks', 'Email');
-
/**
- * synckey sent from client
- *
- * @var string
+ *
+ * @var Syncroton_Model_Folder
*/
- protected $_syncKey;
- protected $_parentId;
- protected $_displayName;
- protected $_serverId;
+ protected $_folderUpdate;
/**
* parse FolderUpdate request
*
*/
public function handle()
{
$xml = simplexml_import_dom($this->_requestBody);
- $this->_syncKey = (int)$xml->SyncKey;
- $this->_serverId = (string)$xml->ServerId;
+ $syncKey = (int)$xml->SyncKey;
if ($this->_logger instanceof Zend_Log)
- $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " synckey is $this->_syncKey parentId $this->_parentId name $this->_displayName");
+ $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " synckey is $syncKey");
- $this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $this->_syncKey);
+ if (!($this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $syncKey)) instanceof Syncroton_Model_SyncState) {
+
+ $this->_syncStateBackend->resetState($this->_device, 'FolderSync');
+
+ return;
+ }
+
+ $folderUpdate = new Syncroton_Model_Folder($xml);
+
+ if ($this->_logger instanceof Zend_Log)
+ $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " parentId: {$folderUpdate->parentId} displayName: {$folderUpdate->displayName}");
try {
- $folder = $this->_folderBackend->getFolder($this->_device, $this->_serverId);
+ $folder = $this->_folderBackend->getFolder($this->_device, $folderUpdate->serverId);
- $folder->displayname = (string)$xml->DisplayName;
- $folder->parentid = (string)$xml->ParentId;
+ $folder->displayName = $folderUpdate->displayName;
+ $folder->parentId = $folderUpdate->parentId;
$dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
+ // update folder in data backend
$dataController->updateFolder($folder);
+ // update folder status in Syncroton backend
$this->_folderBackend->update($folder);
} catch (Syncroton_Exception_NotFound $senf) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $senf->getMessage());
}
}
/**
* generate FolderUpdate response
*
* @todo currently we support only the main folder which contains all contacts/tasks/events/notes per class
*/
public function getResponse($_keepSession = FALSE)
{
$folderUpdate = $this->_outputDom->documentElement;
- if($this->_syncState == false) {
+ if (!$this->_syncState instanceof Syncroton_Model_SyncState) {
if ($this->_logger instanceof Zend_Log)
- $this->_logger->info(__METHOD__ . '::' . __LINE__ . " INVALID synckey");
- $folderUpdate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncroton_Command_FolderSync::STATUS_INVALID_SYNC_KEY));
+ $this->_logger->info(__METHOD__ . '::' . __LINE__ . " invalid synckey provided. FolderSync 0 needed.");
+ $folderUpdate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncroton_Command_FolderSync::STATUS_INVALID_SYNC_KEY));
+
} else {
$this->_syncState->counter++;
+ $this->_syncState->lastsync = $this->_syncTimeStamp;
+ // store folder in state backend
+ $this->_syncStateBackend->update($this->_syncState);
+
// create xml output
$folderUpdate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncroton_Command_FolderSync::STATUS_SUCCESS));
$folderUpdate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'SyncKey', $this->_syncState->counter));
-
- $this->_syncStateBackend->update($this->_syncState);
}
return $this->_outputDom;
}
}
diff --git a/lib/ext/Syncroton/Command/ItemOperations.php b/lib/ext/Syncroton/Command/ItemOperations.php
index 12fd0ea..7692970 100644
--- a/lib/ext/Syncroton/Command/ItemOperations.php
+++ b/lib/ext/Syncroton/Command/ItemOperations.php
@@ -1,140 +1,139 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync ItemOperations command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_ItemOperations extends Syncroton_Command_Wbxml
{
const STATUS_SUCCESS = 1;
const STATUS_PROTOCOL_ERROR = 2;
const STATUS_SERVER_ERROR = 3;
const STATUS_ITEM_FAILED_CONVERSION = 14;
protected $_defaultNameSpace = 'uri:ItemOperations';
protected $_documentElement = 'ItemOperations';
/**
* list of items to move
*
* @var array
*/
protected $_fetches = array();
/**
* parse MoveItems request
*
*/
public function handle()
{
$xml = simplexml_import_dom($this->_requestBody);
if (isset($xml->Fetch)) {
foreach ($xml->Fetch as $fetch) {
$fetchArray = array(
'store' => (string)$fetch->Store
);
// try to fetch element from namespace AirSync
$airSync = $fetch->children('uri:AirSync');
if (isset($airSync->CollectionId)) {
$fetchArray['collectionId'] = (string)$airSync->CollectionId;
$fetchArray['serverId'] = (string)$airSync->ServerId;
}
// try to fetch element from namespace AirSyncBase
$airSyncBase = $fetch->children('uri:AirSyncBase');
if (isset($airSyncBase->FileReference)) {
$fetchArray['fileReference'] = (string)$airSyncBase->FileReference;
}
if (isset($fetch->Options)) {
// try to fetch element from namespace AirSyncBase
$airSyncBase = $fetch->Options->children('uri:AirSyncBase');
if (isset($airSyncBase->BodyPreference)) {
// required
$fetchArray['bodyPreferenceType'] = (int) $airSyncBase->BodyPreference->Type;
// optional
if (isset($airSyncBase->BodyPreference->TruncationSize)) {
$fetchArray['truncationSize'] = (int) $airSyncBase->BodyPreference->TruncationSize;
}
}
}
$this->_fetches[] = $fetchArray;
}
}
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " fetches: " . print_r($this->_fetches, true));
}
/**
* generate ItemOperations response
*/
public function getResponse()
{
// add aditional namespaces
$this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:AirSyncBase' , 'uri:AirSyncBase');
$this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:AirSync' , 'uri:AirSync');
$itemOperations = $this->_outputDom->documentElement;
$itemOperations->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SUCCESS));
$response = $itemOperations->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Response'));
foreach ($this->_fetches as $fetch) {
$fetchTag = $response->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Fetch'));
try {
$dataController = Syncroton_Data_Factory::factory($fetch['store'], $this->_device, $this->_syncTimeStamp);
if (isset($fetch['collectionId'])) {
$fetchTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SUCCESS));
$fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $fetch['collectionId']));
$fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $fetch['serverId']));
$properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties');
$dataController
->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $fetch['collectionId'])), $fetch['serverId'])
->appendXML($properties);
$fetchTag->appendChild($properties);
} elseif (isset($fetch['fileReference'])) {
$fetchTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SUCCESS));
$fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSyncBase', 'FileReference', $fetch['fileReference']));
$properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties');
$dataController
->getFileReference($fetch['fileReference'])
->appendXML($properties);
$fetchTag->appendChild($properties);
}
} catch (Syncroton_Exception_NotFound $e) {
- //echo __LINE__;
$response->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_ITEM_FAILED_CONVERSION));
} catch (Exception $e) {
- //echo __LINE__; echo $e->getMessage(); echo $e->getTraceAsString();
+ //echo __LINE__; echo $e->getMessage(); echo $e->getTraceAsString();
$response->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SERVER_ERROR));
}
}
return $this->_outputDom;
}
}
diff --git a/lib/ext/Syncroton/Command/Options.php b/lib/ext/Syncroton/Command/Options.php
index cbb68c1..35a98e7 100644
--- a/lib/ext/Syncroton/Command/Options.php
+++ b/lib/ext/Syncroton/Command/Options.php
@@ -1,38 +1,32 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync http options request
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_Options
{
/**
* this function generates the response for the client
*
* @return void
*/
public function getResponse()
{
- // same header like Exchange 2003
- #header("MS-Server-ActiveSync: 8.3");
- #header("MS-ASProtocolVersions: 2.5,12.0");
- #header("MS-ASProtocolCommands: CreateCollection,DeleteCollection,FolderCreate,FolderDelete,FolderSync,FolderUpdate,GetAttachment,GetItemEstimate,ItemOperations,MeetingResponse,MoveCollection,MoveItems,Provision,ResolveRecipients,Ping,SendMail,Search,Settings,SmartForward,SmartReply,Sync");
-
// same header like Exchange 2xxx???
header('MS-Server-ActiveSync: 14.00.0536.000');
- header("MS-ASProtocolVersions: 2.5,12.0,12.1,14.0,14.1");
+ header("MS-ASProtocolVersions: 12.0,12.1,14.0,14.1");
header("MS-ASProtocolCommands: CreateCollection,DeleteCollection,FolderCreate,FolderDelete,FolderSync,FolderUpdate,GetAttachment,GetHierarchy,GetItemEstimate,ItemOperations,MeetingResponse,MoveCollection,MoveItems,Provision,ResolveRecipients,Ping,SendMail,Search,Settings,SmartForward,SmartReply,Sync,ValidateCert");
- header('MS-ASProtocolRevisions: 12.1r1');
}
}
diff --git a/lib/ext/Syncroton/Command/Ping.php b/lib/ext/Syncroton/Command/Ping.php
index a2c88b6..e0c101e 100644
--- a/lib/ext/Syncroton/Command/Ping.php
+++ b/lib/ext/Syncroton/Command/Ping.php
@@ -1,169 +1,169 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Ping command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
{
const STATUS_NO_CHANGES_FOUND = 1;
const STATUS_CHANGES_FOUND = 2;
const STATUS_MISSING_PARAMETERS = 3;
const STATUS_REQUEST_FORMAT_ERROR = 4;
const STATUS_INTERVAL_TO_GREAT_OR_SMALL = 5;
const STATUS_TO_MUCH_FOLDERS = 6;
const STATUS_FOLDER_NOT_FOUND = 7;
const STATUS_GENERAL_ERROR = 8;
protected $_skipValidatePolicyKey = true;
protected $_changesDetected = false;
const PING_TIMEOUT = 60;
/**
* Enter description here...
*
* @var Syncroton_Backend_StandAlone_Abstract
*/
protected $_dataBackend;
protected $_defaultNameSpace = 'uri:Ping';
protected $_documentElement = 'Ping';
protected $_foldersWithChanges = array();
/**
* process the XML file and add, change, delete or fetches data
*
* @todo can we get rid of LIBXML_NOWARNING
* @todo we need to stored the initial data for folders and lifetime as the phone is sending them only when they change
* @return resource
*/
public function handle()
{
$intervalStart = time();
$status = self::STATUS_NO_CHANGES_FOUND;
-
+
// the client does not send a wbxml document, if the Ping parameters did not change compared with the last request
if($this->_requestBody instanceof DOMDocument) {
$xml = simplexml_import_dom($this->_requestBody);
$xml->registerXPathNamespace('Ping', 'Ping');
if(isset($xml->HeartBeatInterval)) {
$this->_device->pinglifetime = (int)$xml->HeartBeatInterval;
}
if(isset($xml->Folders->Folder)) {
$folders = array();
foreach ($xml->Folders->Folder as $folderXml) {
try {
// does the folder exist?
$folder = $this->_folderBackend->getFolder($this->_device, (string)$folderXml->Id);
$folders[] = $folder;
} catch (Syncroton_Exception_NotFound $senf) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $senf->getMessage());
$status = self::STATUS_FOLDER_NOT_FOUND;
break;
}
}
$this->_device->pingfolder = serialize($folders);
}
$this->_device = $this->_deviceBackend->update($this->_device);
}
$lifeTime = $this->_device->pinglifetime;
#Tinebase_Core::setExecutionLifeTime($lifeTime);
$intervalEnd = $intervalStart + $lifeTime;
$secondsLeft = $intervalEnd;
$folders = unserialize($this->_device->pingfolder);
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folders to monitor($lifeTime / $intervalStart / $intervalEnd / $status): " . print_r($folders, true));
if($status === self::STATUS_NO_CHANGES_FOUND) {
$folderWithChanges = array();
do {
foreach((array) $folders as $folder) {
$dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
try {
$syncState = $this->_syncStateBackend->getSyncState($this->_device, $folder);
$foundChanges = !!$dataController->getCountOfChanges($this->_contentStateBackend, $folder, $syncState);
} catch (Syncroton_Exception_NotFound $e) {
// folder got never synchronized to client
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
if ($this->_logger instanceof Zend_Log)
- $this->_logger->info(__METHOD__ . '::' . __LINE__ . ' syncstate not found. enforce sync for folder: ' . $folder->folderid);
+ $this->_logger->info(__METHOD__ . '::' . __LINE__ . ' syncstate not found. enforce sync for folder: ' . $folder->serverId);
$foundChanges = true;
}
if($foundChanges == true) {
$this->_foldersWithChanges[] = $folder;
$status = self::STATUS_CHANGES_FOUND;
}
}
if($status === self::STATUS_CHANGES_FOUND) {
break;
}
// another process synchronized data already
if(isset($syncState) && $syncState->lastsync > $this->_syncTimeStamp) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " terminate ping process. Some other process updated data already.");
break;
}
sleep(self::PING_TIMEOUT);
$secondsLeft = $intervalEnd - time();
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " seconds left: " . $secondsLeft);
} while($secondsLeft > 0);
}
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " Lifetime: $lifeTime SecondsLeft: $secondsLeft Status: $status)");
$ping = $this->_outputDom->documentElement;
$ping->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Status', $status));
if($status === self::STATUS_CHANGES_FOUND) {
$folders = $ping->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Folders'));
foreach($this->_foldersWithChanges as $changedFolder) {
- $folder = $folders->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Folder', $changedFolder->folderid));
+ $folder = $folders->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Folder', $changedFolder->serverId));
if ($this->_logger instanceof Zend_Log)
- $this->_logger->info(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " changes in folder: " . $changedFolder->folderid);
+ $this->_logger->info(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " changes in folder: " . $changedFolder->serverId);
}
}
}
/**
* generate ping command response
*
*/
public function getResponse()
{
return $this->_outputDom;
}
}
diff --git a/lib/ext/Syncroton/Command/Provision.php b/lib/ext/Syncroton/Command/Provision.php
index 7cf53a2..b4ddf41 100644
--- a/lib/ext/Syncroton/Command/Provision.php
+++ b/lib/ext/Syncroton/Command/Provision.php
@@ -1,184 +1,174 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Provision command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_Provision extends Syncroton_Command_Wbxml
{
protected $_defaultNameSpace = 'uri:Provision';
protected $_documentElement = 'Provision';
const POLICYTYPE_XML = 'MS-WAP-Provisioning-XML';
const POLICYTYPE_WBXML = 'MS-EAS-Provisioning-WBXML';
const STATUS_SUCCESS = 1;
const STATUS_PROTOCOL_ERROR = 2;
const STATUS_GENERAL_SERVER_ERROR = 3;
const STATUS_DEVICE_MANAGED_EXTERNALLY = 4;
const REMOTEWIPE_REQUESTED = 1;
const REMOTEWIPE_CONFIRMED = 2;
protected $_skipValidatePolicyKey = true;
protected $_policyType;
protected $_sendPolicyKey;
+ protected $_deviceInformationSet = false;
/**
* process the XML file and add, change, delete or fetches data
*
* @return resource
*/
public function handle()
{
$xml = simplexml_import_dom($this->_requestBody);
$this->_policyType = isset($xml->Policies->Policy->PolicyType) ? (string) $xml->Policies->Policy->PolicyType : null;
$this->_sendPolicyKey = isset($xml->Policies->Policy->PolicyKey) ? (int) $xml->Policies->Policy->PolicyKey : null;
if ($this->_device->remotewipe == self::REMOTEWIPE_REQUESTED && isset($xml->RemoteWipe->Status) && (int)$xml->RemoteWipe->Status == self::STATUS_SUCCESS) {
$this->_device->remotewipe = self::REMOTEWIPE_CONFIRMED;
$this->_device = $this->_deviceBackend->update($this->_device);
}
+ // try to fetch element from Settings namespace
+ $settings = $xml->children('uri:Settings');
+ if (isset($settings->DeviceInformation) && isset($settings->DeviceInformation->Set)) {
+ // @todo update device informations
+ $this->_deviceInformationSet = true;
+ }
}
/**
* generate search command response
*
*/
public function getResponse()
{
+ $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Settings', 'uri:Settings');
+
// should we wipe the device
if ($this->_device->remotewipe >= self::REMOTEWIPE_REQUESTED) {
$this->_sendRemoteWipe();
} else {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . ' PolicyType: ' . $this->_policyType . ' PolicyKey: ' . $this->_sendPolicyKey);
if($this->_sendPolicyKey === NULL) {
$this->_sendPolicy();
- } else {
+ } elseif ($this->_sendPolicyKey == $this->_device->policykey) {
$this->_acknowledgePolicy();
}
}
return $this->_outputDom;
}
/**
* function the send policy to client
*
* 4131 (Enforce password on device) 0: enabled 1: disabled
* 4133 (Unlock from computer) 0: disabled 1: enabled
* AEFrequencyType 0: no inactivity time 1: inactivity time is set
* AEFrequencyValue inactivity time in minutes
* DeviceWipeThreshold after how many worng password to device should get wiped
* CodewordFrequency validate every 3 wrong passwords, that a person is using the device which is able to read and write. should be half of DeviceWipeThreshold
* MinimumPasswordLength minimum password length
* PasswordComplexity 0: Require alphanumeric 1: Require only numeric, 2: anything goes
*
*/
protected function _sendPolicy()
{
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . ' send policy to device');
- $policyData = '<wap-provisioningdoc>
- <characteristic type="SecurityPolicy">
- <parm name="4131" value="0"/>
- <parm name="4133" value="0"/>
- </characteristic>
- <characteristic type="Registry">
- <characteristic type="HKLM\Comm\Security\Policy\LASSD\AE\{50C13377-C66D-400C-889E-C316FC4AB374}">
- <parm name="AEFrequencyType" value="1"/>
- <parm name="AEFrequencyValue" value="3"/>
- </characteristic>
- <characteristic type="HKLM\Comm\Security\Policy\LASSD">
- <parm name="DeviceWipeThreshold" value="6"/>
- </characteristic>
- <characteristic type="HKLM\Comm\Security\Policy\LASSD">
- <parm name="CodewordFrequency" value="3"/>
- </characteristic>
- <characteristic type="HKLM\Comm\Security\Policy\LASSD\LAP\lap_pw">
- <parm name="MinimumPasswordLength" value="5"/>
- </characteristic>
- <characteristic type="HKLM\Comm\Security\Policy\LASSD\LAP\lap_pw">
- <parm name="PasswordComplexity" value="2"/>
- </characteristic>
- </characteristic>
- </wap-provisioningdoc>';
-
$this->_device->policykey = $this->generatePolicyKey();
$provision = $sync = $this->_outputDom->documentElement;
+ if ($this->_deviceInformationSet === true) {
+ $deviceInformation = $provision->appendChild($this->_outputDom->createElementNS('uri:Settings', 'DeviceInformation'));
+ $deviceInformation->appendChild($this->_outputDom->createElementNS('uri:Settings', 'Status', 1));
+ }
$provision->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Status', 1));
+
$policies = $provision->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Policies'));
$policy = $policies->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Policy'));
$policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'PolicyType', $this->_policyType));
$policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Status', 1));
$policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'PolicyKey', $this->_device->policykey));
if ($this->_policyType == self::POLICYTYPE_XML) {
- $data = $policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Data', $policyData));
+ $data = $policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Data'));
} else {
$data = $policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Data'));
$easProvisionDoc = $data->appendChild($this->_outputDom->createElementNS('uri:Provision', 'EASProvisionDoc'));
- $easProvisionDoc->appendChild($this->_outputDom->createElementNS('uri:Provision', 'DevicePasswordEnabled', 1));
- #$easProvisionDoc->appendChild($this->_outputDom->createElementNS('uri:Provision', 'MinDevicePasswordLength', 4));
- #$easProvisionDoc->appendChild($this->_outputDom->createElementNS('uri:Provision', 'MaxDevicePasswordFailedAttempts', 4));
- #$easProvisionDoc->appendChild($this->_outputDom->createElementNS('uri:Provision', 'MaxInactivityTimeDeviceLock', 60));
+ $this->_policyBackend
+ ->get($this->_device->policyId)
+ ->appendXML($easProvisionDoc);
}
$this->_deviceBackend->update($this->_device);
}
/**
* function the send remote wipe command
*/
protected function _sendRemoteWipe()
{
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . ' send remote wipe to device');
$provision = $sync = $this->_outputDom->documentElement;
$provision->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Status', 1));
$provision->appendChild($this->_outputDom->createElementNS('uri:Provision', 'RemoteWipe'));
}
protected function _acknowledgePolicy()
{
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . ' acknowledge policy');
- $this->_device->policykey = $this->generatePolicyKey();
+ $policykey = $this->_policyBackend->get($this->_device->policyId)->policyKey;
$provision = $sync = $this->_outputDom->documentElement;
$provision->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Status', 1));
$policies = $provision->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Policies'));
$policy = $policies->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Policy'));
$policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'PolicyType', $this->_policyType));
$policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Status', 1));
- $policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'PolicyKey', $this->_device->policykey));
+ $policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'PolicyKey', $policykey));
+ $this->_device->policykey = null;
$this->_deviceBackend->update($this->_device);
}
-
+
+ /**
+ * generate a random string used as PolicyKey
+ */
public static function generatePolicyKey()
{
- $policyKey = mt_rand(1, mt_getrandmax());
-
- return $policyKey;
+ return sha1(mt_rand(). microtime());
}
}
diff --git a/lib/ext/Syncroton/Command/Search.php b/lib/ext/Syncroton/Command/Search.php
index 4f5f667..7febe29 100644
--- a/lib/ext/Syncroton/Command/Search.php
+++ b/lib/ext/Syncroton/Command/Search.php
@@ -1,111 +1,112 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Search command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_Search extends Syncroton_Command_Wbxml
{
const STATUS_SUCCESS = 1;
const STATUS_SERVER_ERROR = 3;
protected $_defaultNameSpace = 'uri:Search';
protected $_documentElement = 'Search';
/**
* store data
*
* @var Syncroton_Model_StoreRequest
*/
protected $_store;
/**
* parse search command request
*
*/
public function handle()
{
$xml = simplexml_import_dom($this->_requestBody);
$this->_store = new Syncroton_Model_StoreRequest($xml->Store);
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " stores: " . print_r($this->_store, true));
}
/**
* generate search command response
*
*/
public function getResponse()
{
+ $dataController = Syncroton_Data_Factory::factory($this->_store->name, $this->_device, new DateTime());
+
+ if (! $dataController instanceof Syncroton_Data_IDataSearch) {
+ throw new RuntimeException('class must be instanceof Syncroton_Data_IDataSearch');
+ }
+
$storeResponse = new Syncroton_Model_StoreResponse(array(
'Status' => self::STATUS_SUCCESS,
));
+
+ try {
+ $options = $this->_store->options;
+
+ // Search
+ $results = $dataController->search($this->_store->query, $options);
+
+ // Calculate requested range
+ $start = $options['range'][0];
+ $limit = $options['range'][1] + 1;
+ $total = count($results);
+ $storeResponse->Total = $total;
+
+ if ($total) {
+ if ($start > $total) {
+ $start = $total;
+ }
+ if ($limit > $total) {
+ $limit = max($start+1, $total);
+ }
- if ($this->_store->name == Syncroton_Data_Factory::STORE_GAL || $this->_store->name == Syncroton_Data_Factory::STORE_EMAIL) {
- $timestamp = new DateTime();
- $dataController = Syncroton_Data_Factory::factory($this->_store->name, $this->_device, $timestamp);
-
- try {
- $options = $this->_store->options;
- // Search
- $results = $dataController->search($this->_store->query, $options);
-
- // Calculate requested range
- $start = $options['range'][0];
- $limit = $options['range'][1] + 1;
- $total = count($results);
- $storeResponse->Total = $total;
-
- if ($total) {
- if ($start > $total) {
- $start = $total;
- }
- if ($limit > $total) {
- $limit = max($start+1, $total);
- }
-
- if ($start > 0 || $limit < $total) {
- $results = array_slice($results, $start, $limit-$start);
- }
-
- $storeResponse->Range = array($start, $start + count($results) - 1);
+ if ($start > 0 || $limit < $total) {
+ $results = array_slice($results, $start, $limit-$start);
}
- foreach ($results as $result) {
- if (empty($result->Properties)) {
- $result->Properties = $dataController->getSearchEntry($result->LongId, $this->_store->options);
- }
+ $storeResponse->Range = array($start, $start + count($results) - 1);
+ }
- $storeResponse->Result[] = $result;
+ foreach ($results as $result) {
+ if (empty($result->Properties)) {
+ $result->Properties = $dataController->getSearchEntry($result->LongId, $this->_store->options);
}
+
+ $storeResponse->Result[] = $result;
}
- catch (Exception $e) {
- $storeResponse->Status = self::STATUS_SERVER_ERROR;
- }
+ } catch (Exception $e) {
+ $storeResponse->Status = self::STATUS_SERVER_ERROR;
}
$search = $this->_outputDom->documentElement;
$search->appendChild($this->_outputDom->createElementNS($this->_defaultNameSpace, 'Status', self::STATUS_SUCCESS));
$response = $search->appendChild($this->_outputDom->createElementNS($this->_defaultNameSpace, 'Response'));
$store = $response->appendChild($this->_outputDom->createElementNS($this->_defaultNameSpace, 'Store'));
$storeResponse->appendXML($store);
return $this->_outputDom;
}
}
diff --git a/lib/ext/Syncroton/Command/SendMail.php b/lib/ext/Syncroton/Command/SendMail.php
index 768eda4..773e27f 100644
--- a/lib/ext/Syncroton/Command/SendMail.php
+++ b/lib/ext/Syncroton/Command/SendMail.php
@@ -1,69 +1,76 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sendmail command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_SendMail extends Syncroton_Command_Wbxml
{
protected $_defaultNameSpace = 'uri:ComposeMail';
protected $_documentElement = 'Sendmail';
protected $_saveInSent;
- protected $_itemId;
- protected $_collectionId;
- protected $_mime;
+ protected $_source;
+ protected $_replaceMime = false;
/**
* process the XML file and add, change, delete or fetches data
*
* @return resource
*/
public function handle()
{
if ($this->_requestParameters['contentType'] == 'message/rfc822') {
$this->_mime = $this->_requestBody;
- $this->_saveInSent = $this->_requestParameters['SaveInSent'] == 'T';
+ $this->_saveInSent = $this->_requestParameters['saveInSent'] == 'T';
- $this->_collectionId = $this->_requestParameters['CollectionId'];
- $this->_itemId = $this->_requestParameters['ItemId'];
+ $this->_collectionId = $this->_requestParameters['collectionId'];
+ $this->_itemId = $this->_requestParameters['itemId'];
} else {
$xml = simplexml_import_dom($this->_requestBody);
$this->_mime = (string) $xml->Mime;
$this->_saveInSent = isset($xml->SaveInSentItems);
+ $this->_replaceMime = isset($xml->ReplaceMime);
if (isset ($xml->Source)) {
- $this->_collectionId = (string)$xml->Source->FolderId;
- $this->_itemId = (string)$xml->Source->ItemId;
+ if ($xml->Source->LongId) {
+ $this->_source = $xml->Source->LongId;
+ } else {
+ $this->_source = array(
+ 'collectionId' => (string)$xml->Source->FolderId,
+ 'itemId' => (string)$xml->Source->ItemId,
+ 'instanceId' => isset($xml->Source->InstanceId) ? (string)$xml->Source->InstanceId : null
+ );
+ }
}
}
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " saveInSent: " . (int)$this->_saveInSent);
}
/**
* this function generates the response for the client
*
* @return void
*/
public function getResponse()
{
$dataController = Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_EMAIL, $this->_device, $this->_syncTimeStamp);
$dataController->sendEmail($this->_mime, $this->_saveInSent);
}
}
diff --git a/lib/ext/Syncroton/Command/SmartForward.php b/lib/ext/Syncroton/Command/SmartForward.php
index 3ee31e7..e5ce2d1 100644
--- a/lib/ext/Syncroton/Command/SmartForward.php
+++ b/lib/ext/Syncroton/Command/SmartForward.php
@@ -1,33 +1,33 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync SmartForward command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_SmartForward extends Syncroton_Command_SmartReply
{
protected $_defaultNameSpace = 'uri:ComposeMail';
protected $_documentElement = 'SmartForward';
/**
* this function generates the response for the client
*
* @return void
*/
public function getResponse()
{
$dataController = Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_EMAIL, $this->_device, $this->_syncTimeStamp);
- $dataController->forwardEmail($this->_collectionId, $this->_itemId, $this->_mime, $this->_saveInSent);
+ $dataController->forwardEmail($this->_source, $this->_mime, $this->_saveInSent, $this->_replaceMime);
}
}
diff --git a/lib/ext/Syncroton/Command/SmartReply.php b/lib/ext/Syncroton/Command/SmartReply.php
index 0f03341..f17d16d 100644
--- a/lib/ext/Syncroton/Command/SmartReply.php
+++ b/lib/ext/Syncroton/Command/SmartReply.php
@@ -1,34 +1,34 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync SmartReply command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_SmartReply extends Syncroton_Command_SendMail
{
protected $_defaultNameSpace = 'uri:ComposeMail';
protected $_documentElement = 'SmartReply';
/**
* this function generates the response for the client
*
* @return void
*/
public function getResponse()
{
$dataController = Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_EMAIL, $this->_device, $this->_syncTimeStamp);
- $dataController->replyEmail($this->_collectionId, $this->_itemId, $this->_mime, $this->_saveInSent);
+ $dataController->replyEmail($this->_source, $this->_mime, $this->_saveInSent, $this->_replaceMime);
}
}
diff --git a/lib/ext/Syncroton/Command/Sync.php b/lib/ext/Syncroton/Command/Sync.php
index 426b739..e9fead9 100644
--- a/lib/ext/Syncroton/Command/Sync.php
+++ b/lib/ext/Syncroton/Command/Sync.php
@@ -1,769 +1,760 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sync command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
{
const STATUS_SUCCESS = 1;
const STATUS_PROTOCOL_VERSION_MISMATCH = 2;
const STATUS_INVALID_SYNC_KEY = 3;
const STATUS_PROTOCOL_ERROR = 4;
const STATUS_SERVER_ERROR = 5;
const STATUS_ERROR_IN_CLIENT_SERVER_CONVERSION = 6;
const STATUS_CONFLICT_MATCHING_THE_CLIENT_AND_SERVER_OBJECT = 7;
const STATUS_OBJECT_NOT_FOUND = 8;
const STATUS_USER_ACCOUNT_MAYBE_OUT_OF_DISK_SPACE = 9;
const STATUS_ERROR_SETTING_NOTIFICATION_GUID = 10;
const STATUS_DEVICE_NOT_PROVISIONED_FOR_NOTIFICATIONS = 11;
const STATUS_FOLDER_HIERARCHY_HAS_CHANGED = 12;
const STATUS_RESEND_FULL_XML = 13;
const STATUS_WAIT_INTERVAL_OUT_OF_RANGE = 14;
const CONFLICT_OVERWRITE_SERVER = 0;
const CONFLICT_OVERWRITE_PIM = 1;
const MIMESUPPORT_DONT_SEND_MIME = 0;
const MIMESUPPORT_SMIME_ONLY = 1;
const MIMESUPPORT_SEND_MIME = 2;
const BODY_TYPE_PLAIN_TEXT = 1;
const BODY_TYPE_HTML = 2;
const BODY_TYPE_RTF = 3;
const BODY_TYPE_MIME = 4;
/**
* truncate types
*/
const TRUNCATE_ALL = 0;
const TRUNCATE_4096 = 1;
const TRUNCATE_5120 = 2;
const TRUNCATE_7168 = 3;
const TRUNCATE_10240 = 4;
const TRUNCATE_20480 = 5;
const TRUNCATE_51200 = 6;
const TRUNCATE_102400 = 7;
const TRUNCATE_NOTHING = 8;
/**
* filter types
*/
const FILTER_NOTHING = 0;
const FILTER_1_DAY_BACK = 1;
const FILTER_3_DAYS_BACK = 2;
const FILTER_1_WEEK_BACK = 3;
const FILTER_2_WEEKS_BACK = 4;
const FILTER_1_MONTH_BACK = 5;
const FILTER_3_MONTHS_BACK = 6;
const FILTER_6_MONTHS_BACK = 7;
const FILTER_INCOMPLETE = 8;
protected $_defaultNameSpace = 'uri:AirSync';
protected $_documentElement = 'Sync';
/**
* list of collections
*
* @var array
*/
protected $_collections = array();
protected $_modifications = array();
/**
- * total count of items in all collections
+ * the global WindowSize
*
* @var integer
*/
- protected $_totalCount;
+ protected $_globalWindowSize;
/**
* there are more entries than WindowSize available
* the MoreAvailable tag hot added to the xml output
*
* @var boolean
*/
protected $_moreAvailable = false;
/**
* @var Syncroton_Model_SyncState
*/
protected $_syncState;
/**
* process the XML file and add, change, delete or fetches data
*/
public function handle()
{
- #if ($this->_statusProvisioning = $this->_checkProvisioningNeeded() !== false) {
- # if (version_compare($this->_device->acsversion, '14.0', '<')) {
- # throw new Syncroton_Exception_ProvisioningNeeded();
- # } else {
- # return;
- # }
- #}
-
// input xml
$xml = simplexml_import_dom($this->_requestBody);
+ $this->_globalWindowSize = isset($xml->WindowSize) ? (int)$xml->WindowSize : 100;
+
foreach ($xml->Collections->Collection as $xmlCollection) {
$collectionData = new Syncroton_Model_SyncCollection($xmlCollection);
// got the folder synchronized to the device already
try {
$collectionData->folder = $this->_folderBackend->getFolder($this->_device, $collectionData->collectionId);
} catch (Syncroton_Exception_NotFound $senf) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " folder {$collectionData->collectionId} not found");
// trigger INVALID_SYNCKEY instead of OBJECT_NOTFOUND when synckey is higher than 0
// to avoid a syncloop for the iPhone
if ($collectionData->syncKey > 0) {
$collectionData->folder = new Syncroton_Model_Folder(array(
- 'device_id' => $this->_device,
- 'folderid' => $collectionData->collectionId
+ 'deviceId' => $this->_device,
+ 'serverId' => $collectionData->collectionId
));
}
$this->_collections[] = $collectionData;
continue;
}
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " SyncKey is {$collectionData->syncKey} Class: {$collectionData->folder->class} CollectionId: {$collectionData->collectionId}");
// initial synckey
if($collectionData->syncKey === 0) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " initial client synckey 0 provided");
// reset sync state for this folder
$this->_syncStateBackend->resetState($this->_device, $collectionData->folder);
$this->_contentStateBackend->resetState($this->_device, $collectionData->folder);
$collectionData->syncState = new Syncroton_Model_SyncState(array(
'device_id' => $this->_device,
'counter' => 0,
'type' => $collectionData->folder,
'lastsync' => $this->_syncTimeStamp
));
$this->_collections[] = $collectionData;
continue;
}
// check for invalid sycnkey
if(($collectionData->syncState = $this->_syncStateBackend->validate($this->_device, $collectionData->folder, $collectionData->syncKey)) === false) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " invalid synckey {$collectionData->syncKey} provided");
// reset sync state for this folder
$this->_syncStateBackend->resetState($this->_device, $collectionData->folder);
$this->_contentStateBackend->resetState($this->_device, $collectionData->folder);
$this->_collections[] = $collectionData;
continue;
}
$dataController = Syncroton_Data_Factory::factory($collectionData->folder->class, $this->_device, $this->_syncTimeStamp);
switch($collectionData->folder->class) {
case Syncroton_Data_Factory::CLASS_CALENDAR:
$dataClass = 'Syncroton_Model_Event';
break;
case Syncroton_Data_Factory::CLASS_CONTACTS:
$dataClass = 'Syncroton_Model_Contact';
break;
case Syncroton_Data_Factory::CLASS_EMAIL:
$dataClass = 'Syncroton_Model_Email';
break;
case Syncroton_Data_Factory::CLASS_TASKS:
$dataClass = 'Syncroton_Model_Task';
break;
default:
throw new Syncroton_Exception_UnexpectedValue('invalid class provided');
break;
}
$clientModifications = array(
'added' => array(),
'changed' => array(),
'deleted' => array(),
'forceAdd' => array(),
'forceChange' => array(),
'toBeFetched' => array(),
);
// handle incoming data
if($collectionData->hasClientAdds()) {
$adds = $collectionData->getClientAdds();
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " found " . count($adds) . " entries to be added to server");
foreach ($adds as $add) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " add entry with clientId " . (string) $add->ClientId);
try {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " adding entry as new");
$serverId = $dataController->createEntry($collectionData->collectionId, new $dataClass($add->ApplicationData));
$clientModifications['added'][$serverId] = array(
'clientId' => (string)$add->ClientId,
'serverId' => $serverId,
'status' => self::STATUS_SUCCESS,
'contentState' => $this->_contentStateBackend->create(new Syncroton_Model_Content(array(
'device_id' => $this->_device,
'folder_id' => $collectionData->folder,
'contentid' => $serverId,
'creation_time' => $this->_syncTimeStamp,
'creation_synckey' => $collectionData->syncKey + 1
)))
);
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " failed to add entry " . $e->getMessage());
$clientModifications['added'][] = array(
'clientId' => (string)$add->ClientId,
'status' => self::STATUS_SERVER_ERROR
);
}
}
}
// handle changes, but only if not first sync
if($collectionData->syncKey > 1 && $collectionData->hasClientChanges()) {
$changes = $collectionData->getClientChanges();
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " found " . count($changes) . " entries to be updated on server");
foreach ($changes as $change) {
$serverId = (string)$change->ServerId;
try {
$dataController->updateEntry($collectionData->collectionId, $serverId, new $dataClass($change->ApplicationData));
$clientModifications['changed'][$serverId] = self::STATUS_SUCCESS;
} catch (Syncroton_Exception_AccessDenied $e) {
$clientModifications['changed'][$serverId] = self::STATUS_CONFLICT_MATCHING_THE_CLIENT_AND_SERVER_OBJECT;
$clientModifications['forceChange'][$serverId] = $serverId;
} catch (Syncroton_Exception_NotFound $e) {
// entry does not exist anymore, will get deleted automaticaly
$clientModifications['changed'][$serverId] = self::STATUS_OBJECT_NOT_FOUND;
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " failed to update entry " . $e);
// something went wrong while trying to update the entry
$clientModifications['changed'][$serverId] = self::STATUS_SERVER_ERROR;
}
}
}
// handle deletes, but only if not first sync
if($collectionData->hasClientDeletes()) {
$deletes = $collectionData->getClientDeletes();
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " found " . count($deletes) . " entries to be deleted on server");
foreach ($deletes as $delete) {
$serverId = (string)$delete->ServerId;
try {
// check if we have sent this entry to the phone
$state = $this->_contentStateBackend->getContentState($this->_device, $collectionData->folder, $serverId);
try {
$dataController->deleteEntry($collectionData->collectionId, $serverId, $collectionData);
} catch(Syncroton_Exception_NotFound $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . ' tried to delete entry ' . $serverId . ' but entry was not found');
} catch (Syncroton_Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . ' tried to delete entry ' . $serverId . ' but a error occured: ' . $e->getMessage());
$clientModifications['forceAdd'][$serverId] = $serverId;
}
$this->_contentStateBackend->delete($state);
} catch (Syncroton_Exception_NotFound $senf) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . ' ' . $serverId . ' should have been removed from client already');
// should we send a special status???
//$collectionData->deleted[$serverId] = self::STATUS_SUCCESS;
}
$clientModifications['deleted'][$serverId] = self::STATUS_SUCCESS;
}
}
// handle fetches, but only if not first sync
if($collectionData->syncKey > 1 && $collectionData->hasClientFetches()) {
// the default value for GetChanges is 1. If the phone don't want the changes it must set GetChanges to 0
// some prevoius versions of iOS did not set GetChanges to 0 for fetches. Let's enforce getChanges to false here.
$collectionData->getChanges = false;
$fetches = $collectionData->getClientFetches();
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " found " . count($fetches) . " entries to be fetched from server");
$toBeFecthed = array();
foreach ($fetches as $fetch) {
$serverId = (string)$fetch->ServerId;
$toBeFetched[$serverId] = $serverId;
}
$collectionData->toBeFetched = $toBeFetched;
}
$this->_collections[] = $collectionData;
$this->_modifications[$collectionData->collectionId] = $clientModifications;
}
}
/**
* (non-PHPdoc)
* @see Syncroton_Command_Wbxml::getResponse()
*/
public function getResponse()
{
$sync = $this->_outputDom->documentElement;
- // provioning needed?
- #if ($this->_statusProvisioning !== false) {
- # if ($this->_logger instanceof Zend_Log)
- # $this->_logger->info(__METHOD__ . '::' . __LINE__ . " provisiong needed or remote wipe requested");
- # $sync->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', $this->_statusProvisioning));
- #
- # return $this->_outputDom;
- #}
-
$collections = $sync->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collections'));
+ $totalChanges = 0;
+
foreach($this->_collections as $collectionData) {
+ $moreAvailable = false;
+ $collectionChanges = 0;
+
// invalid collectionid provided
if (! ($collectionData->folder instanceof Syncroton_Model_IFolder)) {
$collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection'));
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', 0));
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData->collectionId));
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_FOLDER_HIERARCHY_HAS_CHANGED));
// invalid synckey provided
} elseif (! ($collectionData->syncState instanceof Syncroton_Model_ISyncState)) {
// set synckey to 0
$collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection'));
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', 0));
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData->collectionId));
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_INVALID_SYNC_KEY));
// initial sync
} elseif ($collectionData->syncState->counter === 0) {
$collectionData->syncState->counter++;
// initial sync
// send back a new SyncKey only
$collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection'));
if (!empty($collectionData->folder->class)) {
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData->folder->class));
}
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', $collectionData->syncState->counter));
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData->collectionId));
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS));
} else {
$dataController = Syncroton_Data_Factory::factory($collectionData->folder->class , $this->_device, $this->_syncTimeStamp);
$clientModifications = $this->_modifications[$collectionData->collectionId];
$serverModifications = array(
'added' => array(),
'changed' => array(),
'deleted' => array(),
);
- $moreAvailable = false;
-
if($collectionData->getChanges === true) {
// continue sync session?
if(is_array($collectionData->syncState->pendingdata)) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " restored from sync state ");
$serverModifications = $collectionData->syncState->pendingdata;
} else {
// fetch entries added since last sync
$allClientEntries = $this->_contentStateBackend->getFolderState($this->_device, $collectionData->folder);
$allServerEntries = $dataController->getServerEntries($collectionData->collectionId, $collectionData->options['filterType']);
// add entries
$serverDiff = array_diff($allServerEntries, $allClientEntries);
// add entries which produced problems during delete from client
$serverModifications['added'] = $clientModifications['forceAdd'];
// add entries not yet sent to client
$serverModifications['added'] = array_unique(array_merge($serverModifications['added'], $serverDiff));
# @todo still needed?
foreach($serverModifications['added'] as $id => $serverId) {
// skip entries added by client during this sync session
if(isset($clientModifications['added'][$serverId]) && !isset($clientModifications['forceAdd'][$serverId])) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped added entry: " . $serverId);
unset($serverModifications['added'][$id]);
}
}
// entries to be deleted
$serverModifications['deleted'] = array_diff($allClientEntries, $allServerEntries);
// fetch entries changed since last sync
$serverModifications['changed'] = $dataController->getChangedEntries($collectionData->collectionId, $collectionData->syncState->lastsync, $this->_syncTimeStamp);
$serverModifications['changed'] = array_merge($serverModifications['changed'], $clientModifications['forceChange']);
foreach($serverModifications['changed'] as $id => $serverId) {
// skip entry, if it got changed by client during current sync
if(isset($clientModifications['changed'][$serverId]) && !isset($clientModifications['forceChange'][$serverId])) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped changed entry: " . $serverId);
unset($serverModifications['changed'][$id]);
}
// skip entry, make sure we don't sent entries already added by client in this request
else if (isset($clientModifications['added'][$serverId]) && !isset($clientModifications['forceAdd'][$serverId])) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped change for added entry: " . $serverId);
unset($serverModifications['changed'][$id]);
}
}
// entries comeing in scope are already in $serverModifications['added'] and do not need to
// be send with $serverCanges
$serverModifications['changed'] = array_diff($serverModifications['changed'], $serverModifications['added']);
}
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " found (added/changed/deleted) " . count($serverModifications['added']) . '/' . count($serverModifications['changed']) . '/' . count($serverModifications['deleted']) . ' entries for sync from server to client');
}
if (!empty($clientModifications['added']) || !empty($clientModifications['changed']) || !empty($clientModifications['deleted']) ||
!empty($serverModifications['added']) || !empty($serverModifications['changed']) || !empty($serverModifications['deleted'])) {
$collectionData->syncState->counter++;
}
// collection header
$collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection'));
if (!empty($collectionData->folder->class)) {
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData->folder->class));
}
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', $collectionData->syncState->counter));
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData->collectionId));
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS));
$responses = $this->_outputDom->createElementNS('uri:AirSync', 'Responses');
// send reponse for newly added entries
if(!empty($clientModifications['added'])) {
foreach($clientModifications['added'] as $entryData) {
$add = $responses->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Add'));
$add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ClientId', $entryData['clientId']));
// we have no serverId is the add failed
if(isset($entryData['serverId'])) {
$add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $entryData['serverId']));
}
$add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', $entryData['status']));
}
}
// send reponse for changed entries
if(!empty($clientModifications['changed'])) {
foreach($clientModifications['changed'] as $serverId => $status) {
if ($status !== Syncroton_Command_Sync::STATUS_SUCCESS) {
$change = $responses->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Change'));
$change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
$change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', $status));
}
}
}
// send response for to be fetched entries
if(!empty($collectionData->toBeFetched)) {
foreach($collectionData->toBeFetched as $serverId) {
$fetch = $responses->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Fetch'));
$fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
try {
$applicationData = $this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData');
$dataController
->getEntry($collectionData, $serverId)
->appendXML($applicationData);
$fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS));
$fetch->appendChild($applicationData);
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
$fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_OBJECT_NOT_FOUND));
}
}
}
if ($responses->hasChildNodes() === true) {
$collection->appendChild($responses);
}
- if ((count($serverModifications['added']) + count($serverModifications['changed']) + count($serverModifications['deleted'])) > $collectionData->windowSize ) {
- $moreAvailable = true;
- $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'MoreAvailable'));
- }
-
$commands = $this->_outputDom->createElementNS('uri:AirSync', 'Commands');
-
/**
* process entries added on server side
*/
$newContentStates = array();
foreach($serverModifications['added'] as $id => $serverId) {
- if($this->_totalCount === $collectionData->windowSize) {
+ if($collectionChanges === $collectionData->windowSize || $totalChanges + $collectionChanges === $this->_globalWindowSize) {
break;
}
#/**
# * somewhere is a problem in the logic for handling moreAvailable
# *
# * it can happen, that we have a contentstate (which means we sent the entry to the client
# * and that this entry is yet in $collectionData->syncState->pendingdata['serverAdds']
# * I have no idea how this can happen, but the next lines of code work around this problem
# */
#try {
# $this->_contentStateBackend->getContentState($this->_device, $collectionData->folder, $serverId);
#
# if ($this->_logger instanceof Zend_Log)
# $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped an entry($serverId) which is already on the client");
#
# unset($serverModifications['added'][$id]);
# continue;
#
#} catch (Syncroton_Exception_NotFound $senf) {
# // do nothing => content state should not exist yet
#}
try {
$add = $this->_outputDom->createElementNS('uri:AirSync', 'Add');
$add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
$applicationData = $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData'));
$dataController
->getEntry($collectionData, $serverId)
->appendXML($applicationData);
$commands->appendChild($add);
- $this->_totalCount++;
+ $collectionChanges++;
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
}
// mark as send to the client, even the conversion to xml might have failed
$newContentStates[] = new Syncroton_Model_Content(array(
'device_id' => $this->_device,
'folder_id' => $collectionData->folder,
'contentid' => $serverId,
'creation_time' => $this->_syncTimeStamp,
'creation_synckey' => $collectionData->syncState->counter
));
unset($serverModifications['added'][$id]);
}
/**
* process entries changed on server side
*/
foreach($serverModifications['changed'] as $id => $serverId) {
- if($this->_totalCount === $collectionData->windowSize) {
+ if($collectionChanges === $collectionData->windowSize || $totalChanges + $collectionChanges === $this->_globalWindowSize) {
break;
}
-
+
try {
$change = $this->_outputDom->createElementNS('uri:AirSync', 'Change');
$change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
$applicationData = $change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData'));
$dataController
->getEntry($collectionData, $serverId)
->appendXML($applicationData);
$commands->appendChild($change);
- $this->_totalCount++;
+ $collectionChanges++;
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
}
unset($serverModifications['changed'][$id]);
}
/**
* process entries deleted on server side
*/
$deletedContentStates = array();
foreach($serverModifications['deleted'] as $id => $serverId) {
- if($this->_totalCount === $collectionData->windowSize) {
+ if($collectionChanges === $collectionData->windowSize || $totalChanges + $collectionChanges === $this->_globalWindowSize) {
break;
}
-
+
try {
// check if we have sent this entry to the phone
$state = $this->_contentStateBackend->getContentState($this->_device, $collectionData->folder, $serverId);
$delete = $this->_outputDom->createElementNS('uri:AirSync', 'Delete');
$delete->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
$deletedContentStates[] = $state;
$commands->appendChild($delete);
- $this->_totalCount++;
+ $collectionChanges++;
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
}
- unset($serverModifications['deleted'][$id]);
+ unset($serverModifications['deleted'][$id]);
}
if ($commands->hasChildNodes() === true) {
+
+ $countOfPendingChanges = (count($serverModifications['added']) + count($serverModifications['changed']) + count($serverModifications['deleted']));
+ if ($countOfPendingChanges > 0) {
+ $moreAvailable = true;
+ $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'MoreAvailable'));
+ }
+
$collection->appendChild($commands);
}
+ $totalChanges += $collectionChanges;
+
if ($this->_logger instanceof Zend_Log)
- $this->_logger->info(__METHOD__ . '::' . __LINE__ . " new synckey is ". $collectionData->syncState->counter);
+ $this->_logger->info(__METHOD__ . '::' . __LINE__ . " new synckey is ". $collectionData->syncState->counter);
}
if (isset($collectionData->syncState) && $collectionData->syncState instanceof Syncroton_Model_ISyncState &&
$collectionData->syncState->counter != $collectionData->syncKey) {
// increment sync timestamp by 1 second
$this->_syncTimeStamp->modify('+1 sec');
// store pending data in sync state when needed
if(isset($moreAvailable) && $moreAvailable === true) {
$collectionData->syncState->pendingdata = array(
'added' => (array)$serverModifications['added'],
'changed' => (array)$serverModifications['changed'],
'deleted' => (array)$serverModifications['deleted']
);
} else {
$collectionData->syncState->pendingdata = null;
}
if (!empty($clientModifications['added'])) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " remove previous synckey as client added new entries");
$keepPreviousSyncKey = false;
} else {
$keepPreviousSyncKey = true;
}
$collectionData->syncState->lastsync = $this->_syncTimeStamp;
try {
$transactionId = Syncroton_Registry::getTransactionManager()->startTransaction(Syncroton_Registry::getDatabase());
// store new synckey
$this->_syncStateBackend->create($collectionData->syncState, $keepPreviousSyncKey);
// store contentstates for new entries added to client
if (isset($newContentStates)) {
foreach($newContentStates as $state) {
$this->_contentStateBackend->create($state);
}
}
// remove contentstates for entries to be deleted on client
if (isset($deletedContentStates)) {
foreach($deletedContentStates as $state) {
$this->_contentStateBackend->delete($state);
}
}
Syncroton_Registry::getTransactionManager()->commitTransaction($transactionId);
} catch (Zend_Db_Statement_Exception $zdse) {
// something went wrong
// maybe another parallel request added a new synckey
// we must remove data added from client
if (!empty($clientModifications['added'])) {
foreach ($clientModifications['added'] as $added) {
$this->_contentStateBackend->delete($added['contentState']);
$dataController->deleteEntry($collectionData->collectionId, $added['serverId'], array());
}
}
Syncroton_Registry::getTransactionManager()->rollBack();
throw $zdse;
}
// store current filter type
try {
$folderState = $this->_folderBackend->getFolder($this->_device, $collectionData->collectionId);
$folderState->lastfiltertype = $collectionData->options['filterType'];
$this->_folderBackend->update($folderState);
} catch (Syncroton_Exception_NotFound $senf) {
// failed to get folderstate => should not happen but is also no problem in this state
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . ' failed to get content state for: ' . $collectionData->collectionId);
}
}
}
return $this->_outputDom;
}
}
diff --git a/lib/ext/Syncroton/Command/Wbxml.php b/lib/ext/Syncroton/Command/Wbxml.php
index 0b69857..537900a 100644
--- a/lib/ext/Syncroton/Command/Wbxml.php
+++ b/lib/ext/Syncroton/Command/Wbxml.php
@@ -1,167 +1,188 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* abstract class for all commands using wbxml encoded content
*
* @package Syncroton
* @subpackage Command
*/
abstract class Syncroton_Command_Wbxml implements Syncroton_Command_ICommand
{
/**
* informations about the currently device
*
* @var Syncroton_Model_Device
*/
protected $_device;
/**
* informations about the currently device
*
* @var Syncroton_Backend_IDevice
*/
protected $_deviceBackend;
/**
* informations about the currently device
*
* @var Syncroton_Backend_IFolder
*/
protected $_folderBackend;
/**
* @var Syncroton_Backend_ISyncState
*/
protected $_syncStateBackend;
/**
* @var Syncroton_Backend_IContent
*/
protected $_contentStateBackend;
+ /**
+ *
+ * @var Syncroton_Backend_IPolicy
+ */
+ protected $_policyBackend;
+
/**
* the domDocument containing the xml response from the server
*
* @var DOMDocument
*/
protected $_outputDom;
/**
* the domDocucment containing the xml request from the client
*
* @var DOMDocument
*/
protected $_requestBody;
/**
* the default namespace
*
* @var string
*/
protected $_defaultNameSpace;
/**
* the main xml tag
*
* @var string
*/
protected $_documentElement;
/**
* @var array
*/
protected $_requestParameters;
/**
* @var Syncroton_Model_SyncState
*/
protected $_syncState;
protected $_skipValidatePolicyKey = false;
/**
* timestamp to use for all sync requests
*
* @var DateTime
*/
protected $_syncTimeStamp;
/**
* @var string
*/
protected $_transactionId;
/**
* @var string
*/
protected $_policyKey;
/**
* @var Zend_Log
*/
protected $_logger;
/**
* the constructor
*
* @param mixed $requestBody
* @param Syncroton_Model_Device $device
* @param array $requestParameters
*/
public function __construct($requestBody, Syncroton_Model_IDevice $device, $requestParameters)
{
$this->_requestBody = $requestBody;
$this->_device = $device;
$this->_requestParameters = $requestParameters;
$this->_policyKey = $requestParameters['policyKey'];
$this->_deviceBackend = Syncroton_Registry::getDeviceBackend();
$this->_folderBackend = Syncroton_Registry::getFolderBackend();
$this->_syncStateBackend = Syncroton_Registry::getSyncStateBackend();
$this->_contentStateBackend = Syncroton_Registry::getContentStateBackend();
+ $this->_policyBackend = Syncroton_Registry::getPolicyBackend();
if (Syncroton_Registry::isRegistered('loggerBackend')) {
$this->_logger = Syncroton_Registry::get('loggerBackend');
}
- if ($this->_skipValidatePolicyKey !== true && $this->_policyKey === null) {
- #throw new Syncroton_Exception_PolicyKeyMissing();
- }
-
- if ($this->_skipValidatePolicyKey !== true && ($this->_policyKey === 0 || $this->_device->policykey != $this->_policyKey)) {
- #throw new Syncroton_Exception_ProvisioningNeeded();
- }
-
- // should we wipe the mobile phone?
- if ($this->_skipValidatePolicyKey !== true && !empty($this->_policyKey) && $this->_device->remotewipe >= Syncroton_Command_Provision::REMOTEWIPE_REQUESTED) {
- throw new Syncroton_Exception_ProvisioningNeeded();
- }
-
-
$this->_syncTimeStamp = new DateTime(null, new DateTimeZone('UTC'));
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " sync timestamp: " . $this->_syncTimeStamp->format('Y-m-d H:i:s'));
// Creates an instance of the DOMImplementation class
$imp = new DOMImplementation();
// Creates a DOMDocumentType instance
$dtd = $imp->createDocumentType('AirSync', "-//AIRSYNC//DTD AirSync//EN", "http://www.microsoft.com/");
// Creates a DOMDocument instance
$this->_outputDom = $imp->createDocument($this->_defaultNameSpace, $this->_documentElement, $dtd);
$this->_outputDom->formatOutput = false;
$this->_outputDom->encoding = 'utf-8';
- }
+ if ($this->_skipValidatePolicyKey != true) {
+ if (!empty($this->_device->policyId)) {
+ if ($this->_policyKey === null) {
+ throw new Syncroton_Exception_PolicyKeyMissing();
+ }
+
+ $policy = $this->_policyBackend->get($this->_device->policyId);
+
+ if($policy->policyKey != $this->_policyKey) {
+ $this->_outputDom->documentElement->appendChild($this->_outputDom->createElementNS($this->_defaultNameSpace, 'Status', 142));
+
+ $sepn = new Syncroton_Exception_ProvisioningNeeded();
+ $sepn->domDocument = $this->_outputDom;
+
+ throw $sepn;
+ }
+
+ // should we wipe the mobile phone?
+ if ($this->_device->remotewipe >= Syncroton_Command_Provision::REMOTEWIPE_REQUESTED) {
+ $this->_outputDom->documentElement->appendChild($this->_outputDom->createElementNS($this->_defaultNameSpace, 'Status', 140));
+
+ $sepn = new Syncroton_Exception_ProvisioningNeeded();
+ $sepn->domDocument = $this->_outputDom;
+
+ throw $sepn;
+ }
+ }
+ }
+ }
}
diff --git a/lib/ext/Syncroton/Data/AData.php b/lib/ext/Syncroton/Data/AData.php
index 8a9b3b6..4e02ef5 100644
--- a/lib/ext/Syncroton/Data/AData.php
+++ b/lib/ext/Syncroton/Data/AData.php
@@ -1,204 +1,196 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sync command
*
* @package Model
*/
abstract class Syncroton_Data_AData implements Syncroton_Data_IData
{
/**
* used by unit tests only to simulated added folders
*/
public static $folders = array();
/**
* used by unit tests only to simulated added folders
*/
public static $entries = array();
/**
* used by unit tests only to simulated added folders
*/
public static $changedEntries = array();
public function __construct(Syncroton_Model_IDevice $_device, DateTime $_timeStamp)
{
$this->_device = $_device;
$this->_timestamp = $_timeStamp;
$this->_db = Syncroton_Registry::getDatabase();
$this->_tablePrefix = 'Syncroton_';
$this->_initData();
}
public function createFolder(Syncroton_Model_IFolder $folder)
{
$folder->id = sha1(mt_rand(). microtime());
// normaly generated on server backend
- $folder->folderid = sha1(mt_rand(). microtime());
+ $folder->serverId = sha1(mt_rand(). microtime());
- Syncroton_Data_AData::$folders[get_class($this)][$folder->folderid] = $folder;
+ Syncroton_Data_AData::$folders[get_class($this)][$folder->serverId] = $folder;
- return Syncroton_Data_AData::$folders[get_class($this)][$folder->folderid];
+ return Syncroton_Data_AData::$folders[get_class($this)][$folder->serverId];
}
public function createEntry($_folderId, Syncroton_Model_IEntry $_entry)
{
$id = sha1(mt_rand(). microtime());
#Syncroton_Data_AData::$entries[get_class($this)][$_folderId][$id] = $_entry;
$this->_db->insert($this->_tablePrefix . 'data', array(
'id' => $id,
'type' => get_class($this),
'folder_id' => $_folderId,
'data' => serialize($_entry)
));
return $id;
}
public function deleteEntry($_folderId, $_serverId, $_collectionData)
{
- #$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->folderid : $_folderId;
+ #$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->serverId : $_folderId;
$result = $this->_db->delete($this->_tablePrefix . 'data', array('id = ?' => $_serverId));
return (bool) $result;
#unset(Syncroton_Data_AData::$entries[get_class($this)][$folderId][$_serverId]);
}
public function deleteFolder($_folderId)
{
- $folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->folderid : $_folderId;
+ $folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->serverId : $_folderId;
unset(Syncroton_Data_AData::$folders[get_class($this)][$folderId]);
unset(Syncroton_Data_AData::$entries[get_class($this)][$folderId]);
}
public function getAllFolders()
{
return Syncroton_Data_AData::$folders[get_class($this)];
}
public function getChangedEntries($_folderId, DateTime $_startTimeStamp, DateTime $_endTimeStamp = NULL)
{
if (!isset(Syncroton_Data_AData::$changedEntries[get_class($this)])) {
return array();
} else {
return Syncroton_Data_AData::$changedEntries[get_class($this)];
}
}
/**
* @param Syncroton_Model_IFolder|string $_folderId
* @param string $_filter
* @return array
*/
public function getServerEntries($_folderId, $_filter)
{
$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->id : $_folderId;
$select = $this->_db->select()
->from($this->_tablePrefix . 'data', array('id'))
->where('folder_id = ?', $_folderId);
$ids = array();
$stmt = $this->_db->query($select);
while ($id = $stmt->fetchColumn()) {
$ids[] = $id;
}
return $ids;
}
public function getCountOfChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState)
{
$allClientEntries = $contentBackend->getFolderState($this->_device, $folder);
- $allServerEntries = $this->getServerEntries($folder->folderid, $folder->lastfiltertype);
+ $allServerEntries = $this->getServerEntries($folder->serverId, $folder->lastfiltertype);
$addedEntries = array_diff($allServerEntries, $allClientEntries);
$deletedEntries = array_diff($allClientEntries, $allServerEntries);
- $changedEntries = $this->getChangedEntries($folder->folderid, $syncState->lastsync);
+ $changedEntries = $this->getChangedEntries($folder->serverId, $syncState->lastsync);
return count($addedEntries) + count($deletedEntries) + count($changedEntries);
}
public function getFileReference($fileReference)
{
throw new Syncroton_Exception_NotFound('filereference not found');
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IData::getEntry()
*/
public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId)
{
$select = $this->_db->select()
->from($this->_tablePrefix . 'data', array('data'))
->where('id = ?', $serverId);
$stmt = $this->_db->query($select);
$entry = $stmt->fetchColumn();
if ($entry === false) {
throw new Syncroton_Exception_NotFound("entry $serverId not found in folder {$collection->collectionId}");
}
return unserialize($entry);
-
- #if (!isset(Syncroton_Data_AData::$entries[get_class($this)][$collection->collectionId][$serverId])) {
- # throw new OutOfBoundsException("entry $serverId not found in folder {$collection->collectionId}");
- #}
-
- #return Syncroton_Data_AData::$entries[get_class($this)][$collection->collectionId][$serverId];
-
-
}
public function moveItem($_srcFolderId, $_serverId, $_dstFolderId)
{
$this->_db->update($this->_tablePrefix . 'data', array(
'folder_id' => $_dstFolderId,
), array(
'id = ?' => $_serverId
));
return $_serverId;
}
public function updateEntry($_folderId, $_serverId, Syncroton_Model_IEntry $_entry)
{
$this->_db->update($this->_tablePrefix . 'data', array(
'type' => get_class($this),
'folder_id' => $_folderId,
'data' => serialize($_entry)
), array(
'id = ?' => $_serverId
));
}
public function updateFolder(Syncroton_Model_IFolder $folder)
{
- Syncroton_Data_AData::$folders[get_class($this)][$folder->folderid] = $folder;
+ Syncroton_Data_AData::$folders[get_class($this)][$folder->serverId] = $folder;
}
abstract protected function _initData();
}
diff --git a/lib/ext/Syncroton/Data/Calendar.php b/lib/ext/Syncroton/Data/Calendar.php
index 539d9c2..9f24590 100644
--- a/lib/ext/Syncroton/Data/Calendar.php
+++ b/lib/ext/Syncroton/Data/Calendar.php
@@ -1,43 +1,43 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sync command
*
* @package Model
*/
class Syncroton_Data_Calendar extends Syncroton_Data_AData
{
protected function _initData()
{
/**
* used by unit tests only to simulated added folders
*/
Syncroton_Data_AData::$folders[get_class($this)] = array(
'calendarFolderId' => new Syncroton_Model_Folder(array(
'id' => sha1(mt_rand(). microtime()),
- 'folderid' => 'calendarFolderId',
- 'parentid' => null,
- 'displayname' => 'Default Contacts Folder',
+ 'serverId' => 'calendarFolderId',
+ 'parentId' => 0,
+ 'displayName' => 'Default Contacts Folder',
'type' => Syncroton_Command_FolderSync::FOLDERTYPE_CALENDAR
))
);
#/**
# * used by unit tests only to simulated added folders
# */
#Syncroton_Data_AData::$entries[get_class($this)] = array(
# 'calendarFolderId' => array(
# )
#);
}
}
diff --git a/lib/ext/Syncroton/Data/Contacts.php b/lib/ext/Syncroton/Data/Contacts.php
index 5ba8560..133ae00 100644
--- a/lib/ext/Syncroton/Data/Contacts.php
+++ b/lib/ext/Syncroton/Data/Contacts.php
@@ -1,100 +1,140 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sync command
*
* @package Model
*/
-class Syncroton_Data_Contacts extends Syncroton_Data_AData
+class Syncroton_Data_Contacts extends Syncroton_Data_AData implements Syncroton_Data_IDataSearch
{
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Data_IDataSearch::getSearchEntry()
+ */
+ public function getSearchEntry($longId, $options)
+ {
+ list($collectionId, $serverId) = explode('-', $longId, 2);
+
+ $contact = $this->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $collectionId)), $serverId);
+
+ return new Syncroton_Model_GAL(array(
+ 'FirstName' => $contact->FirstName,
+ 'LastName' => $contact->LastName
+ ));
+ }
+
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Data_IDataSearch::search()
+ */
+ public function search($query, $options)
+ {
+ $found = array();
+
+ $serverIds = $this->getServerEntries('addressbookFolderId', Syncroton_Command_Sync::FILTER_NOTHING);
+
+ foreach ($serverIds as $serverId) {
+ $contact = $this->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => 'addressbookFolderId')), $serverId);
+
+ if ($contact->FirstName == $query) {
+ $found[] = new Syncroton_Model_StoreResponseResult(array(
+ 'LongId' => 'addressbookFolderId-' . $serverId,
+ 'Properties' => $this->getSearchEntry('addressbookFolderId-' . $serverId, $options)
+ ));
+ }
+ }
+
+ return $found;
+ }
+
protected function _initData()
{
/**
* used by unit tests only to simulated added folders
*/
if (!isset(Syncroton_Data_AData::$folders[get_class($this)])) {
Syncroton_Data_AData::$folders[get_class($this)] = array(
'addressbookFolderId' => new Syncroton_Model_Folder(array(
'id' => sha1(mt_rand(). microtime()),
- 'folderid' => 'addressbookFolderId',
- 'parentid' => null,
- 'displayname' => 'Default Contacts Folder',
+ 'serverId' => 'addressbookFolderId',
+ 'parentId' => 0,
+ 'displayName' => 'Default Contacts Folder',
'type' => Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT
)),
'anotherAddressbookFolderId' => new Syncroton_Model_Folder(array(
'id' => sha1(mt_rand(). microtime()),
- 'folderid' => 'anotherAddressbookFolderId',
- 'parentid' => null,
- 'displayname' => 'Another Contacts Folder',
+ 'serverId' => 'anotherAddressbookFolderId',
+ 'parentId' => 0,
+ 'displayName' => 'Another Contacts Folder',
'type' => Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED
))
);
}
/**
* used by unit tests only to simulated added folders
*/
$entries = $this->getServerEntries('addressbookFolderId', 1);
if (count($entries) == 0) {
$testData = array(
'addressbookFolderId' => array(
'contact1' => new Syncroton_Model_Contact(array(
'FirstName' => 'Lars',
'LastName' => 'Kneschke'
)),
'contact2' => new Syncroton_Model_Contact(array(
'FirstName' => 'Cornelius',
'LastName' => 'Weiß'
)),
'contact3' => new Syncroton_Model_Contact(array(
'FirstName' => 'Lars',
'LastName' => 'Kneschke'
)),
'contact4' => new Syncroton_Model_Contact(array(
'FirstName' => 'Cornelius',
'LastName' => 'Weiß'
)),
'contact5' => new Syncroton_Model_Contact(array(
'FirstName' => 'Lars',
'LastName' => 'Kneschke'
)),
'contact6' => new Syncroton_Model_Contact(array(
'FirstName' => 'Cornelius',
'LastName' => 'Weiß'
)),
'contact7' => new Syncroton_Model_Contact(array(
'FirstName' => 'Lars',
'LastName' => 'Kneschke'
)),
'contact8' => new Syncroton_Model_Contact(array(
'FirstName' => 'Cornelius',
'LastName' => 'Weiß'
)),
'contact9' => new Syncroton_Model_Contact(array(
'FirstName' => 'Lars',
'LastName' => 'Kneschke'
)),
'contact10' => new Syncroton_Model_Contact(array(
'FirstName' => 'Cornelius',
'LastName' => 'Weiß'
))
)
);
foreach ($testData['addressbookFolderId'] as $data) {
$this->createEntry('addressbookFolderId', $data);
}
}
}
}
diff --git a/lib/ext/Syncroton/Data/Email.php b/lib/ext/Syncroton/Data/Email.php
index 73c280f..9765a7b 100644
--- a/lib/ext/Syncroton/Data/Email.php
+++ b/lib/ext/Syncroton/Data/Email.php
@@ -1,125 +1,133 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sync command
*
* @package Model
*/
class Syncroton_Data_Email extends Syncroton_Data_AData implements Syncroton_Data_IDataEmail
{
/**
* used by unit tests only to simulated added folders
*/
public static $entries = array(
);
- public function forwardEmail($collectionId, $itemId, $inputStream, $saveInSent)
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Data_IDataEmail::forwardEmail()
+ */
+ public function forwardEmail($source, $inputStream, $saveInSent, $replaceMime)
{
// forward email
}
public function getFileReference($fileReference)
{
list($messageId, $partId) = explode('-', $fileReference, 2);
// example code
//$file = $this->_imapBackend->getMessagePart($messageId, $partId);
// example code
return new Syncroton_Model_FileReference(array(
- 'ContentType' => 'text/plain',
- 'Data' => 'Lars'
+ 'ContentType' => 'text/plain',
+ 'Data' => 'Lars'
));
}
-
- public function replyEmail($collectionId, $itemId, $inputStream, $saveInSent)
+
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Data_IDataEmail::replyEmail()
+ */
+ public function replyEmail($source, $inputStream, $saveInSent, $replaceMime)
{
// forward email
}
public function updateEntry($_folderId, $_serverId, Syncroton_Model_IEntry $_entry)
{
// not used by email
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IDataEmail::sendEmail()
*/
public function sendEmail($inputStream, $saveInSent)
{
// send email
}
protected function _initData()
{
/**
* used by unit tests only to simulated added folders
*/
Syncroton_Data_AData::$folders[get_class($this)] = array(
'emailInboxFolderId' => new Syncroton_Model_Folder(array(
'id' => sha1(mt_rand(). microtime()),
- 'folderid' => 'emailInboxFolderId',
- 'parentid' => null,
- 'displayname' => 'Inbox',
+ 'serverId' => 'emailInboxFolderId',
+ 'parentId' => 0,
+ 'displayName' => 'Inbox',
'type' => Syncroton_Command_FolderSync::FOLDERTYPE_INBOX
)),
'emailSentFolderId' => new Syncroton_Model_Folder(array(
'id' => sha1(mt_rand(). microtime()),
- 'folderid' => 'emailSentFolderId',
- 'parentid' => null,
- 'displayname' => 'Sent',
+ 'serverId' => 'emailSentFolderId',
+ 'parentId' => 0,
+ 'displayName' => 'Sent',
'type' => Syncroton_Command_FolderSync::FOLDERTYPE_SENTMAIL
))
);
/**
* used by unit tests only to simulated added folders
*/
$entries = $this->getServerEntries('emailInboxFolderId', 1);
if (count($entries) == 0) {
$testData = array(
'emailInboxFolderId' => array(
'email1' => new Syncroton_Model_Email(array(
'AccountId' => 'FooBar',
'Attachments' => array(
new Syncroton_Model_EmailAttachment(array(
'FileReference' => '12345abcd',
'UmAttOrder' => 1
))
),
'Categories' => array('123', '456'),
'Cc' => 'l.kneschke@metaways.de',
'DateReceived' => new DateTime('2012-03-21 14:00:00', new DateTimeZone('UTC')),
'From' => 'k.kneschke@metaways.de',
'Subject' => 'Test Subject',
'To' => 'j.kneschke@metaways.de',
'Read' => 1,
'Body' => new Syncroton_Model_EmailBody(array(
'Type' => Syncroton_Model_EmailBody::TYPE_PLAINTEXT,
'Data' => 'Hello!',
'Truncated' => true,
'EstimatedDataSize' => 600
))
)),
)
);
foreach ($testData['emailInboxFolderId'] as $data) {
$this->createEntry('emailInboxFolderId', $data);
}
}
}
}
diff --git a/lib/ext/Syncroton/Data/Factory.php b/lib/ext/Syncroton/Data/Factory.php
index 29c2bdd..1be6946 100644
--- a/lib/ext/Syncroton/Data/Factory.php
+++ b/lib/ext/Syncroton/Data/Factory.php
@@ -1,79 +1,74 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sync command
*
* @package Model
*/
class Syncroton_Data_Factory
{
const CLASS_CALENDAR = 'Calendar';
const CLASS_CONTACTS = 'Contacts';
const CLASS_EMAIL = 'Email';
const CLASS_TASKS = 'Tasks';
const STORE_EMAIL = 'Mailbox';
const STORE_GAL = 'GAL';
protected static $_classMap = array();
/**
* @param unknown_type $_class
* @param Syncroton_Model_IDevice $_device
* @param DateTime $_timeStamp
* @throws InvalidArgumentException
* @return Syncroton_Data_IData
*/
public static function factory($_classFactory, Syncroton_Model_IDevice $_device, DateTime $_timeStamp)
{
switch($_classFactory) {
case self::CLASS_CALENDAR:
$className = Syncroton_Registry::get(Syncroton_Registry::CALENDAR_DATA_CLASS);
break;
case self::CLASS_CONTACTS:
$className = Syncroton_Registry::get(Syncroton_Registry::CONTACTS_DATA_CLASS);
break;
case self::STORE_EMAIL:
case self::CLASS_EMAIL:
$className = Syncroton_Registry::get(Syncroton_Registry::EMAIL_DATA_CLASS);
break;
case self::CLASS_TASKS:
$className = Syncroton_Registry::get(Syncroton_Registry::TASKS_DATA_CLASS);
break;
case self::STORE_GAL:
$className = Syncroton_Registry::get(Syncroton_Registry::GAL_DATA_CLASS);
break;
default:
- throw new InvalidArgumentException('invalid class type provided');
+ throw new Syncroton_Exception_UnexpectedValue('invalid class type provided');
breeak;
}
$class = new $className($_device, $_timeStamp);
- if ($_classFactory == STORE_EMAIL || $_classFactory == STORE_GAL) {
- if (! $class instanceof Syncroton_Data_IDataSearch) {
- throw new RuntimeException('class must be instanceof Syncroton_Data_IDataSearch');
- }
- }
- else if (! $class instanceof Syncroton_Data_IData) {
+ if (! $class instanceof Syncroton_Data_IData) {
throw new RuntimeException('class must be instanceof Syncroton_Data_IData');
}
return $class;
}
}
diff --git a/lib/ext/Syncroton/Data/IDataEmail.php b/lib/ext/Syncroton/Data/IDataEmail.php
index 731d187..7ecbb37 100644
--- a/lib/ext/Syncroton/Data/IDataEmail.php
+++ b/lib/ext/Syncroton/Data/IDataEmail.php
@@ -1,47 +1,44 @@
<?php
-
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
- * class to handle ActiveSync Sync command
+ * intace for email backend
*
* @package Model
*/
interface Syncroton_Data_IDataEmail
{
/**
* send an email
*
* @param resource $inputStream
* @param boolean $saveInSent
*/
public function sendEmail($inputStream, $saveInSent);
/**
* forward an email
*
- * @param string $collectionId
- * @param string $itemId
- * @param string $inputStream
- * @param string $saveInSent
+ * @param string|array $source is either a string(LongId) or an arrey with following properties collectionId, itemId and instanceId
+ * @param string $inputStream
+ * @param string $saveInSent
*/
- public function forwardEmail($collectionId, $itemId, $inputStream, $saveInSent);
+ public function forwardEmail($source, $inputStream, $saveInSent, $replaceMime);
/**
* reply to an email
*
- * @param string $collectionId
- * @param string $itemId
- * @param string $inputStream
- * @param string $saveInSent
+ * @param string|array $source is either a string(LongId) or an arrey with following properties collectionId, itemId and instanceId
+ * @param string $inputStream
+ * @param string $saveInSent
*/
- public function replyEmail($collectionId, $itemId, $inputStream, $saveInSent);
+ public function replyEmail($source, $inputStream, $saveInSent, $replaceMime);
}
diff --git a/lib/ext/Syncroton/Data/Tasks.php b/lib/ext/Syncroton/Data/Tasks.php
index 6fc6f4d..30a80d2 100644
--- a/lib/ext/Syncroton/Data/Tasks.php
+++ b/lib/ext/Syncroton/Data/Tasks.php
@@ -1,43 +1,43 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sync command
*
* @package Model
*/
class Syncroton_Data_Tasks extends Syncroton_Data_AData
{
protected function _initData()
{
/**
* used by unit tests only to simulated added folders
*/
Syncroton_Data_AData::$folders[get_class($this)] = array(
'tasksFolderId' => new Syncroton_Model_Folder(array(
'id' => sha1(mt_rand(). microtime()),
- 'folderid' => 'tasksFolderId',
- 'parentid' => null,
- 'displayname' => 'Default Tasks Folder',
+ 'serverId' => 'tasksFolderId',
+ 'parentId' => 0,
+ 'displayName' => 'Default Tasks Folder',
'type' => Syncroton_Command_FolderSync::FOLDERTYPE_TASK
))
);
/**
* used by unit tests only to simulated added folders
*/
#Syncroton_Data_AData::$entries[get_class($this)] = array(
# 'tasksFolderId' => array(
# )
#);
}
}
diff --git a/lib/ext/Syncroton/Exception/ProvisioningNeeded.php b/lib/ext/Syncroton/Exception/ProvisioningNeeded.php
index f2cd5f2..10db726 100644
--- a/lib/ext/Syncroton/Exception/ProvisioningNeeded.php
+++ b/lib/ext/Syncroton/Exception/ProvisioningNeeded.php
@@ -1,20 +1,21 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Exception
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* exception for data not found
*
* @package Syncroton
* @subpackage Exception
*/
class Syncroton_Exception_ProvisioningNeeded extends Syncroton_Exception
{
+ public $domDocument;
}
diff --git a/lib/ext/Syncroton/Model/AEntry.php b/lib/ext/Syncroton/Model/AEntry.php
index 029118a..7667d11 100644
--- a/lib/ext/Syncroton/Model/AEntry.php
+++ b/lib/ext/Syncroton/Model/AEntry.php
@@ -1,179 +1,252 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* abstract class to handle ActiveSync entry
*
* @package Syncroton
* @subpackage Model
*/
abstract class Syncroton_Model_AEntry implements Syncroton_Model_IEntry, IteratorAggregate, Countable
{
+ protected $_xmlBaseElement;
+
protected $_elements = array();
protected $_properties = array();
+ protected $_dateTimeFormat = "Y-m-d\TH:i:s.000\Z";
+
public function __construct($properties = null)
{
if ($properties instanceof SimpleXMLElement) {
$this->setFromSimpleXMLElement($properties);
} elseif (is_array($properties)) {
$this->setFromArray($properties);
}
}
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Model_IEntry::appendXML()
+ */
+ public function appendXML(DOMElement $_domParrent)
+ {
+ $this->_addXMLNamespaces($_domParrent);
+
+ foreach($this->_elements as $elementName => $value) {
+ // skip empty values
+ if($value === null || $value === '' || (is_array($value) && empty($value))) {
+ continue;
+ }
+
+ list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
+
+ if ($nameSpace == 'Internal') {
+ continue;
+ }
+
+ $nameSpace = 'uri:' . $nameSpace;
+
+ // strip off any non printable control characters
+ if (!ctype_print($value)) {
+ #$value = $this->removeControlChars($value);
+ }
+
+ $element = $_domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementName));
+
+ if (is_array($value)) {
+ foreach($value as $subValue) {
+ $subElement = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementProperties['childName']);
+
+ $this->_appendXMLElement($subElement, array(), $subValue);
+
+ $element->appendChild($subElement);
+ }
+ } else {
+ $this->_appendXMLElement($element, $elementProperties, $value);
+ }
+
+ $_domParrent->appendChild($element);
+ }
+ }
+
/**
* (non-PHPdoc)
* @see Countable::count()
*/
public function count()
{
return count($this->_elements);
}
/**
* (non-PHPdoc)
* @see IteratorAggregate::getIterator()
*/
public function getIterator()
{
return new ArrayIterator($this->_elements);
}
/**
* (non-PHPdoc)
* @see Syncroton_Model_IEntry::getProperties()
*/
public function getProperties()
{
$properties = array();
foreach($this->_properties as $namespace => $namespaceProperties) {
$properties = array_merge($properties, array_keys($namespaceProperties));
}
return $properties;
}
public function setFromArray(array $properties)
{
$this->_elements = array();
foreach($properties as $key => $value) {
try {
$this->$key = $value; //echo __LINE__ . PHP_EOL;
} catch (InvalidArgumentException $iae) {
//ignore invalid properties
//echo __LINE__ . PHP_EOL; echo $iae->getMessage(); echo $iae->getTraceAsString();
}
}
}
/**
* set properties from SimpleXMLElement object
*
* @param SimpleXMLElement $xmlCollection
* @throws InvalidArgumentException
*/
public function setFromSimpleXMLElement(SimpleXMLElement $properties)
{
- if ($properties->getName() !== $this->_xmlBaseElement) {
+ if (!in_array($properties->getName(), (array) $this->_xmlBaseElement)) {
throw new InvalidArgumentException('Unexpected element name: ' . $properties->getName());
}
$this->_elements = array();
- foreach (array_keys($this->_properties) as $namespace) {
+ foreach (array_keys($this->_properties) as $namespace) {
+ if ($namespace == 'Internal') {
+ continue;
+ }
$functionName = '_parse' . $namespace . 'Namespace';
$this->$functionName($properties);
}
return;
}
/**
* add needed xml namespaces to DomDocument
*
* @param unknown_type $_domParrent
*/
protected function _addXMLNamespaces(DOMElement $_domParrent)
{
foreach($this->_properties as $namespace => $namespaceProperties) {
$_domParrent->ownerDocument->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:'.$namespace, 'uri:'.$namespace);
}
}
+
+ protected function _appendXMLElement($element, $elementProperties, $value)
+ {
+ if ($value instanceof Syncroton_Model_IEntry) {
+ $value->appendXML($element);
+ } else {
+ if ($value instanceof DateTime) {
+ $value = $value->format($this->_dateTimeFormat);
+
+ } elseif (isset($elementProperties['encoding']) && $elementProperties['encoding'] == 'base64') {
+ if (is_resource($value)) {
+ stream_filter_append($value, 'convert.base64-encode');
+ $value = stream_get_contents($value);
+ } else {
+ $value = base64_encode($value);
+ }
+ }
+
+ $element->appendChild($element->ownerDocument->createTextNode($value));
+ }
+ }
/**
*
* @param unknown_type $element
* @throws InvalidArgumentException
* @return multitype:unknown
*/
protected function _getElementProperties($element)
- {
- foreach($this->_properties as $namespace => $namespaceProperties) {
+ {
+ foreach($this->_properties as $namespace => $namespaceProperties) {
if (array_key_exists($element, $namespaceProperties)) {
return array($namespace, $namespaceProperties[$element]);
}
}
-
+
throw new InvalidArgumentException("$element is no valid property of this object");
}
protected function _parseAirSyncBaseNamespace(SimpleXMLElement $properties)
{
// fetch data from Contacts2 namespace
$children = $properties->children('uri:AirSyncBase');
foreach ($children as $elementName => $xmlElement) {
switch ($elementName) {
case 'Body':
$this->$elementName = new Syncroton_Model_EmailBody($xmlElement);
break;
default:
$this->$elementName = (string) $xmlElement;
}
}
}
public function &__get($name)
{
$this->_getElementProperties($name);
return $this->_elements[$name];
}
public function __set($name, $value)
{
list ($namespace, $properties) = $this->_getElementProperties($name);
if ($properties['type'] == 'datetime' && !$value instanceof DateTime) {
throw new InvalidArgumentException("value for $name must be an instance of DateTime");
}
$this->_elements[$name] = $value;
}
public function __isset($name)
{
return isset($this->_elements[$name]);
}
public function __unset($name)
{
unset($this->_elements[$name]);
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/Contact.php b/lib/ext/Syncroton/Model/Contact.php
index 31e2bdc..d5f20e4 100644
--- a/lib/ext/Syncroton/Model/Contact.php
+++ b/lib/ext/Syncroton/Model/Contact.php
@@ -1,258 +1,178 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync contact
*
* @package Syncroton
* @subpackage Model
* @property string Alias
* @property DateTime Anniversary
* @property string AssistantName
* @property string AssistantPhoneNumber
* @property DateTime Birthday
* @property string Business2PhoneNumber
* @property string BusinessAddressCity
* @property Syncroton_Model_EmailBody Body
*/
class Syncroton_Model_Contact extends Syncroton_Model_AEntry
{
protected $_xmlBaseElement = 'ApplicationData';
protected $_properties = array(
'AirSyncBase' => array(
'Body' => array('type' => 'container')
),
'Contacts' => array(
'Alias' => array('type' => 'string'),
'Anniversary' => array('type' => 'datetime'),
'AssistantName' => array('type' => 'string'),
'AssistantPhoneNumber' => array('type' => 'string'),
'Birthday' => array('type' => 'datetime'),
//'BodySize' => 0x0a,
//'BodyTruncated' => 0x0b,
'Business2PhoneNumber' => array('type' => 'string'),
'BusinessAddressCity' => array('type' => 'string'),
'BusinessAddressCountry' => array('type' => 'string'),
'BusinessAddressPostalCode' => array('type' => 'string'),
'BusinessAddressState' => array('type' => 'string'),
'BusinessAddressStreet' => array('type' => 'string'),
'BusinessFaxNumber' => array('type' => 'string'),
'BusinessPhoneNumber' => array('type' => 'string'),
'CarPhoneNumber' => array('type' => 'string'),
- 'Categories' => array('type' => 'container'),
+ 'Categories' => array('type' => 'container', 'childName' => 'Category'),
//'Category' => array('type' => 'string'),
- 'Children' => array('type' => 'container'),
+ 'Children' => array('type' => 'container', 'childName' => 'Child'),
//'Child' => array('type' => 'string'),
'CompanyName' => array('type' => 'string'),
'Department' => array('type' => 'string'),
'Email1Address' => array('type' => 'string'),
'Email2Address' => array('type' => 'string'),
'Email3Address' => array('type' => 'string'),
'FileAs' => array('type' => 'string'),
'FirstName' => array('type' => 'string'),
'Home2PhoneNumber' => array('type' => 'string'),
'HomeAddressCity' => array('type' => 'string'),
'HomeAddressCountry' => array('type' => 'string'),
'HomeAddressPostalCode' => array('type' => 'string'),
'HomeAddressState' => array('type' => 'string'),
'HomeAddressStreet' => array('type' => 'string'),
'HomeFaxNumber' => array('type' => 'string'),
'HomePhoneNumber' => array('type' => 'string'),
'JobTitle' => array('type' => 'string'),
'LastName' => array('type' => 'string'),
'MiddleName' => array('type' => 'string'),
'MobilePhoneNumber' => array('type' => 'string'),
'OfficeLocation' => array('type' => 'string'),
'OtherAddressCity' => array('type' => 'string'),
'OtherAddressCountry' => array('type' => 'string'),
'OtherAddressPostalCode' => array('type' => 'string'),
'OtherAddressState' => array('type' => 'string'),
'OtherAddressStreet' => array('type' => 'string'),
'PagerNumber' => array('type' => 'string'),
'Picture' => array('type' => 'string', 'encoding' => 'base64'),
'RadioPhoneNumber' => array('type' => 'string'),
'Rtf' => array('type' => 'string'),
'Spouse' => array('type' => 'string'),
'Suffix' => array('type' => 'string'),
'Title' => array('type' => 'string'),
'WebPage' => array('type' => 'string'),
'WeightedRank' => array('type' => 'string'),
'YomiCompanyName' => array('type' => 'string'),
'YomiFirstName' => array('type' => 'string'),
'YomiLastName' => array('type' => 'string'),
),
'Contacts2' => array(
'AccountName' => array('type' => 'string'),
'CompanyMainPhone' => array('type' => 'string'),
'CustomerId' => array('type' => 'string'),
'GovernmentId' => array('type' => 'string'),
'IMAddress' => array('type' => 'string'),
'IMAddress2' => array('type' => 'string'),
'IMAddress3' => array('type' => 'string'),
'ManagerName' => array('type' => 'string'),
'MMS' => array('type' => 'string'),
'NickName' => array('type' => 'string'),
)
);
- public function appendXML(DOMElement $_domParrent)
- {
- $this->_addXMLNamespaces($_domParrent);
-
- foreach($this->_elements as $elementName => $value) {
- // skip empty values
- if($value === null || $value === '' || (is_array($value) && empty($value))) {
- continue;
- }
-
- list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
-
- $nameSpace = 'uri:' . $nameSpace;
-
- // strip off any non printable control characters
- if (!ctype_print($value)) {
- #$value = $this->removeControlChars($value);
- }
-
- switch($elementName) {
- case 'Body':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- $value->appendXML($element);
-
- $_domParrent->appendChild($element);
-
- break;
-
- case 'Categories':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- foreach($value as $category) {
- $categoryElement = $_domParrent->ownerDocument->createElementNS($nameSpace, 'Category');
- $categoryElement->appendChild($_domParrent->ownerDocument->createTextNode($category));
-
- $element->appendChild($categoryElement);
- }
-
- $_domParrent->appendChild($element);
-
- break;
-
- case 'Children':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- foreach($value as $child) {
- $childElement = $_domParrent->ownerDocument->createElementNS($nameSpace, 'Child');
- $childElement->appendChild($_domParrent->ownerDocument->createTextNode($child));
-
- $element->appendChild($childElement);
- }
-
- $_domParrent->appendChild($element);
-
- break;
-
- default:
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- if ($value instanceof DateTime) {
- $value = $value->format("Y-m-d\TH:i:s.000\Z");
- }
-
- if (isset($elementProperties['encoding']) && $elementProperties['encoding'] == 'base64') {
- if (is_resource($value)) {
- stream_filter_append($value, 'convert.base64-encode');
- $value = stream_get_contents($value);
- } else {
- $value = base64_encode($value);
- }
- }
-
- $element->appendChild($_domParrent->ownerDocument->createTextNode($value));
-
- $_domParrent->appendChild($element);
- }
- }
- }
-
protected function _parseContactsNamespace(SimpleXMLElement $properties)
{
// fetch data from Contacts namespace
$children = $properties->children('uri:Contacts');
foreach ($children as $elementName => $xmlElement) {
switch ($elementName) {
case 'Categories':
$categories = array();
foreach ($xmlElement->Category as $category) {
$categories[] = (string) $category;
}
$this->$elementName = $categories;
break;
case 'Children':
$children = array();
foreach ($xmlElement->Child as $child) {
$children[] = (string) $child;
}
$this->$elementName = $children;
break;
case 'Picture':
$this->$elementName = base64_decode((string) $xmlElement);
break;
default:
list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
switch ($elementProperties['type']) {
case 'datetime':
$this->$elementName = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
break;
case 'number':
$this->$elementName = (int) $xmlElement;
break;
default:
$this->$elementName = (string) $xmlElement;
break;
}
}
}
}
protected function _parseContacts2Namespace(SimpleXMLElement $properties)
{
// fetch data from Contacts2 namespace
$children = $properties->children('uri:Contacts2');
foreach ($children as $elementName => $xmlElement) {
switch ($elementName) {
default:
$this->$elementName = (string) $xmlElement;
}
}
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/Email.php b/lib/ext/Syncroton/Model/Email.php
index 9985d81..e3aa3ce 100644
--- a/lib/ext/Syncroton/Model/Email.php
+++ b/lib/ext/Syncroton/Model/Email.php
@@ -1,292 +1,200 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync event
*
* @package Model
* @property array Attachments
* @property string ContentType
* @property Syncroton_Model_EmailBody Body
* @property array Cc
* @property array To
* @property int Read
*/
class Syncroton_Model_Email extends Syncroton_Model_AEntry
{
protected $_xmlBaseElement = 'ApplicationData';
protected $_properties = array(
'AirSyncBase' => array(
- 'Attachments' => array('type' => 'container'),
+ 'Attachments' => array('type' => 'container', 'childName' => 'Attachment'),
'ContentType' => array('type' => 'string'),
'Body' => array('type' => 'container'),
'NativeBodyType' => array('type' => 'number'),
),
'Email' => array(
'BusyStatus' => array('type' => 'number'),
- 'Categories' => array('type' => 'container'),
- 'Category' => array('type' => 'string'),
+ 'Categories' => array('type' => 'container', 'childName' => 'Category'),
'Cc' => array('type' => 'string'),
'CompleteTime' => array('type' => 'datetime'),
'ContentClass' => array('type' => 'string'),
'DateReceived' => array('type' => 'datetime'),
- 'DayOfMonth' => array('type' => 'number'),
- 'DayOfWeek' => array('type' => 'number'),
+ #'DayOfMonth' => array('type' => 'number'),
+ #'DayOfWeek' => array('type' => 'number'),
'DisallowNewTimeProposal' => array('type' => 'number'),
'DisplayTo' => array('type' => 'string'),
'DTStamp' => array('type' => 'datetime'),
'EndTime' => array('type' => 'datetime'),
'Flag' => array('type' => 'container'),
'FlagType' => array('type' => 'string'),
'From' => array('type' => 'string'),
'GlobalObjId' => array('type' => 'string'),
'Importance' => array('type' => 'number'),
'InstanceType' => array('type' => 'number'),
'InternetCPID' => array('type' => 'string'),
- 'Interval' => array('type' => 'number'),
+ #'Interval' => array('type' => 'number'),
'Location' => array('type' => 'string'),
'MeetingRequest' => array('type' => 'container'),
'MessageClass' => array('type' => 'string'),
- 'MonthOfYear' => array('type' => 'number'),
- 'Occurrences' => array('type' => 'number'),
+ #'MonthOfYear' => array('type' => 'number'),
+ #'Occurrences' => array('type' => 'number'),
'Organizer' => array('type' => 'string'),
'Read' => array('type' => 'number'),
- 'Recurrence' => array('type' => 'container'),
- 'RecurrenceId' => array('type' => 'datetime'),
+ #'Recurrence' => array('type' => 'container'),
+ #'RecurrenceId' => array('type' => 'datetime'),
'Recurrences' => array('type' => 'container'),
'Reminder' => array('type' => 'number'),
'ReplyTo' => array('type' => 'string'),
'ResponseRequested' => array('type' => 'number'),
'Sensitivity' => array('type' => 'number'),
'StartTime' => array('type' => 'datetime'),
'Status' => array('type' => 'number'),
'Subject' => array('type' => 'string'),
'ThreadTopic' => array('type' => 'string'),
'TimeZone' => array('type' => 'timezone'),
'To' => array('type' => 'string'),
- 'Type' => array('type' => 'number'),
- 'Until' => array('type' => 'datetime'),
- 'WeekOfMonth' => array('type' => 'number'),
-
- //'AttName' => 0x07,
- //'AttSize' => 0x08,
- //'Att0Id' => 0x09,
- //'AttMethod' => 0x0a,
- //'AttRemoved' => 0x0b,
- //'Body' => 0x0c,
- //'BodySize' => 0x0d,
- //'BodyTruncated' => 0x0e,
- //'MIMEData' => 0x36,
- //'MIMETruncated' => 0x37,
- //'MIMESize' => 0x38,
+ #'Type' => array('type' => 'number'),
+ #'Until' => array('type' => 'datetime'),
+ #'WeekOfMonth' => array('type' => 'number'),
),
'Email2' => array(
'AccountId' => array('type' => 'string'),
- 'CalendarType' => array('type' => 'number'),
+ #'CalendarType' => array('type' => 'number'),
'ConversationId' => array('type' => 'byteArray'), // @todo handle this
'ConversationIndex' => array('type' => 'byteArray'), // @todo handle this
- 'FirstDayOfWeek' => array('type' => 'number'),
- 'IsLeapMonth' => array('type' => 'number'),
+ #'FirstDayOfWeek' => array('type' => 'number'),
+ #'IsLeapMonth' => array('type' => 'number'),
'LastVerbExecuted' => array('type' => 'number'),
'LastVerbExecutionTime' => array('type' => 'datetime'),
'MeetingMessageType' => array('type' => 'number'),
'ReceivedAsBcc' => array('type' => 'number'),
'Sender' => array('type' => 'string'),
'UmCallerID' => array('type' => 'string'),
'UmUserNotes' => array('type' => 'string'),
),
);
- public function appendXML(DOMElement $_domParrent)
- {
- $this->_addXMLNamespaces($_domParrent);
-
- foreach($this->_elements as $elementName => $value) {
- // skip empty values
- if($value === null || $value === '' || (is_array($value) && empty($value))) {
- continue;
- }
-
- list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
-
- $nameSpace = 'uri:' . $nameSpace;
-
- // strip off any non printable control characters
- if (!ctype_print($value)) {
- #$value = $this->removeControlChars($value);
- }
-
- switch($elementName) {
- case 'Attachments':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- foreach($value as $attachment) {
- $attachmentElement = $_domParrent->ownerDocument->createElementNS($nameSpace, 'Attachment');
- $attachment->appendXML($attachmentElement);
-
- $element->appendChild($attachmentElement);
- }
-
- $_domParrent->appendChild($element);
-
- break;
-
- case 'Body':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- $value->appendXML($element);
-
- $_domParrent->appendChild($element);
- break;
-
- case 'Categories':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- foreach($value as $category) {
- $categoryElement = $_domParrent->ownerDocument->createElementNS($nameSpace, 'Category');
- $categoryElement->appendChild($_domParrent->ownerDocument->createTextNode($category));
-
- $element->appendChild($categoryElement);
- }
-
- $_domParrent->appendChild($element);
-
- break;
-
- case 'Recurrence':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- $value->appendXML($element);
-
- $_domParrent->appendChild($element);
-
- break;
-
- default:
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- if ($value instanceof DateTime) {
- $value = $value->format("Y-m-d\TH:i:s.000\Z");
- }
- $element->appendChild($_domParrent->ownerDocument->createTextNode($value));
-
- $_domParrent->appendChild($element);
- }
- }
-
- }
-
protected function _parseAirSyncBaseNamespace(SimpleXMLElement $properties)
{
// fetch data from AirSyncBase namespace
$children = $properties->children('uri:AirSyncBase');
foreach ($children as $elementName => $xmlElement) {
switch ($elementName) {
case 'Body':
$this->$elementName = new Syncroton_Model_EmailBody($xmlElement);
break;
default:
list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
switch ($properties['type']) {
case 'datetime':
$this->$elementName = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
break;
case 'number':
$this->$elementName = (int) $xmlElement;
break;
default:
$this->$elementName = (string) $xmlElement;
break;
}
}
}
}
protected function _parseEmailNamespace(SimpleXMLElement $properties)
{
// fetch data from AirSyncBase namespace
$children = $properties->children('uri:Email');
foreach ($children as $elementName => $xmlElement) {
switch ($elementName) {
case 'Body':
$this->$elementName = new Syncroton_Model_EmailBody($xmlElement);
break;
default:
list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
switch ($properties['type']) {
case 'datetime':
$this->$elementName = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
break;
case 'number':
$this->$elementName = (int) $xmlElement;
break;
default:
$this->$elementName = (string) $xmlElement;
break;
}
}
}
}
protected function _parseEmail2Namespace(SimpleXMLElement $properties)
{
// fetch data from AirSyncBase namespace
$children = $properties->children('uri:Email2');
foreach ($children as $elementName => $xmlElement) {
switch ($elementName) {
case 'Body':
$this->$elementName = new Syncroton_Model_EmailBody($xmlElement);
break;
default:
list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
switch ($properties['type']) {
case 'datetime':
$this->$elementName = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
break;
case 'number':
$this->$elementName = (int) $xmlElement;
break;
default:
$this->$elementName = (string) $xmlElement;
break;
}
}
}
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/EmailAttachment.php b/lib/ext/Syncroton/Model/EmailAttachment.php
index 2bbd69e..e958f7f 100644
--- a/lib/ext/Syncroton/Model/EmailAttachment.php
+++ b/lib/ext/Syncroton/Model/EmailAttachment.php
@@ -1,272 +1,187 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync event
*
* @package Model
* @property string class
* @property string collectionId
* @property bool deletesAsMoves
* @property bool getChanges
* @property string syncKey
* @property int windowSize
*/
-
class Syncroton_Model_EmailAttachment extends Syncroton_Model_AEntry
{
protected $_xmlBaseElement = 'Attachment';
- // @todo handle body
protected $_properties = array(
'AirSyncBase' => array(
'ContentId' => array('type' => 'string'),
'ContentLocation' => array('type' => 'string'),
'DisplayName' => array('type' => 'string'),
'EstimatedDataSize' => array('type' => 'string'),
'FileReference' => array('type' => 'string'),
'IsInline' => array('type' => 'number'),
'Method' => array('type' => 'string'),
),
'Email2' => array(
'UmAttDuration' => array('type' => 'number'),
'UmAttOrder' => array('type' => 'number'),
),
);
- public function appendXML(DOMElement $_domParrent)
- {
- $_domParrent->ownerDocument->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:AirSyncBase', 'uri:AirSyncBase');
- $_domParrent->ownerDocument->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Email2', 'uri:Email2');
-
- foreach($this->_elements as $elementName => $value) {
- // skip empty values
- if($value === null || $value === '' || (is_array($value) && empty($value))) {
- continue;
- }
-
- $elementProperties = (isset($this->_properties['AirSyncBase'][$elementName])) ?
- $this->_properties['AirSyncBase'][$elementName] :
- $this->_properties['Email2'][$elementName];
-
- $nameSpace = isset($this->_properties['AirSyncBase'][$elementName]) ? 'uri:AirSyncBase' : 'uri:Email2';
-
- // strip off any non printable control characters
- if (!ctype_print($value)) {
- #$value = $this->removeControlChars($value);
- }
-
- switch($elementName) {
- case 'Categories':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- foreach($value as $category) {
- $categoryElement = $_domParrent->ownerDocument->createElementNS($nameSpace, 'Category');
- $categoryElement->appendChild($_domParrent->ownerDocument->createTextNode($category));
-
- $element->appendChild($categoryElement);
- }
-
- $_domParrent->appendChild($element);
-
- break;
-
- case 'Recurrence':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- $value->appendXML($element);
-
- $_domParrent->appendChild($element);
-
- break;
-
- default:
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- if ($value instanceof DateTime) {
- $value = $value->format("Y-m-d\TH:i:s.000\Z");
- }
- $element->appendChild($_domParrent->ownerDocument->createTextNode($value));
-
- $_domParrent->appendChild($element);
- }
- }
-
- }
-
- /**
- *
- * @param SimpleXMLElement $xmlCollection
- * @throws InvalidArgumentException
- */
- public function setFromSimpleXMLElement(SimpleXMLElement $properties)
- {
- if ($properties->getName() !== $this->_xmlBaseElement) {
- throw new InvalidArgumentException('Unexpected element name: ' . $properties->getName());
- }
-
- $this->_elements = array();
-
- foreach (array_keys($this->_properties) as $namespace) {
- $functionName = '_parse' . $namespace . 'Namespace';
- $this->$functionName($properties);
- }
-
- $airSyncBaseData = $properties->children('uri:AirSyncBase');
-
- return;
- }
-
protected function _parseAirSyncBaseNamespace(SimpleXMLElement $properties)
{
// fetch data from Email namespace
$children = $properties->children('uri:AirSyncBase');
foreach ($children as $elementName => $xmlElement) {
switch ($elementName) {
case 'Attachments':
$attachments = array();
foreach ($xmlElement->$elementName as $attachment) {
$attachments[] = new Syncroton_Model_EmailAttachment($attachment);
}
$this->$elementName = $attachments;
break;
case 'Recurrence':
$this->$elementName = new Syncroton_Model_TaskRecurrence($xmlElement);
break;
default:
$properties = $this->_properties['Email'][$elementName];
switch ($properties['type']) {
case 'datetime':
$this->$elementName = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
break;
case 'number':
$this->$elementName = (int) $xmlElement;
break;
default:
$this->$elementName = (string) $xmlElement;
break;
}
}
}
}
protected function _parseEmailNamespace(SimpleXMLElement $properties)
{
// fetch data from Email namespace
$children = $properties->children('uri:Email');
foreach ($children as $elementName => $xmlElement) {
switch ($elementName) {
case 'Categories':
$categories = array();
foreach ($xmlElement->$elementName as $category) {
$categories[] = (string) $category;
}
$this->$elementName = $categories;
break;
case 'Recurrence':
$this->$elementName = new Syncroton_Model_TaskRecurrence($xmlElement);
break;
default:
$properties = $this->_properties['Email'][$elementName];
switch ($properties['type']) {
case 'datetime':
$this->$elementName = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
break;
case 'number':
$this->$elementName = (int) $xmlElement;
break;
default:
$this->$elementName = (string) $xmlElement;
break;
}
}
}
}
protected function _parseEmail2Namespace(SimpleXMLElement $properties)
{
// fetch data from Email2 namespace
$children = $properties->children('uri:Email2');
foreach ($children as $elementName => $xmlElement) {
switch ($elementName) {
default:
$properties = $this->_properties['Email'][$elementName];
switch ($properties['type']) {
case 'datetime':
$this->$elementName = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
break;
case 'number':
$this->$elementName = (int) $xmlElement;
break;
default:
$this->$elementName = (string) $xmlElement;
break;
}
}
}
}
public function &__get($name)
{
if (!array_key_exists($name, $this->_properties['AirSyncBase']) && !array_key_exists($name, $this->_properties['Email2'])) {
throw new InvalidArgumentException("$name is no valid property of this object");
}
return $this->_elements[$name];
}
public function __set($name, $value)
{
if (!array_key_exists($name, $this->_properties['AirSyncBase']) && !array_key_exists($name, $this->_properties['Email2'])) {
throw new InvalidArgumentException("$name is no valid property of this object");
}
$properties = isset($this->_properties['AirSyncBase'][$name]) ? $this->_properties['AirSyncBase'][$name] : $this->_properties['Email2'][$name];
if ($properties['type'] == 'datetime' && !$value instanceof DateTime) {
throw new InvalidArgumentException("value for $name must be an instance of DateTime");
}
$this->_elements[$name] = $value;
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/EmailBody.php b/lib/ext/Syncroton/Model/EmailBody.php
index 8f8b318..4f2af2c 100644
--- a/lib/ext/Syncroton/Model/EmailBody.php
+++ b/lib/ext/Syncroton/Model/EmailBody.php
@@ -1,222 +1,188 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle AirSyncBase:Body
*
* @package Model
* @property int EstimatedDataSize
* @property string Data
* @property string Part
* @property string Preview
* @property bool Truncated
* @property string Type
*/
class Syncroton_Model_EmailBody extends Syncroton_Model_AEntry
{
const TYPE_PLAINTEXT = 1;
const TYPE_HTML = 2;
const TYPE_RTF = 3;
const TYPE_MIME = 4;
protected $_xmlBaseElement = 'Body';
// @todo handle body
protected $_properties = array(
'AirSyncBase' => array(
'Type' => array('type' => 'string'),
'EstimatedDataSize' => array('type' => 'string'),
'Data' => array('type' => 'string'),
'Truncated' => array('type' => 'number'),
'Part' => array('type' => 'number'),
'Preview' => array('type' => 'string'),
),
);
- public function appendXML(DOMElement $_domParrent)
- {
- $this->_addXMLNamespaces($_domParrent);
-
- foreach($this->_elements as $elementName => $value) {
- // skip empty values
- if($value === null || $value === '' || (is_array($value) && empty($value))) {
- continue;
- }
-
- list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
-
- $nameSpace = 'uri:' . $nameSpace;
-
- // strip off any non printable control characters
- if (!ctype_print($value)) {
- #$value = $this->removeControlChars($value);
- }
-
- switch($elementName) {
- default:
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- if ($value instanceof DateTime) {
- $value = $value->format("Y-m-d\TH:i:s.000\Z");
- }
- $element->appendChild($_domParrent->ownerDocument->createTextNode($value));
-
- $_domParrent->appendChild($element);
- }
- }
-
- }
-
/**
*
* @param SimpleXMLElement $xmlCollection
* @throws InvalidArgumentException
*/
public function setFromSimpleXMLElement(SimpleXMLElement $properties)
{
if ($properties->getName() !== $this->_xmlBaseElement) {
throw new InvalidArgumentException('Unexpected element name: ' . $properties->getName());
}
$this->_elements = array();
foreach (array_keys($this->_properties) as $namespace) {
$functionName = '_parse' . $namespace . 'Namespace';
$this->$functionName($properties);
}
$airSyncBaseData = $properties->children('uri:AirSyncBase');
return;
}
protected function _parseAirSyncBaseNamespace(SimpleXMLElement $properties)
{
// fetch data from Email namespace
$children = $properties->children('uri:AirSyncBase');
foreach ($children as $elementName => $xmlElement) {
switch ($elementName) {
case 'Attachments':
$attachments = array();
foreach ($xmlElement->$elementName as $attachment) {
$attachments[] = new Syncroton_Model_EmailAttachment($attachment);
}
$this->$elementName = $attachments;
break;
case 'Recurrence':
$this->$elementName = new Syncroton_Model_TaskRecurrence($xmlElement);
break;
default:
list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
switch ($elementProperties['type']) {
case 'datetime':
$this->$elementName = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
break;
case 'number':
$this->$elementName = (int) $xmlElement;
break;
default:
$this->$elementName = (string) $xmlElement;
break;
}
}
}
}
protected function _parseEmailNamespace(SimpleXMLElement $properties)
{
// fetch data from Email namespace
$children = $properties->children('uri:Email');
foreach ($children as $elementName => $xmlElement) {
switch ($elementName) {
case 'Categories':
$categories = array();
foreach ($xmlElement->$elementName as $category) {
$categories[] = (string) $category;
}
$this->$elementName = $categories;
break;
case 'Recurrence':
$this->$elementName = new Syncroton_Model_TaskRecurrence($xmlElement);
break;
default:
$properties = $this->_properties['Email'][$elementName];
switch ($properties['type']) {
case 'datetime':
$this->$elementName = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
break;
case 'number':
$this->$elementName = (int) $xmlElement;
break;
default:
$this->$elementName = (string) $xmlElement;
break;
}
}
}
}
protected function _parseEmail2Namespace(SimpleXMLElement $properties)
{
// fetch data from Email2 namespace
$children = $properties->children('uri:Email2');
foreach ($children as $elementName => $xmlElement) {
switch ($elementName) {
default:
$properties = $this->_properties['Email'][$elementName];
switch ($properties['type']) {
case 'datetime':
$this->$elementName = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
break;
case 'number':
$this->$elementName = (int) $xmlElement;
break;
default:
$this->$elementName = (string) $xmlElement;
break;
}
}
}
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/Event.php b/lib/ext/Syncroton/Model/Event.php
index cc37324..1571b30 100644
--- a/lib/ext/Syncroton/Model/Event.php
+++ b/lib/ext/Syncroton/Model/Event.php
@@ -1,264 +1,173 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync event
*
* @package Model
* @property string class
* @property string collectionId
* @property bool deletesAsMoves
* @property bool getChanges
* @property string syncKey
* @property int windowSize
*/
class Syncroton_Model_Event extends Syncroton_Model_AEntry
{
/**
* busy status constants
*/
const BUSY_STATUS_FREE = 0;
const BUSY_STATUS_TENATTIVE = 1;
const BUSY_STATUS_BUSY = 2;
+ protected $_dateTimeFormat = "Ymd\THis\Z";
+
protected $_xmlBaseElement = 'ApplicationData';
- // @todo handle body
protected $_properties = array(
'AirSyncBase' => array(
'Body' => array('type' => 'container')
),
'Calendar' => array(
'AllDayEvent' => array('type' => 'number'),
'AppointmentReplyTime' => array('type' => 'datetime'),
- 'Attendees' => array('type' => 'container'),
+ 'Attendees' => array('type' => 'container', 'childName' => 'Attendee'),
//'Body' => 0x0b,
//'BodyTruncated' => 0x0c,
'BusyStatus' => array('type' => 'number'),
- 'Categories' => array('type' => 'container'),
+ 'Categories' => array('type' => 'container', 'childName' => 'Category'),
'DisallowNewTimeProposal' => array('type' => 'number'),
'DtStamp' => array('type' => 'datetime'),
'EndTime' => array('type' => 'datetime'),
- 'Exceptions' => array('type' => 'container'),
+ 'Exceptions' => array('type' => 'container', 'childName' => 'Exception'),
'Location' => array('type' => 'string'),
'MeetingStatus' => array('type' => 'number'),
'OnlineMeetingConfLink' => array('type' => 'string'),
'OnlineMeetingExternalLink' => array('type' => 'string'),
'OrganizerEmail' => array('type' => 'string'),
'OrganizerName' => array('type' => 'string'),
'Recurrence' => array('type' => 'container'),
'Reminder' => array('type' => 'number'),
'ResponseRequested' => array('type' => 'number'),
'ResponseType' => array('type' => 'number'),
//'Rtf' => 0x10,
'Sensitivity' => array('type' => 'number'),
'StartTime' => array('type' => 'datetime'),
'Subject' => array('type' => 'string'),
'Timezone' => array('type' => 'timezone'),
'UID' => array('type' => 'string'),
)
);
- public function appendXML(DOMElement $_domParrent)
- {
- $this->_addXMLNamespaces($_domParrent);
-
- foreach($this->_elements as $elementName => $value) {
- // skip empty values
- if($value === null || $value === '' || (is_array($value) && empty($value))) {
- continue;
- }
-
- list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
-
- $nameSpace = 'uri:' . $nameSpace;
-
- // strip off any non printable control characters
- if (!ctype_print($value)) {
- #$value = $this->removeControlChars($value);
- }
-
- switch($elementName) {
- case 'Attendees':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- foreach ($value as $attendee) {
- $attendeeElement = $_domParrent->ownerDocument->createElementNS($nameSpace, 'Attendee');
- $attendee->appendXML($attendeeElement);
- $element->appendChild($attendeeElement);
- }
-
- $_domParrent->appendChild($element);
-
- break;
-
- case 'Body':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- $value->appendXML($element);
-
- $_domParrent->appendChild($element);
-
- break;
-
- case 'Categories':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- foreach($value as $category) {
- $categoryElement = $_domParrent->ownerDocument->createElementNS($nameSpace, 'Category');
- $categoryElement->appendChild($_domParrent->ownerDocument->createTextNode($category));
-
- $element->appendChild($categoryElement);
- }
-
- $_domParrent->appendChild($element);
-
- break;
-
- case 'Exceptions':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- foreach ($value as $exception) {
- $exceptionElement = $_domParrent->ownerDocument->createElementNS($nameSpace, 'Exception');
- $exception->appendXML($exceptionElement);
- $element->appendChild($exceptionElement);
- }
-
- $_domParrent->appendChild($element);
-
- break;
-
- case 'Recurrence':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- $value->appendXML($element);
-
- $_domParrent->appendChild($element);
-
- break;
-
- default:
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- if ($value instanceof DateTime) {
- $value = $value->format("Ymd\THis\Z");
- }
- $element->appendChild($_domParrent->ownerDocument->createTextNode($value));
-
- $_domParrent->appendChild($element);
- }
- }
-
- }
-
protected function _parseCalendarNamespace(SimpleXMLElement $properties)
{
// fetch data from Contacts namespace
$children = $properties->children('uri:Calendar');
foreach ($children as $elementName => $xmlElement) {
switch ($elementName) {
case 'Attendees':
$attendees = array();
foreach ($xmlElement->Attendee as $attendee) {
$attendees[] = new Syncroton_Model_EventAttendee($attendee);
}
$this->$elementName = $attendees;
break;
case 'Categories':
$categories = array();
foreach ($xmlElement->$elementName as $category) {
$categories[] = (string) $category;
}
$this->$elementName = $categories;
break;
case 'Exceptions':
$exceptions = array();
foreach ($xmlElement->Exception as $exception) {
$exceptions[] = new Syncroton_Model_EventException($exception);
}
$this->$elementName = $exceptions;
break;
case 'Recurrence':
$this->$elementName = new Syncroton_Model_EventRecurrence($xmlElement);
break;
default:
list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
switch ($elementProperties['type']) {
case 'datetime':
$this->$elementName = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
break;
case 'number':
$this->$elementName = (int) $xmlElement;
break;
default:
$this->$elementName = (string) $xmlElement;
break;
}
}
}
}
protected function _parseAirSyncBaseNamespace(SimpleXMLElement $properties)
{
// fetch data from AirSyncBase namespace
$children = $properties->children('uri:AirSyncBase');
foreach ($children as $elementName => $xmlElement) {
switch ($elementName) {
case 'Body':
$this->$elementName = new Syncroton_Model_EmailBody($xmlElement);
break;
default:
list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
switch ($properties['type']) {
case 'datetime':
$this->$elementName = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
break;
case 'number':
$this->$elementName = (int) $xmlElement;
break;
default:
$this->$elementName = (string) $xmlElement;
break;
}
}
}
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/EventAttendee.php b/lib/ext/Syncroton/Model/EventAttendee.php
index 3e9946d..7827e30 100644
--- a/lib/ext/Syncroton/Model/EventAttendee.php
+++ b/lib/ext/Syncroton/Model/EventAttendee.php
@@ -1,203 +1,126 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync event
*
* @package Model
* @property string class
* @property string collectionId
* @property bool deletesAsMoves
* @property bool getChanges
* @property string syncKey
* @property int windowSize
*/
class Syncroton_Model_EventAttendee extends Syncroton_Model_AEntry
{
/**
* attendee status
*/
const ATTENDEE_STATUS_UNKNOWN = 0;
const ATTENDEE_STATUS_TENTATIVE = 2;
const ATTENDEE_STATUS_ACCEPTED = 3;
const ATTENDEE_STATUS_DECLINED = 4;
const ATTENDEE_STATUS_NOTRESPONDED = 5;
/**
* attendee types
*/
const ATTENDEE_TYPE_REQUIRED = 1;
const ATTENDEE_TYPE_OPTIONAL = 2;
const ATTENDEE_TYPE_RESOURCE = 3;
- // @todo handle body
protected $_properties = array(
'Calendar' => array(
'AttendeeStatus' => array('type' => 'number'),
'AttendeeType' => array('type' => 'number'),
'Email' => array('type' => 'string'),
'Name' => array('type' => 'string'),
)
);
- public function appendXML(DOMElement $_domParrent)
- {
- $_domParrent->ownerDocument->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Calendar', 'uri:Calendar');
-
- foreach($this->_elements as $elementName => $value) {
- // skip empty values
- if($value === null || $value === '' || (is_array($value) && empty($value))) {
- continue;
- }
-
- $elementProperties = $this->_properties['Calendar'][$elementName];
-
- $nameSpace = 'uri:Calendar';
-
- // strip off any non printable control characters
- if (!ctype_print($value)) {
- #$value = $this->removeControlChars($value);
- }
-
- switch($elementName) {
- case 'Categories':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- foreach($value as $category) {
- $categoryElement = $_domParrent->ownerDocument->createElementNS($nameSpace, 'Category');
- $categoryElement->appendChild($_domParrent->ownerDocument->createTextNode($category));
-
- $element->appendChild($categoryElement);
- }
-
- $_domParrent->appendChild($element);
-
- break;
-
- case 'Children':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- foreach($value as $child) {
- $childElement = $_domParrent->ownerDocument->createElementNS($nameSpace, 'Child');
- $childElement->appendChild($_domParrent->ownerDocument->createTextNode($child));
-
- $element->appendChild($childElement);
- }
-
- $_domParrent->appendChild($element);
-
- break;
-
- case 'Picture':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- if (is_resource($value)) {
- stream_filter_append($value, 'convert.base64-encode');
- $element->appendChild($_domParrent->ownerDocument->createTextNode(stream_get_contents($value)));
- } else {
- $element->appendChild($_domParrent->ownerDocument->createTextNode(base64_encode($value)));
- }
-
- $_domParrent->appendChild($element);
-
- break;
-
- default:
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- if ($value instanceof DateTime) {
- $value = $value->format("Ymd\THis\Z");
- }
- $element->appendChild($_domParrent->ownerDocument->createTextNode($value));
-
- $_domParrent->appendChild($element);
- }
- }
-
- }
-
/**
*
* @param SimpleXMLElement $xmlCollection
* @throws InvalidArgumentException
*/
public function setFromSimpleXMLElement(SimpleXMLElement $properties)
{
if ($properties->getName() !== 'Attendee') {
throw new InvalidArgumentException('Unexpected element name: ' . $properties->getName());
}
$this->_elements = array();
foreach (array_keys($this->_properties) as $namespace) {
$functionName = '_parse' . $namespace . 'Namespace';
$this->$functionName($properties);
}
$airSyncBaseData = $properties->children('uri:AirSyncBase');
return;
}
protected function _parseCalendarNamespace(SimpleXMLElement $properties)
{
// fetch data from Contacts namespace
$children = $properties->children('uri:Calendar');
foreach ($children as $elementName => $xmlElement) {
switch ($elementName) {
default:
$properties = $this->_properties['Calendar'][$elementName];
switch ($properties['type']) {
case 'datetime':
$this->$elementName = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
break;
case 'number':
$this->$elementName = (int) $xmlElement;
break;
default:
$this->$elementName = (string) $xmlElement;
break;
}
}
}
}
public function &__get($name)
{
if (!array_key_exists($name, $this->_properties['Calendar'])) {
throw new InvalidArgumentException("$name is no valid property of this object");
}
return $this->_elements[$name];
}
public function __set($name, $value)
{
if (!array_key_exists($name, $this->_properties['Calendar'])) {
throw new InvalidArgumentException("$name is no valid property of this object");
}
$properties = $this->_properties['Calendar'][$name];
if ($properties['type'] == 'datetime' && !$value instanceof DateTime) {
throw new InvalidArgumentException("value for $name must be an instance of DateTime");
}
$this->_elements[$name] = $value;
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/EventException.php b/lib/ext/Syncroton/Model/EventException.php
index e6881c9..7ab97d7 100644
--- a/lib/ext/Syncroton/Model/EventException.php
+++ b/lib/ext/Syncroton/Model/EventException.php
@@ -1,48 +1,49 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync event
*
* @package Model
* @property string class
* @property string collectionId
* @property bool deletesAsMoves
* @property bool getChanges
* @property string syncKey
* @property int windowSize
*/
class Syncroton_Model_EventException extends Syncroton_Model_Event
{
protected $_xmlBaseElement = 'Exception';
- // @todo handle body
+ protected $_dateTimeFormat = "Ymd\THis\Z";
+
protected $_properties = array(
'Calendar' => array(
'AllDayEvent' => array('type' => 'number'),
'AppointmentReplyTime' => array('type' => 'datetime'),
'Attendees' => array('type' => 'container'),
'BusyStatus' => array('type' => 'number'),
'Categories' => array('type' => 'container'),
'Deleted' => array('type' => 'number'),
'DtStamp' => array('type' => 'datetime'),
'EndTime' => array('type' => 'datetime'),
'ExceptionStartTime' => array('type' => 'datetime'),
'Location' => array('type' => 'string'),
'MeetingStatus' => array('type' => 'number'),
'Reminder' => array('type' => 'number'),
'ResponseType' => array('type' => 'number'),
'Sensitivity' => array('type' => 'number'),
'StartTime' => array('type' => 'datetime'),
'Subject' => array('type' => 'string'),
)
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/EventRecurrence.php b/lib/ext/Syncroton/Model/EventRecurrence.php
index 83fe039..d8b9f4c 100644
--- a/lib/ext/Syncroton/Model/EventRecurrence.php
+++ b/lib/ext/Syncroton/Model/EventRecurrence.php
@@ -1,133 +1,100 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync event
*
* @package Model
* @property int CalendarType
* @property int DayOfMonth
* @property int DayOfWeek
* @property int FirstDayOfWeek
* @property int Interval
* @property int IsLeapMonth
* @property int MonthOfYear
* @property int Occurrences
* @property int Type
* @property DateTime Until
* @property int WeekOfMonth
*/
class Syncroton_Model_EventRecurrence extends Syncroton_Model_AEntry
{
protected $_xmlBaseElement = 'Recurrence';
/**
* recur types
*/
const TYPE_DAILY = 0; // Recurs daily.
const TYPE_WEEKLY = 1; // Recurs weekly
const TYPE_MONTHLY = 2; // Recurs monthly
const TYPE_MONTHLY_DAYN = 3; // Recurs monthly on the nth day
const TYPE_YEARLY = 5; // Recurs yearly
const TYPE_YEARLY_DAYN = 6; // Recurs yearly on the nth day
/**
* day of week constants
*/
const RECUR_DOW_SUNDAY = 1;
const RECUR_DOW_MONDAY = 2;
const RECUR_DOW_TUESDAY = 4;
const RECUR_DOW_WEDNESDAY = 8;
const RECUR_DOW_THURSDAY = 16;
const RECUR_DOW_FRIDAY = 32;
const RECUR_DOW_SATURDAY = 64;
-
- // @todo handle body
+
+ protected $_dateTimeFormat = "Ymd\THis\Z";
+
protected $_properties = array(
'Calendar' => array(
'CalendarType' => array('type' => 'number'),
'DayOfMonth' => array('type' => 'number'),
'DayOfWeek' => array('type' => 'number'),
'FirstDayOfWeek' => array('type' => 'number'),
'Interval' => array('type' => 'number'),
'IsLeapMonth' => array('type' => 'number'),
'MonthOfYear' => array('type' => 'number'),
'Occurrences' => array('type' => 'number'),
'Type' => array('type' => 'number'),
'Until' => array('type' => 'datetime'),
'WeekOfMonth' => array('type' => 'number'),
)
);
- public function appendXML(DOMElement $_domParrent)
- {
- $this->_addXMLNamespaces($_domParrent);
-
- foreach($this->_elements as $elementName => $value) {
- // skip empty values
- if($value === null || $value === '' || (is_array($value) && empty($value))) {
- continue;
- }
-
- list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
-
- $nameSpace = 'uri:' . $nameSpace;
-
- // strip off any non printable control characters
- if (!ctype_print($value)) {
- #$value = $this->removeControlChars($value);
- }
-
- switch($elementName) {
- default:
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- if ($value instanceof DateTime) {
- $value = $value->format("Ymd\THis\Z");
- }
- $element->appendChild($_domParrent->ownerDocument->createTextNode($value));
-
- $_domParrent->appendChild($element);
- }
- }
-
- }
-
protected function _parseCalendarNamespace(SimpleXMLElement $properties)
{
// fetch data from Contacts namespace
$children = $properties->children('uri:Calendar');
foreach ($children as $elementName => $xmlElement) {
switch ($elementName) {
default:
$properties = $this->_properties['Calendar'][$elementName];
switch ($properties['type']) {
case 'datetime':
$this->$elementName = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
break;
case 'number':
$this->$elementName = (int) $xmlElement;
break;
default:
$this->$elementName = (string) $xmlElement;
break;
}
}
}
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/FileReference.php b/lib/ext/Syncroton/Model/FileReference.php
index b22bcb0..6c2e67d 100644
--- a/lib/ext/Syncroton/Model/FileReference.php
+++ b/lib/ext/Syncroton/Model/FileReference.php
@@ -1,93 +1,43 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync event
*
* @package Model
* @property string ContentType
* @property string Data
*/
class Syncroton_Model_FileReference extends Syncroton_Model_AEntry
{
protected $_xmlBaseElement = 'ApplicationData';
protected $_properties = array(
'AirSyncBase' => array(
'ContentType' => array('type' => 'string'),
),
'ItemOperations' => array(
'Data' => array('type' => 'string', 'encoding' => 'base64'),
)
);
- /**
- * append email data to xml element
- *
- * @param DOMElement $_domParrent the parrent xml node
- * @param string $_folderId the local folder id
- */
- public function appendXML(DOMElement $_domParrent)
- {
- $this->_addXMLNamespaces($_domParrent);
-
- foreach($this->_elements as $elementName => $value) {
- // skip empty values
- if($value === null || $value === '' || (is_array($value) && empty($value))) {
- continue;
- }
-
- list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
-
- $nameSpace = 'uri:' . $nameSpace;
-
- // strip off any non printable control characters
- if (!ctype_print($value)) {
- #$value = $this->removeControlChars($value);
- }
-
- switch($elementName) {
- default:
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- if ($value instanceof DateTime) {
- $value = $value->format("Y-m-d\TH:i:s.000\Z");
- }
-
- if (isset($elementProperties['encoding']) && $elementProperties['encoding'] == 'base64') {
- if (is_resource($value)) {
- stream_filter_append($value, 'convert.base64-encode');
- $value = stream_get_contents($value);
- } else {
- $value = base64_encode($value);
- }
- }
-
- $element->appendChild($_domParrent->ownerDocument->createTextNode($value));
-
- $_domParrent->appendChild($element);
- }
- }
-
- }
-
/**
*
* @param SimpleXMLElement $xmlCollection
* @throws InvalidArgumentException
*/
public function setFromSimpleXMLElement(SimpleXMLElement $properties)
{
//do nothing
return;
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/Folder.php b/lib/ext/Syncroton/Model/Folder.php
index 7aaf91a..50cb189 100644
--- a/lib/ext/Syncroton/Model/Folder.php
+++ b/lib/ext/Syncroton/Model/Folder.php
@@ -1,32 +1,73 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sync command
*
* @package Model
*/
-class Syncroton_Model_Folder implements Syncroton_Model_IFolder
+class Syncroton_Model_Folder extends Syncroton_Model_AEntry implements Syncroton_Model_IFolder
{
- public function __construct(array $_data = array())
- {
- $this->setFromArray($_data);
- }
+ protected $_xmlBaseElement = array('FolderUpdate', 'FolderCreate');
- public function setFromArray(array $_data)
- {
- foreach($_data as $key => $value) {
- $this->$key = $value;
- }
+ protected $_properties = array(
+ 'FolderHierarchy' => array(
+ 'parentId' => array('type' => 'string'),
+ 'serverId' => array('type' => 'string'),
+ 'displayName' => array('type' => 'string'),
+ 'type' => array('type' => 'number')
+ ),
+ 'Internal' => array(
+ 'id' => array('type' => 'string'),
+ 'deviceId' => array('type' => 'string'),
+ 'class' => array('type' => 'string'),
+ 'creationTime' => array('type' => 'datetime'),
+ 'lastfiltertype' => array('type' => 'number')
+ ),
+ );
+
+ protected function _parseFolderHierarchyNamespace(SimpleXMLElement $properties)
+ {
+ // fetch data from Contacts namespace
+ $children = $properties->children('uri:FolderHierarchy');
+
+ foreach ($children as $elementName => $xmlElement) {
+ $elementName = lcfirst($elementName);
+
+ if (!isset($this->_properties['FolderHierarchy'][$elementName])) {
+ continue;
+ }
+
+ switch ($elementName) {
+ default:
+ list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
+
+ switch ($elementProperties['type']) {
+ case 'datetime':
+ $this->$elementName = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
+
+ break;
+
+ case 'number':
+ $this->$elementName = (int) $xmlElement;
+
+ break;
+ default:
+ $this->$elementName = (string) $xmlElement;
+
+ break;
+ }
+ }
+ }
}
}
diff --git a/lib/ext/Syncroton/Model/IDevice.php b/lib/ext/Syncroton/Model/IDevice.php
index c084f8c..e2c347e 100644
--- a/lib/ext/Syncroton/Model/IDevice.php
+++ b/lib/ext/Syncroton/Model/IDevice.php
@@ -1,39 +1,39 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sync command
*
* @package Model
* @property string id
* @property string deviceid
* @property string devicetype
* @property string policykey
- * @property string policy_id
- * @property string owner_id
+ * @property string policyId
+ * @property string ownerId
* @property string acsversion
* @property string pingfolder
* @property string pinglifetime
* @property string remotewipe
* @property string useragent
*/
interface Syncroton_Model_IDevice
{
/**
* Returns major firmware version of this device
*
* @return int/string
*/
public function getMajorVersion();
}
diff --git a/lib/ext/Syncroton/Model/IFolder.php b/lib/ext/Syncroton/Model/IFolder.php
index ca1e8e3..4ec3700 100644
--- a/lib/ext/Syncroton/Model/IFolder.php
+++ b/lib/ext/Syncroton/Model/IFolder.php
@@ -1,30 +1,30 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sync command
*
* @package Model
* @property string id
- * @property string device_id
+ * @property string deviceId
* @property string class
- * @property string folderid
- * @property string parentid
- * @property string displayname
- * @property string creation_time
+ * @property string serverId
+ * @property string parentId
+ * @property string displayName
+ * @property string creationTime
* @property string lastfiltertype
*/
interface Syncroton_Model_IFolder
{
}
diff --git a/lib/ext/Syncroton/Model/IDevice.php b/lib/ext/Syncroton/Model/IPolicy.php
similarity index 69%
copy from lib/ext/Syncroton/Model/IDevice.php
copy to lib/ext/Syncroton/Model/IPolicy.php
index c084f8c..b6929d3 100644
--- a/lib/ext/Syncroton/Model/IDevice.php
+++ b/lib/ext/Syncroton/Model/IPolicy.php
@@ -1,39 +1,32 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sync command
*
* @package Model
* @property string id
* @property string deviceid
* @property string devicetype
- * @property string policykey
- * @property string policy_id
- * @property string owner_id
+ * @property string policyKey
+ * @property string policyId
+ * @property string ownerId
* @property string acsversion
* @property string pingfolder
* @property string pinglifetime
* @property string remotewipe
* @property string useragent
*/
-interface Syncroton_Model_IDevice
+interface Syncroton_Model_IPolicy
{
- /**
- * Returns major firmware version of this device
- *
- * @return int/string
- */
- public function getMajorVersion();
-
}
diff --git a/lib/ext/Syncroton/Model/Policy.php b/lib/ext/Syncroton/Model/Policy.php
new file mode 100644
index 0000000..9b1dcf3
--- /dev/null
+++ b/lib/ext/Syncroton/Model/Policy.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * Syncroton
+ *
+ * @package Model
+ * @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
+ * @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author Lars Kneschke <l.kneschke@metaways.de>
+ */
+
+/**
+ * class to handle ActiveSync Sync command
+ *
+ * @package Model
+ */
+
+class Syncroton_Model_Policy extends Syncroton_Model_AEntry implements Syncroton_Model_IPolicy
+{
+ protected $_xmlBaseElement = 'EASProvisionDoc';
+
+ protected $_properties = array(
+ 'Internal' => array(
+ 'id' => array('type' => 'string'),
+ 'description' => array('type' => 'string'),
+ 'name' => array('type' => 'string'),
+ 'policyKey' => array('type' => 'string'),
+ ),
+ 'Provision' => array(
+ 'allowBluetooth' => array('type' => 'number'),
+ 'allowSMIMEEncryptionAlgorithmNegotiation' => array('type' => 'number'),
+ 'allowBrowser' => array('type' => 'number'),
+ 'allowCamera' => array('type' => 'number'),
+ 'allowConsumerEmail' => array('type' => 'number'),
+ 'allowDesktopSync' => array('type' => 'number'),
+ 'allowHTMLEmail' => array('type' => 'number'),
+ 'allowInternetSharing' => array('type' => 'number'),
+ 'allowIrDA' => array('type' => 'number'),
+ 'allowPOPIMAPEmail' => array('type' => 'number'),
+ 'allowRemoteDesktop' => array('type' => 'number'),
+ 'allowSimpleDevicePassword' => array('type' => 'number'),
+ 'allowSMIMEEncryptionAlgorithmNegotiation' => array('type' => 'number'),
+ 'allowSMIMESoftCerts' => array('type' => 'number'),
+ 'allowStorageCard' => array('type' => 'number'),
+ 'allowTextMessaging' => array('type' => 'number'),
+ 'allowUnsignedApplications' => array('type' => 'number'),
+ 'allowUnsignedInstallationPackages' => array('type' => 'number'),
+ 'allowWifi' => array('type' => 'number'),
+ 'alphanumericDevicePasswordRequired' => array('type' => 'number'),
+ 'approvedApplicationList' => array('type' => 'container', 'childName' => 'Hash'),
+ 'attachmentsEnabled' => array('type' => 'number'),
+ 'devicePasswordEnabled' => array('type' => 'number'),
+ 'devicePasswordExpiration' => array('type' => 'number'),
+ 'devicePasswordHistory' => array('type' => 'number'),
+ 'maxAttachmentSize' => array('type' => 'number'),
+ 'maxCalendarAgeFilter' => array('type' => 'number'),
+ 'maxDevicePasswordFailedAttempts' => array('type' => 'number'),
+ 'maxEmailAgeFilter' => array('type' => 'number'),
+ 'maxEmailBodyTruncationSize' => array('type' => 'number'),
+ 'maxEmailHTMLBodyTruncationSize' => array('type' => 'number'),
+ 'maxInactivityTimeDeviceLock' => array('type' => 'number'),
+ 'minDevicePasswordComplexCharacters' => array('type' => 'number'),
+ 'minDevicePasswordLength' => array('type' => 'number'),
+ 'passwordRecoveryEnabled' => array('type' => 'number'),
+ 'requireDeviceEncryption' => array('type' => 'number'),
+ 'requireEncryptedSMIMEMessages' => array('type' => 'number'),
+ 'requireEncryptionSMIMEAlgorithm' => array('type' => 'number'),
+ 'requireManualSyncWhenRoaming' => array('type' => 'number'),
+ 'requireSignedSMIMEAlgorithm' => array('type' => 'number'),
+ 'requireSignedSMIMEMessages' => array('type' => 'number'),
+ 'requireStorageCardEncryption' => array('type' => 'number'),
+ 'unapprovedInROMApplicationList' => array('type' => 'container', 'childName' => 'ApplicationName')
+ )
+ );
+}
+
diff --git a/lib/ext/Syncroton/Model/StoreRequest.php b/lib/ext/Syncroton/Model/StoreRequest.php
index a108e35..e78f41c 100644
--- a/lib/ext/Syncroton/Model/StoreRequest.php
+++ b/lib/ext/Syncroton/Model/StoreRequest.php
@@ -1,238 +1,235 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @copyright Copyright (c) 2012 Kolab Systems AG (http://kolabsys.com)
* @author Lars Kneschke <l.kneschke@metaways.de>
* @author Aleksander Machniak <machniak@kolabsys.com>
*/
/**
* class to handle ActiveSync Search Store request
*
* @package Model
* @property string name
* @property array options
* @property array query
*/
class Syncroton_Model_StoreRequest
{
protected $_store = array();
protected $_xmlStore;
public function __construct($properties = null)
{
if ($properties instanceof SimpleXMLElement) {
$this->setFromSimpleXMLElement($properties);
} elseif (is_array($properties)) {
$this->setFromArray($properties);
}
}
public function setFromArray(array $properties)
{
$this->_store = array(
'options' => array(
'mimeSupport' => Syncroton_Command_Sync::MIMESUPPORT_DONT_SEND_MIME,
'bodyPreferences' => array()
),
);
foreach ($properties as $key => $value) {
try {
$this->$key = $value; //echo __LINE__ . PHP_EOL;
} catch (InvalidArgumentException $iae) {
//ignore invalid properties
//echo __LINE__ . PHP_EOL;
}
}
}
/**
*
* @param SimpleXMLElement $xmlStore
* @throws InvalidArgumentException
*/
public function setFromSimpleXMLElement(SimpleXMLElement $xmlStore)
{
if ($xmlStore->getName() !== 'Store') {
throw new InvalidArgumentException('Unexpected element name: ' . $xmlStore->getName());
}
$this->_xmlStore = $xmlStore;
$this->_store = array(
'name' => (string) $xmlStore->Name,
'options' => array(
'mimeSupport' => Syncroton_Command_Sync::MIMESUPPORT_DONT_SEND_MIME,
'bodyPreferences' => array(),
),
);
// Process Query
if ($this->_store['name'] == 'GAL') {
// @FIXME: In GAL search request Query is a string:
// <Store><Name>GAL</Name><Query>string</Query><Options><Range>0-11</Range></Options></Store>
if (isset($xmlStore->Query)) {
$this->_store['query'] = (string) $xmlStore->Query;
}
- }
- else if (isset($xmlStore->Query)) {
+ } elseif (isset($xmlStore->Query)) {
if (isset($xmlStore->Query->And)) {
if (isset($xmlStore->Query->And->FreeText)) {
- $this->_store['query']['and']['freeText'] = (string) $xmlStore->Query->And->FreeText;
+ $this->_store['query']['and']['freetext'] = (string) $xmlStore->Query->And->FreeText;
}
if (isset($xmlStore->Query->And->ConversationId)) {
$this->_store['query']['and']['conversationId'] = (string) $xmlStore->Query->And->ConversationId;
}
if (isset($xmlStore->Query->And->GreaterThan)) {
if (isset($xmlStore->Query->And->GreaterThan->Value)) {
$this->_store['query']['and']['greaterThan']['value'] = (string) $xmlStore->Query->And->GreaterThan->Value;
}
$email = $xmlStore->Query->And->GreaterThan->children('uri:Email');
if (isset($email->DateReceived)) {
$this->_store['query']['and']['greaterThan']['dateReceived'] = new DateTime((string) $email->DateReceived, new DateTimeZone('UTC'));
}
}
if (isset($xmlStore->Query->And->LessThan)) {
if (isset($xmlStore->Query->And->LessThan->Value)) {
$this->_store['query']['and']['lessThan']['value'] = (string) $xmlStore->Query->And->LessThan->Value;
}
$email = $xmlStore->Query->And->LessThan->children('uri:Email');
if (isset($email->DateReceived)) {
$this->_store['query']['and']['leasThan']['dateReceived'] = new DateTime((string) $email->DateReceived, new DateTimeZone('UTC'));
}
}
$airSync = $xmlStore->Query->And->children('uri:AirSync');
foreach ($airSync as $name => $value) {
if ($name == 'Class') {
$this->_store['query']['and']['classes'][] = (string) $value;
- }
- else if ($name == 'CollectionId') {
+ } elseif ($name == 'CollectionId') {
$this->_store['query']['and']['collections'][] = (string) $value;
}
}
}
if (isset($xmlStore->Query->EqualTo)) {
if (isset($xmlStore->Query->EqualTo->Value)) {
$this->_store['query']['equalTo']['value'] = (string) $xmlStore->Query->EqualTo->Value;
}
$doclib = $xmlStore->Query->EqualTo->children('uri:DocumentLibrary');
if (isset($doclib->LinkId)) {
$this->_store['query']['equalTo']['linkId'] = (string) $doclib->LinkId;
}
}
}
// Process options
if (isset($xmlStore->Options)) {
// optional parameters
if (isset($xmlStore->Options->DeepTraversal)) {
$this->_store['options']['deepTraversal'] = true;
}
if (isset($xmlStore->Options->RebuildResults)) {
$this->_store['options']['rebuildResults'] = true;
}
if (isset($xmlStore->Options->UserName)) {
$this->_store['options']['userName'] = (string) $xmlStore->Options->UserName;
}
if (isset($xmlStore->Options->Password)) {
$this->_store['options']['password'] = (string) $xmlStore->Options->Password;
}
if (isset($xmlStore->Options->Picture)) {
if (isset($xmlStore->Options->Picture->MaxSize)) {
$this->_store['options']['picture']['maxSize'] = (int) $xmlStore->Options->Picture->MaxSize;
}
if (isset($xmlStore->Options->Picture->MaxPictures)) {
$this->_store['options']['picture']['maxPictures'] = (int) $xmlStore->Options->Picture->MaxPictures;
}
}
if (!empty($xmlStore->Options->Range)) {
$this->_store['options']['range'] = (string) $xmlStore->Options->Range;
- }
- else {
+ } else {
switch ($this->_store['name']) {
case 'DocumentLibrary':
case 'Document Library': //?
'0-999';
break;
case 'Mailbox':
case 'GAL':
default:
'0-99';
break;
}
}
$this->_store['options']['range'] = explode('-', $this->_store['options']['range']);
if (isset($xmlStore->Options->MIMESupport)) {
$this->_store['options']['mimeSupport'] = (int) $xmlStore->Options->MIMESupport;
}
/*
if (isset($xmlStore->Options->MIMETruncation)) {
$this->_store['options']['mimeTruncation'] = (int)$xmlStore->Options->MIMETruncation;
}
*/
// try to fetch element from AirSyncBase:BodyPreference
$airSyncBase = $xmlStore->Options->children('uri:AirSyncBase');
if (isset($airSyncBase->BodyPreference)) {
foreach ($airSyncBase->BodyPreference as $bodyPreference) {
$type = (int) $bodyPreference->Type;
$this->_store['options']['bodyPreferences'][$type] = array(
'type' => $type
);
// optional
if (isset($bodyPreference->TruncationSize)) {
$this->_store['options']['bodyPreferences'][$type]['truncationSize'] = (int) $bodyPreference->TruncationSize;
}
}
}
if (isset($airSyncBase->BodyPartPreference)) {
// process BodyPartPreference elements
}
}
}
public function &__get($name)
{
if (array_key_exists($name, $this->_store)) {
return $this->_store[$name];
}
//echo $name . PHP_EOL;
return null;
}
public function __set($name, $value)
{
$this->_store[$name] = $value;
}
public function __isset($name)
{
return isset($this->_store[$name]);
}
public function __unset($name)
{
unset($this->_store[$name]);
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/StoreResponse.php b/lib/ext/Syncroton/Model/StoreResponse.php
index 51706a1..cd1770f 100644
--- a/lib/ext/Syncroton/Model/StoreResponse.php
+++ b/lib/ext/Syncroton/Model/StoreResponse.php
@@ -1,82 +1,82 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* Class to handle ActiveSync Search/Response/Store element
*
* @package Syncroton
* @subpackage Model
*/
class Syncroton_Model_StoreResponse extends Syncroton_Model_AEntry
{
/**
* status constants
*/
const STATUS_SUCCESS = 1;
const STATUS_INVALIDREQUEST = 2;
const STATUS_SERVERERROR = 3;
const STATUS_BADLINK = 4;
const STATUS_ACCESSDENIED = 5;
const STATUS_NOTFOUND = 6;
const STATUS_CONNECTIONFAILED = 7;
const STATUS_TOOCOMPLEX = 8;
const STATUS_TIMEDOUT = 10;
const STATUS_FOLDERSYNCREQUIRED = 11;
const STATUS_ENDOFRANGE = 12;
const STATUS_ACCESSBLOCKED = 13;
const STATUS_CREDENTIALSREQUIRED = 14;
- protected $_xmlBaseElement = 'ApplicationData';
+ protected $_xmlBaseElement = 'Store';
protected $_properties = array(
'Search' => array(
'Status' => array('type' => 'number'),
'Result' => array('type' => 'container'),
'Range' => array('type' => 'string'),
'Total' => array('type' => 'number'),
)
);
public function appendXML(DOMElement $_domParrent)
{
$this->_addXMLNamespaces($_domParrent);
foreach ($this->_elements as $elementName => $value) {
// skip empty values
if ($value === null || $value === '') {
continue;
}
list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
$nameSpace = 'uri:' . $nameSpace;
switch ($elementName) {
case 'Result':
foreach ($value as $result) {
$element = $_domParrent->ownerDocument->createElementNS($nameSpace, 'Result');
$result->appendXML($element);
$_domParrent->appendChild($element);
}
break;
case 'Range':
if (is_array($value) && count($value) == 2) {
$value = implode('-', $value);
}
default:
$element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
$element->appendChild($_domParrent->ownerDocument->createTextNode($value));
$_domParrent->appendChild($element);
}
}
}
}
diff --git a/lib/ext/Syncroton/Model/StoreResponseResult.php b/lib/ext/Syncroton/Model/StoreResponseResult.php
index 2e1dcca..b79f863 100644
--- a/lib/ext/Syncroton/Model/StoreResponseResult.php
+++ b/lib/ext/Syncroton/Model/StoreResponseResult.php
@@ -1,60 +1,31 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Search/Response/Store/Result elements
*
* @package Syncroton
* @subpackage Model
*/
class Syncroton_Model_StoreResponseResult extends Syncroton_Model_AEntry
{
- protected $_xmlBaseElement = 'ApplicationData';
+ protected $_xmlBaseElement = 'Result';
protected $_properties = array(
'AirSync' => array(
'Class' => array('type' => 'string'),
'CollectionId' => array('type' => 'string'),
),
'Search' => array(
'LongId' => array('type' => 'string'),
'Properties' => array('type' => 'container'),
)
);
-
- public function appendXML(DOMElement $_domParrent)
- {
- $this->_addXMLNamespaces($_domParrent);
-
- foreach ($this->_elements as $elementName => $value) {
- // skip empty values
- if ($value === null || $value === '') {
- continue;
- }
-
- list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
-
- $nameSpace = 'uri:' . $nameSpace;
-
- switch ($elementName) {
- case 'Properties':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
- $value->appendXML($element);
- $_domParrent->appendChild($element);
- break;
-
- default:
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
- $element->appendChild($_domParrent->ownerDocument->createTextNode($value));
- $_domParrent->appendChild($element);
- }
- }
- }
}
diff --git a/lib/ext/Syncroton/Model/SyncCollection.php b/lib/ext/Syncroton/Model/SyncCollection.php
index 140881d..736c582 100644
--- a/lib/ext/Syncroton/Model/SyncCollection.php
+++ b/lib/ext/Syncroton/Model/SyncCollection.php
@@ -1,272 +1,272 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sync collection
*
* @package Model
* @property string class
* @property string collectionId
* @property bool deletesAsMoves
* @property bool getChanges
* @property string syncKey
* @property int windowSize
*/
class Syncroton_Model_SyncCollection
{
protected $_collection = array();
protected $_xmlCollection;
public function __construct($properties = null)
{
if ($properties instanceof SimpleXMLElement) {
$this->setFromSimpleXMLElement($properties);
} elseif (is_array($properties)) {
$this->setFromArray($properties);
}
}
/**
* return XML element which holds all client Add commands
*
* @return SimpleXMLElement
*/
public function getClientAdds()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
throw new InvalidArgumentException('no collection xml element set');
}
return $this->_xmlCollection->Commands->Add;
}
/**
* return XML element which holds all client Change commands
*
* @return SimpleXMLElement
*/
public function getClientChanges()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
throw new InvalidArgumentException('no collection xml element set');
}
return $this->_xmlCollection->Commands->Change;
}
/**
* return XML element which holds all client Delete commands
*
* @return SimpleXMLElement
*/
public function getClientDeletes()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
throw new InvalidArgumentException('no collection xml element set');
}
return $this->_xmlCollection->Commands->Delete;
}
/**
* return XML element which holds all client Fetch commands
*
* @return SimpleXMLElement
*/
public function getClientFetches()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
throw new InvalidArgumentException('no collection xml element set');
}
return $this->_xmlCollection->Commands->Fetch;
}
/**
* check if client sent a Add command
*
* @throws InvalidArgumentException
* @return bool
*/
public function hasClientAdds()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
throw new InvalidArgumentException('no collection xml element set');
}
return isset($this->_xmlCollection->Commands->Add);
}
/**
* check if client sent a Change command
*
* @throws InvalidArgumentException
* @return bool
*/
public function hasClientChanges()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
throw new InvalidArgumentException('no collection xml element set');
}
return isset($this->_xmlCollection->Commands->Change);
}
/**
* check if client sent a Delete command
*
* @throws InvalidArgumentException
* @return bool
*/
public function hasClientDeletes()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
throw new InvalidArgumentException('no collection xml element set');
}
return isset($this->_xmlCollection->Commands->Delete);
}
/**
* check if client sent a Fetch command
*
* @throws InvalidArgumentException
* @return bool
*/
public function hasClientFetches()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
throw new InvalidArgumentException('no collection xml element set');
}
return isset($this->_xmlCollection->Commands->Fetch);
}
public function setFromArray(array $properties)
{
$this->_collection = array('options' => array(
'filterType' => Syncroton_Command_Sync::FILTER_NOTHING,
'mimeSupport' => Syncroton_Command_Sync::MIMESUPPORT_DONT_SEND_MIME,
'mimeTruncation' => Syncroton_Command_Sync::TRUNCATE_NOTHING,
'bodyPreferences' => array()
));
foreach($properties as $key => $value) {
try {
$this->$key = $value; //echo __LINE__ . PHP_EOL;
} catch (InvalidArgumentException $iae) {
//ignore invalid properties
//echo __LINE__ . PHP_EOL;
}
}
}
/**
*
* @param SimpleXMLElement $xmlCollection
* @throws InvalidArgumentException
*/
public function setFromSimpleXMLElement(SimpleXMLElement $xmlCollection)
{
if ($xmlCollection->getName() !== 'Collection') {
throw new InvalidArgumentException('Unexpected element name: ' . $xmlCollection->getName());
}
$this->_xmlCollection = $xmlCollection;
$this->_collection = array(
'syncKey' => (int)$xmlCollection->SyncKey,
'collectionId' => (string)$xmlCollection->CollectionId,
'deletesAsMoves' => isset($xmlCollection->DeletesAsMoves) && (string)$xmlCollection->DeletesAsMoves === '0' ? false : true,
'conversationMode' => isset($xmlCollection->ConversationMode) && (string)$xmlCollection->ConversationMode === '0' ? false : true,
- 'getChanges' => isset($xmlCollection->GetChanges) ? true : false,
+ 'getChanges' => isset($xmlCollection->GetChanges) && (string) $xmlCollection->GetChanges === '0' ? false : true,
'windowSize' => isset($xmlCollection->WindowSize) ? (int)$xmlCollection->WindowSize : 100,
'class' => isset($xmlCollection->Class) ? (string)$xmlCollection->Class : null,
'options' => array(
'filterType' => Syncroton_Command_Sync::FILTER_NOTHING,
'mimeSupport' => Syncroton_Command_Sync::MIMESUPPORT_DONT_SEND_MIME,
'mimeTruncation' => Syncroton_Command_Sync::TRUNCATE_NOTHING,
'bodyPreferences' => array()
),
'syncState' => null,
'folder' => null
);
if (isset($xmlCollection->Supported)) {
// @todo collected supported elements
}
// process options
if (isset($xmlCollection->Options)) {
// optional parameters
if (isset($xmlCollection->Options->FilterType)) {
$this->_collection['options']['filterType'] = (int)$xmlCollection->Options->FilterType;
}
if (isset($xmlCollection->Options->MIMESupport)) {
$this->_collection['options']['mimeSupport'] = (int)$xmlCollection->Options->MIMESupport;
}
if (isset($xmlCollection->Options->MIMETruncation)) {
$this->_collection['options']['mimeTruncation'] = (int)$xmlCollection->Options->MIMETruncation;
}
if (isset($xmlCollection->Options->Class)) {
$this->_collection['options']['class'] = (string)$xmlCollection->Options->Class;
}
// try to fetch element from AirSyncBase:BodyPreference
$airSyncBase = $xmlCollection->Options->children('uri:AirSyncBase');
if (isset($airSyncBase->BodyPreference)) {
foreach ($airSyncBase->BodyPreference as $bodyPreference) {
$type = (int) $bodyPreference->Type;
$this->_collection['options']['bodyPreferences'][$type] = array(
'type' => $type
);
// optional
if (isset($bodyPreference->TruncationSize)) {
$this->_collection['options']['bodyPreferences'][$type]['truncationSize'] = (int) $bodyPreference->TruncationSize;
}
}
}
if (isset($airSyncBase->BodyPartPreference)) {
// process BodyPartPreference elements
}
}
}
public function &__get($name)
{
if (array_key_exists($name, $this->_collection)) {
return $this->_collection[$name];
}
//echo $name . PHP_EOL;
return null;
}
public function __set($name, $value)
{
$this->_collection[$name] = $value;
}
public function __isset($name)
{
return isset($this->_collection[$name]);
}
public function __unset($name)
{
unset($this->_collection[$name]);
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/Task.php b/lib/ext/Syncroton/Model/Task.php
index 30ebc70..ec42cff 100644
--- a/lib/ext/Syncroton/Model/Task.php
+++ b/lib/ext/Syncroton/Model/Task.php
@@ -1,163 +1,93 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync event
*
* @package Model
* @property string class
* @property string collectionId
* @property bool deletesAsMoves
* @property bool getChanges
* @property string syncKey
* @property int windowSize
*/
class Syncroton_Model_Task extends Syncroton_Model_AEntry
{
protected $_xmlBaseElement = 'ApplicationData';
- // @todo handle body
protected $_properties = array(
'AirSyncBase' => array(
'Body' => array('type' => 'container')
),
'Tasks' => array(
- //'Body' => 0x05,
- //'BodySize' => 0x06,
- //'BodyTruncated' => 0x07,
- 'Categories' => array('type' => 'container'),
+ 'Categories' => array('type' => 'container', 'childName' => 'Category'),
'Complete' => array('type' => 'number'),
'DateCompleted' => array('type' => 'datetime'),
'DueDate' => array('type' => 'datetime'),
'Importance' => array('type' => 'number'),
'Recurrence' => array('type' => 'container'),
'ReminderSet' => array('type' => 'number'),
'ReminderTime' => array('type' => 'datetime'),
'Sensitivity' => array('type' => 'number'),
'StartDate' => array('type' => 'datetime'),
'Subject' => array('type' => 'string'),
'UtcDueDate' => array('type' => 'datetime'),
'UtcStartDate' => array('type' => 'datetime'),
)
);
- public function appendXML(DOMElement $_domParrent)
- {
- $this->_addXMLNamespaces($_domParrent);
-
- foreach($this->_elements as $elementName => $value) {
- // skip empty values
- if($value === null || $value === '' || (is_array($value) && empty($value))) {
- continue;
- }
-
- list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
-
- $nameSpace = 'uri:' . $nameSpace;
-
- // strip off any non printable control characters
- if (!ctype_print($value)) {
- #$value = $this->removeControlChars($value);
- }
-
- switch($elementName) {
- case 'Body':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- $value->appendXML($element);
-
- $_domParrent->appendChild($element);
-
- break;
-
- case 'Categories':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- foreach($value as $category) {
- $categoryElement = $_domParrent->ownerDocument->createElementNS($nameSpace, 'Category');
- $categoryElement->appendChild($_domParrent->ownerDocument->createTextNode($category));
-
- $element->appendChild($categoryElement);
- }
-
- $_domParrent->appendChild($element);
-
- break;
-
- case 'Recurrence':
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- $value->appendXML($element);
-
- $_domParrent->appendChild($element);
-
- break;
-
- default:
- $element = $_domParrent->ownerDocument->createElementNS($nameSpace, $elementName);
-
- if ($value instanceof DateTime) {
- $value = $value->format("Y-m-d\TH:i:s.000\Z");
- }
- $element->appendChild($_domParrent->ownerDocument->createTextNode($value));
-
- $_domParrent->appendChild($element);
- }
- }
-
- }
-
protected function _parseTasksNamespace(SimpleXMLElement $properties)
{
// fetch data from Contacts namespace
$children = $properties->children('uri:Tasks');
foreach ($children as $elementName => $xmlElement) {
switch ($elementName) {
case 'Categories':
$categories = array();
foreach ($xmlElement->$elementName as $category) {
$categories[] = (string) $category;
}
$this->$elementName = $categories;
break;
case 'Recurrence':
$this->$elementName = new Syncroton_Model_TaskRecurrence($xmlElement);
break;
default:
list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
switch ($elementProperties['type']) {
case 'datetime':
$this->$elementName = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
break;
case 'number':
$this->$elementName = (int) $xmlElement;
break;
default:
$this->$elementName = (string) $xmlElement;
break;
}
}
}
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Registry.php b/lib/ext/Syncroton/Registry.php
index 32838a6..c971456 100644
--- a/lib/ext/Syncroton/Registry.php
+++ b/lib/ext/Syncroton/Registry.php
@@ -1,368 +1,386 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@zend.com so we can send you a copy immediately.
*
* @category Syncroton
* @package Syncroton_Registry
* @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @version $Id: Registry.php 10020 2009-08-18 14:34:09Z j.fischer@metaways.de $
*/
/**
* Generic storage class helps to manage global data.
*
* @category Syncroton
* @package Syncroton_Registry
* @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Syncroton_Registry extends ArrayObject
{
const CALENDAR_DATA_CLASS = 'calendar_data_class';
const CONTACTS_DATA_CLASS = 'contacts_data_class';
const EMAIL_DATA_CLASS = 'email_data_class';
const TASKS_DATA_CLASS = 'tasks_data_class';
const GAL_DATA_CLASS = 'gal_data_class';
const DATABASE = 'database';
const TRANSACTIONMANAGER = 'transactionmanager';
const CONTENTSTATEBACKEND = 'contentstatebackend';
const DEVICEBACKEND = 'devicebackend';
const FOLDERBACKEND = 'folderbackend';
+ const POLICYBACKEND = 'policybackend';
const SYNCSTATEBACKEND = 'syncstatebackend';
/**
* Class name of the singleton registry object.
* @var string
*/
private static $_registryClassName = 'Syncroton_Registry';
/**
* Registry object provides storage for shared objects.
* @var Syncroton_Registry
*/
private static $_registry = null;
/**
* Retrieves the default registry instance.
*
* @return Syncroton_Registry
*/
public static function getInstance()
{
if (self::$_registry === null) {
self::init();
}
return self::$_registry;
}
/**
* @return Zend_Db_Adapter_Abstract
*/
public static function getDatabase()
{
return self::get(self::DATABASE);
}
/**
* return transaction manager class
*
* @return Syncroton_TransactionManagerInterface
*/
public static function getTransactionManager()
{
if (!self::isRegistered(self::TRANSACTIONMANAGER)) {
self::set(self::TRANSACTIONMANAGER, Syncroton_TransactionManager::getInstance());
}
return self::get(self::TRANSACTIONMANAGER);
}
/**
* Set the default registry instance to a specified instance.
*
* @param Syncroton_Registry $registry An object instance of type Syncroton_Registry,
* or a subclass.
* @return void
* @throws Zend_Exception if registry is already initialized.
*/
public static function setInstance(Syncroton_Registry $registry)
{
if (self::$_registry !== null) {
require_once 'Zend/Exception.php';
throw new Zend_Exception('Registry is already initialized');
}
self::setClassName(get_class($registry));
self::$_registry = $registry;
}
/**
* Initialize the default registry instance.
*
* @return void
*/
protected static function init()
{
self::setInstance(new self::$_registryClassName());
}
/**
* Set the class name to use for the default registry instance.
* Does not affect the currently initialized instance, it only applies
* for the next time you instantiate.
*
* @param string $registryClassName
* @return void
* @throws Zend_Exception if the registry is initialized or if the
* class name is not valid.
*/
public static function setClassName($registryClassName = 'Syncroton_Registry')
{
if (self::$_registry !== null) {
require_once 'Zend/Exception.php';
throw new Zend_Exception('Registry is already initialized');
}
if (!is_string($registryClassName)) {
require_once 'Zend/Exception.php';
throw new Zend_Exception("Argument is not a class name");
}
/**
* @see Zend_Loader
*/
if (!class_exists($registryClassName)) {
require_once 'Zend/Loader.php';
Zend_Loader::loadClass($registryClassName);
}
self::$_registryClassName = $registryClassName;
}
/**
* Unset the default registry instance.
* Primarily used in tearDown() in unit tests.
* @returns void
*/
public static function _unsetInstance()
{
self::$_registry = null;
}
/**
* getter method, basically same as offsetGet().
*
* This method can be called from an object of type Syncroton_Registry, or it
* can be called statically. In the latter case, it uses the default
* static instance stored in the class.
*
* @param string $index - get the value associated with $index
* @return mixed
* @throws Zend_Exception if no entry is registerd for $index.
*/
public static function get($index)
{
$instance = self::getInstance();
if (!$instance->offsetExists($index)) {
require_once 'Zend/Exception.php';
throw new Zend_Exception("No entry is registered for key '$index'");
}
return $instance->offsetGet($index);
}
/**
* returns content state backend
*
* creates Syncroton_Backend_Content on the fly if not before via
* Syncroton_Registry::set(self::CONTENTSTATEBACKEND, $backend);
*
* @return Syncroton_Backend_IContent
*/
public static function getContentStateBackend()
{
if (!self::isRegistered(self::CONTENTSTATEBACKEND)) {
self::set(self::CONTENTSTATEBACKEND, new Syncroton_Backend_Content(self::getDatabase()));
}
return self::get(self::CONTENTSTATEBACKEND);
}
/**
* returns device backend
*
* creates Syncroton_Backend_Device on the fly if not before via
* Syncroton_Registry::set(self::DEVICEBACKEND, $backend);
*
* @return Syncroton_Backend_IDevice
*/
public static function getDeviceBackend()
{
if (!self::isRegistered(self::DEVICEBACKEND)) {
self::set(self::DEVICEBACKEND, new Syncroton_Backend_Device(self::getDatabase()));
}
return self::get(self::DEVICEBACKEND);
}
/**
* returns folder backend
*
* creates Syncroton_Backend_Folder on the fly if not before via
* Syncroton_Registry::set(self::FOLDERBACKEND, $backend);
*
* @return Syncroton_Backend_IFolder
*/
public static function getFolderBackend()
{
if (!self::isRegistered(self::FOLDERBACKEND)) {
self::set(self::FOLDERBACKEND, new Syncroton_Backend_Folder(self::getDatabase()));
}
return self::get(self::FOLDERBACKEND);
}
+ /**
+ * returns policy backend
+ *
+ * creates Syncroton_Backend_Policy on the fly if not set before via
+ * Syncroton_Registry::set(self::POLICYBACKEND, $backend);
+ *
+ * @return Syncroton_Backend_ISyncState
+ */
+ public static function getPolicyBackend()
+ {
+ if (!self::isRegistered(self::POLICYBACKEND)) {
+ self::set(self::POLICYBACKEND, new Syncroton_Backend_Policy(self::getDatabase()));
+ }
+
+ return self::get(self::POLICYBACKEND);
+ }
+
/**
* returns syncstate backend
*
* creates Syncroton_Backend_SyncState on the fly if not before via
* Syncroton_Registry::set(self::SYNCSTATEBACKEND, $backend);
*
* @return Syncroton_Backend_ISyncState
*/
public static function getSyncStateBackend()
{
if (!self::isRegistered(self::SYNCSTATEBACKEND)) {
self::set(self::SYNCSTATEBACKEND, new Syncroton_Backend_SyncState(self::getDatabase()));
}
return self::get(self::SYNCSTATEBACKEND);
}
/**
* setter method, basically same as offsetSet().
*
* This method can be called from an object of type Syncroton_Registry, or it
* can be called statically. In the latter case, it uses the default
* static instance stored in the class.
*
* @param string $index The location in the ArrayObject in which to store
* the value.
* @param mixed $value The object to store in the ArrayObject.
* @return void
*/
public static function set($index, $value)
{
$instance = self::getInstance();
$instance->offsetSet($index, $value);
}
public static function setDatabase(Zend_Db_Adapter_Abstract $db)
{
self::set(self::DATABASE, $db);
}
public static function setCalendarDataClass($className)
{
if (!class_exists($className)) {
throw new InvalidArgumentException('invalid $_className provided');
}
self::set(self::CALENDAR_DATA_CLASS, $className);
}
public static function setContactsDataClass($className)
{
if (!class_exists($className)) {
throw new InvalidArgumentException('invalid $_className provided');
}
self::set(self::CONTACTS_DATA_CLASS, $className);
}
public static function setEmailDataClass($className)
{
if (!class_exists($className)) {
throw new InvalidArgumentException('invalid $_className provided');
}
self::set(self::EMAIL_DATA_CLASS, $className);
}
public static function setTasksDataClass($className)
{
if (!class_exists($className)) {
throw new InvalidArgumentException('invalid $_className provided');
}
self::set(self::TASKS_DATA_CLASS, $className);
}
public static function setGALDataClass($className)
{
if (!class_exists($className)) {
throw new InvalidArgumentException('invalid $_className provided');
}
self::set(self::GAL_DATA_CLASS, $className);
}
public static function setTransactionManager($manager)
{
self::set(self::TRANSACTIONMANAGER, $manager);
}
/**
* Returns TRUE if the $index is a named value in the registry,
* or FALSE if $index was not found in the registry.
*
* @param string $index
* @return boolean
*/
public static function isRegistered($index)
{
if (self::$_registry === null) {
return false;
}
return self::$_registry->offsetExists($index);
}
/**
* Constructs a parent ArrayObject with default
* ARRAY_AS_PROPS to allow acces as an object
*
* @param array $array data array
* @param integer $flags ArrayObject flags
*/
public function __construct($array = array(), $flags = parent::ARRAY_AS_PROPS)
{
parent::__construct($array, $flags);
}
/**
* @param string $index
* @returns mixed
*
* Workaround for http://bugs.php.net/bug.php?id=40442 (ZF-960).
*/
public function offsetExists($index)
{
return array_key_exists($index, $this);
}
}
diff --git a/lib/ext/Syncroton/Server.php b/lib/ext/Syncroton/Server.php
index 94fa4a9..862614c 100644
--- a/lib/ext/Syncroton/Server.php
+++ b/lib/ext/Syncroton/Server.php
@@ -1,332 +1,339 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle incoming http ActiveSync requests
*
* @package Syncroton
*/
class Syncroton_Server
{
protected $_body;
/**
* informations about the currently device
*
* @var Syncroton_Backend_IDevice
*/
protected $_deviceBackend;
/**
* @var Zend_Log
*/
protected $_logger;
/**
* @var Zend_Controller_Request_Http
*/
protected $_request;
protected $_userId;
public function __construct($userId, Zend_Controller_Request_Http $request = null, $body = null)
{
if (Syncroton_Registry::isRegistered('loggerBackend')) {
$this->_logger = Syncroton_Registry::get('loggerBackend');
}
$this->_userId = $userId;
$this->_request = $request instanceof Zend_Controller_Request_Http ? $request : new Zend_Controller_Request_Http();
$this->_body = $body !== null ? $body : fopen('php://input', 'r');
$this->_deviceBackend = Syncroton_Registry::getDeviceBackend();
}
public function handle()
{
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . ' REQUEST METHOD: ' . $this->_request->getMethod());
switch($this->_request->getMethod()) {
case 'OPTIONS':
$this->_handleOptions();
break;
case 'POST':
$this->_handlePost();
break;
case 'GET':
echo "It works!<br>Your userid is: {$this->_userId} and your IP address is: {$_SERVER['REMOTE_ADDR']}.";
break;
}
}
/**
* handle options request
*
*/
protected function _handleOptions()
{
$command = new Syncroton_Command_Options();
$command->getResponse();
}
protected function _handlePost()
{
$requestParameters = $this->_getRequestParameters($this->_request);
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . ' REQUEST ' . print_r($requestParameters, true));
$className = 'Syncroton_Command_' . $requestParameters['command'];
if(!class_exists($className)) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " command not supported: " . $requestParameters['command']);
header("HTTP/1.1 501 not implemented");
return;
}
// get user device
$device = $this->_getUserDevice($this->_userId, $requestParameters);
if ($requestParameters['contentType'] == 'application/vnd.ms-sync.wbxml') {
// decode wbxml request
try {
$decoder = new Syncroton_Wbxml_Decoder($this->_body);
$requestBody = $decoder->decode();
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " xml request: " . $requestBody->saveXML());
} catch(Syncroton_Wbxml_Exception_UnexpectedEndOfFile $e) {
$requestBody = NULL;
}
} else {
$requestBody = $this->_body;
}
if (PHP_SAPI !== 'cli') {
header("MS-Server-ActiveSync: 14.00.0536.000");
}
try {
$command = new $className($requestBody, $device, $requestParameters);
$command->handle();
$response = $command->getResponse();
} catch (Syncroton_Exception_PolicyKeyMissing $sepkm) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " X-MS-POLICYKEY missing (" . $_command. ')');
header("HTTP/1.1 400 header X-MS-POLICYKEY not found");
} catch (Syncroton_Exception_ProvisioningNeeded $sepn) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " provisioning needed");
- header("HTTP/1.1 449 Retry after sending a PROVISION command");
-
- return;
+ if (version_compare($device->acsversion, '14.0', '>=')) {
+ $response = $sepn->domDocument;
+ } else {
+ // pre 14.0 method
+ header("HTTP/1.1 449 Retry after sending a PROVISION command");
+
+ return;
+ }
+
+
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " unexpected exception occured: " . get_class($e));
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " exception message: " . $e->getMessage());
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " " . $e->getTraceAsString());
header("HTTP/1.1 500 Internal server error");
return;
}
if ($response instanceof DOMDocument) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " xml response: " . $response->saveXML());
$outputStream = fopen("php://temp", 'r+');
$encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3);
$encoder->encode($response);
// avoid sending headers while running on cli (phpunit)
if (PHP_SAPI !== 'cli') {
header("Content-Type: application/vnd.ms-sync.wbxml");
}
rewind($outputStream);
fpassthru($outputStream);
}
}
/**
* return request params
*
* @return array
*/
protected function _getRequestParameters(Zend_Controller_Request_Http $request)
{
if(count($_GET) == 1) {
$arrayKeys = array_keys($_GET);
$base64Decoded = base64_decode($arrayKeys[0]);
$stream = fopen("php://temp", 'r+');
fwrite($stream, base64_decode($arrayKeys[0]));
rewind($stream);
#fpassthru($stream);rewind($stream);
$protocolVersion = ord(fread($stream, 1));
switch (ord(fread($stream, 1))) {
case 0:
$command = 'Sync';
break;
case 1:
$command = 'SendMail';
break;
case 2:
$command = 'SmartForward';
break;
case 3:
$command = 'SmartReply';
break;
case 4:
$command = 'GetAttachment';
break;
case 9:
$command = 'FolderSync';
break;
case 10:
$command = 'FolderCreate';
break;
case 11:
$command = 'FolderDelete';
break;
case 12:
$command = 'FolderUpdate';
break;
case 13:
$command = 'MoveItems';
break;
case 14:
$command = 'GetItemEstimate';
break;
case 15:
$command = 'MeetingResponse';
break;
case 16:
$command = 'Search';
break;
case 17:
$command = 'Settings';
break;
case 18:
$command = 'Ping';
break;
case 19:
$command = 'ItemOperations';
break;
case 20:
$command = 'Provision';
break;
case 21:
$command = 'ResolveRecipients';
break;
case 22:
$command = 'ValidateCert';
break;
}
$locale = fread($stream, 2);
$deviceIdLength = ord(fread($stream, 1));
if ($deviceIdLength > 0) {
$deviceId = fread($stream, $deviceIdLength);
}
$policyKeyLength = ord(fread($stream, 1));
if ($policyKeyLength > 0) {
$policyKey = fread($stream, 4);
}
$deviceTypeLength = ord(fread($stream, 1));
$deviceType = fread($stream, $deviceTypeLength);
// @todo parse command parameters
$result = array(
'protocolVersion' => $protocolVersion,
'command' => $command,
'deviceId' => $deviceId,
'deviceType' => $deviceType,
'saveInSent' => null,
'collectionId' => null,
'itemId' => null
);
} else {
$result = array(
'protocolVersion' => $request->getServer('HTTP_MS_ASPROTOCOLVERSION'),
'command' => $request->getQuery('Cmd'),
'deviceId' => $request->getQuery('DeviceId'),
'deviceType' => $request->getQuery('DeviceType'),
'saveInSent' => $request->getQuery('SaveInSent'),
'collectionId' => $request->getQuery('CollectionId'),
'itemId' => $request->getQuery('ItemId')
);
}
$result['userAgent'] = $request->getServer('HTTP_USER_AGENT', $result['deviceType']);
$result['policyKey'] = $request->getServer('HTTP_X_MS_POLICYKEY');
$result['contentType'] = $request->getServer('CONTENT_TYPE');
return $result;
}
/**
* get existing device of owner or create new device for owner
*
* @param unknown_type $ownerId
* @param unknown_type $deviceId
* @param unknown_type $deviceType
* @param unknown_type $userAgent
* @param unknown_type $protocolVersion
* @return Syncroton_Model_Device
*/
protected function _getUserDevice($ownerId, $requestParameters)
{
try {
$device = $this->_deviceBackend->getUserDevice($ownerId, $requestParameters['deviceId']);
$device->useragent = $requestParameters['userAgent'];
$device->acsversion = $requestParameters['protocolVersion'];
$device = $this->_deviceBackend->update($device);
} catch (Syncroton_Exception_NotFound $senf) {
$device = $this->_deviceBackend->create(new Syncroton_Model_Device(array(
'owner_id' => $ownerId,
'deviceid' => $requestParameters['deviceId'],
'devicetype' => $requestParameters['deviceType'],
'useragent' => $requestParameters['userAgent'],
'acsversion' => $requestParameters['protocolVersion']
)));
}
return $device;
}
}
\ No newline at end of file
diff --git a/lib/init.php b/lib/init.php
index f137342..3e63921 100644
--- a/lib/init.php
+++ b/lib/init.php
@@ -1,122 +1,120 @@
<?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> |
+--------------------------------------------------------------------------+
*/
// Roundcube Framework constants
define('RCMAIL_START', microtime(true));
define('RCMAIL_VERSION', '0.9-svn');
define('RCMAIL_CHARSET', 'UTF-8');
define('INSTALL_PATH', realpath(dirname(__FILE__) . '/../') . '/');
define('RCMAIL_CONFIG_DIR', INSTALL_PATH . 'config');
// PHP configuration
$config = array(
'error_reporting' => E_ALL &~ (E_NOTICE | E_STRICT),
'mbstring.func_overload' => 0,
// 'suhosin.session.encrypt' => 0,
// 'session.auto_start' => 0,
// 'file_uploads' => 1,
'magic_quotes_runtime' => 0,
'magic_quotes_sybase' => 0,
);
foreach ($config as $optname => $optval) {
if ($optval != ini_get($optname) && @ini_set($optname, $optval) === false) {
die("ERROR: Wrong '$optname' option value!");
}
}
// Define include path
$include_path = INSTALL_PATH . 'lib' . PATH_SEPARATOR;
$include_path .= INSTALL_PATH . 'lib/ext' . PATH_SEPARATOR;
$include_path .= INSTALL_PATH . 'lib/kolab' . PATH_SEPARATOR;
$include_path .= INSTALL_PATH . 'lib/ext/Roundcube' . PATH_SEPARATOR;
$include_path .= ini_get('include_path');
set_include_path($include_path);
// @TODO: what is a reasonable value for ActiveSync?
@set_time_limit(600);
// set internal encoding for mbstring extension
if (extension_loaded('mbstring')) {
mb_internal_encoding(RCMAIL_CHARSET);
@mb_regex_encoding(RCMAIL_CHARSET);
}
// include global functions from Roundcube Framework
require_once 'rcube_shared.inc';
// Register main autoloader
spl_autoload_register('kolab_sync_autoload');
// set PEAR error handling (will also load the PEAR main class)
PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'rcube_pear_error');
// Autoloader for Syncroton
//require_once 'Zend/Loader/Autoloader.php';
//$autoloader = Zend_Loader_Autoloader::getInstance();
//$autoloader->setFallbackAutoloader(true);
/**
* Use PHP5 autoload for dynamic class loading
*/
function kolab_sync_autoload($classname)
{
// Roundcube Framework
$filename = preg_replace(
array(
- '/MDB2_(.+)/',
'/Mail_(.+)/',
'/Net_(.+)/',
'/Auth_(.+)/',
'/^html_.+/',
'/^utf8$/',
),
array(
- 'MDB2/\\1',
'Mail/\\1',
'Net/\\1',
'Auth/\\1',
'html',
'utf8.class',
),
$classname
);
if ($fp = @fopen("$filename.php", 'r', true)) {
fclose($fp);
include_once "$filename.php";
return true;
}
// Syncroton, replacement for Zend autoloader
$filename = str_replace('_', DIRECTORY_SEPARATOR, $classname);
if ($fp = @fopen("$filename.php", 'r', true)) {
fclose($fp);
include_once "$filename.php";
return true;
}
return false;
}
diff --git a/lib/kolab_sync.php b/lib/kolab_sync.php
index 647515d..741fb04 100644
--- a/lib/kolab_sync.php
+++ b/lib/kolab_sync.php
@@ -1,331 +1,332 @@
<?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> |
+--------------------------------------------------------------------------+
*/
/**
* Main application class (based on Roundcube Framework)
*/
class kolab_sync extends rcube
{
public $app_name = 'ActiveSync for Kolab'; // no double quotes inside
private $data = array();
const CHARSET = 'UTF-8';
/**
* This implements the 'singleton' design pattern
*
* @return kolab_sync The one and only instance
*/
static function get_instance()
{
if (!self::$instance || !is_a(self::$instance, 'kolab_sync')) {
self::$instance = new kolab_sync();
self::$instance->startup(); // init AFTER object was linked with self::$instance
}
return self::$instance;
}
public function startup()
{
// Initialize Syncroton Logger
$this->logger = new Zend_Log();
if ($this->config->get('activesync_debug')) {
$priority = Zend_Log::DEBUG;
$log_file = 'logs/console';
}
else {
$priority = Zend_Log::ERR; // Zend_Log::WARN
$log_file = 'logs/errors';
}
$writer = new Zend_Log_Writer_Stream($log_file);
$filter = new Zend_Log_Filter_Priority($priority);
$this->logger->addWriter($writer);
$this->logger->addFilter($filter);
}
/**
* Application execution (authentication and ActiveSync)
*/
public function run()
{
// when used with (f)cgi no PHP_AUTH* variables are available without defining a special rewrite rule
if (!isset($_SERVER['PHP_AUTH_USER'])) {
// "Basic didhfiefdhfu4fjfjdsa34drsdfterrde..."
if (isset($_SERVER["REMOTE_USER"])) {
$basicAuthData = base64_decode(substr($_SERVER["REMOTE_USER"], 6));
} elseif (isset($_SERVER["REDIRECT_REMOTE_USER"])) {
$basicAuthData = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"], 6));
} elseif (isset($_SERVER["Authorization"])) {
$basicAuthData = base64_decode(substr($_SERVER["Authorization"], 6));
} elseif (isset($_SERVER["HTTP_AUTHORIZATION"])) {
$basicAuthData = base64_decode(substr($_SERVER["HTTP_AUTHORIZATION"], 6));
}
if (isset($basicAuthData) && !empty($basicAuthData)) {
list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(":", $basicAuthData);
}
}
if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) {
// Convert domain.tld\username into username@domain (?)
$username = explode("\\", $_SERVER['PHP_AUTH_USER']);
if (count($username) == 2) {
$_SERVER['PHP_AUTH_USER'] = $username[1];
if (!strpos($_SERVER['PHP_AUTH_USER'], '@') && !empty($username[0])) {
$_SERVER['PHP_AUTH_USER'] .= '@' . $username[0];
}
}
$userid = $this->authenticate($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
}
if (empty($userid)) {
header('WWW-Authenticate: Basic realm="' . $this->app_name .'"');
header('HTTP/1.1 401 Unauthorized');
exit;
}
// Register Syncroton backends
Syncroton_Registry::set('loggerBackend', $this->logger);
Syncroton_Registry::set(Syncroton_Registry::DATABASE, new kolab_sync_db);
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);
Syncroton_Registry::set(Syncroton_Registry::SYNCSTATEBACKEND, new kolab_sync_backend_state);
Syncroton_Registry::set(Syncroton_Registry::CONTENTSTATEBACKEND, new kolab_sync_backend_content);
+ Syncroton_Registry::set(Syncroton_Registry::POLICYBACKEND, new kolab_sync_backend_policy);
Syncroton_Registry::setContactsDataClass('kolab_sync_data_contacts');
Syncroton_Registry::setCalendarDataClass('kolab_sync_data_calendar');
Syncroton_Registry::setEmailDataClass('kolab_sync_data_email');
Syncroton_Registry::setTasksDataClass('kolab_sync_data_tasks');
Syncroton_Registry::setGALDataClass('kolab_sync_data_gal');
// Run Syncroton
$syncroton = new Syncroton_Server($userid);
$syncroton->handle();
}
/**
* Authenticates a user in LDAP and Roundcube
*/
public function authenticate($username, $password)
{
// Get IMAP host
$host = $this->config->get('default_host');
$host = rcube_utils::parse_host($host);
// Get user
$user = $this->get_ldap_user($username, $host);
// Get Roundcube user ID
$userid = $this->get_rcube_user($user, $password, $host);
return $userid;
}
/**
* Returns user login attribute from LDAP server
*/
private function get_ldap_user($username, $host)
{
$login_attr = $this->config->get('kolab_auth_login');
$addressbook = $this->config->get('kolab_auth_addressbook');
$filter = $this->config->get('kolab_auth_filter');
$filter = $this->parse_vars($filter, $username, $host);
if (!is_array($addressbook)) {
$ldap_config = (array)$this->config->get('ldap_public');
$addressbook = $ldap_config[$addressbook];
}
if (empty($addressbook)) {
return null;
}
if (empty($login_attr)) {
return null;
}
$addressbook['filter'] = $filter;
$addressbook['sizelimit'] = 2;
$this->ldap = new rcube_ldap(
$addressbook,
$this->config->get('ldap_debug'),
$this->config->mail_domain($host)
);
if (!$this->ldap->ready) {
return null;
}
// get record
$results = $this->ldap->list_records();
if (count($results->records) != 1) {
return null;
}
if ($record = $results->records[0]) {
return is_array($record[$login_attr]) ? $record[$login_attr][0] : $record[$login_attr];
}
}
/**
* Prepares filter query for LDAP search
*/
private function parse_vars($str, $user, $host)
{
$domain = $this->config->get('username_domain');
if (!empty($domain) && strpos($user, '@') === false) {
if (is_array($domain) && isset($domain[$host]))
$user .= '@'.rcube_utils::parse_host($domain[$host], $host);
else if (is_string($domain))
$user .= '@'.rcube_utils::parse_host($domain, $host);
}
// replace variables in filter
list($u, $d) = explode('@', $user);
$dc = 'dc='.strtr($d, array('.' => ',dc=')); // hierarchal domain string
$replaces = array('%dc' => $dc, '%d' => $d, '%fu' => $user, '%u' => $u);
return strtr($str, $replaces);
}
/**
* Returns Roundcube user ID for specified username and host.
* Also sets IMAP connection credentials.
*/
private function get_rcube_user($username, $password, $host)
{
if (empty($username)) {
return null;
}
$login_lc = $this->config->get('login_lc');
// parse $host
$a_host = parse_url($host);
if ($a_host['host']) {
$host = $a_host['host'];
$ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? $a_host['scheme'] : null;
if (!empty($a_host['port'])) {
$port = $a_host['port'];
}
else if ($ssl && $ssl != 'tls' && (!$config['default_port'] || $config['default_port'] == 143)) {
$port = 993;
}
}
if (!$port) {
$port = $this->config->get('default_port');
}
// Convert username to lowercase. If storage backend
// is case-insensitive we need to store always the same username
if ($login_lc) {
$username = mb_strtolower($username);
}
// Here we need IDNA ASCII
// Only rcube_contacts class is using domain names in Unicode
$host = rcube_utils::idn_to_ascii($host);
if (strpos($username, '@')) {
// lowercase domain name
list($local, $domain) = explode('@', $username);
$username = $local . '@' . mb_strtolower($domain);
$username = rcube_utils::idn_to_ascii($username);
}
// user already registered?
$user = rcube_user::query($username, $host);
if (!is_object($user)) {
// @TODO: log error
return null;
}
// Configure environment
$this->user = $user;
// rcube::get_storage() uses session, kolab-sync doesn't
// @TODO: modify framework to support private class variables
// or other method to provide storage credentials
global $_SESSION;
$_SESSION['storage_host'] = $host;
$_SESSION['username'] = $username;
$_SESSION['storage_port'] = $port;
$_SESSION['storage_ssl'] = $ssl;
$_SESSION['password'] = $this->encrypt($password);
// $this->set_storage_prop();
// force reloading complete list of subscribed mailboxes
// $storage->clear_cache('mailboxes', true);
// overwrite config with user preferences
$this->config->set_user_prefs((array)$this->user->get_prefs());
return $user->ID;
}
/**
* Function to be executed in script shutdown
*/
public function shutdown()
{
parent::shutdown();
if ($this->ldap) {
$this->ldap->close();
}
// write performance stats to logs/console
if ($this->config->get('devel_mode')) {
if (function_exists('memory_get_usage'))
$mem = sprintf('%.1f', memory_get_usage() / 1048576);
if (function_exists('memory_get_peak_usage'))
$mem .= '/' . sprintf('%.1f', memory_get_peak_usage() / 1048576);
$log = $_SERVER['REQUEST_URI'] . ($mem ? " [$mem]" : '');
if (defined('RCMAIL_START'))
self::print_timer(RCMAIL_START, $log);
else
self::console($log);
}
}
}
diff --git a/lib/kolab_sync_backend.php b/lib/kolab_sync_backend.php
index d9413fa..8a64878 100644
--- a/lib/kolab_sync_backend.php
+++ b/lib/kolab_sync_backend.php
@@ -1,951 +1,952 @@
<?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> |
+--------------------------------------------------------------------------+
*/
class kolab_sync_backend
{
/**
* Singleton instace of kolab_sync_backend
*
* @var kolab_sync_backend
*/
static protected $instance;
protected $storage;
protected $folder_meta;
protected $folder_uids;
protected $root_meta;
static protected $types = array(
1 => '',
2 => 'mail.inbox',
3 => 'mail.drafts',
4 => 'mail.wastebasket',
5 => 'mail.sentitems',
6 => 'mail.outbox',
7 => 'task.default',
8 => 'event.default',
9 => 'contact.default',
10 => 'note.default',
11 => 'journal.default',
12 => 'mail',
13 => 'event',
14 => 'contact',
15 => 'task',
16 => 'journal',
17 => 'note',
);
static protected $classes = array(
Syncroton_Data_Factory::CLASS_CALENDAR => 'event',
Syncroton_Data_Factory::CLASS_CONTACTS => 'contact',
Syncroton_Data_Factory::CLASS_EMAIL => 'mail',
Syncroton_Data_Factory::CLASS_TASKS => 'task',
);
const ROOT_MAILBOX = 'INBOX';
// const ROOT_MAILBOX = '';
const ASYNC_KEY = '/private/vendor/kolab/activesync';
const UID_KEY = '/shared/vendor/cmu/cyrus-imapd/uniqueid';
/**
* This implements the 'singleton' design pattern
*
* @return kolab_sync_backend The one and only instance
*/
static function get_instance()
{
if (!self::$instance) {
self::$instance = new kolab_sync_backend;
self::$instance->startup(); // init AFTER object was linked with self::$instance
}
return self::$instance;
}
/**
* Class initialization
*/
public function startup()
{
$this->storage = rcube::get_instance()->get_storage();
+
// @TODO: reset cache? if we do this for every request the cache would be useless
// There's no session here
//$this->storage->clear_cache('mailboxes.', true);
// set additional header used by libkolab
$this->storage->set_options(array(
// @TODO: there can be Roundcube plugins defining additional headers,
// we maybe would need to add them here
'fetch_headers' => 'X-KOLAB-TYPE X-KOLAB-MIME-VERSION',
'skip_deleted' => true,
'threading' => false,
));
// Disable paging
$this->storage->set_pagesize(999999);
}
/**
* List known devices
*
* @return array Device list as hash array
*/
public function devices_list()
{
if ($this->root_meta === null) {
// @TODO: consider server annotation instead of INBOX
if ($meta = $this->storage->get_metadata(self::ROOT_MAILBOX, self::ASYNC_KEY)) {
$this->root_meta = $this->unserialize_metadata($meta[self::ROOT_MAILBOX][self::ASYNC_KEY]);
}
else {
$this->root_meta = array();
}
}
if (!empty($this->root_meta['DEVICE']) && is_array($this->root_meta['DEVICE'])) {
return $this->root_meta['DEVICE'];
}
return array();
}
/**
* Get list of folders available for sync
*
* @param string $deviceid Device identifier
* @param string $type Folder type
*
* @return array List of mailbox folders
*/
public function folders_list($deviceid, $type)
{
$folders_list = array();
// get all folders of specified type
$folders = (array) kolab_storage::list_folders('', '*', $type, false, $typedata);
// get folders activesync config
$folderdata = $this->folder_meta();
// check if folders are "subscribed" for activesync
foreach ($folderdata as $folder => $meta) {
if (empty($meta['FOLDER']) || empty($meta['FOLDER'][$deviceid])
|| empty($meta['FOLDER'][$deviceid]['S'])
) {
continue;
}
if (!empty($type) && !in_array($folder, $folders)) {
continue;
}
// Activesync folder identifier (serverId)
$folder_id = self::folder_id($folder, $typedata[$folder]);
$folders_list[$folder_id] = $this->folder_data($folder, $typedata[$folder]);
}
return $folders_list;
}
/**
* Getter for folder metadata
*
* @return array Hash array with meta data for each folder
*/
public function folder_meta()
{
if (!isset($this->folder_meta)) {
$this->folder_meta = array();
// get folders activesync config
$folderdata = $this->storage->get_metadata("*", self::ASYNC_KEY);
foreach ($folderdata as $folder => $meta) {
if ($asyncdata = $meta[self::ASYNC_KEY]) {
if ($metadata = $this->unserialize_metadata($asyncdata)) {
$this->folder_meta[$folder] = $metadata;
}
}
}
}
return $this->folder_meta;
}
/**
* Creates folder and subscribes to the device
*
* @param string $name Folder name (UTF7-IMAP)
* @param int $type Folder (ActiveSync) type
* @param string $deviceid Device identifier
*
* @return bool True on success, False on failure
*/
public function folder_create($name, $type, $deviceid)
{
if ($this->storage->folder_exists($name)) {
$created = true;
}
else {
$type = self::type_activesync2kolab($type);
$created = kolab_storage::folder_create($name, $kolab_type);
}
if ($created) {
// Set ActiveSync subscription flag
$this->folder_set($name, $deviceid, 1);
return true;
}
return false;
}
/**
* Renames a folder
*
* @param string $old_name Old folder name (UTF7-IMAP)
* @param string $new_name New folder name (UTF7-IMAP)
* @param int $type Folder (ActiveSync) type
* @param string $deviceid Device identifier
*
* @return bool True on success, False on failure
*/
public function folder_rename($old_name, $new_name, $type, $deviceid)
{
$type = self::type_activesync2kolab($type);
$moved = kolab_storage::folder_rename($old_name, $new_name);
if ($moved) {
// UnSet ActiveSync subscription flag
$this->folder_set($old_name, $deviceid, 0);
// Set ActiveSync subscription flag
$this->folder_set($new_name, $deviceid, 1);
return true;
}
return false;
}
/**
* Deletes folder
*
* @param string $name Folder name (UTF7-IMAP)
* @param string $deviceid Device identifier
*
*/
public function folder_delete($name, $deviceid)
{
unset($this->folder_meta[$name]);
return kolab_storage::folder_delete($name);
}
/**
* Sets ActiveSync subscription flag on a folder
*
* @param string $name Folder name (UTF7-IMAP)
* @param string $deviceid Device identifier
* @param int $flag Flag value (0|1|2)
*/
public function folder_set($name, $deviceid, $flag)
{
// get folders activesync config
$metadata = $this->folder_meta();
$metadata = $metadata[$name];
if ($flag) {
if (empty($metadata)) {
$metadata = array();
}
if (empty($metadata['FOLDER'])) {
$metadata['FOLDER'] = array();
}
if (empty($metadata['FOLDER'][$deviceid])) {
$metadata['FOLDER'][$deviceid] = array();
}
// Z-Push uses:
// 1 - synchronize, no alarms
// 2 - synchronize with alarms
$metadata['FOLDER'][$deviceid]['S'] = $flag;
}
if (!$flag) {
unset($metadata['FOLDER'][$deviceid]['S']);
if (empty($metadata['FOLDER'][$deviceid])) {
unset($metadata['FOLDER'][$deviceid]);
}
if (empty($metadata['FOLDER'])) {
unset($metadata['FOLDER']);
}
if (empty($metadata)) {
$metadata = null;
}
}
// Return if nothing's been changed
if (!self::data_array_diff($this->folder_meta[$name], $metadata)) {
return true;
}
$this->folder_meta[$name] = $metadata;
return $this->storage->set_metadata($name, array(
self::ASYNC_KEY => $this->serialize_metadata($metadata)));
}
public function device_get($id)
{
$devices_list = $this->devices_list();
$result = $devices_list[$id];
return $result;
}
/**
* Registers new device on server
*
* @param array $device Device data
* @param string $id Device ID
*
* @return bool True on success, False on failure
*/
public function device_create($device, $id)
{
// Fill local cache
$this->devices_list();
// Old Kolab_ZPush device parameters
// MODE: -1 | 0 | 1 (not set | flatmode | foldermode)
// TYPE: device type string
// ALIAS: user-friendly device name
// Syncroton (kolab_sync_backend_device) uses
// ID: internal identifier in syncroton database
// TYPE: device type string
$metadata = $this->root_meta;
$metadata['DEVICE'][$id] = $device;
$metadata = array(self::ASYNC_KEY => $this->serialize_metadata($metadata));
$result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata);
if ($result) {
// Update local cache
$this->root_meta['DEVICE'][$id] = $device;
// Subscribe to default folders
$foldertypes = $this->storage->get_metadata('*', kolab_storage::CTYPE_KEY);
$folders = array('INBOX');
$types = array(
'mail.drafts',
'mail.wastebasket',
'mail.sentitems',
'mail.outbox',
'event.default',
'contact.default',
);
$foldertypes = array_map('implode', $foldertypes);
$foldertypes = array_intersect($foldertypes, $types);
// get default folders
foreach ($foldertypes as $folder => $type) {
// only personal folders
if ($this->storage->folder_namespace($folder) == 'personal') {
$this->folder_set($folder, $id, 1);
}
}
}
return $result;
}
public function device_update($device, $id)
{
$devices_list = $this->devices_list();
$old_device = $devices_list[$id];
if (!$old_device) {
return false;
}
// Do nothing if nothing is changed
if (!self::data_array_diff($old_device, $device)) {
return true;
}
$device = array_merge($old_device, $device);
$metadata = $this->root_meta;
$metadata['DEVICE'][$id] = $device;
$metadata = array(self::ASYNC_KEY => $this->serialize_metadata($metadata));
$result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata);
if ($result) {
// Update local cache
$this->root_meta['DEVICE'][$id] = $device;
}
return $result;
}
/**
* Device delete.
*
* @param string $id Device ID
*
* @return bool True on success, False on failure
*/
public function device_delete($id)
{
$device = $this->device_get($id);
if (!$device) {
return false;
}
unset($this->root_meta['DEVICE'][$id], $this->root_meta['FOLDER'][$id]);
if (empty($this->root_meta['DEVICE'])) {
unset($this->root_meta['DEVICE']);
}
if (empty($this->root_meta['FOLDER'])) {
unset($this->root_meta['FOLDER']);
}
$metadata = $this->serialize_metadata($this->root_meta);
$metadata = array(self::ASYNC_KEY => $metadata);
// update meta data
$result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata);
if ($result) {
// remove device annotation for every folder
foreach ($this->folder_meta() as $folder => $meta) {
// skip root folder (already handled above)
if ($folder == self::ROOT_MAILBOX)
continue;
if (!empty($meta['FOLDER']) && isset($meta['FOLDER'][$id])) {
unset($meta['FOLDER'][$id]);
if (empty($meta['FOLDER'])) {
unset($this->folder_meta[$folder]['FOLDER']);
unset($meta['FOLDER']);
}
if (empty($meta)) {
unset($this->folder_meta[$folder]);
$meta = null;
}
$metadata = array(self::ASYNC_KEY => $this->serialize_metadata($meta));
$res = $this->storage->set_metadata($folder, $metadata);
if ($res && $meta) {
$this->folder_meta[$folder] = $meta;
}
}
}
}
return $result;
}
/**
* Helper method to decode saved IMAP metadata
*/
private function unserialize_metadata($str)
{
if (!empty($str)) {
// Support old Z-Push annotation format
if ($str[0] != '{') {
$str = base64_decode($str);
}
$data = json_decode($str, true);
return $data;
}
return null;
}
/**
* Helper method to encode IMAP metadata for saving
*/
private function serialize_metadata($data)
{
if (!empty($data) && is_array($data)) {
$data = json_encode($data);
// $data = base64_encode($data);
return $data;
}
return null;
}
/**
* Returns Kolab folder type for specified ActiveSync type ID
*/
public static function type_activesync2kolab($type)
{
if (!empty(self::$types[$type])) {
return self::$types[$type];
}
return '';
}
/**
* Returns ActiveSync folder type for specified Kolab type
*/
public static function type_kolab2activesync($type)
{
if ($key = array_search($type, self::$types)) {
return $key;
}
return key(self::$types);
}
/**
* Returns Kolab folder type for specified ActiveSync class name
*/
public static function class_activesync2kolab($class)
{
if (!empty(self::$classes[$class])) {
return self::$classes[$class];
}
return '';
}
private function folder_data($folder, $type)
{
// Folder name parameters
$delim = $this->storage->get_hierarchy_delimiter();
$items = explode($delim, $folder);
$name = array_pop($items);
// Folder UID
$folder_id = $this->folder_id($folder);
// Syncroton folder data array
return array(
- 'folderid' => $folder_id,
- 'parentid' => count($items) ? self::folder_id(implode($delim, $items)) : 0,
- 'displayname' => rcube_charset::convert($name, 'UTF7-IMAP', kolab_sync::CHARSET),
+ 'serverId' => $folder_id,
+ 'parentId' => count($items) ? self::folder_id(implode($delim, $items)) : 0,
+ 'displayName' => rcube_charset::convert($name, 'UTF7-IMAP', kolab_sync::CHARSET),
'type' => self::type_kolab2activesync($type),
);
}
/**
* Builds folder ID based on folder name
*/
public function folder_id($name, $type = null)
{
// ActiveSync expects folder identifiers to be max.64 characters
// So we can't use just folder name
if ($name === '' || !is_string($name)) {
return null;
}
if (isset($this->folder_uids[$name])) {
return $this->folder_uids[$name];
}
/*
@TODO: For now uniqueid annotation doesn't work, we will create UIDs by ourselves.
There's one inconvenience of this solution: folder name/type change
would be handled in ActiveSync as delete + create.
// get folders unique identifier
$folderdata = $this->storage->get_metadata($name, self::UID_KEY);
if ($folderdata && !empty($folderdata[$name])) {
$uid = $folderdata[$name][self::UID_KEY];
$this->folder_uids[$name] = $uid;
return $uid;
}
*/
// Add type to folder UID hash, so type change can be detected by Syncroton
if (!$type) {
$metadata = $this->storage->get_metadata($name, kolab_storage::CTYPE_KEY);
$type = $metadata[$name][kolab_storage::CTYPE_KEY];
}
$uid = md5($name . '!!' . $type);
return $this->folder_uids[$name] = $uid;
return $uid;
}
/**
* Returns IMAP folder name
*
* @param string $id Folder identifier
* @param string $deviceid Device dentifier
*
* @return string Folder name (UTF7-IMAP)
*/
public function folder_id2name($id, $deviceid)
{
// check in cache first
if (!empty($this->folder_uids)) {
if (($name = array_search($id, $this->folder_uids)) !== false) {
return $name;
}
}
/*
@TODO: see folder_id()
// get folders unique identifier
$folderdata = $this->storage->get_metadata('*', self::UID_KEY);
foreach ((array)$folderdata as $folder => $data) {
if (!empty($data[self::UID_KEY])) {
$uid = $data[self::UID_KEY];
$this->folder_uids[$folder] = $uid;
if ($uid == $id) {
$name = $folder;
}
}
}
*/
// get all folders of specified type
$folderdata = $this->folder_meta();
// check if folders are "subscribed" for activesync
foreach ($folderdata as $folder => $meta) {
if (empty($meta['FOLDER']) || empty($meta['FOLDER'][$deviceid])
|| empty($meta['FOLDER'][$deviceid]['S'])
) {
continue;
}
$uid = self::folder_id($folder);
$this->folder_uids[$folder] = $uid;
if ($uid == $id) {
$name = $folder;
}
}
return $name;
}
/**
* Compares two arrays
*
* @param array $array1
* @param array $array2
*
* @return bool True if arrays differs, False otherwise
*/
private static function data_array_diff($array1, $array2)
{
if (!is_array($array1) || !is_array($array2)) {
return $array1 != $array2;
}
if (count($array1) != count($array2)) {
return true;
}
foreach ($array1 as $key => $val) {
if (!array_key_exists($key, $array2)) {
return true;
}
if ($val !== $array2[$key]) {
return true;
}
}
return false;
}
/**
* Send the given message using the configured method.
*
* @param string $message Complete message source
* @param array $smtp_error SMTP error array (reference)
* @param array $smtp_opts SMTP options (e.g. DSN request)
*
* @return boolean Send status.
*/
public function send_message(&$message, &$smtp_error, $smtp_opts = null)
{
$rcube = rcube::get_instance();
list($headers, $message) = $this->parse_mime($message);
$mailto = $headers['To'];
$headers['User-Agent'] .= $rcube->app_name;
if ($agent = $rcube->config->get('useragent')) {
$headers['User-Agent'] .= '/' . $agent;
}
// send thru SMTP server using custom SMTP library
if ($rcube->config->get('smtp_server')) {
$smtp_headers = $headers;
// generate list of recipients
$recipients = array();
if (!empty($headers['To']))
$recipients[] = $headers['To'];
if (!empty($headers['Cc']))
$recipients[] = $headers['Cc'];
if (!empty($headers['Bcc']))
$recipients[] = $headers['Bcc'];
// remove Bcc header
unset($smtp_headers['Bcc']);
// send message
if (!is_object($rcube->smtp)) {
$rcube->smtp_init(true);
}
$sent = $rcube->smtp->send_mail($headers['From'], $recipients, $smtp_headers, $message, $smtp_opts);
$smtp_response = $rcube->smtp->get_response();
$smtp_error = $rcube->smtp->get_error();
// log error
if (!$sent) {
rcube::raise_error(array('code' => 800, 'type' => 'smtp',
'line' => __LINE__, 'file' => __FILE__,
'message' => "SMTP error: ".join("\n", $smtp_response)), true, false);
}
}
// send mail using PHP's mail() function
else {
$mail_headers = $headers;
$delim = $rcube->config->header_delimiter();
$subject = $headers['Subject'];
$to = $headers['To'];
// unset some headers because they will be added by the mail() function
unset($mail_headers['To'], $mail_headers['Subject']);
// #1485779
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
if (preg_match_all('/<([^@]+@[^>]+)>/', $to, $m)) {
$to = implode(', ', $m[1]);
}
}
foreach ($mail_headers as $header => $header_value) {
$mail_headers[$header] = $header . ': ' . $header_value;
}
$header_str = rtrim(implode("\r\n", $mail_headers));
if ($delim != "\r\n") {
$header_str = str_replace("\r\n", $delim, $header_str);
$msg_body = str_replace("\r\n", $delim, $message);
$to = str_replace("\r\n", $delim, $to);
$subject = str_replace("\r\n", $delim, $subject);
}
if (ini_get('safe_mode'))
$sent = mail($to, $subject, $message, $header_str);
else
$sent = mail($to, $subject, $message, $header_str, "-f$from");
}
if ($sent) {
$rcube->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $message));
// remove MDN headers after sending
unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
// get all recipients
if ($headers['Cc'])
$mailto .= ' ' . $headers['Cc'];
if ($headers['Bcc'])
$mailto .= ' ' . $headers['Bcc'];
if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
$mailto = implode(', ', array_unique($m[1]));
if ($rcube->config->get('smtp_log')) {
rcube::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
$rcube->get_user_name(),
$_SERVER['REMOTE_ADDR'],
$mailto,
!empty($smtp_response) ? join('; ', $smtp_response) : ''));
}
}
unset($headers['Bcc']);
// Build the message back
foreach ($headers as $header => $header_value) {
$headers[$header] = $header . ': ' . $header_value;
}
$message = trim(implode("\r\n", $headers)) . "\r\n\r\n" . ltrim($message);
return $sent;
}
public function parse_mime($message, $decode_body = false)
{
list($headers, $message) = preg_split('/\r?\n\r?\n/', $message, 2, PREG_SPLIT_NO_EMPTY);
// Parse headers to get sender and recipients
$headers = str_replace("\r\n", "\n", $headers);
$headers = explode("\n", trim($headers));
$ln = 0;
$lines = array();
foreach ($headers as $line) {
if (ord($line[0]) <= 32) {
$lines[$ln] .= (empty($lines[$ln]) ? '' : "\n") . trim($line);
}
else {
$lines[++$ln] = trim($line);
}
}
$headers = array();
$headers_map = array(
'subject' => 'Subject',
'from' => 'From',
'to' => 'To',
'cc' => 'Cc',
'bcc' => 'Bcc',
'content-transfer-encoding' => 'Content-Transfer-Encoding',
);
foreach ($lines as $line) {
list($field, $string) = explode(':', $line, 2);
$_field = strtolower($field);
if (isset($headers_map[$_field])) {
$field = $headers_map[$_field];
}
$headers[$field] = trim($string);
}
// Decode body
if ($decode_body) {
$message = str_replace("\r\n", "\n", $message);
$encoding = strtolower($headers['Content-Transfer-Encoding']);
switch ($encoding) {
case 'base64':
$message = base64_decode($message);
break;
case 'quoted-printable':
$message = quoted_printable_decode($message);
break;
}
}
return array($headers, $message);
}
/**
* Creates complete MIME message body
*
* @param array $headers Message headers
* @param string $body Message body
*
* @return string Message source
*/
public function build_mime($headers, $body)
{
// Encode the body
$encoding = strtolower($headers['Content-Transfer-Encoding']);
switch ($encoding) {
case 'base64':
$body = base64_encode($body);
$body = chunk_split($body, 76, "\r\n");
break;
case 'quoted-printable':
$body = quoted_printable_encode($body);
break;
}
foreach ($headers as $header => $header_value) {
$headers[$header] = $header . ': ' . $header_value;
}
// Build the complete message
return trim(implode("\r\n", $headers)) . "\r\n\r\n" . ltrim($body);
}
/**
* Unique Message-ID generator.
*
* @return string Message-ID
*/
/*
public function gen_message_id()
{
$local_part = md5(uniqid('rcmail'.mt_rand(),true));
// $domain_part = $this->user->get_username('domain');
// Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) {
$host = preg_replace('/:[0-9]+$/', '', $host);
if ($host && preg_match('/\.[a-z]+$/i', $host)) {
$domain_part = $host;
}
}
}
return sprintf('<%s@%s>', $local_part, $domain_part);
}
*/
}
diff --git a/lib/kolab_sync_backend_common.php b/lib/kolab_sync_backend_common.php
new file mode 100644
index 0000000..34e9623
--- /dev/null
+++ b/lib/kolab_sync_backend_common.php
@@ -0,0 +1,212 @@
+<?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> |
+ +--------------------------------------------------------------------------+
+*/
+
+/**
+ * Parent backend class for kolab backends
+ */
+class kolab_sync_backend_common implements Syncroton_Backend_IBackend
+{
+ /**
+ * Table name
+ *
+ * @var string
+ */
+ protected $table_name;
+
+ /**
+ * Model interface name
+ *
+ * @var string
+ */
+ protected $interface_name;
+
+ /**
+ * Backend interface name
+ *
+ * @var string
+ */
+ protected $class_name;
+
+ /**
+ * SQL Database engine
+ *
+ * @var rcube_db
+ */
+ protected $db;
+
+
+ /**
+ * Constructor
+ */
+ function __construct()
+ {
+ $this->db = rcube::get_instance()->get_dbh();
+
+ if (empty($this->class_name)) {
+ $this->class_name = str_replace('Model_I', 'Model_', $this->interface_name);
+ }
+ }
+
+ /**
+ * Creates new Syncroton data object in database
+ *
+ * @param Syncroton_Model_* $object
+ * @throws InvalidArgumentException
+ * @return Syncroton_Model_*
+ */
+ public function create($object)
+ {
+ if (! $object instanceof $this->interface_name) {
+ throw new InvalidArgumentException('$object must be instanace of ' . $this->interface_name);
+ }
+
+ $data = $this->object_to_array($object);
+ $insert = array();
+
+ $data['id'] = sha1(mt_rand(). microtime());
+
+ foreach ($data as $key => $value) {
+ $insert[$this->db->quote_identifier($key)] = $this->db->quote($value);
+ }
+
+ $this->db->query('INSERT INTO ' . $this->table_name
+ . ' (' . implode(', ', array_keys($insert)) . ')' . ' VALUES(' . implode(', ', $insert) . ')');
+
+ return $this->get($data['id']);
+ }
+
+ /**
+ * Returns Syncroton data object
+ *
+ * @param string $id
+ * @throws Syncroton_Exception_NotFound
+ * @return Syncroton_Model_*
+ */
+ public function get($id)
+ {
+ $id = $id instanceof $this->interface_name ? $id->id : $id;
+
+ $select = $this->db->query('SELECT * FROM ' . $this->table_name . ' WHERE id = ?', array($id));
+ $data = $this->db->fetch_assoc($select);
+
+ if (empty($data)) {
+ throw new Syncroton_Exception_NotFound('Object not found');
+ }
+
+ return $this->get_object($data);
+ }
+
+ /**
+ * Deletes Syncroton data object
+ *
+ * @param string $id
+ * @return bool
+ */
+ public function delete($id)
+ {
+ $id = $id instanceof $this->interface_name ? $id->id : $id;
+
+ $result = $this->db->query('DELETE FROM ' . $this->table_name .' WHERE id = ?', array($id));
+
+ return (bool) $this->db->affected_rows($result);
+ }
+
+ /**
+ * Updates Syncroton data object
+ *
+ * @param Syncroton_Model_* $object
+ * @throws InvalidArgumentException
+ * @return Syncroton_Model_*
+ */
+ public function update($object)
+ {
+ if (! $object instanceof $this->interface_name) {
+ throw new InvalidArgumentException('$object must be instanace of ' . $this->interface_name);
+ }
+
+ $data = $this->object_to_array($object);
+ $set = array();
+
+ foreach ($data as $key => $value) {
+ $set[] = $this->db->quote_identifier($key) . ' = ' . $this->db->quote($value);
+ }
+
+ $this->db->query('UPDATE ' . $this->table_name . ' SET ' . implode(', ', $set)
+ . ' WHERE ' . $this->db->quote_identifier('id') . ' = ' . $this->db->quote($object->id));
+
+ return $this->get($object->id);
+ }
+
+ /**
+ *
+ */
+ protected function get_object($data)
+ {
+ foreach ($data as $key => $value) {
+ unset($data[$key]);
+
+ if (!empty($value) && preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $value)) { // 2012-08-12 07:43:26
+ $value = new DateTime($value, new DateTimeZone('utc'));
+ }
+
+ $data[$this->to_camelcase($key, false)] = $value;
+ }
+
+ return new $this->class_name($data);
+ }
+
+ protected function object_to_array($object)
+ {
+ $data = array();
+
+ foreach ($object as $key => $value) {
+ if ($value instanceof DateTime) {
+ $value = $value->format('Y-m-d H:i:s');
+ } elseif (is_object($value) && isset($value->id)) {
+ $value = $value->id;
+ }
+
+ $data[$this->from_camelcase($key)] = $value;
+ }
+
+ return $data;
+ }
+
+ protected function from_camelcase($string)
+ {
+ $string = lcfirst($string);
+
+ return preg_replace_callback('/([A-Z])/', function ($string) { return '_' . strtolower($string[0]); }, $string);
+ }
+
+ protected function to_camelcase($string, $ucFirst = true)
+ {
+ if ($ucFirst) {
+ $string = ucfirst($string);
+ }
+
+ return preg_replace_callback('/_([a-z])/', function ($string) { return strtoupper($string[1]); }, $string);
+ }
+}
diff --git a/lib/kolab_sync_backend_content.php b/lib/kolab_sync_backend_content.php
index 27c2a29..c13a343 100644
--- a/lib/kolab_sync_backend_content.php
+++ b/lib/kolab_sync_backend_content.php
@@ -1,187 +1,119 @@
<?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> |
+--------------------------------------------------------------------------+
*/
/**
- *
- * @package Syncroton
- * @subpackage Backend
+ * Kolab backend class for content storage
*/
-class kolab_sync_backend_content implements Syncroton_Backend_IContent
+class kolab_sync_backend_content extends kolab_sync_backend_common implements Syncroton_Backend_IContent
{
- /**
- * the database adapter
- *
- * @var rcube_mdb2
- */
- protected $db;
+ protected $table_name = 'syncroton_content';
+ protected $interface_name = 'Syncroton_Model_IContent';
- public function __construct()
- {
- $this->db = rcube::get_instance()->get_dbh();
- }
-
- /**
- * create new content state
- *
- * @param Syncroton_Model_IContent $_state
- * @return Syncroton_Model_IContent
- */
- public function create(Syncroton_Model_IContent $_state)
- {
- $id = sha1(mt_rand(). microtime());
-
- $deviceId = $_state->device_id instanceof Syncroton_Model_IDevice ? $_state->device_id->id : $_state->device_id;
- $folderId = $_state->folder_id instanceof Syncroton_Model_IFolder ? $_state->folder_id->id : $_state->folder_id;
-
- $insert[$this->db->quote_identifier('id')] = $this->db->quote($id);
- $insert[$this->db->quote_identifier('device_id')] = $this->db->quote($deviceId);
- $insert[$this->db->quote_identifier('folder_id')] = $this->db->quote($folderId);
- $insert[$this->db->quote_identifier('contentid')] = $this->db->quote($_state->contentid);
- $insert[$this->db->quote_identifier('creation_time')] = $this->db->quote($_state->creation_time->format('Y-m-d H:i:s'));
- $insert[$this->db->quote_identifier('creation_synckey')] = $this->db->quote($_state->creation_synckey);
- $insert[$this->db->quote_identifier('is_deleted')] = $this->db->quote(isset($_state->is_deleted) ? (int)!!$_state->is_deleted : 0);
-
- $this->db->query('INSERT INTO syncroton_content '
- . ' (' . implode(', ', array_keys($insert)) . ')' . ' VALUES(' . implode(', ', $insert) . ')');
-
- return $this->get($id);
- }
-
/**
* mark state as deleted. The state gets removed finally,
* when the synckey gets validated during next sync.
*
- * @param Syncroton_Model_IContent|string $_id
+ * @param Syncroton_Model_IContent|string $id
*/
- public function delete($_id)
+ public function delete($id)
{
- $id = $_id instanceof Syncroton_Model_IContent ? $_id->id : $_id;
+ $id = $id instanceof Syncroton_Model_IContent ? $id->id : $id;
- $result = $this->db->query('UPDATE syncroton_content SET is_deleted = 1 WHERE id = ?', array($id));
+ $result = $this->db->query('UPDATE ' . $this->table_name . ' SET is_deleted = 1 WHERE id = ?', array($id));
-// return (bool) $this->db->affected_rows($result);
+ return (bool) $this->db->affected_rows($result);
}
- /**
- * @param string $_id
- * @throws Syncroton_Exception_NotFound
- * @return Syncroton_Model_IContent
- */
- public function get($_id)
- {
- $select = $this->db->query('SELECT * FROM syncroton_content WHERE id = ?', array($_id));
-
- if ($state = $this->db->fetch_assoc($select)) {
- $state = new Syncroton_Model_Content($state);
- }
-
- if (! $state instanceof Syncroton_Model_IContent) {
- throw new Syncroton_Exception_NotFound('id not found');
- }
-
- if (!empty($state->creation_time)) {
- $state->creation_time = new DateTime($state->creation_time, new DateTimeZone('utc'));
- }
-
- return $state;
- }
/**
* @param Syncroton_Model_IDevice|string $_deviceId
* @param Syncroton_Model_IFolder|string $_folderId
* @param string $_contentId
* @return Syncroton_Model_IContent
*/
public function getContentState($_deviceId, $_folderId, $_contentId)
{
$deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->id : $_folderId;
$where[] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($deviceId);
$where[] = $this->db->quote_identifier('folder_id') . ' = ' . $this->db->quote($folderId);
$where[] = $this->db->quote_identifier('contentid') . ' = ' . $this->db->quote($_contentId);
$where[] = $this->db->quote_identifier('is_deleted') . ' = 0';
- $select = $this->db->query('SELECT * FROM syncroton_content WHERE ' . implode(' AND ', $where));
-
- if ($state = $this->db->fetch_assoc($select)) {
- $state = new Syncroton_Model_Content($state);
- }
-
- if (! $state instanceof Syncroton_Model_IContent) {
- throw new Syncroton_Exception_NotFound('id not found');
- }
+ $select = $this->db->query('SELECT * FROM ' . $this->table_name .' WHERE ' . implode(' AND ', $where));
+ $state = $this->db->fetch_assoc($select);
- if (!empty($state->creation_time)) {
- $state->creation_time = new DateTime($state->creation_time, new DateTimeZone('utc'));
+ if (empty($state)) {
+ throw new Syncroton_Exception_NotFound('Content not found');
}
- return $state;
+ return $this->get_object($state);
}
/**
* get array of ids which got send to the client for a given class
*
* @param Syncroton_Model_IDevice|string $_deviceId
* @param Syncroton_Model_IFolder|string $_folderId
* @return array
*/
public function getFolderState($_deviceId, $_folderId)
{
$deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->id : $_folderId;
$where[] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($deviceId);
$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 syncroton_content 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)) {
$result[] = $state['contentid'];
}
return $result;
}
/**
* reset list of stored id
*
* @param Syncroton_Model_IDevice|string $_deviceId
* @param Syncroton_Model_IFolder|string $_folderId
*/
public function resetState($_deviceId, $_folderId)
{
$deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->id : $_folderId;
$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 syncroton_content WHERE ' . implode(' AND ', $where));
+ $this->db->query('DELETE FROM ' . $this->table_name .' WHERE ' . implode(' AND ', $where));
}
}
diff --git a/lib/kolab_sync_backend_device.php b/lib/kolab_sync_backend_device.php
index 2c5e8fe..34a9759 100644
--- a/lib/kolab_sync_backend_device.php
+++ b/lib/kolab_sync_backend_device.php
@@ -1,171 +1,129 @@
<?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> |
+--------------------------------------------------------------------------+
*/
/**
- *
+ * Kolab backend class for device storage
*/
-class kolab_sync_backend_device implements Syncroton_Backend_IDevice
+class kolab_sync_backend_device extends kolab_sync_backend_common implements Syncroton_Backend_IDevice
{
+ protected $table_name = 'syncroton_device';
+ protected $interface_name = 'Syncroton_Model_IDevice';
+
/**
* Kolab Sync backend
*
* @var kolab_sync_backend
*/
protected $backend;
+
/**
- * the database adapter
- *
- * @var rcube_mdb2
+ * Constructor
*/
- protected $db;
-
-
public function __construct()
{
+ parent::__construct();
$this->backend = kolab_sync_backend::get_instance();
- $this->db = rcube::get_instance()->get_dbh();
}
+
/**
* create new device
*
- * @param Syncroton_Model_IDevice $_device
+ * @param Syncroton_Model_IDevice $device
+ *
* @return Syncroton_Model_IDevice
*/
- public function create(Syncroton_Model_IDevice $device)
+ public function create($device)
{
- $id = sha1(mt_rand() . microtime());
+ $device = parent::create($device);
// Create device entry in kolab backend
$created = $this->backend->device_create(array(
- 'ID' => $id,
+ 'ID' => $device->id,
'TYPE' => $device->devicetype,
), $device->deviceid);
if (!$created) {
- throw new Syncroton_Exception_NotFound('device creation failed');
+ throw new Syncroton_Exception_NotFound('Device creation failed');
}
- $insert[$this->db->quote_identifier('id')] = $this->db->quote($id);
- $insert[$this->db->quote_identifier('deviceid')] = $this->db->quote($device->deviceid);
- $insert[$this->db->quote_identifier('devicetype')] = $this->db->quote($device->devicetype);
- $insert[$this->db->quote_identifier('owner_id')] = $this->db->quote($device->owner_id);
- $insert[$this->db->quote_identifier('policy_id')] = isset($device->policy_id) ? $this->db->quote($device->policy_id) : 1;
- $insert[$this->db->quote_identifier('policykey')] = isset($device->policykey) ? $this->db->quote($device->policykey) : 'NULL';
- $insert[$this->db->quote_identifier('useragent')] = isset($device->useragent) ? $this->db->quote($device->useragent) : "''";
- $insert[$this->db->quote_identifier('acsversion')] = isset($device->acsversion) ? $this->db->quote($device->acsversion) : "''";
- $insert[$this->db->quote_identifier('remotewipe')] = isset($device->remotewipe) ? $this->db->quote($device->remotewipe) : 'NULL';
-
- // Add entry to database
- $this->db->query('INSERT INTO syncroton_device'
- . ' (' . implode(', ', array_keys($insert)) . ')' . ' VALUES(' . implode(', ', $insert) . ')');
-
- return $this->get($id);
+ return $device;
}
+
/**
- * @param string $_id
- * @throws Syncroton_Exception_NotFound
- * @return Syncroton_Model_IDevice
+ * Delete a device
+ *
+ * @param Syncroton_Model_IDevice $device
+ *
+ * @return bool True on success, False on failure
*/
- public function get($id)
+ public function delete($device)
{
- $select = $this->db->query('SELECT * FROM syncroton_device WHERE id = ?', array($id));
-
- if ($device = $this->db->fetch_assoc($select)) {
- $device = new Syncroton_Model_Device($device);
- }
+ $result = $this->backend->device_delete($device->deviceid);
- if (! $device instanceof Syncroton_Model_IDevice) {
- throw new Syncroton_Exception_NotFound('id not found');
+ if ($result) {
+ $result = parent::delete($device);
}
- return $device;
+ return $result;
}
+
/**
* return device for this user
*
* @param string $ownerId
* @param string $deviceId
* @throws Syncroton_Exception_NotFound
* @return Syncroton_Model_Device
*/
public function getUserDevice($ownerId, $deviceId)
{
$where[] = $this->db->quote_identifier('deviceid') . ' = ' . $this->db->quote($deviceId);
$where[] = $this->db->quote_identifier('owner_id') . ' = ' . $this->db->quote($ownerId);
- $select = $this->db->query('SELECT * FROM syncroton_device WHERE ' . implode(' AND ', $where));
+ $select = $this->db->query('SELECT * FROM ' . $this->table_name . ' WHERE ' . implode(' AND ', $where));
+ $device = $this->db->fetch_assoc($select);
- if ($device = $this->db->fetch_assoc($select)) {
- $device = new Syncroton_Model_Device($device);
+ if (empty($device)) {
+ throw new Syncroton_Exception_NotFound('Device not found');
}
- if (! $device instanceof Syncroton_Model_IDevice) {
- throw new Syncroton_Exception_NotFound('id not found');
- }
+ $device = $this->get_object($device);
// Make sure device exists (could be deleted by the user)
$dev = $this->backend->device_get($deviceId);
if (empty($dev)) {
// Remove the device (and related cached data) from database
$this->delete($device);
- throw new Syncroton_Exception_NotFound('device not found');
+ throw new Syncroton_Exception_NotFound('Device not found');
}
return $device;
}
-
- public function delete($device)
- {
- $result = $this->backend->device_delete($device->deviceid);
-
- if ($result) {
- $result = $this->db->query('DELETE FROM syncroton_device'
- . ' WHERE ' . $this->db->quote_identifier('id') . ' = ' . $this->db->quote($device->id));
- $result = (bool) $this->db->affected_rows($result);
- }
-
- return $result;
- }
-
- public function update(Syncroton_Model_IDevice $device)
- {
- $set[] = $this->db->quote_identifier('policykey') . ' = ' . (isset($device->policykey) ? $this->db->quote($device->policykey) : 'NULL');
- $set[] = $this->db->quote_identifier('useragent') . ' = ' . (isset($device->useragent) ? $this->db->quote($device->useragent) : "''");
- $set[] = $this->db->quote_identifier('acsversion') . ' = ' . (isset($device->acsversion) ? $this->db->quote($device->acsversion) : "''");
- $set[] = $this->db->quote_identifier('remotewipe') . ' = ' . (isset($device->remotewipe) ? $this->db->quote($device->remotewipe) : 'NULL');
- $set[] = $this->db->quote_identifier('pinglifetime') . ' = ' . (isset($device->pinglifetime) ? $this->db->quote($device->pinglifetime) : 'NULL');
- $set[] = $this->db->quote_identifier('pingfolder') . ' = ' . (isset($device->pingfolder) ? $this->db->quote($device->pingfolder) : 'NULL');
-
- $this->db->query('UPDATE syncroton_device SET ' . implode(', ', $set)
- . ' WHERE ' . $this->db->quote_identifier('id') . ' = ' . $this->db->quote($device->id));
-
- return $this->get($device->id);
- }
}
diff --git a/lib/kolab_sync_backend_folder.php b/lib/kolab_sync_backend_folder.php
index 4fcb295..8b60b18 100644
--- a/lib/kolab_sync_backend_folder.php
+++ b/lib/kolab_sync_backend_folder.php
@@ -1,186 +1,147 @@
<?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> |
+--------------------------------------------------------------------------+
*/
/**
- * sql backend class for the folder state
- *
- * @package Syncroton
- * @subpackage Backend
+ * Kolab backend class for the folder state storage
*/
-class kolab_sync_backend_folder implements Syncroton_Backend_IFolder
+class kolab_sync_backend_folder extends kolab_sync_backend_common implements Syncroton_Backend_IFolder
{
- /**
- * the database adapter
- *
- * @var rcube_mdb2
- */
- protected $db;
-
-
- public function __construct()
- {
- $this->db = rcube::get_instance()->get_dbh();
- }
-
- /**
- * create new folder state
- *
- * @param Syncroton_Model_IFolder $_folder
- * @return Syncroton_Model_IFolder
- */
- public function create(Syncroton_Model_IFolder $_folder)
- {
- $id = sha1(mt_rand(). microtime());
- $deviceId = $_folder->device_id instanceof Syncroton_Model_IDevice ? $_folder->device_id->id : $_folder->device_id;
- $folderId = $_folder->folderid instanceof Syncroton_Model_IFolder ? $_folder->folderid->id : $_folder->folderid;
-
- $insert[$this->db->quote_identifier('id')] = $this->db->quote($id);
- $insert[$this->db->quote_identifier('device_id')] = $this->db->quote($deviceId);
- $insert[$this->db->quote_identifier('class')] = $this->db->quote($_folder->class);
- $insert[$this->db->quote_identifier('folderid')] = $this->db->quote($folderId);
- $insert[$this->db->quote_identifier('parentid')] = $this->db->quote($_folder->parentid);
- $insert[$this->db->quote_identifier('displayname')] = $this->db->quote($_folder->displayname);
- $insert[$this->db->quote_identifier('type')] = $this->db->quote($_folder->type);
- $insert[$this->db->quote_identifier('creation_time')] = $this->db->quote($_folder->creation_time->format('Y-m-d H:i:s'));
- $insert[$this->db->quote_identifier('lastfiltertype')] = $this->db->quote($_folder->lastfiltertype);
-
- $this->db->query('INSERT INTO syncroton_folder'
- . ' (' . implode(', ', array_keys($insert)) . ')' . ' VALUES(' . implode(', ', $insert) . ')');
-
- return $this->get($id);
- }
-
- public function delete($_id)
- {
- $id = $_id instanceof Syncroton_Model_IFolder ? $_id->id : $_id;
-
- $result = $this->db->query('DELETE FROM syncroton_folder WHERE id = ?', array($id));
-
- return (bool) $this->db->affected_rows($result);
- }
-
- /**
- * @param string $_id
- * @throws Syncroton_Exception_NotFound
- * @return Syncroton_Model_IFolder
- */
- public function get($_id)
- {
- $select = $this->db->query('SELECT * FROM syncroton_folder WHERE id = ?', array($_id));
-
- if ($state = $this->db->fetch_assoc($select)) {
- $state = new Syncroton_Model_Folder($state);
- }
-
- if (! $state instanceof Syncroton_Model_IFolder) {
- throw new Syncroton_Exception_NotFound('id not found');
- }
-
- if (!empty($state->creation_time)) {
- $state->creation_time = new DateTime($state->creation_time, new DateTimeZone('utc'));
- }
-
- return $state;
- }
+ protected $table_name = 'syncroton_folder';
+ protected $interface_name = 'Syncroton_Model_IFolder';
/**
* delete all stored folderId's for given device
*
* @param Syncroton_Model_Device|string $_deviceId
* @param string $_class
*/
public function resetState($_deviceId)
{
$deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
$where[] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($deviceId);
- $this->db->query('DELETE FROM syncroton_folder WHERE ' . implode(' AND ', $where));
+ $this->db->query('DELETE FROM ' . $this->table_name .' WHERE ' . implode(' AND ', $where));
}
- public function update(Syncroton_Model_IFolder $_folder)
- {
- $this->db->query('UPDATE syncroton_folder SET lastfiltertype = ? WHERE id = ?',
- array($_folder->lastfiltertype, $_folder->id));
-
- return $this->get($_folder->id);
- }
/**
* get array of ids which got send to the client for a given class
*
* @param Syncroton_Model_Device|string $_deviceId
* @param string $_class
*
* @return array
*/
public function getFolderState($_deviceId, $_class)
{
$deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
$where[] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($deviceId);
$where[] = $this->db->quote_identifier('class') . ' = ' . $this->db->quote($_class);
- $select = $this->db->query('SELECT * FROM syncroton_folder WHERE ' . implode(' AND ', $where));
+ $select = $this->db->query('SELECT * FROM ' . $this->table_name .' WHERE ' . implode(' AND ', $where));
$result = array();
- while ($row = $this->db->fetch_assoc($select)) {
- $result[$row['folderid']] = new Syncroton_Model_Folder($row);
+ while ($folder = $this->db->fetch_assoc($select)) {
+ $result[$folder['folderid']] = $this->get_object($folder);
}
return $result;
}
/**
* get folder indentified by $_folderId
*
* @param Syncroton_Model_Device|string $_deviceId
* @param string $_folderId
* @return Syncroton_Model_IFolder
*/
public function getFolder($_deviceId, $_folderId)
{
$deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
$where[] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($deviceId);
$where[] = $this->db->quote_identifier('folderid') . ' = ' . $this->db->quote($_folderId);
- $select = $this->db->query('SELECT * FROM syncroton_folder WHERE ' . implode(' AND ', $where));
+ $select = $this->db->query('SELECT * FROM ' . $this->table_name .' WHERE ' . implode(' AND ', $where));
+ $folder = $this->db->fetch_assoc($select);
- if ($folder = $this->db->fetch_assoc($select)) {
- $folder = new Syncroton_Model_Folder($folder);
+ if (empty($folder)) {
+ throw new Syncroton_Exception_NotFound('Folder not found');
}
- if (! $folder instanceof Syncroton_Model_IFolder) {
- throw new Syncroton_Exception_NotFound('folder not found');
- }
+ return $this->get_object($folder);
+ }
+
- if (!empty($folder->creation_time)) {
- $folder->creation_time = new DateTime($folder->creation_time, new DateTimeZone('utc'));
+ /**
+ * (non-PHPdoc)
+ * @see kolab_sync_backend_common::from_camelcase()
+ */
+ protected function from_camelcase($string)
+ {
+ switch ($string) {
+ case 'displayName':
+ case 'parentId':
+ return strtolower($string);
+ break;
+
+ case 'serverId':
+ return 'folderid';
+ break;
+
+ default:
+ return parent::from_camelcase($string);
+ break;
}
+ }
- return $folder;
+
+ /**
+ * (non-PHPdoc)
+ * @see kolab_sync_backend_common::to_camelcase()
+ */
+ protected function to_camelcase($string, $ucFirst = true)
+ {
+ switch ($string) {
+ case 'displayname':
+ return 'displayName';
+ break;
+
+ case 'parentid':
+ return 'parentId';
+ break;
+
+ case 'folderid':
+ return 'serverId';
+ break;
+
+ default:
+ return parent::to_camelcase($string, $ucFirst);
+ break;
+ }
}
}
diff --git a/lib/kolab_sync_db.php b/lib/kolab_sync_backend_policy.php
similarity index 72%
copy from lib/kolab_sync_db.php
copy to lib/kolab_sync_backend_policy.php
index 80ddf72..d7b8f09 100644
--- a/lib/kolab_sync_db.php
+++ b/lib/kolab_sync_backend_policy.php
@@ -1,64 +1,33 @@
<?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
+ * sql backend class for policy storage
*/
-class kolab_sync_db
+class kolab_sync_backend_policy extends kolab_sync_backend_common //implements Syncroton_Backend_IPolicy
{
- /**
- * the database adapter
- *
- * @var rcube_mdb2
- */
- 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);
- }
+ protected $table_name = 'syncroton_policy';
+ protected $interface_name = 'Syncroton_Model_IPolicy';
}
diff --git a/lib/kolab_sync_backend_state.php b/lib/kolab_sync_backend_state.php
index a59ef43..dfd2127 100644
--- a/lib/kolab_sync_backend_state.php
+++ b/lib/kolab_sync_backend_state.php
@@ -1,247 +1,202 @@
<?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> |
+--------------------------------------------------------------------------+
*/
/**
- * sql backend class for the folder state
- *
- * @package Syncroton
- * @subpackage Backend
+ * Kolab backend class for the folder state storage
*/
-class kolab_sync_backend_state implements Syncroton_Backend_ISyncState
+class kolab_sync_backend_state extends kolab_sync_backend_common implements Syncroton_Backend_ISyncState
{
- /**
- * the database adapter
- *
- * @var rcube_mdb2
- */
- protected $db;
-
-
- public function __construct()
- {
- $this->db = rcube::get_instance()->get_dbh();
- }
+ protected $table_name = 'syncroton_synckey';
+ protected $interface_name = 'Syncroton_Model_ISyncState';
/**
* create new sync state
*
* @param Syncroton_Model_ISyncState $_syncState
* @return Syncroton_Model_SyncState
*/
- public function create(Syncroton_Model_ISyncState $_syncState, $_keepPreviousSyncState = true)
+ public function create($object, $_keepPreviousSyncState = true)
{
- $id = sha1(mt_rand(). microtime());
- $deviceId = $_syncState->device_id instanceof Syncroton_Model_IDevice ? $_syncState->device_id->id : $_syncState->device_id;
- $type = $_syncState->type instanceof Syncroton_Model_IFolder ? $_syncState->type->id : $_syncState->type;
- $data = is_array($_syncState->pendingdata) ? json_encode($_syncState->pendingdata) : null;
-
- $insert[$this->db->quote_identifier('id')] = $this->db->quote($id);
- $insert[$this->db->quote_identifier('device_id')] = $this->db->quote($deviceId);
- $insert[$this->db->quote_identifier('type')] = $this->db->quote($type);
- $insert[$this->db->quote_identifier('counter')] = $this->db->quote($_syncState->counter);
- $insert[$this->db->quote_identifier('lastsync')] = $this->db->quote($_syncState->lastsync->format('Y-m-d H:i:s'));
- $insert[$this->db->quote_identifier('pendingdata')] = $data ? $this->db->quote($data) : 'NULL';
-
- $this->db->query('INSERT INTO syncroton_synckey'
- . ' (' . implode(', ', array_keys($insert)) . ')' . ' VALUES(' . implode(', ', $insert) . ')');
-
- $state = $this->get($id);
+ $object = parent::create($object);
if ($_keepPreviousSyncState !== true) {
// remove all other synckeys
- $this->_deleteOtherStates($state);
+ $this->_deleteOtherStates($object);
}
- return $state;
+ return $object;
}
protected function _deleteOtherStates(Syncroton_Model_ISyncState $_state)
{
// remove all other synckeys
$where[] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($_state->device_id);
$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 syncroton_synckey WHERE ' . implode(' AND ', $where));
+ $this->db->query('DELETE FROM ' . $this->table_name .' WHERE ' . implode(' AND ', $where));
return true;
}
+
/**
- * @param string $_id
- * @throws Syncroton_Exception_NotFound
- * @return Syncroton_Model_SyncState
+ * @see kolab_sync_backend_common::object_to_array()
*/
- public function get($_id)
+ protected function object_to_array($object)
{
- $select = $this->db->query('SELECT * FROM syncroton_synckey WHERE id = ?', array($_id));
+ $data = parent::object_to_array($object);
- if ($state = $this->db->fetch_assoc($select)) {
- $state = new Syncroton_Model_SyncState($state);
+ if (is_array($object->pendingdata)) {
+ $data['pendingdata'] = json_encode($object->pendingdata);
}
- if (! $state instanceof Syncroton_Model_ISyncState) {
- throw new Syncroton_Exception_NotFound('id not found');
- }
-
- $this->_convertFields($state);
-
- return $state;
+ return $data;
}
- protected function _convertFields(Syncroton_Model_SyncState $state)
+
+ /**
+ * @see kolab_sync_backend_common::get_object()
+ */
+ protected function get_object($data)
{
- if (!empty($state->lastsync)) {
- $state->lastsync = new DateTime($state->lastsync, new DateTimeZone('utc'));
- }
- if ($state->pendingdata) {
- $state->pendingdata = json_decode($state->pendingdata);
+ $object = parent::get_object($data);
+
+ if ($object->pendingdata) {
+ $object->pendingdata = json_decode($object->pendingdata);
}
+
+ return $object;
}
+
/**
* always returns the latest syncstate
*
* @param Syncroton_Model_IDevice|string $_deviceId
* @param Syncroton_Model_IFolder|string $_folderId
* @return Syncroton_Model_SyncState
*/
public function getSyncState($_deviceId, $_folderId)
{
$deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->id : $_folderId;
$where[] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($deviceId);
$where[] = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($folderId);
- $select = $this->db->limitquery('SELECT * FROM syncroton_synckey WHERE ' . implode(' AND ', $where)
+ $select = $this->db->limitquery('SELECT * FROM ' . $this->table_name . ' WHERE ' . implode(' AND ', $where)
.' ORDER BY counter DESC', 0, 1);
- if ($state = $this->db->fetch_assoc($select)) {
- $state = new Syncroton_Model_SyncState($state);
- }
+ $state = $this->db->fetch_assoc($select);
- if (! $state instanceof Syncroton_Model_ISyncState) {
- throw new Syncroton_Exception_NotFound('id not found');
+ if (empty($state)) {
+ throw new Syncroton_Exception_NotFound('SyncState not found');
}
- $this->_convertFields($state);
-
- return $state;
+ return $this->get_object($state);
}
/**
* delete all stored synckeys for given type
*
* @param Syncroton_Model_IDevice|string $_deviceId
* @param Syncroton_Model_IFolder|string $_folderId
*/
public function resetState($_deviceId, $_folderId)
{
$deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->id : $_folderId;
$where[] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($deviceId);
$where[] = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($folderId);
- $this->db->query('DELETE FROM syncroton_synckey WHERE ' . implode(' AND ', $where));
+ $this->db->query('DELETE FROM ' . $this->table_name .' WHERE ' . implode(' AND ', $where));
}
- public function update(Syncroton_Model_ISyncState $_syncState)
- {
- $data = is_array($_syncState->pendingdata) ? json_encode($_syncState->pendingdata) : 'null';
-
- $this->db->query('UPDATE syncroton_synckey SET counter = ?, lastsync = ?, pendingdata = ? WHERE id = ?',
- array($_syncState->counter, $_syncState->lastsync->format('Y-m-d H:i:s'), $data, $_syncState->id));
-
- return $this->get($_syncState->id);
- }
/**
* get array of ids which got send to the client for a given class
*
* @param Syncroton_Model_IDevice|string $_deviceId
* @param Syncroton_Model_IFolder|string $_folderId
*
* @return Syncroton_Model_SyncState
*/
public function validate($_deviceId, $_folderId, $_syncKey)
{
$deviceId = $_deviceId instanceof Syncroton_Model_IDevice ? $_deviceId->id : $_deviceId;
$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->id : $_folderId;
$where['device_id'] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($deviceId);
$where['type'] = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($folderId);
$where['counter'] = $this->db->quote_identifier('counter') . ' = ' . $this->db->quote($_syncKey);
$select = $this->db->query('SELECT * FROM syncroton_synckey WHERE ' . implode(' AND ', $where));
+ $state = $this->db->fetch_assoc($select);
- if ($state = $this->db->fetch_assoc($select)) {
- $state = new Syncroton_Model_SyncState($state);
- }
-
- if (! $state instanceof Syncroton_Model_ISyncState) {
+ if (empty($state)) {
return false;
}
- $this->_convertFields($state);
+ $state = $this->get_object($state);
// check if this was the latest syncKey
$where['counter'] = $this->db->quote_identifier('counter') . ' = ' . $this->db->quote($_syncKey + 1);
$select = $this->db->query('SELECT * FROM syncroton_synckey WHERE ' . implode(' AND ', $where));
if ($moreRecentState = $this->db->fetch_assoc($select)) {
- $moreRecentState = new Syncroton_Model_SyncState($moreRecentState);
+ $moreRecentState = $this->get_object($moreRecentState);
}
$where = array();
$where['device_id'] = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($deviceId);
$where['folder_id'] = $this->db->quote_identifier('folder_id') . ' = ' . $this->db->quote($folderId);
$where['is_deleted'] = $this->db->quote_identifier('is_deleted') . ' = 1';
// found more recent synckey => the last sync repsonse got not received by the client
if ($moreRecentState instanceof Syncroton_Model_ISyncState) {
$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));
// 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));
}
else {
// finaly delete all entries marked for removal in Syncroton_content table
$this->db->query('DELETE FROM syncroton_content WHERE ' . implode(' AND ', $where));
}
// remove all other synckeys
$this->_deleteOtherStates($state);
return $state;
}
}
diff --git a/lib/kolab_sync_data.php b/lib/kolab_sync_data.php
index b64568d..3820c24 100644
--- a/lib/kolab_sync_data.php
+++ b/lib/kolab_sync_data.php
@@ -1,1274 +1,1274 @@
<?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> |
+--------------------------------------------------------------------------+
*/
abstract class kolab_sync_data implements Syncroton_Data_IData
{
/**
* ActiveSync protocol version
*
* @var int
*/
protected $asversion = 0;
/**
* information about the current device
*
* @var Syncroton_Model_IDevice
*/
protected $device;
/**
* timestamp to use for all sync requests
*
* @var DateTime
*/
protected $syncTimeStamp;
/**
* name of model to use
*
* @var string
*/
protected $modelName;
/**
* type of the default folder
*
* @var int
*/
protected $defaultFolderType;
/**
* default container for new entries
*
* @var string
*/
protected $defaultFolder;
/**
* type of user created folders
*
* @var int
*/
protected $folderType;
/**
* Default namespace
*
* @var string
*/
protected $defaultNS = 'Calendar';
/**
* Internal cache for kolab_storage folder objects
*
* @var array
*/
protected $folders = array();
/**
* Timezone
*
* @var string
*/
protected $timezone;
const RESULT_OBJECT = 0;
const RESULT_UID = 1;
const RESULT_COUNT = 2;
/**
* Recurrence types
*/
const RECUR_TYPE_DAILY = 0; // Recurs daily.
const RECUR_TYPE_WEEKLY = 1; // Recurs weekly
const RECUR_TYPE_MONTHLY = 2; // Recurs monthly
const RECUR_TYPE_MONTHLY_DAYN = 3; // Recurs monthly on the nth day
const RECUR_TYPE_YEARLY = 5; // Recurs yearly
const RECUR_TYPE_YEARLY_DAYN = 6; // Recurs yearly on the nth day
/**
* Day of week constants
*/
const RECUR_DOW_SUNDAY = 1;
const RECUR_DOW_MONDAY = 2;
const RECUR_DOW_TUESDAY = 4;
const RECUR_DOW_WEDNESDAY = 8;
const RECUR_DOW_THURSDAY = 16;
const RECUR_DOW_FRIDAY = 32;
const RECUR_DOW_SATURDAY = 64;
const RECUR_DOW_LAST = 127; // The last day of the month. Used as a special value in monthly or yearly recurrences.
/**
* Mapping of recurrence types
*
* @var array
*/
protected $recurTypeMap = array(
'DAILY' => self::RECUR_TYPE_DAILY,
'WEEKLY' => self::RECUR_TYPE_WEEKLY,
'MONTHLY' => self::RECUR_TYPE_MONTHLY,
'MONTHLY' => self::RECUR_TYPE_MONTHLY_DAYN,
'YEARLY' => self::RECUR_TYPE_YEARLY,
'YEARLY' => self::RECUR_TYPE_YEARLY_DAYN,
);
/**
* Mapping of weekdays
* NOTE: ActiveSync uses a bitmask
*
* @var array
*/
protected $recurDayMap = array(
'SU' => self::RECUR_DOW_SUNDAY,
'MO' => self::RECUR_DOW_MONDAY,
'TU' => self::RECUR_DOW_TUESDAY,
'WE' => self::RECUR_DOW_WEDNESDAY,
'TH' => self::RECUR_DOW_THURSDAY,
'FR' => self::RECUR_DOW_FRIDAY,
'SA' => self::RECUR_DOW_SATURDAY,
);
/**
* the constructor
*
* @param Syncroton_Model_IDevice $device
* @param DateTime $syncTimeStamp
*/
public function __construct(Syncroton_Model_IDevice $device, DateTime $syncTimeStamp)
{
$this->backend = kolab_sync_backend::get_instance();
$this->device = $device;
$this->asversion = floatval($device->acsversion);
$this->syncTimeStamp = $syncTimeStamp;
$this->defaultRootFolder = $this->defaultFolder . '::Syncroton';
// set internal timezone of kolab_format to user timezone
try {
$this->timezone = rcube::get_instance()->config->get('timezone', 'GMT');
kolab_format::$timezone = new DateTimeZone($this->timezone);
}
catch (Exception $e) {
//rcube::raise_error($e, true);
$this->timezone = 'GMT';
kolab_format::$timezone = new DateTimeZone('GMT');
}
}
/**
* return list of supported folders for this backend
*
* @return array
*/
public function getAllFolders()
{
$list = array();
// device supports multiple folders ?
if (in_array(strtolower($this->device->devicetype), array('iphone', 'ipad', 'thundertine', 'windowsphone'))) {
// get the folders the user has access to
$list = $this->backend->folders_list($this->device->deviceid, $this->modelName);
}
if ($default = $this->getDefaultFolder()) {
- $list = array($default['folderid'] => $default);
+ $list = array($default['serverId'] => $default);
}
foreach ($list as $idx => $folder) {
$list[$idx] = new Syncroton_Model_Folder($folder);
}
return $list;
}
/**
* Returns default folder for current class type.
*/
protected function getDefaultFolder()
{
// Check if there's any folder configured for sync
$folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
if (empty($folders)) {
return null;
}
foreach ($folders as $folder) {
if ($folder['type'] == $this->defaultFolderType) {
$default = $folder;
break;
}
}
// Return first on the list if there's no default
if (empty($default)) {
$default = $folders[key($folders)];
}
// Remember real folder ID and set ID/name to root folder
- $default['realid'] = $default['folderid'];
- $default['folderid'] = $this->defaultRootFolder;
- $default['displayname'] = $this->defaultFolder;
+ $default['realid'] = $default['serverId'];
+ $default['serverId'] = $this->defaultRootFolder;
+ $default['displayName'] = $this->defaultFolder;
return $default;
}
/**
* Creates a folder
*/
public function createFolder(Syncroton_Model_IFolder $folder)
{
- $parentid = $folder->parentid;
+ $parentid = $folder->parentId;
$type = $folder->type;
- $display_name = $folder->displayname;
+ $display_name = $folder->displayName;
if ($parentid) {
$parent = $this->backend->folder_id2name($parentid, $this->device->deviceid);
}
$name = rcube_charset::convert($display_name, kolab_sync::CHARSET, 'UTF7-IMAP');
if ($parent !== null) {
$rcube = rcube::get_instance();
$storage = $rcube->get_storage();
$delim = $storage->get_hierarchy_delimiter();
$name = $parent . $delim . $name;
}
// Create IMAP folder
$result = $this->backend->folder_create($name, $type, $this->device->deviceid);
if ($result) {
- $folder->folderid = $this->backend->folder_id($name);
+ $folder->serverId = $this->backend->folder_id($name);
return $folder;
}
// @TODO: throw exception
}
/**
* Updates a folder
*/
public function updateFolder(Syncroton_Model_IFolder $folder)
{
- $parentid = $folder->parentid;
+ $parentid = $folder->parentId;
$type = $folder->type;
- $display_name = $folder->displayname;
- $old_name = $this->backend->folder_id2name($folder->folderid, $this->device->deviceid);
+ $display_name = $folder->displayName;
+ $old_name = $this->backend->folder_id2name($folder->serverId, $this->device->deviceid);
if ($parentid) {
$parent = $this->backend->folder_id2name($parentid, $this->device->deviceid);
}
$name = rcube_charset::convert($display_name, kolab_sync::CHARSET, 'UTF7-IMAP');
if ($parent !== null) {
$rcube = rcube::get_instance();
$storage = $rcube->get_storage();
$delim = $storage->get_hierarchy_delimiter();
$name = $parent . $delim . $name;
}
// Rename/move IMAP folder
if ($name == $old_name) {
$result = true;
// @TODO: folder type change?
}
else {
$result = $this->backend->folder_rename($old_name, $name, $type, $this->device->deviceid);
}
if ($result) {
- $folder->folderid = $this->backend->folder_id($name);
+ $folder->serverId = $this->backend->folder_id($name);
return $folder;
}
// @TODO: throw exception
}
/**
* Deletes a folder
*/
public function deleteFolder($folder)
{
if ($folder instanceof Syncroton_Model_IFolder) {
- $folder = $folder->folderid;
+ $folder = $folder->serverId;
}
$name = $this->backend->folder_id2name($folder, $this->device->deviceid);
// @TODO: throw exception
return $this->backend->folder_delete($name, $this->device->deviceid);
}
public function moveItem($srcFolderId, $serverId, $dstFolderId)
{
$item = $this->getObject($srcFolderId, $serverId, $folder);
if ($item && $folder) {
$dstname = $this->backend->folder_id2name($dstFolderId, $this->device->deviceid);
if ($dstname === null) {
return;
}
if (!$folder->move($serverId, $dstname)) {
return;
}
}
return $item['uid'];
}
/**
* Add entry
*
* @param string $folderId Folder identifier
* @param Syncroton_Model_IEntry $entry Entry object
*
* @return string ID of the created entry
*/
public function createEntry($folderId, Syncroton_Model_IEntry $entry)
{
$entry = $this->toKolab($entry, $folderId);
$entry = $this->createObject($folderId, $entry);
// @TODO: throw exception on error
return $entry['uid'];
}
/**
* update existing entry
*
* @param string $folderId
* @param string $serverId
* @param SimpleXMLElement $entry
*
* @return string ID of the updated entry
*/
public function updateEntry($folderId, $serverId, Syncroton_Model_IEntry $entry)
{
/* @TODO:
Certain in-schema properties remain untouched in the following three cases:
If there is only an email:Flag ([MS-ASEMAIL] section 2.2.2.27), email:Read ([MS-ASEMAIL] section 2.2.2.47), or email:Categories ([MS-ASEMAIL] section 2.2.2.9) change (that is, if only an email:Flag, email:Categories or email:Read element is present), all other properties will remain unchanged and the client SHOULD NOT send the other elements in the request. If all the other elements are sent, extra bandwidth is used, but no errors occur.
If a calendar:Exceptions ([MS-ASCAL] section 2.2.2.20) node is not specified, the properties for that calendar:Exceptions node will remain unchanged. If a calendar:Exception ([MS-ASCAL] section 2.2.2.19) node within the calendar:Exceptions node is not present, that particular exception will remain unchanged.
If the airsyncbase:Body, airsyncbase:Data, or contacts:Picture elements are not present, the corresponding properties will remain unchanged.
In all other cases, if an in-schema property is not specified in a change request, the property is actively deleted from the item on the server. A client MUST be aware of this when it is sending Sync requests; otherwise, data can be unintentionally removed.
*/
$oldEntry = $this->getObject($folderId, $serverId);
if (empty($oldEntry)) {
throw new Syncroton_Exception_NotFound('id not found');
}
$entry = $this->toKolab($entry, $folderId, $oldEntry);
$entry = $this->updateObject($folderId, $serverId, $entry);
// @TODO: throw exception on error
return $entry['uid'];
}
/**
* delete entry
*
* @param string $folderId
* @param string $serverId
* @param array $collectionData
*/
public function deleteEntry($folderId, $serverId, $collectionData)
{
$this->deleteObject($folderId, $serverId);
}
public function getFileReference($fileReference)
{
// to be implemented by Email data class
// @TODO: throw "unimplemented" exception here?
}
/**
* Search for existing entries
*
* @param string $folderid
* @param array $filter
* @param int $result_type Type of the result (see RESULT_* constants)
*
* @return array|int Search result as count or array of uids/objects
*/
protected function searchEntries($folderid, $filter = array(), $result_type = self::RESULT_UID)
{
if ($folderid == $this->defaultRootFolder) {
$folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
$folders = array_keys($folders);
}
else {
$folders = array($folderid);
}
// there's a PHP Warning from kolab_storage if $filter isn't an array
if (empty($filter)) {
$filter = array();
}
$result = $result_type == self::RESULT_COUNT ? 0 : array();
foreach ($folders as $folderid) {
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
if ($foldername === null) {
continue;
}
$folder = $this->getFolderObject($foldername);
if (!$folder) {
continue;
}
switch ($result_type) {
case self::RESULT_COUNT:
$result += (int) $folder->count($filter);
break;
case self::RESULT_UID:
if ($uids = $folder->get_uids($filter)) {
$result = array_merge($result, $uids);
}
break;
case self::RESULT_OBJECT:
default:
if ($objects = $folder->select($filter)) {
$result = array_merge($result, $objects);
}
}
}
return $result;
}
/**
* Returns filter query array according to specified ActiveSync FilterType
*
* @param int $filter_type Filter type
*
* @param array Filter query
*/
protected function filter($filter_type = 0)
{
/*
From Syncroton_Command_Sync:
const FILTER_NOTHING = 0;
const FILTER_1_DAY_BACK = 1;
const FILTER_3_DAYS_BACK = 2;
const FILTER_1_WEEK_BACK = 3;
const FILTER_2_WEEKS_BACK = 4;
const FILTER_1_MONTH_BACK = 5;
const FILTER_3_MONTHS_BACK = 6;
const FILTER_6_MONTHS_BACK = 7;
const FILTER_INCOMPLETE = 8;
Doc: MS-ASCMD 2.2.3.64.2 FilterType (Sync)
*/
// overwrite by child class according to specified type
return array();
}
/**
* get all entries changed between to dates
*
* @param string $folderId
* @param DateTime $start
* @param DateTime $end
* @return array
*/
public function getChangedEntries($folderId, DateTime $start, DateTime $end = null)
{
$filter = array();
$filter[] = array('changed', '>', $start);
if ($endTimeStamp) {
$filter[] = array('changed', '<=', $end);
}
$result = $this->searchEntries($folderId, $filter, self::RESULT_UID);
return $result;
}
/**
* get count of entries changed between two dates
*
* @param string $folderId
* @param DateTime $start
* @param DateTime $end
*
* @return int
*/
public function getChangedEntriesCount($folderId, DateTime $start, DateTime $end = null)
{
$filter = array();
$filter[] = array('changed', '>', $start);
if ($endTimeStamp) {
$filter[] = array('changed', '<=', $end);
}
$result = $this->searchEntries($folderId, $filter, self::RESULT_COUNT);
return $result;
}
/**
* get id's of all entries available on the server
*
* @param string $folderId
* @param int $filterType
*
* @return array
*/
public function getServerEntries($folder_id, $filter_type)
{
$filter = $this->filter($filter_type);
$result = $this->searchEntries($folder_id, $filter, self::RESULT_UID);
return $result;
}
/**
* get count of all entries available on the server
*
* @param string $folderId
* @param int $filterType
*
* @return int
*/
public function getServerEntriesCount($folder_id, $filter_type)
{
$filter = $this->filter($filter_type);
$result = $this->searchEntries($folder_id, $filter, self::RESULT_COUNT);
return $result;
}
public function getCountOfChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState)
{
$allClientEntries = $contentBackend->getFolderState($this->device, $folder);
// @TODO: Consider looping over all folders here, not in getServerEntries() and
// getChangesEntriesCount(). This way we could break the loop and not check all folders (see @TODO below)
// or at least skip redundant cache sync of the same folder
- $allServerEntries = $this->getServerEntries($folder->folderid, $folder->lastfiltertype);
+ $allServerEntries = $this->getServerEntries($folder->serverId, $folder->lastfiltertype);
$addedEntries = array_diff($allServerEntries, $allClientEntries);
$deletedEntries = array_diff($allClientEntries, $allServerEntries);
// @TODO: Count is needed only for GetItemEstimate command
// in Ping command we need only information that anything is changed/added/deleted
// so in case when count($addedEntries) + count($deletedEntries) != 0 we don't need
// to count changed entries here. We could also revert the order in such case
// and execute getChangedEntriesCount() before
- $changedEntries = $this->getChangedEntriesCount($folder->folderid, $syncState->lastsync);
+ $changedEntries = $this->getChangedEntriesCount($folder->serverId, $syncState->lastsync);
return count($addedEntries) + count($deletedEntries) + $changedEntries;
}
/**
* Fetches the entry from the backend
*/
protected function getObject($folderid, $entryid, &$folder = null)
{
if ($folderid instanceof Syncroton_Model_IFolder) {
- $folderid = $folderid->folderid;
+ $folderid = $folderid->serverId;
}
if ($folderid == $this->defaultRootFolder) {
$folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
$folders = array_keys($folders);
}
else {
$folders = array($folderid);
}
foreach ($folders as $folderid) {
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
if ($foldername === null) {
continue;
}
$folder = $this->getFolderObject($foldername);
if ($object = $folder->get_object($entryid)) {
$object['_folderid'] = $folderid;
return $object;
}
}
}
/**
* Saves the entry on the backend
*/
protected function createObject($folderid, $data)
{
if ($folderid == $this->defaultRootFolder) {
$default = $this->getDefaultFolder();
- $folderid = isset($default['realid']) ? $default['realid'] : $default['folderid'];
+ $folderid = isset($default['realid']) ? $default['realid'] : $default['serverId'];
}
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
$folder = $this->getFolderObject($foldername);
if ($folder->save($data)) {
return $data;
}
}
/**
* Updates the entry on the backend
*/
protected function updateObject($folderid, $entryid, $data)
{
$object = $this->getObject($folderid, $entryid);
if ($object) {
$folder = $this->getFolderObject($object['_mailbox']);
if ($folder->save($data)) {
return $data;
}
}
}
/**
* Removes the entry from the backend
*/
protected function deleteObject($folderid, $entryid)
{
$object = $this->getObject($folderid, $entryid);
if ($object) {
$folder = $this->getFolderObject($object['_mailbox']);
return $folder->delete($entryid);
}
}
/**
* Returns Folder object (uses internal cache)
*
* @param string $name Folder name (UTF7-IMAP)
*
* @return kolab_storage_folder Folder object
*/
protected function getFolderObject($name)
{
if (!isset($this->folders[$name])) {
$this->folders[$name] = kolab_storage::get_folder($name);
}
return $this->folders[$name];
}
/**
* Returns ActiveSync settings of specified folder
*
* @param string $name Folder name (UTF7-IMAP)
*
* @return array Folder settings
*/
protected function getFolderConfig($name)
{
$metadata = $this->backend->folder_meta();
$deviceid = $this->device->deviceid;
$config = $metadata[$name]['FOLDER'][$deviceid];
return array(
'ALARMS' => $config['S'] == 2,
);
}
/**
* Convert contact from xml to kolab format
*
* @param Syncroton_Model_IEntry $data Contact data
* @param string $folderId Folder identifier
* @param array $entry Old Contact data for merge
*
* @return array
*/
abstract function toKolab(Syncroton_Model_IEntry $data, $folderId, $entry = null);
/**
* Removes control chars from string which are not allowed in ActiveSync
*
* @param string $value Text
*
* @return string Text
* @deprecated
*/
public static function quote($value)
{
if ($value && !ctype_print($value)) {
$value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', null, $value);
}
return $value;
}
/**
* Extracts data from kolab data array
*/
protected function getKolabDataItem($data, $name)
{
$name_items = explode('.', $name);
$count = count($name_items);
// multi-level array (e.g. address, phone)
if ($count == 3) {
$name = $name_items[0];
$type = $name_items[1];
$key_name = $name_items[2];
if (!empty($data[$name]) && is_array($data[$name])) {
foreach ($data[$name] as $element) {
if ($element['type'] == $type) {
return $element[$key_name];
}
}
}
return null;
}
/*
// hash array e.g. organizer
else if ($count == 2) {
$name = $name_items[0];
$type = $name_items[1];
$key_name = $name_items[2];
if (!empty($data[$name]) && is_array($data[$name])) {
foreach ($data[$name] as $element) {
if ($element['type'] == $type) {
return $element[$key_name];
}
}
}
return null;
}
*/
$name_items = explode(':', $name);
$name = $name_items[0];
if (empty($data[$name])) {
return null;
}
// simple array (e.g. email)
if (count($name_items) == 2) {
return $data[$name][$name_items[1]];
}
return $data[$name];
}
/**
* Saves data in kolab data array
*/
protected function setKolabDataItem(&$data, $name, $value)
{
if (empty($value)) {
return $this->unsetKolabDataItem($data, $name);
}
$name_items = explode('.', $name);
// multi-level array (e.g. address, phone)
if (count($name_items) == 3) {
$name = $name_items[0];
$type = $name_items[1];
$key_name = $name_items[2];
if (!isset($data[$name])) {
$data[$name] = array();
}
foreach ($data[$name] as $idx => $element) {
if ($element['type'] == $type) {
$found = $idx;
break;
}
}
if (!isset($found)) {
$data[$name] = array_values($data[$name]);
$found = count($data[$name]);
$data[$name][$found] = array('type' => $type);
}
$data[$name][$found][$key_name] = $value;
return;
}
$name_items = explode(':', $name);
$name = $name_items[0];
// simple array (e.g. email)
if (count($name_items) == 2) {
$data[$name][$name_items[1]] = $value;
return;
}
$data[$name] = $value;
}
/**
* Unsets data item in kolab data array
*/
protected function unsetKolabDataItem(&$data, $name)
{
$name_items = explode('.', $name);
// multi-level array (e.g. address, phone)
if (count($name_items) == 3) {
$name = $name_items[0];
$type = $name_items[1];
$key_name = $name_items[2];
if (!isset($data[$name])) {
return;
}
foreach ($data[$name] as $idx => $element) {
if ($element['type'] == $type) {
$found = $idx;
break;
}
}
if (!isset($found)) {
return;
}
unset($data[$name][$found][$key_name]);
// if there's only one element and it's 'type', remove it
if (count($data[$name][$found]) == 1 && isset($data[$name][$found]['type'])) {
unset($data[$name][$found]['type']);
}
if (empty($data[$name][$found])) {
unset($data[$name][$found]);
}
if (empty($data[$name])) {
unset($data[$name]);
}
return;
}
$name_items = explode(':', $name);
$name = $name_items[0];
// simple array (e.g. email)
if (count($name_items) == 2) {
unset($data[$name][$name_items[1]]);
if (empty($data[$name])) {
unset($data[$name]);
}
return;
}
unset($data[$name]);
}
/**
* Setter for Body attribute according to client version
*
* @param string $value Body
* @param array $param Body parameters
*
* @reurn Syncroton_Model_EmailBody Body element
*/
protected function setBody($value, $params = array())
{
if (empty($value)) {
return;
}
$params['Data'] = $value;
if (!isset($params['Type'])) {
$params['Type'] = 1;
}
return new Syncroton_Model_EmailBody($params);
/*
// strip off any non printable control characters
$value = self::quote($value);
if ($this->asversion >= 12) {
$body = $domParent->appendChild(new DOMElement('Body', null, 'uri:AirSyncBase'));
$body->appendChild(new DOMElement('Type', 1, 'uri:AirSyncBase'));
$dataTag = new DOMElement('Data', null, 'uri:AirSyncBase');
// ... append it to parent node aka append it to the document ...
$body->appendChild($dataTag);
// ... and now add the content (DomText takes care of special chars)
$dataTag->appendChild(new DOMText($value));
} else {
// create a new DOMElement ...
$node = new DOMElement('Body', null, 'uri:' . $this->defaultNS);
// ... append it to parent node aka append it to the document ...
$domParent->appendChild($node);
// ... and now add the content (DomText takes care of special chars)
$node->appendChild(new DOMText($value));
}
*/
}
/**
* Getter for Body attribute value according to client version
*
* @param mixed $data Data element
*/
protected function getBody($data)
{
if ($data && $data->Data) {
return $data->Data;
}
/*
if ($this->asversion >= 12) {
$airSyncBase = $data->children('uri:AirSyncBase');
if (isset($airSyncBase->Body)) {
return (string)$airSyncBase->Body->Data;
}
}
else if (isset($classData->Body)) {
return (string) $classData->Body;
}
*/
}
/**
* Converts PHP DateTime, date (YYYY-MM-DD) or unixtimestamp into PHP DateTime in UTC
*
* @param DateTime|int|string $date Unix timestamp, date (YYYY-MM-DD) or PHP DateTime object
*
* @return DateTime Datetime object
*/
protected static function date_from_kolab($date)
{
if (!empty($date)) {
if (is_numeric($date)) {
$date = new DateTime('@' . $date);
}
else if (is_string($date)) {
$date = new DateTime($date, new DateTimeZone('UTC'));
}
else {
$date = clone $date;
$tz = $date->getTimezone();
$tz_name = $tz->getName();
// convert to UTC if needed
if ($tz_name != 'UTC') {
$date->setTimezone(new DateTimeZone('UTC'));
}
}
return $date;
}
}
/**
* Convert Kolab event/task recurrence into ActiveSync
*/
protected function recurrence_from_kolab($data, $type = 'Event')
{
if (empty($data['recurrence'])) {
return null;
}
$recurrence = array();
$r = $data['recurrence'];
// required fields
switch($r['FREQ']) {
case 'DAILY':
$recurrence['Type'] = self::RECUR_TYPE_DAILY;
break;
case 'WEEKLY':
$recurrence['Type'] = self::RECUR_TYPE_WEEKLY;
$recurrence['DayOfWeek'] = $this->day2bitmask($r['BYDAY']);
break;
case 'MONTHLY':
if (!empty($r['BYMONTHDAY'])) {
// @TODO: ActiveSync doesn't support multi-valued month days,
// should we replicate the recurrence element for each day of month?
$month_day = array_shift(explode(',', $r['BYMONTHDAY']));
$recurrence['Type'] = self::RECUR_TYPE_MONTHLY;
$recurrence['DayOfMonth'] = $month_day;
}
else {
$week = (int) substr($r['BYDAY'], 0, -2);
$week = ($week == -1) ? 5 : $week;
$day = substr($r['BYDAY'], -2);
$recurrence['Type'] = self::RECUR_TYPE_MONTHLY_DAYN;
$recurrence['WeekOfMonth'] = $week;
$recurrence['DayOfWeek'] = $this->day2bitmask($day);
}
break;
case 'YEARLY':
// @TODO: ActiveSync doesn't support multi-valued months,
// should we replicate the recurrence element for each month?
$month = array_shift(explode(',', $r['BYMONTH']));
if (!empty($r['BYDAY'])) {
$week = (int) substr($r['BYDAY'], 0, -2);
$week = ($week == -1) ? 5 : $week;
$day = substr($r['BYDAY'], -2);
$recurrence['Type'] = self::RECUR_TYPE_YEARLY_DAYN;
$recurrence['WeekOfMonth'] = $week;
$recurrence['DayOfWeek'] = $this->day2bitmask($day);
$recurrence['MonthOfYear'] = $month;
}
else if (!empty($r['BYMONTHDAY'])) {
// @TODO: ActiveSync doesn't support multi-valued month days,
// should we replicate the recurrence element for each day of month?
$month_day = array_shift(explode(',', $r['BYMONTHDAY']));
$recurrence['Type'] = self::RECUR_TYPE_YEARLY;
$recurrence['DayOfMonth'] = $month_day;
$recurrence['MonthOfYear'] = $month;
}
else {
$recurrence['Type'] = self::RECUR_TYPE_YEARLY;
$recurrence['MonthOfYear'] = $month;
}
break;
}
// required field
$recurrence['Interval'] = $r['INTERVAL'] ? $r['INTERVAL'] : 1;
if (!empty($r['UNTIL'])) {
$recurrence['Until'] = $this->date_from_kolab($r['UNTIL']);
}
else if (!empty($r['COUNT'])) {
$recurrence['Occurrences'] = $r['COUNT'];
}
$class = 'Syncroton_Model_' . $type . 'Recurrence';
return new $class($recurrence);
}
/**
* Convert ActiveSync event/task recurrence into Kolab
*/
protected function recurrence_to_kolab($data, $timezone = null)
{
if (!isset($data->Recurrence) || isset($data->Recurrence->Type)) {
return null;
}
$type = $data->Recurrence->Type;
$rrule['FREQ'] = array_search($type, $this->recurTypeMap);
switch ($type) {
case self::RECUR_TYPE_DAILY:
break;
case self::RECUR_TYPE_WEEKLY:
$rrule['BYDAY'] = $this->bitmask2day($data->Recurrence->DayOfWeek);
break;
case self::RECUR_TYPE_MONTHLY:
$rrule['BYMONTHDAY'] = $data->Recurrence->DayOfMonth;
break;
case self::RECUR_TYPE_MONTHLY_DAYN:
$week = $data->Recurrence->WeekOfMonth;
$day = $data->Recurrence->DayOfWeek;
$byDay = $week == 5 ? -1 : $week;
$byDay .= $this->bitmask2day($day);
$rrule['BYDAY'] = $byDay;
break;
case self::RECUR_TYPE_YEARLY:
$rrule['BYMONTH'] = $data->Recurrence->MonthOfYear;
$rrule['BYMONTHDAY'] = $data->Recurrence->DayOfMonth;
break;
case self::RECUR_TYPE_YEARLY_DAYN:
$rrule['BYMONTH'] = $data->Recurrence->MonthOfYear;
$week = $data->Recurrence->WeekOfMonth;
$day = $data->Recurrence->DayOfWeek;
$byDay = $week == 5 ? -1 : $week;
$byDay .= $this->bitmask2day($day);
$rrule['BYDAY'] = $byDay;
break;
}
$rrule['INTERVAL'] = isset($data->Recurrence->Interval) ? $data->Recurrence->Interval : 1;
if (isset($data->Recurrence->Until)) {
if ($timezone) {
$data->Recurrence->Until->setTimezone($timezone);
}
$rrule['UNTIL'] = $data->Recurrence->Until;
}
else if (!empty($data->Recurrence->Occurrences)) {
$rrule['COUNT'] = $data->Recurrence->Occurrences;
}
return $rrule;
}
/**
* Convert Kolab event recurrence exceptions into ActiveSync
*/
protected function exceptions_from_kolab($data)
{
/*
// handle exceptions of repeating events
if($data->exdate && $data->exdate->count() > 0) {
$exceptionsTag = $_domParent->appendChild(new DOMElement('Exceptions', null, 'uri:Calendar'));
foreach ($data->exdate as $exception) {
$exceptionTag = $exceptionsTag->appendChild(new DOMElement('Exception', null, 'uri:Calendar'));
$exceptionTag->appendChild(new DOMElement('Deleted', (int)$exception->is_deleted, 'uri:Calendar'));
$exceptionTag->appendChild(new DOMElement('ExceptionStartTime', $exception->getOriginalDtStart()->format('Ymd\THis') . 'Z', 'uri:Calendar'));
if ((int)$exception->is_deleted === 0) {
$this->appendXML($exceptionTag, $_collectionData, $exception);
}
}
}
*/
}
/**
* Convert ActiveSync event recurrence exceptions into Kolab
*/
protected function exceptions_to_kolab($data, $timezone = null)
{
/*
// handle exceptions from recurrence
if (isset($xmlData->Exceptions)) {
$exdates = new Tinebase_Record_RecordSet('Calendar_Model_Event');
foreach ($xmlData->Exceptions->Exception as $exception) {
$eventException = new Calendar_Model_Event(array(
'recurid' => new Tinebase_DateTime((string)$exception->ExceptionStartTime)
));
if ((int)$exception->Deleted === 0) {
$eventException->is_deleted = false;
$this->toTineModel($exception, $eventException);
} else {
$eventException->is_deleted = true;
}
$exdates->addRecord($eventException);
}
$event->exdate = $exdates;
}
*/
}
/**
* Converts string of days (TU,TH) to bitmask used by ActiveSync
*
* @param string $days
*
* @return int
*/
protected function day2bitmask($days)
{
$days = explode(',', $days);
$result = 0;
foreach ($days as $day) {
$result = $result + $this->recurDayMap[$day];
}
return $result;
}
/**
* Convert bitmask used by ActiveSync to string of days (TU,TH)
*
* @param int $days
*
* @return string
*/
protected function bitmask2day($days)
{
$days_arr = array();
for ($bitmask = 1; $bitmask <= self::RECUR_DOW_SATURDAY; $bitmask = $bitmask << 1) {
$dayMatch = $days & $bitmask;
if ($dayMatch === $bitmask) {
$days_arr[] = array_search($bitmask, $this->recurDayMap);
}
}
$result = implode(',', $days_arr);
return $result;
}
}
diff --git a/lib/kolab_sync_data_email.php b/lib/kolab_sync_data_email.php
index 6cdc476..46ec0d6 100644
--- a/lib/kolab_sync_data_email.php
+++ b/lib/kolab_sync_data_email.php
@@ -1,1170 +1,1167 @@
<?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> |
+--------------------------------------------------------------------------+
*/
/**
*
*/
class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_IDataSearch
{
const MAX_SEARCH_RESULT = 200;
/**
* Mapping from ActiveSync Email namespace fields
*/
protected $mapping = array(
'Cc' => 'cc',
//'ContentClass' => 'contentclass',
'DateReceived' => 'internaldate',
//'DisplayTo' => 'displayto', //?
//'Flag' => 'flag',
//'FlagType' => 'flagtype', // child of Flag element
'From' => 'from',
//'Importance' => 'importance',
'InternetCPID' => 'charset',
//'MessageClass' => 'messageclass',
'ReplyTo' => 'replyto',
//'Read' => 'read',
'Subject' => 'subject',
//'ThreadTopic' => 'threadtopic',
'To' => 'to',
);
/**
* Kolab object type
*
* @var string
*/
protected $modelName = 'mail';
/**
* Type of the default folder
*
* @var int
*/
protected $defaultFolderType = Syncroton_Command_FolderSync::FOLDERTYPE_INBOX;
/**
* Default container for new entries
*
* @var string
*/
protected $defaultFolder = 'INBOX';
/**
* Type of user created folders
*
* @var int
*/
protected $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_MAIL_USER_CREATED;
/**
* Default namespace
*
* @var string
*/
protected $defaultNS = 'Email';
/**
* the constructor
*
* @param Syncroton_Model_IDevice $device
* @param DateTime $syncTimeStamp
*/
public function __construct(Syncroton_Model_IDevice $device, DateTime $syncTimeStamp)
{
parent::__construct($device, $syncTimeStamp);
$this->storage = rcube::get_instance()->get_storage();
}
/**
* Creates model object
*
* @param Syncroton_Model_SyncCollection $collection Collection data
* @param string $serverId Local entry identifier
*
* @return Syncroton_Model_Email Email object
*/
public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId)
{
$message = $this->getObject($serverId);
if (empty($message)) {
// @TODO: exception
return;
}
$msg = $this->parseMessageId($serverId);
$headers = $message->headers; // rcube_message_header
// Calendar namespace fields
foreach ($this->mapping as $key => $name) {
$value = null;
switch ($name) {
case 'internaldate':
$value = self::date_from_kolab(rcube_imap_generic::strToTime($headers->internaldate));
break;
case 'cc':
case 'to':
case 'replyto':
case 'from':
$addresses = rcube_mime::decode_address_list($headers->$name, null, true, $headers->charset);
foreach ($addresses as $idx => $part) {
$name = $part['name'];
$mailto = $part['mailto'];
$string = $part['string'];
// @TODO: convert to utf8?
// @FIXME: set name + address or address only?
//rcube_utils::idn_to_utf8();
$addresses[$idx] = format_email_recipient($mailto, $name);
}
$value = implode(',', $addresses);
break;
case 'subject':
$value = $headers->get('subject');
break;
case 'charset':
$value = self::charset_to_cp($headers->charset);
break;
}
if (empty($value) || is_array($value)) {
continue;
}
$result[$key] = $value;
}
// $result['ConversationId'] = 'FF68022058BD485996BE15F6F6D99320';
// $result['ConversationIndex'] = 'CA2CFA8A23';
// Read flag
$result['Read'] = intval(!empty($headers->flags['SEEN']));
// Flagged message
if (!empty($headers->flags['FLAGGED'])) {
// Use FollowUp flag which is used in Android when message is marked with a star
/*
@TODO:
$result['Flag'] = array(
'FlagType' => 'FollowUp',
'Status' => 1,
);
*/
}
// Importance/Priority
if ($headers->priority) {
if ($headers->priority < 3) {
$result['importance'] = 2; // High
}
else if ($headers->priority > 3) {
$result['Importance'] = 0; // Low
}
}
// get truncation
$truncateAt = null;
$opts = $collection->options;
$prefs = $opts['bodyPreferences'];
if ($opts['mimeSupport'] == 2 && ($this->asversion <= 12 || !empty($prefs[4]))) {
if (!empty($prefs[4]) && !empty($prefs[4]['truncationSize'])) {
$truncateAt = $prefs[4]['truncationSize'];
}
$airSyncBaseType = 4;
}
else if (!empty($prefs[2])) {
if (!empty($prefs[2]['truncationSize'])) {
$truncateAt = $prefs[2]['truncationSize'];
}
$airSyncBaseType = 2;
}
else {
// @TODO: In Sync examples there's one in which bodyPreferences is not defined
// in such case Truncated=1 and there's no body sent to the client
// only it's estimated size
if (!empty($prefs[1]) && !empty($prefs[1]['truncationSize'])) {
$truncateAt = $prefs[1]['truncationSize'];
}
$airSyncBaseType = 1;
}
if (isset($opts['mimeTruncation']) && $opts['mimeTruncation'] < 8) {
switch ($opts['mimeTruncation']) {
case Syncroton_Command_Sync::TRUNCATE_ALL:
$truncateAt = 0;
break;
case Syncroton_Command_Sync::TRUNCATE_4096:
$truncateAt = 4096;
break;
case Syncroton_Command_Sync::TRUNCATE_5120:
$truncateAt = 5120;
break;
case Syncroton_Command_Sync::TRUNCATE_7168:
$truncateAt = 7168;
break;
case Syncroton_Command_Sync::TRUNCATE_10240:
$truncateAt = 10240;
break;
case Syncroton_Command_Sync::TRUNCATE_20480:
$truncateAt = 20480;
break;
case Syncroton_Command_Sync::TRUNCATE_51200:
$truncateAt = 51200;
break;
case Syncroton_Command_Sync::TRUNCATE_102400:
$truncateAt = 102400;
break;
}
}
// Message body
if ($airSyncBaseType == 4) {
$messageBody = $this->storage->get_raw_body($message->uid);
/*
if ($this->asversion < 12) {
// if the email contains non 7bit ascii characters we can't transfer
// them via MIMEData xml and we need to fall back to plain text
if (preg_match('/[^\x00-\x7F]/', $messageBody)) {
$airSyncBaseType = 1;
$messageBody = $this->getMessageBody($message);
}
}
*/
}
else {
$messageBody = $this->getMessageBody($message, $airSyncBaseType == 2);
}
// strip out any non utf-8 characters
$messageBody = rcube_charset::clean($messageBody);
$real_length = $body_length = strlen($messageBody);
if ($truncateAt !== null && $body_length > $truncateAt) {
$messageBody = substr($messageBody, 0, $truncateAt);
$body_length = $truncateAt;
$isTruncacted = 1;
}
else {
$isTruncacted = 0;
}
if ($body_length > 0) {
$body_params = array(
'Type' => $airSyncBaseType,
'Truncated' => $isTruncacted,
);
if ($isTruncated) {
$body_params['EstimatedDataSize'] = $real_length;
}
$result['Body'] = $this->setBody($messageBody, $body_params);
/*
if ($this->asversion >= 12) {
$body = $domParrent->appendChild(new DOMElement('Body', null, 'uri:AirSyncBase'));
$body->appendChild(new DOMElement('Type', $airSyncBaseType, 'uri:AirSyncBase'));
$body->appendChild(new DOMElement('Truncated', $isTruncacted, 'uri:AirSyncBase'));
$body->appendChild(new DOMElement('EstimatedDataSize', $data->size, 'uri:AirSyncBase'));
$dataTag = $body->appendChild(new DOMElement('Data', null, 'uri:AirSyncBase'));
$dataTag->appendChild(new DOMText($messageBody));
}
else {
if ($airSyncBaseType == 4) {
$domParrent->appendChild(new DOMElement('MIMETruncated', $isTruncacted, 'uri:Email'));
$body = $domParrent->appendChild(new DOMElement('MIMEData', null, 'uri:Email'));
$body->appendChild(new DOMText($messageBody));
}
else {
$domParrent->appendChild(new DOMElement('BodyTruncated', $isTruncacted, 'uri:Email'));
$body = $domParrent->appendChild(new DOMElement('Body', null, 'uri:Email'));
$body->appendChild(new DOMText($messageBody));
}
}
*/
}
$result['NativeBodyType'] = intval($message->has_html_part(false));
// Message class
$result['MessageClass'] = 'IPM.Note' . ($airSyncBaseType == 4 ? '.SMIME' : '');
$result['ContentClass'] = 'urn:content-classes:message';
// attachments
$attachments = array_merge($message->attachments, $message->inline_parts);
if (!empty($attachments)) {
$result['Attachments'] = array();
foreach ($attachments as $attachment) {
$att = array();
$filename = $attachment->filename;
if (empty($filename) && $attachment->mimetype == 'text/html') {
$filename = 'HTML Part';
}
$att['DisplayName'] = $filename;
$att['FileReference'] = $serverId . '::' . $attachment->mime_id;
$att['Method'] = 1;
$att['EstimatedDataSize'] = $attachment->size;
if (!empty($attachment->content_id)) {
$att['ContentId'] = $attachment->content_id;
}
if (!empty($attachment->content_location)) {
$att['ContentLocation'] = $attachment->content_location;
}
if (in_array($attachment, $message->inline_parts)) {
$att['IsInline'] = 1;
}
$result['Attachments'][] = new Syncroton_Model_EmailAttachment($att);
}
}
return new Syncroton_Model_Email($result);
}
/**
* Returns properties of a message for Search response
*
* @param string $longId Message identifier
* @param array $options Search options
*
* @return Syncroton_Model_Email Email object
*/
public function getSearchEntry($longId, $options)
{
$collection = new Syncroton_Model_SyncCollection(array(
'options' => $options,
));
return $this->getEntry($collection, $longId);
}
/**
* convert contact from xml to libkolab array
*
* @param Syncroton_Model_IEntry $data Contact to convert
* @param string $folderid Folder identifier
* @param array $entry Existing entry
*
* @return array
*/
public function toKolab(Syncroton_Model_IEntry $data, $folderid, $entry = null)
{
// does nothing => you can't add emails via ActiveSync
}
/**
* Returns filter query array according to specified ActiveSync FilterType
*
* @param int $filter_type Filter type
*
* @param array Filter query
*/
protected function filter($filter_type = 0)
{
$filter = array();
switch ($filter_type) {
case Syncroton_Command_Sync::FILTER_1_DAY_BACK:
$mod = '-1 day';
break;
case Syncroton_Command_Sync::FILTER_3_DAYS_BACK:
$mod = '-3 days';
break;
case Syncroton_Command_Sync::FILTER_2_WEEKS_BACK:
$mod = '-2 weeks';
break;
case Syncroton_Command_Sync::FILTER_1_MONTH_BACK:
$mod = '-1 month';
break;
}
if (!empty($mod)) {
$dt = new DateTime('now', new DateTimeZone('UTC'));
$dt->modify($mod);
// RFC3501: IMAP SEARCH
$filter[] = 'SINCE ' . $dt->format('d-m-Y');
}
return $filter;
}
/**
* Returns default folder for current class type.
*/
protected function getDefaultFolder()
{
// There's always INBOX
$real_id = $this->backend->folder_id('INBOX', 'mail.inbox');
$folder_id = $this->defaultRootFolder;
return array(
'displayname' => 'Inbox',
'realid' => $real_id,
'folderid' => $folder_id,
'parentid' => 0,
'type' => $this->defaultFolderType,
);
}
public function moveItem($srcFolderId, $serverId, $dstFolderId)
{
$msg = $this->parseMessageId($serverId);
$dstname = $this->backend->folder_id2name($dstFolderId, $this->device->deviceid);
if ($dstname === null) {
return;
}
if (!$this->storage->move_message($msg['uid'], $dstname, $msg['foldername'])) {
return;
}
// Use COPYUID feature (RFC2359) to get the new UID of the copied message
$copyuid = $this->storage->conn->data['COPYUID'];
if (is_array($copyuid) && ($uid = $copyuid[1])) {
return $uid;
}
}
/**
* add entry from xml data
*
* @param string $folderId
* @param Syncroton_Model_IEntry $entry
*
* @return array
*/
public function createEntry($folderId, Syncroton_Model_IEntry $entry)
{
}
/**
* update existing entry
*
* @param string $folderId
* @param string $serverId
* @param Syncroton_Model_IEntry $entry
* @return array
*/
public function updateEntry($folderId, $serverId, Syncroton_Model_IEntry $entry)
{
$msg = $this->parseMessageId($serverId);
$message = $this->getObject($serverId);
if (isset($entry->Read)) {
// here we update only Read flag
$flag = (((int)$entry->Read != 1) ? 'UN' : '') . 'SEEN';
$this->storage->set_flag($msg['uid'], $flag, $msg['foldername']);
}
$is_flagged = !empty($message->headers->flags['FLAGGED']);
// @TODO: Flag change
if (empty($entry->Flag)) {
if ($is_flagged) {
$this->storage->set_flag($msg['uid'], 'UNFLAGGED', $msg['foldername']);
}
}
else if (!$is_flagged) {
/*
if ($entry->Flag->FlagType && preg_match('/^follow\s*up/i', $entry->Flag->FlagType)) {
$this->storage->set_flag($msg['uid'], 'FLAGGED', $msg['foldername']);
}
*/
}
}
/**
* delete entry
*
* @param string $folderId
* @param string $serverId
* @param Syncroton_Model_SyncCollection $collection
*/
public function deleteEntry($folderId, $serverId, $collection)
{
$trash = kolab_sync::get_instance()->config->get('trash_mbox');
$msg = $this->parseMessageId($serverId);
// move message to trash folder
if ($collection->deletesAsMoves
&& strlen($trash)
&& $trash != $msg['foldername']
&& $this->storage->folder_exists($trash)
) {
$this->storage->move_message($msg['uid'], $trash, $msg['foldername']);
}
// set delete flag
else {
$this->storage->set_flag($msg['uid'], 'DELETED', $msg['foldername']);
}
}
public function sendEmail($body, $saveInSent)
{
$sent = $this->backend->send_message($body, $smtp_error);
if ($sent && $saveInSent) {
$this->saveInSent($body);
}
}
- public function forwardEmail($collectionId, $itemId, $body, $saveInSent)
+ public function forwardEmail($itemId, $body, $saveInSent, $replaceMime)
{
/*
@TODO:
The SmartForward command can be applied to a meeting. When SmartForward is applied to a recurring meeting,
the InstanceId element (section 2.2.3.83.2) specifies the ID of a particular occurrence in the recurring meeting.
If SmartForward is applied to a recurring meeting and the InstanceId element is absent, the server SHOULD
forward the entire recurring meeting. If the value of the InstanceId element is invalid, the server responds
with Status element (section 2.2.3.162.15) value 104, as specified in section 2.2.4.
When the SmartForward command is used for an appointment, the original message is included by the server
as an attachment to the outgoing message. When the SmartForward command is used for a normal message
or a meeting, the behavior of the SmartForward command is the same as that of the SmartReply command (section 2.2.2.18).
*/
- /*
- @TODO:
- The LongId element is an optional child element of the Source element in SmartForward command requests and
- SmartReply command requests that specifies the long ID for the source message, which is returned in the Search
- command response message (section 2.2.2.14.2). If the LongId element is present, the FolderId (section 2.2.3.69),
- ItemId (section 2.2.3.84), and InstanceId (section 2.2.3.83.2) elements are not present.
- The LongId element value can be up to 256 characters in length.
- */
$msg = $this->parseMessageId($itemId);
$message = $this->getObject($itemId);
// Parse message
list($headers, $body) = $this->backend->parse_mime($body, true);
// Get original message body
// @TODO: here we're assuming that reply message is in text/plain format
// So, original message will be converted to plain text if needed
// @TODO: what about forward-as-attachment?
$message_body = $this->getMessageBody($message, false);
$message_body = trim($message_body);
// Join bodies
$body = rtrim($body) . "\n\n" . ltrim($message_body);
// @TODO: add attachments from the original message
// Create complete message source
$body = $this->backend->build_mime($headers, $body);
// Send message
$sent = $this->backend->send_message($body, $smtp_error);
// Set FORWARDED flag on the replied message
if ($sent && empty($message->headers->flags['FORWARDED'])) {
$this->storage->set_flag($msg['uid'], 'FORWARDED', $msg['foldername']);
}
// Save sent message in Sent folder
if ($sent && $saveInSent) {
$this->saveInSent($body);
}
}
- public function replyEmail($collectionId, $itemId, $body, $saveInSent)
+ public function replyEmail($itemId, $body, $saveInSent, $replaceMime)
{
$msg = $this->parseMessageId($itemId);
$message = $this->getObject($itemId);
// Parse message
// @TODO: messages with attachments
list($headers, $body) = $this->backend->parse_mime($body, true);
// Get original message body
// @TODO: here we're assuming that reply message is in text/plain format
// So, original message will be converted to plain text if needed
$message_body = $this->getMessageBody($message, false);
// Quote original message body
$message_body = self::wrap_and_quote(trim($message_body), 72);
// Join bodies
$body = rtrim($body) . "\n" . ltrim($message_body);
// Create complete message source
$body = $this->backend->build_mime($headers, $body);
// Send message
$sent = $this->backend->send_message($body, $smtp_error);
// Set ANSWERED flag on the replied message
if ($sent && empty($message->headers->flags['ANSWERED'])) {
$this->storage->set_flag($msg['uid'], 'ANSWERED', $msg['foldername']);
}
// Save sent message in Sent folder
if ($sent && $saveInSent) {
$this->saveInSent($body);
}
}
/**
* Append the message into Sent folder
*
* @param string $message Complete message source
*
* @return int|bool Appended message UID or True on success, False on error
*/
protected function saveInSent($message)
{
$sent_folder = kolab_sync::get_instance()->config->get('sent_mbox');
if (strlen($sent_folder) && $this->storage->folder_exists($sent_folder)) {
return $this->storage->save_message($sent_folder, $message);
}
return false;
}
/**
* Search for existing entries
*
* @param string $folderid
* @param array $filter
* @param int $result_type Type of the result (see RESULT_* constants)
*
* @return array|int Search result as count or array of uids/objects
*/
protected function searchEntries($folderid, $filter = array(), $result_type = self::RESULT_UID)
{
if ($folderid == $this->defaultRootFolder) {
$folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
$folders = array_keys($folders);
}
else {
$folders = array($folderid);
}
$filter_str = 'ALL UNDELETED';
// convert filter into one IMAP search string
foreach ($filter as $idx => $filter_item) {
if (is_array($filter_item)) {
// @TODO
// convert 'changed' entries into IMAP search string
// for now we just return empty result
return $result_type == self::RESULT_COUNT ? 0 : array();
}
else {
$filter_str .= ' ' . $filter_item;
}
}
$result = $result_type == self::RESULT_COUNT ? 0 : array();
// no sorting for best performance
$sort_by = null;
foreach ($folders as $folderid) {
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
if ($foldername === null) {
continue;
}
// $this->storage->set_folder($foldername);
$this->storage->folder_sync($foldername);
$this->storage->search($foldername, $filter_str, RCMAIL_CHARSET, $sort_by);
$search = $this->storage->get_search_set();
$search = $search[1];
if (!($search instanceof rcube_result_index)) {
continue;
}
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($folderid, $uid);
}
$result = array_merge($result, $uids);
}
break;
/*
case self::RESULT_OBJECT:
default:
if ($objects = $folder->select($filter)) {
$result = array_merge($result, $objects);
}
*/
}
}
return $result;
}
/**
* ActiveSync Search handler
*
* @param array $query Search query parameters
* @param array $options Search options
*
* @return array List of Syncroton_Model_StoreResponseResult objects
*/
public function search($query, $options)
{
list ($folders, $search_str) = $this->parse_search_query($query, $options);
if (empty($search_str)) {
return array();
}
$result = array();
// no sorting for best performance
$sort_by = null;
// @TODO: caching with Options->RebuildResults support
foreach ($folders as $folderid) {
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
if ($foldername === null) {
continue;
}
// $this->storage->set_folder($foldername);
$this->storage->folder_sync($foldername);
$this->storage->search($foldername, $search_str, RCMAIL_CHARSET, $sort_by);
$search = $this->storage->get_search_set();
$search = $search[1];
if (!($search instanceof rcube_result_index)) {
continue;
}
if ($uids = $search->get()) {
foreach ($uids as $idx => $uid) {
$uids[$idx] = new Syncroton_Model_StoreResponseResult(array(
'LongId' => $this->createMessageId($folderid, $uid),
'CollectionId' => $folderid,
'Class' => 'Email',
));
}
$result = array_merge($result, $uids);
}
// We don't want to search all folders if we've got already a lot messages
if (count($result) >= self::MAX_SEARCH_RESULT) {
break;
}
}
return array_values($result);
}
/**
* Converts ActiveSync search parameters into IMAP search string
*/
protected function parse_search_query($query, $options)
{
if (empty($query)) {
return array();
}
$search_str = '';
$folders = array();
if (!is_array($query)) {
$search = $query;
}
else {
if (isset($query['and']['freeText']) && strlen($query['and']['freeText'])) {
$search = $query['and']['freeText'];
}
if (!empty($query['and']['collections'])) {
$folders[] = $query['and']['collections'];
}
if (!empty($query['and']['greaterThan'])
&& !empty($query['and']['greaterThan']['dateReceived'])
&& !empty($query['and']['greaterThan']['value'])
) {
$search_str .= ' SINCE ' . trim($query['and']['greaterThan']['value']);
}
if (!empty($query['and']['lessThan'])
&& !empty($query['and']['lessThan']['dateReceived'])
&& !empty($query['and']['lessThan']['value'])
) {
$search_str .= ' BEFORE ' . trim($query['and']['lessThan']['value']);
}
}
if ($search !== null) {
// @FIXME: should we use TEXT/BODY search?
$search_keys = array('SUBJECT', 'TO', 'FROM', 'CC');
$search_str .= str_repeat(' OR', count($search_keys)-1);
foreach ($search_keys as $key) {
$search_str .= sprintf(" %s {%d}\r\n%s", $key, strlen($search), $search);
}
}
if (empty($search_str)) {
return array();
}
$search_str = 'ALL UNDELETED ' . trim($search_str);
// @TODO: DeepTraversal
if (empty($folders)) {
$folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
$folders = array_keys($folders);
}
return array($folders, $search_str);
}
/**
* Fetches the entry from the backend
*/
protected function getObject($entryid, &$folder = null)
{
$message = $this->parseMessageId($entryid);
if (empty($message)) {
// @TODO: exception?
return null;
}
// set current folder
$this->storage->set_folder($message['foldername']);
// get message
$message = new rcube_message($message['uid']);
return $message;
}
/**
* @return Syncroton_Model_FileReference
*/
public function getFileReference($fileReference)
{
list($folderid, $uid, $part_id) = explode('::', $fileReference);
$message = $this->getObject($fileReference);
$part = $message->mime_parts[$part_id];
$body = $message->get_part_content($part_id);
$content_type = $part->mimetype;
return new Syncroton_Model_FileReference(array(
'ContentType' => $content_type,
'Data' => $body,
));
}
/**
* Parses entry ID to get folder name and UID of the message
*/
protected function parseMessageId($entryid)
{
+ // replyEmail/forwardEmail
+ if (is_array($entryid)) {
+ $entryid = $entryid['itemId'];
+ }
+
list($folderid, $uid) = explode('::', $entryid);
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
if ($foldername === null || $foldername === false) {
// @TODO exception?
return null;
}
return array(
'uid' => $uid,
'folderid' => $folderid,
'foldername' => $foldername,
);
}
/**
* Creates entry ID of the message
*/
public function createMessageId($folderid, $uid)
{
return $folderid . '::' . $uid;
}
/**
* Returns body of the message in specified format
*/
protected function getMessageBody($message, $html = false)
{
if (!is_array($message->parts) && empty($message->body)) {
return '';
}
if (!empty($message->parts)) {
foreach ($message->parts as $part) {
// skip no-content and attachment parts (#1488557)
if ($part->type != 'content' || !$part->size || $message->is_attachment($part)) {
continue;
}
return $this->getMessagePartBody($message, $part, $html);
}
}
return $this->getMessagePartBody($message, $message, $html);
}
/**
* Returns body of the message part in specified format
*/
protected function getMessagePartBody($message, $part, $html = false)
{
// Check if we have enough memory to handle the message in it
// @FIXME: we need up to 5x more memory than the body
if (!rcube_utils::mem_check($part->size * 5)) {
return '';
}
if (empty($part->ctype_parameters) || empty($part->ctype_parameters['charset'])) {
$part->ctype_parameters['charset'] = $message->headers->charset;
}
// fetch part if not available
if (!isset($part->body)) {
$part->body = $message->get_part_content($part->mime_id);
}
// message is cached but not exists, or other error
if ($part->body === false) {
return '';
}
$body = $part->body;
if ($html) {
if ($part->ctype_secondary == 'html') {
}
else {
$body = '<pre>' . $body . '</pre>';
}
}
else {
if ($part->ctype_secondary == 'html') {
$txt = new html2text($body, false, true);
$body = $txt->get_text();
}
else {
if ($part->ctype_secondary == 'plain' && $part->ctype_parameters['format'] == 'flowed') {
$body = rcube_mime::unfold_flowed($body);
}
}
}
return $body;
}
public static function charset_to_cp($charset)
{
// @TODO: ?????
// The body is converted to utf-8 in get_part_content(), what about headers?
return 65001; // UTF-8
$aliases = array(
'asmo708' => 708,
'shiftjis' => 932,
'gb2312' => 936,
'ksc56011987' => 949,
'big5' => 950,
'utf16' => 1200,
'utf16le' => 1200,
'unicodefffe' => 1201,
'utf16be' => 1201,
'johab' => 1361,
'macintosh' => 10000,
'macjapanese' => 10001,
'macchinesetrad' => 10002,
'mackorean' => 10003,
'macarabic' => 10004,
'machebrew' => 10005,
'macgreek' => 10006,
'maccyrillic' => 10007,
'macchinesesimp' => 10008,
'macromanian' => 10010,
'macukrainian' => 10017,
'macthai' => 10021,
'macce' => 10029,
'macicelandic' => 10079,
'macturkish' => 10081,
'maccroatian' => 10082,
'utf32' => 12000,
'utf32be' => 12001,
'chinesecns' => 20000,
'chineseeten' => 20002,
'ia5' => 20105,
'ia5german' => 20106,
'ia5swedish' => 20107,
'ia5norwegian' => 20108,
'usascii' => 20127,
'ibm273' => 20273,
'ibm277' => 20277,
'ibm278' => 20278,
'ibm280' => 20280,
'ibm284' => 20284,
'ibm285' => 20285,
'ibm290' => 20290,
'ibm297' => 20297,
'ibm420' => 20420,
'ibm423' => 20423,
'ibm424' => 20424,
'ebcdickoreanextended' => 20833,
'ibmthai' => 20838,
'koi8r' => 20866,
'ibm871' => 20871,
'ibm880' => 20880,
'ibm905' => 20905,
'ibm00924' => 20924,
'cp1025' => 21025,
'koi8u' => 21866,
'iso88591' => 28591,
'iso88592' => 28592,
'iso88593' => 28593,
'iso88594' => 28594,
'iso88595' => 28595,
'iso88596' => 28596,
'iso88597' => 28597,
'iso88598' => 28598,
'iso88599' => 28599,
'iso885913' => 28603,
'iso885915' => 28605,
'xeuropa' => 29001,
'iso88598i' => 38598,
'iso2022jp' => 50220,
'csiso2022jp' => 50221,
'iso2022jp' => 50222,
'iso2022kr' => 50225,
'eucjp' => 51932,
'euccn' => 51936,
'euckr' => 51949,
'hzgb2312' => 52936,
'gb18030' => 54936,
'isciide' => 57002,
'isciibe' => 57003,
'isciita' => 57004,
'isciite' => 57005,
'isciias' => 57006,
'isciior' => 57007,
'isciika' => 57008,
'isciima' => 57009,
'isciigu' => 57010,
'isciipa' => 57011,
'utf7' => 65000,
'utf8' => 65001,
);
$charset = strtolower($charset);
$charset = preg_replace(array('/^x-/', '/[^a-z0-9]/'), '', $charset);
if (isset($aliases[$charset])) {
return $aliases[$charset];
}
if (preg_match('/^(ibm|dos|cp|windows|win)[0-9]+/', $charset, $m)) {
return substr($charset, strlen($m[1]) + 1);
}
}
/**
* Wrap text to a given number of characters per line
* but respect the mail quotation of replies messages (>).
* Finally add another quotation level by prepending the lines
* with >
*
* @param string $text Text to wrap
* @param int $length The line width
*
* @return string The wrapped text
*/
protected static function wrap_and_quote($text, $length = 72)
{
// Function stolen from Roundcube ;)
// Rebuild the message body with a maximum of $max chars, while keeping quoted message.
$max = min(77, $length + 8);
$lines = preg_split('/\r?\n/', trim($text));
$out = '';
foreach ($lines as $line) {
// don't wrap already quoted lines
if ($line[0] == '>') {
$line = '>' . rtrim($line);
}
else if (mb_strlen($line) > $max) {
$newline = '';
foreach (explode("\n", rcube_mime::wordwrap($line, $length - 2)) as $l) {
if (strlen($l)) {
$newline .= '> ' . $l . "\n";
}
else {
$newline .= ">\n";
}
}
$line = rtrim($newline);
}
else {
$line = '> ' . $line;
}
// Append the line
$out .= $line . "\n";
}
return $out;
}
}
diff --git a/lib/kolab_sync_db.php b/lib/kolab_sync_db.php
index 80ddf72..b56ce52 100644
--- a/lib/kolab_sync_db.php
+++ b/lib/kolab_sync_db.php
@@ -1,64 +1,64 @@
<?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_mdb2
+ * @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);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Apr 18, 10:16 AM (9 h, 34 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
436214
Default Alt Text
(556 KB)

Event Timeline