Page MenuHomePhorge

No OneTemporary

Size
256 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/docs/SQL/mysql.initial.sql b/docs/SQL/mysql.initial.sql
index bbaa2c7..e131845 100644
--- a/docs/SQL/mysql.initial.sql
+++ b/docs/SQL/mysql.initial.sql
@@ -1,115 +1,116 @@
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,
`json_policy` blob NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `syncroton_device` (
`id` varchar(40) NOT NULL,
`deviceid` varchar(64) NOT NULL,
`devicetype` varchar(64) NOT NULL,
`owner_id` varchar(40) NOT NULL,
`acsversion` varchar(40) NOT NULL,
`policykey` varchar(64) DEFAULT NULL,
`policy_id` varchar(40) DEFAULT NULL,
`useragent` varchar(255) DEFAULT NULL,
`imei` varchar(255) DEFAULT NULL,
`model` varchar(255) DEFAULT NULL,
`friendlyname` varchar(255) DEFAULT NULL,
`os` varchar(255) DEFAULT NULL,
`oslanguage` varchar(255) DEFAULT NULL,
`phonenumber` varchar(255) DEFAULT NULL,
`pinglifetime` int(11) DEFAULT NULL,
`remotewipe` int(11) DEFAULT '0',
`pingfolder` longblob,
`lastsynccollection` longblob DEFAULT NULL,
+ `lastping` datetime DEFAULT NULL,
`contactsfilter_id` varchar(40) DEFAULT NULL,
`calendarfilter_id` varchar(40) DEFAULT NULL,
`tasksfilter_id` varchar(40) DEFAULT NULL,
`emailfilter_id` varchar(40) DEFAULT NULL,
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,
`supportedfields` longblob 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 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 IF NOT EXISTS `syncroton_content` (
`id` varchar(40) NOT NULL,
`device_id` varchar(40) NOT NULL,
`folder_id` varchar(40) NOT NULL,
`contentid` varchar(128) NOT 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`(128)),
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,
`class` varchar(40) NOT NULL,
`folder_id` varchar(40) NOT NULL,
`data` longblob,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `syncroton_data_folder` (
`id` varchar(40) NOT NULL,
`type` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`owner_id` varchar(40) NOT NULL,
`parent_id` varchar(40) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `syncroton_modseq` (
`device_id` varchar(40) NOT NULL,
`folder_id` varchar(40) NOT NULL,
`synctime` varchar(14) NOT NULL,
`data` longblob,
PRIMARY KEY (`device_id`,`folder_id`,`synctime`),
KEY `syncroton_modseq::device_id` (`device_id`),
CONSTRAINT `syncroton_modseq::device_id--syncroton_device::id` FOREIGN KEY (`device_id`) REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
-- Roundcube core table should exist if we're using the same database
CREATE TABLE IF NOT EXISTS `system` (
`name` varchar(64) NOT NULL,
`value` mediumtext,
PRIMARY KEY(`name`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2013040900');
diff --git a/docs/SQL/mysql/2013100800.sql b/docs/SQL/mysql/2013100800.sql
new file mode 100644
index 0000000..3f00df5
--- /dev/null
+++ b/docs/SQL/mysql/2013100800.sql
@@ -0,0 +1 @@
+ALTER TABLE `syncroton_device` ADD `lastping` datetime DEFAULT NULL;
diff --git a/lib/ext/Syncroton/Backend/ABackend.php b/lib/ext/Syncroton/Backend/ABackend.php
index 6c50e1c..00869e4 100644
--- a/lib/ext/Syncroton/Backend/ABackend.php
+++ b/lib/ext/Syncroton/Backend/ABackend.php
@@ -1,193 +1,195 @@
<?php
/**
* Syncroton
*
- * @package Command
+ * @package Syncroton
+ * @subpackage Backend
* @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
+ * @package Syncroton
+ * @subpackage 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;
/**
* the constructor
*
* @param Zend_Db_Adapter_Abstract $_db
* @param string $_tablePrefix
*/
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']);
}
/**
* convert iteratable object to array
*
* @param unknown $model
* @return array
*/
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);
}
/**
* convert array to object
*
* @param array $data
* @return object
*/
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);
}
/**
* (non-PHPdoc)
* @see Syncroton_Backend_IBackend::delete()
*/
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;
}
/**
* (non-PHPdoc)
* @see Syncroton_Backend_IBackend::update()
*/
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);
}
/**
* convert from camelCase to camel_case
* @param string $string
* @return string
*/
protected function _fromCamelCase($string)
{
$string = lcfirst($string);
return preg_replace_callback('/([A-Z])/', function ($string) {return '_' . strtolower($string[0]);}, $string);
}
/**
* convert from camel_case to camelCase
*
* @param string $string
* @param bool $ucFirst
* @return 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/Device.php b/lib/ext/Syncroton/Backend/Device.php
index f7646b9..a782d07 100644
--- a/lib/ext/Syncroton/Backend/Device.php
+++ b/lib/ext/Syncroton/Backend/Device.php
@@ -1,56 +1,57 @@
<?php
/**
* Syncroton
*
- * @package Command
+ * @package Syncroton
+ * @subpackage Backend
* @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
+ * @package Syncroton
+ * @subpackage Backend
*/
-
class Syncroton_Backend_Device extends Syncroton_Backend_ABackend implements Syncroton_Backend_IDevice
{
protected $_tableName = 'device';
protected $_modelClassName = 'Syncroton_Model_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 . $this->_tableName)
->where('owner_id = ?', $ownerId)
->where('deviceid = ?', $deviceId);
$stmt = $this->_db->query($select);
$data = $stmt->fetch();
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;
}
$model = new $this->_modelClassName($data);
return $model;
}
}
diff --git a/lib/ext/Syncroton/Backend/IBackend.php b/lib/ext/Syncroton/Backend/IBackend.php
index 56979fb..00e83af 100644
--- a/lib/ext/Syncroton/Backend/IBackend.php
+++ b/lib/ext/Syncroton/Backend/IBackend.php
@@ -1,50 +1,51 @@
<?php
/**
* Syncroton
*
- * @package Command
+ * @package Syncroton
+ * @subpackage Backend
* @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
+ * @package Syncroton
+ * @subpackage Backend
*/
-
interface Syncroton_Backend_IBackend
{
/**
* 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);
/**
* Return a single device
*
* @param string $_id
* @return Syncroton_Model_IDevice
*/
public function get($id);
/**
* Upates an existing persistent record
*
* @param Syncroton_Model_IDevice $_device
* @return Syncroton_Model_IDevice
*/
public function update($model);
}
diff --git a/lib/ext/Syncroton/Backend/IDevice.php b/lib/ext/Syncroton/Backend/IDevice.php
index 27a3a55..01e76e4 100644
--- a/lib/ext/Syncroton/Backend/IDevice.php
+++ b/lib/ext/Syncroton/Backend/IDevice.php
@@ -1,25 +1,26 @@
<?php
/**
* Syncroton
*
- * @package Command
+ * @package Syncroton
+ * @subpackage Backend
* @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
+ * @package Syncroton
+ * @subpackage Backend
*/
-
interface Syncroton_Backend_IDevice extends Syncroton_Backend_IBackend
{
/**
* @param unknown_type $userId
* @param unknown_type $deviceId
* @return Syncroton_Model_IDevice
*/
public function getUserDevice($userId, $deviceId);
}
diff --git a/lib/ext/Syncroton/Backend/Policy.php b/lib/ext/Syncroton/Backend/Policy.php
index bb62e6d..7322fad 100644
--- a/lib/ext/Syncroton/Backend/Policy.php
+++ b/lib/ext/Syncroton/Backend/Policy.php
@@ -1,70 +1,71 @@
<?php
/**
* Syncroton
*
- * @package Command
+ * @package Syncroton
+ * @subpackage Backend
* @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
+ * @package Syncroton
+ * @subpackage 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';
/**
* convert iteratable object to array
*
* @param unknown $model
* @return array
*/
protected function _convertModelToArray($model)
{
$policyValues = $model->getProperties('Provision');
$policy = array();
foreach ($policyValues as $policyName) {
if ($model->$policyName !== NULL) {
$policy[$policyName] = $model->$policyName;
}
unset($model->$policyName);
}
$data = parent::_convertModelToArray($model);
$data['json_policy'] = Zend_Json::encode($policy);
return $data;
}
/**
* convert array to object
*
* @param array $data
* @return object
*/
protected function _getObject($data)
{
$policy = Zend_Json::decode($data['json_policy']);
foreach ($policy as $policyKey => $policyValue) {
$data[$policyKey] = $policyValue;
}
unset($data['json_policy']);
return parent::_getObject($data);
}
}
diff --git a/lib/ext/Syncroton/Command/Ping.php b/lib/ext/Syncroton/Command/Ping.php
index dc06215..08ad41f 100644
--- a/lib/ext/Syncroton/Command/Ping.php
+++ b/lib/ext/Syncroton/Command/Ping.php
@@ -1,200 +1,214 @@
<?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;
/**
* @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->id] = $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(array_keys($folders));
}
+ }
- if ($this->_device->isDirty() && $status == self::STATUS_NO_CHANGES_FOUND) {
- $this->_device = $this->_deviceBackend->update($this->_device);
- }
+ $this->_device->lastping = new DateTime('now', new DateTimeZone('utc'));
+
+ if ($status == self::STATUS_NO_CHANGES_FOUND) {
+ $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 ($status === self::STATUS_NO_CHANGES_FOUND && (!is_array($folders) || count($folders) == 0)) {
$status = self::STATUS_MISSING_PARAMETERS;
}
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 {
// take a break to save battery lifetime
sleep(Syncroton_Registry::getPingTimeout());
-
+
+ // if another Ping command updated lastping property, we can stop processing this Ping command request
+ $device = $this->_deviceBackend->get($this->_device->id);
+ if ((isset($device->lastping) && $device->lastping instanceof DateTime) &&
+ $device->pingfolder === $this->_device->pingfolder &&
+ $device->lastping->getTimestamp() > $this->_device->lastping->getTimestamp() ) {
+ break;
+ }
+
$now = new DateTime('now', new DateTimeZone('utc'));
foreach ($folders as $folderId) {
try {
$folder = $this->_folderBackend->get($folderId);
$dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
+
} catch (Syncroton_Exception_NotFound $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
$status = self::STATUS_FOLDER_NOT_FOUND;
+
break;
+
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->err(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
+
// do nothing, maybe temporal issue, should we stop?
continue;
}
try {
$syncState = $this->_syncStateBackend->getSyncState($this->_device, $folder);
-
- // another process synchronized data of this folder already. let's skip it
- if ($syncState->lastsync > $this->_syncTimeStamp) {
+
+ // another process synchronized data of this folder already. let's skip it
+ if ($syncState->lastsync > $this->_syncTimeStamp) {
continue;
}
-
- // safe battery time by skipping folders which got synchronied less than Syncroton_Registry::getQuietTime() seconds ago
- if (($now->getTimestamp() - $syncState->lastsync->getTimestamp()) < Syncroton_Registry::getQuietTime()) {
- continue;
+
+ // safe battery time by skipping folders which got synchronied less than Syncroton_Registry::getQuietTime() seconds ago
+ if (($now->getTimestamp() - $syncState->lastsync->getTimestamp()) < Syncroton_Registry::getQuietTime()) {
+ continue;
}
$foundChanges = $dataController->hasChanges($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->serverId);
$foundChanges = true;
}
if ($foundChanges == true) {
$this->_foldersWithChanges[] = $folder;
$status = self::STATUS_CHANGES_FOUND;
}
}
if ($status != self::STATUS_NO_CHANGES_FOUND) {
break;
}
$secondsLeft = $intervalEnd - time();
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " seconds left: " . $secondsLeft);
// See: http://www.tine20.org/forum/viewtopic.php?f=12&t=12146
//
// break if there are less than PingTimeout + 10 seconds left for the next loop
// otherwise the response will be returned after the client has finished his Ping
// request already maybe
} while ($secondsLeft > (Syncroton_Registry::getPingTimeout() + 10));
}
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->serverId));
if ($this->_logger instanceof Zend_Log)
$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/Sync.php b/lib/ext/Syncroton/Command/Sync.php
index 40ff6df..45ac895 100644
--- a/lib/ext/Syncroton/Command/Sync.php
+++ b/lib/ext/Syncroton/Command/Sync.php
@@ -1,1100 +1,1102 @@
<?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();
/**
* the global WindowSize
*
* @var integer
*/
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;
protected $_maxWindowSize = 100;
protected $_heartbeatInterval = null;
/**
* process the XML file and add, change, delete or fetches data
*/
public function handle()
{
// input xml
$requestXML = simplexml_import_dom($this->_mergeSyncRequest($this->_requestBody, $this->_device));
if (! isset($requestXML->Collections)) {
$this->_outputDom->documentElement->appendChild(
$this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_RESEND_FULL_XML)
);
return $this->_outputDom;
}
if (isset($requestXML->HeartbeatInterval)) {
$this->_heartbeatInterval = (int)$requestXML->HeartbeatInterval;
} elseif (isset($requestXML->Wait)) {
$this->_heartbeatInterval = (int)$requestXML->Wait * 60;
}
$this->_globalWindowSize = isset($requestXML->WindowSize) ? (int)$requestXML->WindowSize : 100;
if ($this->_globalWindowSize > $this->_maxWindowSize) {
$this->_globalWindowSize = $this->_maxWindowSize;
}
// load options from lastsynccollection
$lastSyncCollection = array('options' => array());
if (!empty($this->_device->lastsynccollection)) {
$lastSyncCollection = Zend_Json::decode($this->_device->lastsynccollection);
if (!array_key_exists('options', $lastSyncCollection) || !is_array($lastSyncCollection['options'])) {
$lastSyncCollection['options'] = array();
}
}
$collections = array();
foreach ($requestXML->Collections->Collection as $xmlCollection) {
$collectionId = (string)$xmlCollection->CollectionId;
$collections[$collectionId] = new Syncroton_Model_SyncCollection($xmlCollection);
// do we have to reuse the options from the previous request?
if (!isset($xmlCollection->Options) && array_key_exists($collectionId, $lastSyncCollection['options'])) {
$collections[$collectionId]->options = $lastSyncCollection['options'][$collectionId];
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " restored options to " . print_r($collections[$collectionId]->options, TRUE));
}
// store current options for next Sync command request (sticky options)
$lastSyncCollection['options'][$collectionId] = $collections[$collectionId]->options;
}
$this->_device->lastsynccollection = Zend_Json::encode($lastSyncCollection);
if ($this->_device->isDirty()) {
Syncroton_Registry::getDeviceBackend()->update($this->_device);
}
foreach ($collections as $collectionData) {
// has the folder been synchronised 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(
'deviceId' => $this->_device,
'serverId' => $collectionData->collectionId
));
}
$this->_collections[$collectionData->collectionId] = $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->collectionId] = $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->collectionId] = $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->collectionId] = $collectionData;
$this->_modifications[$collectionData->collectionId] = $clientModifications;
}
}
/**
* (non-PHPdoc)
* @see Syncroton_Command_Wbxml::getResponse()
*/
public function getResponse()
{
$sync = $this->_outputDom->documentElement;
$collections = $this->_outputDom->createElementNS('uri:AirSync', 'Collections');
$totalChanges = 0;
// continue only if there are changes or no time is left
if ($this->_heartbeatInterval > 0) {
$intervalStart = time();
do {
// take a break to save battery lifetime
sleep(Syncroton_Registry::getPingTimeout());
$now = new DateTime(null, new DateTimeZone('utc'));
foreach($this->_collections as $collectionData) {
// continue immediately if folder does not exist
if (! ($collectionData->folder instanceof Syncroton_Model_IFolder)) {
break 2;
// countinue immediately if syncstate is invalid
} elseif (! ($collectionData->syncState instanceof Syncroton_Model_ISyncState)) {
break 2;
} else {
if ($collectionData->getChanges !== true) {
continue;
}
try {
// just check if the folder still exists
$this->_folderBackend->get($collectionData->folder);
} catch (Syncroton_Exception_NotFound $senf) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " collection does not exist anymore: " . $collectionData->collectionId);
$collectionData->getChanges = false;
// make sure this is the last while loop
// no break 2 here, as we like to check the other folders too
$intervalStart -= $this->_heartbeatInterval;
}
// check that the syncstate still exists and is still valid
try {
$syncState = $this->_syncStateBackend->getSyncState($this->_device, $collectionData->folder);
// another process synchronized data of this folder already. let's skip it
if ($syncState->id !== $collectionData->syncState->id) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " syncstate changed during heartbeat interval for collection: " . $collectionData->folder->serverId);
$collectionData->getChanges = false;
// make sure this is the last while loop
// no break 2 here, as we like to check the other folders too
$intervalStart -= $this->_heartbeatInterval;
}
} catch (Syncroton_Exception_NotFound $senf) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " no syncstate found anymore for collection: " . $collectionData->folder->serverId);
$collectionData->syncState = null;
// make sure this is the last while loop
// no break 2 here, as we like to check the other folders too
$intervalStart -= $this->_heartbeatInterval;
}
// safe battery time by skipping folders which got synchronied less than Syncroton_Command_Ping::$quietTime seconds ago
if ( ! $collectionData->syncState instanceof Syncroton_Model_SyncState ||
($now->getTimestamp() - $collectionData->syncState->lastsync->getTimestamp()) < Syncroton_Registry::getQuietTime()) {
continue;
}
$dataController = Syncroton_Data_Factory::factory($collectionData->folder->class , $this->_device, $this->_syncTimeStamp);
// countinue immediately if there are any changes available
if($dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState)) {
break 2;
}
}
}
// See: http://www.tine20.org/forum/viewtopic.php?f=12&t=12146
//
// break if there are less than PingTimeout + 10 seconds left for the next loop
// otherwise the response will be returned after the client has finished his Ping
// request already maybe
} while (time() - $intervalStart < $this->_heartbeatInterval - (Syncroton_Registry::getPingTimeout() + 10));
}
foreach($this->_collections as $collectionData) {
$collectionChanges = 0;
/**
* keep track of entries added on server side
*/
$newContentStates = array();
/**
* keep track of entries deleted on server side
*/
$deletedContentStates = array();
// 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(),
);
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;
} elseif ($dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState)) {
// update _syncTimeStamp as $dataController->hasChanges might have spent some time
$this->_syncTimeStamp = new DateTime(null, new DateTimeZone('utc'));
try {
// 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,
$collectionData->options['filterType']
);
$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']);
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " Folder state checking failed: " . $e->getMessage());
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folder state checking failed: " . $e->getTraceAsString());
// Prevent from removing client entries when getServerEntries() fails
// @todo: should we set Status and break the loop here?
$serverModifications = array(
'added' => array(),
'changed' => array(),
'deleted' => array(),
);
}
}
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');
}
// collection header
$collection = $this->_outputDom->createElementNS('uri:AirSync', 'Collection');
if (!empty($collectionData->folder->class)) {
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData->folder->class));
}
$syncKeyElement = $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey'));
$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)) {
// unset all truncation settings as entries are not allowed to be truncated during fetch
$fetchCollectionData = clone $collectionData;
// unset truncationSize
if (isset($fetchCollectionData->options['bodyPreferences']) && is_array($fetchCollectionData->options['bodyPreferences'])) {
foreach($fetchCollectionData->options['bodyPreferences'] as $key => $bodyPreference) {
unset($fetchCollectionData->options['bodyPreferences'][$key]['truncationSize']);
}
}
$fetchCollectionData->options['mimeTruncation'] = Syncroton_Command_Sync::TRUNCATE_NOTHING;
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($fetchCollectionData, $serverId)
->appendXML($applicationData, $this->_device);
$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());
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getTraceAsString());
$fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_OBJECT_NOT_FOUND));
}
}
}
if ($responses->hasChildNodes() === true) {
$collection->appendChild($responses);
}
$commands = $this->_outputDom->createElementNS('uri:AirSync', 'Commands');
foreach($serverModifications['added'] as $id => $serverId) {
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, $this->_device);
$commands->appendChild($add);
$collectionChanges++;
} catch (Syncroton_Exception_MemoryExhausted $seme) {
// continue to next entry, as there is not enough memory left for the current entry
// this will lead to MoreAvailable at the end and the entry will be synced during the next Sync command
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " memory exhausted for entry: " . $serverId);
continue;
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
+ if ($this->_logger instanceof Zend_Log)
+ $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getTraceAsString());
}
// mark as sent 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 + 1
));
unset($serverModifications['added'][$id]);
}
/**
* process entries changed on server side
*/
foreach($serverModifications['changed'] as $id => $serverId) {
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, $this->_device);
$commands->appendChild($change);
$collectionChanges++;
} catch (Syncroton_Exception_MemoryExhausted $seme) {
// continue to next entry, as there is not enough memory left for the current entry
// this will lead to MoreAvailable at the end and the entry will be synced during the next Sync command
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " memory exhausted for entry: " . $serverId);
continue;
} 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]);
}
foreach($serverModifications['deleted'] as $id => $serverId) {
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);
$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]);
}
$countOfPendingChanges = (count($serverModifications['added']) + count($serverModifications['changed']) + count($serverModifications['deleted']));
if ($countOfPendingChanges > 0) {
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'MoreAvailable'));
} else {
$serverModifications = null;
}
if ($commands->hasChildNodes() === true) {
$collection->appendChild($commands);
}
$totalChanges += $collectionChanges;
// increase SyncKey if needed
if ((
// sent the clients updates... ?
!empty($clientModifications['added']) ||
!empty($clientModifications['changed']) ||
!empty($clientModifications['deleted'])
) || (
// is the server sending updates to the client... ?
$commands->hasChildNodes() === true
) || (
// changed the pending data... ?
$collectionData->syncState->pendingdata != $serverModifications
)
) {
// ...then increase SyncKey
$collectionData->syncState->counter++;
}
$syncKeyElement->appendChild($this->_outputDom->createTextNode($collectionData->syncState->counter));
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " current synckey is ". $collectionData->syncState->counter);
if ($collection->childNodes->length > 4 || $collectionData->syncState->counter != $collectionData->syncKey) {
$collections->appendChild($collection);
}
}
if (isset($collectionData->syncState) &&
$collectionData->syncState instanceof Syncroton_Model_ISyncState &&
$collectionData->syncState->counter != $collectionData->syncKey
) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " update syncState for collection: " . $collectionData->collectionId);
// store pending data in sync state when needed
if(isset($countOfPendingChanges) && $countOfPendingChanges > 0) {
$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 = clone $this->_syncTimeStamp;
// increment sync timestamp by 1 second
$collectionData->syncState->lastsync->modify('+1 sec');
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
foreach($newContentStates as $state) {
$this->_contentStateBackend->create($state);
}
// remove contentstates for entries to be deleted on client
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->get($collectionData->folder);
$folderState->lastfiltertype = $collectionData->options['filterType'];
if ($folderState->isDirty()) {
$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->warn(__METHOD__ . '::' . __LINE__ . ' failed to get folder state for: ' . $collectionData->collectionId);
}
}
if ($collections->hasChildNodes() === true) {
$sync->appendChild($collections);
}
if ($sync->hasChildNodes()) {
return $this->_outputDom;
}
return null;
}
/**
* remove Commands and Supported from collections XML tree
*
* @param DOMDocument $document
* @return DOMDocument
*/
protected function _cleanUpXML(DOMDocument $document)
{
$cleanedDocument = clone $document;
$xpath = new DomXPath($cleanedDocument);
$xpath->registerNamespace('AirSync', 'uri:AirSync');
$collections = $xpath->query("//AirSync:Sync/AirSync:Collections/AirSync:Collection");
// remove Commands and Supported elements
foreach ($collections as $collection) {
foreach (array('Commands', 'Supported') as $element) {
$childrenToRemove = $collection->getElementsByTagName($element);
foreach ($childrenToRemove as $childToRemove) {
$collection->removeChild($childToRemove);
}
}
}
return $cleanedDocument;
}
/**
* merge a partial XML document with the XML document from the previous request
*
* @param DOMDocument|null $requestBody
* @return SimpleXMLElement
*/
protected function _mergeSyncRequest($requestBody, Syncroton_Model_Device $device)
{
$lastSyncCollection = array();
if (!empty($device->lastsynccollection)) {
$lastSyncCollection = Zend_Json::decode($device->lastsynccollection);
if (!empty($lastSyncCollection['lastXML'])) {
$lastXML = new DOMDocument();
$lastXML->loadXML($lastSyncCollection['lastXML']);
}
}
if (! $requestBody instanceof DOMDocument && isset($lastXML) && $lastXML instanceof DOMDocument) {
$requestBody = $lastXML;
} elseif (! $requestBody instanceof DOMDocument) {
throw new Syncroton_Exception_UnexpectedValue('no xml body found');
}
if ($requestBody->getElementsByTagName('Partial')->length > 0) {
$partialBody = clone $requestBody;
$requestBody = $lastXML;
$xpath = new DomXPath($requestBody);
$xpath->registerNamespace('AirSync', 'uri:AirSync');
foreach ($partialBody->documentElement->childNodes as $child) {
if (! $child instanceof DOMElement) {
continue;
}
if ($child->tagName == 'Partial') {
continue;
}
if ($child->tagName == 'Collections') {
foreach ($child->getElementsByTagName('Collection') as $updatedCollection) {
$collectionId = $updatedCollection->getElementsByTagName('CollectionId')->item(0)->nodeValue;
$existingCollections = $xpath->query("//AirSync:Sync/AirSync:Collections/AirSync:Collection[AirSync:CollectionId='$collectionId']");
if ($existingCollections->length > 0) {
$existingCollection = $existingCollections->item(0);
foreach ($updatedCollection->childNodes as $updatedCollectionChild) {
if (! $updatedCollectionChild instanceof DOMElement) {
continue;
}
$duplicateChild = $existingCollection->getElementsByTagName($updatedCollectionChild->tagName);
if ($duplicateChild->length > 0) {
$existingCollection->replaceChild($requestBody->importNode($updatedCollectionChild, TRUE), $duplicateChild->item(0));
} else {
$existingCollection->appendChild($requestBody->importNode($updatedCollectionChild, TRUE));
}
}
} else {
$importedCollection = $requestBody->importNode($updatedCollection, TRUE);
}
}
} else {
$duplicateChild = $xpath->query("//AirSync:Sync/AirSync:{$child->tagName}");
if ($duplicateChild->length > 0) {
$requestBody->documentElement->replaceChild($requestBody->importNode($child, TRUE), $duplicateChild->item(0));
} else {
$requestBody->documentElement->appendChild($requestBody->importNode($child, TRUE));
}
}
}
}
$lastSyncCollection['lastXML'] = $this->_cleanUpXML($requestBody)->saveXML();
$device->lastsynccollection = Zend_Json::encode($lastSyncCollection);
return $requestBody;
}
}
diff --git a/lib/ext/Syncroton/Data/AData.php b/lib/ext/Syncroton/Data/AData.php
index 1a76021..447d138 100644
--- a/lib/ext/Syncroton/Data/AData.php
+++ b/lib/ext/Syncroton/Data/AData.php
@@ -1,357 +1,358 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Data
* @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
+ * @package Syncroton
+ * @subpackage Data
*/
-
abstract class Syncroton_Data_AData implements Syncroton_Data_IData
{
const LONGID_DELIMITER = "\xe2\x87\x94"; # UTF8 ⇔
/**
* used by unit tests only to simulated added folders
*/
public static $changedEntries = array();
/**
* used by unit tests only to simulated exhausted memory
*/
public static $exhaustedEntries = array();
/**
* the constructor
*
* @param Syncroton_Model_IDevice $_device
* @param DateTime $_timeStamp
*/
public function __construct(Syncroton_Model_IDevice $_device, DateTime $_timeStamp)
{
$this->_device = $_device;
$this->_timestamp = $_timeStamp;
$this->_db = Syncroton_Registry::getDatabase();
$this->_tablePrefix = 'Syncroton_';
$this->_ownerId = '1234';
}
/**
* return one folder identified by id
*
* @param string $id
* @throws Syncroton_Exception_NotFound
* @return Syncroton_Model_Folder
*/
public function getFolder($id)
{
$select = $this->_db->select()
->from($this->_tablePrefix . 'data_folder')
->where('owner_id = ?', $this->_ownerId)
->where('id = ?', $id);
$stmt = $this->_db->query($select);
$folder = $stmt->fetch();
$stmt = null; # see https://bugs.php.net/bug.php?id=44081
if ($folder === false) {
throw new Syncroton_Exception_NotFound("folder $id not found");
}
return new Syncroton_Model_Folder(array(
'serverId' => $folder['id'],
'displayName' => $folder['name'],
'type' => $folder['type'],
'parentId' => !empty($folder['parent_id']) ? $folder['parent_id'] : null
));
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IData::createFolder()
*/
public function createFolder(Syncroton_Model_IFolder $folder)
{
if (!in_array($folder->type, $this->_supportedFolderTypes)) {
throw new Syncroton_Exception_UnexpectedValue();
}
$id = !empty($folder->serverId) ? $folder->serverId : sha1(mt_rand(). microtime());
$this->_db->insert($this->_tablePrefix . 'data_folder', array(
'id' => $id,
'type' => $folder->type,
'name' => $folder->displayName,
'owner_id' => $this->_ownerId,
'parent_id' => $folder->parentId,
'creation_time' => $this->_timestamp->format('Y-m-d H:i:s')
));
return $this->getFolder($id);
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IData::createEntry()
*/
public function createEntry($_folderId, Syncroton_Model_IEntry $_entry)
{
$id = sha1(mt_rand(). microtime());
$this->_db->insert($this->_tablePrefix . 'data', array(
'id' => $id,
'class' => get_class($_entry),
'folder_id' => $_folderId,
'data' => serialize($_entry)
));
return $id;
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IData::deleteEntry()
*/
public function deleteEntry($_folderId, $_serverId, $_collectionData)
{
$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->serverId : $_folderId;
$result = $this->_db->delete($this->_tablePrefix . 'data', array('id = ?' => $_serverId));
return (bool) $result;
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IData::deleteFolder()
*/
public function deleteFolder($_folderId)
{
$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->serverId : $_folderId;
$result = $this->_db->delete($this->_tablePrefix . 'data', array('folder_id = ?' => $folderId));
$result = $this->_db->delete($this->_tablePrefix . 'data_folder', array('id = ?' => $folderId));
return (bool) $result;
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IData::emptyFolderContents()
*/
public function emptyFolderContents($folderId, $options)
{
return true;
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IData::getAllFolders()
*/
public function getAllFolders()
{
$select = $this->_db->select()
->from($this->_tablePrefix . 'data_folder')
->where('type IN (?)', $this->_supportedFolderTypes)
->where('owner_id = ?', $this->_ownerId);
$stmt = $this->_db->query($select);
$folders = $stmt->fetchAll();
$stmt = null; # see https://bugs.php.net/bug.php?id=44081
$result = array();
foreach ((array) $folders as $folder) {
$result[$folder['id']] = new Syncroton_Model_Folder(array(
'serverId' => $folder['id'],
'displayName' => $folder['name'],
'type' => $folder['type'],
'parentId' => $folder['parent_id']
));
}
return $result;
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IData::getChangedEntries()
*/
public function getChangedEntries($_folderId, DateTime $_startTimeStamp, DateTime $_endTimeStamp = NULL, $filterType = NULL)
{
if (!isset(Syncroton_Data_AData::$changedEntries[get_class($this)])) {
return array();
} else {
return Syncroton_Data_AData::$changedEntries[get_class($this)];
}
}
/**
* retrieve folders which were modified since last sync
*
* @param DateTime $startTimeStamp
* @param DateTime $endTimeStamp
* @return array list of Syncroton_Model_Folder
*/
public function getChangedFolders(DateTime $startTimeStamp, DateTime $endTimeStamp)
{
$select = $this->_db->select()
->from($this->_tablePrefix . 'data_folder')
->where('type IN (?)', $this->_supportedFolderTypes)
->where('owner_id = ?', $this->_ownerId)
->where('last_modified_time > ?', $startTimeStamp->format('Y-m-d H:i:s'))
->where('last_modified_time <= ?', $endTimeStamp->format('Y-m-d H:i:s'));
$stmt = $this->_db->query($select);
$folders = $stmt->fetchAll();
$stmt = null; # see https://bugs.php.net/bug.php?id=44081
$result = array();
foreach ((array) $folders as $folder) {
$result[$folder['id']] = new Syncroton_Model_Folder(array(
'serverId' => $folder['id'],
'displayName' => $folder['name'],
'type' => $folder['type'],
'parentId' => $folder['parent_id']
));
}
return $result;
}
/**
* @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;
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IData::getCountOfChanges()
*/
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->serverId, $folder->lastfiltertype);
$addedEntries = array_diff($allServerEntries, $allClientEntries);
$deletedEntries = array_diff($allClientEntries, $allServerEntries);
$changedEntries = $this->getChangedEntries($folder->serverId, $syncState->lastsync, null, $folder->lastfiltertype);
return count($addedEntries) + count($deletedEntries) + count($changedEntries);
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IData::getFileReference()
*/
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)
{
if (isset(self::$exhaustedEntries[get_class($this)]) && is_array(self::$exhaustedEntries[get_class($this)]) && in_array($serverId, self::$exhaustedEntries[get_class($this)])) {
throw new Syncroton_Exception_MemoryExhausted('memory exchausted for ' . $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);
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IData::hasChanges()
*/
public function hasChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState)
{
return !!$this->getCountOfChanges($contentBackend, $folder, $syncState);
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IData::moveItem()
*/
public function moveItem($_srcFolderId, $_serverId, $_dstFolderId)
{
$this->_db->update($this->_tablePrefix . 'data', array(
'folder_id' => $_dstFolderId,
), array(
'id = ?' => $_serverId
));
return $_serverId;
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IData::updateEntry()
*/
public function updateEntry($_folderId, $_serverId, Syncroton_Model_IEntry $_entry)
{
$this->_db->update($this->_tablePrefix . 'data', array(
'folder_id' => $_folderId,
'data' => serialize($_entry)
), array(
'id = ?' => $_serverId
));
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IData::updateFolder()
*/
public function updateFolder(Syncroton_Model_IFolder $folder)
{
$this->_db->update($this->_tablePrefix . 'data_folder', array(
'name' => $folder->displayName,
'parent_id' => $folder->parentId,
'last_modified_time' => $this->_timestamp->format('Y-m-d H:i:s')
), array(
'id = ?' => $folder->serverId,
'owner_id = ?' => $this->_ownerId
));
return $this->getFolder($folder->serverId);
}
}
diff --git a/lib/ext/Syncroton/Data/Calendar.php b/lib/ext/Syncroton/Data/Calendar.php
index 66c8d25..04f3529 100644
--- a/lib/ext/Syncroton/Data/Calendar.php
+++ b/lib/ext/Syncroton/Data/Calendar.php
@@ -1,34 +1,36 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Data
* @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
+ * @package Syncroton
+ * @subpackage Data
*/
class Syncroton_Data_Calendar extends Syncroton_Data_AData implements Syncroton_Data_IDataCalendar
{
protected $_supportedFolderTypes = array(
Syncroton_Command_FolderSync::FOLDERTYPE_CALENDAR,
Syncroton_Command_FolderSync::FOLDERTYPE_CALENDAR_USER_CREATED
);
/**
* set attendee status for meeting
*
* @param Syncroton_Model_MeetingResponse $request the meeting response
* @return string id of new calendar entry
*/
public function setAttendeeStatus(Syncroton_Model_MeetingResponse $reponse)
{
return $reponse->requestId;
}
}
diff --git a/lib/ext/Syncroton/Data/Contacts.php b/lib/ext/Syncroton/Data/Contacts.php
index a9b8d58..86258a0 100644
--- a/lib/ext/Syncroton/Data/Contacts.php
+++ b/lib/ext/Syncroton/Data/Contacts.php
@@ -1,81 +1,83 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Data
* @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
+ * @package Syncroton
+ * @subpackage Data
*/
class Syncroton_Data_Contacts extends Syncroton_Data_AData implements Syncroton_Data_IDataSearch
{
protected $_supportedFolderTypes = array(
Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT,
Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED
);
/**
* (non-PHPdoc)
* @see Syncroton_Data_IDataSearch::getSearchEntry()
*/
public function getSearchEntry($longId, $options)
{
list($collectionId, $serverId) = explode(Syncroton_Data_AData::LONGID_DELIMITER, $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,
'picture' => new Syncroton_Model_GALPicture(array('status' => 1, 'data' => 'abc'))
));
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IDataSearch::search()
*/
public function search(Syncroton_Model_StoreRequest $store)
{
$storeResponse = new Syncroton_Model_StoreResponse();
$serverIds = $this->getServerEntries('addressbookFolderId', Syncroton_Command_Sync::FILTER_NOTHING);
$total = 0;
$found = array();
foreach ($serverIds as $serverId) {
$contact = $this->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => 'addressbookFolderId')), $serverId);
if ($contact->firstName == $store->query) {
$total++;
if (count($found) == $store->options['range'][1]+1) {
continue;
}
$found[] = new Syncroton_Model_StoreResponseResult(array(
'longId' => 'addressbookFolderId' . Syncroton_Data_AData::LONGID_DELIMITER . $serverId,
'properties' => $this->getSearchEntry('addressbookFolderId' . Syncroton_Data_AData::LONGID_DELIMITER . $serverId, $store->options)
));
}
}
if (count($found) > 0) {
$storeResponse->result = $found;
$storeResponse->range = array(0, count($found) - 1);
$storeResponse->total = $total;
} else {
$storeResponse->total = $total;
}
return $storeResponse;
}
}
diff --git a/lib/ext/Syncroton/Data/Email.php b/lib/ext/Syncroton/Data/Email.php
index 5005c97..b55f9e8 100644
--- a/lib/ext/Syncroton/Data/Email.php
+++ b/lib/ext/Syncroton/Data/Email.php
@@ -1,86 +1,87 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Data
* @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
+ * @package Syncroton
+ * @subpackage Data
*/
class Syncroton_Data_Email extends Syncroton_Data_AData implements Syncroton_Data_IDataEmail
{
protected $_supportedFolderTypes = array(
Syncroton_Command_FolderSync::FOLDERTYPE_DELETEDITEMS,
Syncroton_Command_FolderSync::FOLDERTYPE_DRAFTS,
Syncroton_Command_FolderSync::FOLDERTYPE_INBOX,
Syncroton_Command_FolderSync::FOLDERTYPE_MAIL_USER_CREATED,
Syncroton_Command_FolderSync::FOLDERTYPE_OUTBOX,
Syncroton_Command_FolderSync::FOLDERTYPE_SENTMAIL
);
/**
* (non-PHPdoc)
* @see Syncroton_Data_IDataEmail::forwardEmail()
*/
public function forwardEmail($source, $inputStream, $saveInSent, $replaceMime)
{
if ($inputStream == 'triggerException') {
throw new Syncroton_Exception_Status(Syncroton_Exception_Status::MAILBOX_SERVER_OFFLINE);
}
// forward email
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_AData::getFileReference()
*/
public function getFileReference($fileReference)
{
list($messageId, $partId) = explode(Syncroton_Data_AData::LONGID_DELIMITER, $fileReference, 2);
// example code
return new Syncroton_Model_FileReference(array(
'contentType' => 'text/plain',
'data' => 'Lars'
));
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IDataEmail::replyEmail()
*/
public function replyEmail($source, $inputStream, $saveInSent, $replaceMime)
{
// forward email
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_AData::updateEntry()
*/
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)
{
if ($inputStream == 'triggerException') {
throw new Syncroton_Exception_Status(Syncroton_Exception_Status::MAILBOX_SERVER_OFFLINE);
}
// send email
}
}
diff --git a/lib/ext/Syncroton/Data/Factory.php b/lib/ext/Syncroton/Data/Factory.php
index 1be6946..2904b28 100644
--- a/lib/ext/Syncroton/Data/Factory.php
+++ b/lib/ext/Syncroton/Data/Factory.php
@@ -1,74 +1,75 @@
<?php
-
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Data
* @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
+ * @package Syncroton
+ * @subpackage Data
*/
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 Syncroton_Exception_UnexpectedValue('invalid class type provided');
breeak;
}
$class = new $className($_device, $_timeStamp);
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/IData.php b/lib/ext/Syncroton/Data/IData.php
index 2d26335..a7e05b4 100644
--- a/lib/ext/Syncroton/Data/IData.php
+++ b/lib/ext/Syncroton/Data/IData.php
@@ -1,127 +1,127 @@
<?php
-
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Data
* @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
+ * @package Syncroton
+ * @subpackage Data
*/
-
interface Syncroton_Data_IData
{
/**
* create new entry
*
* @param string $folderId
* @param Syncroton_Model_IEntry $entry
* @return string id of created entry
*/
public function createEntry($folderId, Syncroton_Model_IEntry $entry);
/**
* create a new folder in backend
*
* @param Syncroton_Model_IFolder $folder
* @return Syncroton_Model_IFolder
*/
public function createFolder(Syncroton_Model_IFolder $folder);
/**
* delete entry in backend
*
* @param string $_folderId
* @param string $_serverId
* @param unknown_type $_collectionData
*/
public function deleteEntry($_folderId, $_serverId, $_collectionData);
/**
* delete folder
*
* @param string $folderId
*/
public function deleteFolder($folderId);
/**
* empty folder
*
* @param string $folderId
* @param array $options
*/
public function emptyFolderContents($folderId, $options);
/**
* return list off all folders
* @return array of Syncroton_Model_IFolder
*/
public function getAllFolders();
public function getChangedEntries($folderId, DateTime $startTimeStamp, DateTime $endTimeStamp = NULL, $filterType = NULL);
/**
* retrieve folders which were modified since last sync
*
* @param DateTime $startTimeStamp
* @param DateTime $endTimeStamp
*/
public function getChangedFolders(DateTime $startTimeStamp, DateTime $endTimeStamp);
public function getCountOfChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState);
/**
*
* @param Syncroton_Model_SyncCollection $collection
* @param string $serverId
* @return Syncroton_Model_IEntry
*/
public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId);
/**
*
* @param unknown_type $fileReference
* @return Syncroton_Model_FileReference
*/
public function getFileReference($fileReference);
/**
* return array of all id's stored in folder
*
* @param Syncroton_Model_IFolder|string $folderId
* @param string $filter
* @return array
*/
public function getServerEntries($folderId, $filter);
/**
* return true if any data got modified in the backend
*
* @param Syncroton_Backend_IContent $contentBackend
* @param Syncroton_Model_IFolder $folder
* @param Syncroton_Model_ISyncState $syncState
* @return bool
*/
public function hasChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState);
public function moveItem($srcFolderId, $serverId, $dstFolderId);
/**
* update existing entry
*
* @param string $folderId
* @param string $serverId
* @param Syncroton_Model_IEntry $entry
* @return string id of updated entry
*/
public function updateEntry($folderId, $serverId, Syncroton_Model_IEntry $entry);
public function updateFolder(Syncroton_Model_IFolder $folder);
}
diff --git a/lib/ext/Syncroton/Data/IDataCalendar.php b/lib/ext/Syncroton/Data/IDataCalendar.php
index 2ea6831..da04671 100644
--- a/lib/ext/Syncroton/Data/IDataCalendar.php
+++ b/lib/ext/Syncroton/Data/IDataCalendar.php
@@ -1,26 +1,28 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Data
* @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>
*/
/**
* interface for extended calendar backend
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Data
*/
interface Syncroton_Data_IDataCalendar
{
/**
* set attendee status for meeting
*
* @param Syncroton_Model_MeetingResponse $request the meeting response
* @return string id of new calendar entry
*/
public function setAttendeeStatus(Syncroton_Model_MeetingResponse $request);
}
diff --git a/lib/ext/Syncroton/Data/IDataEmail.php b/lib/ext/Syncroton/Data/IDataEmail.php
index bca70b9..ed067d2 100644
--- a/lib/ext/Syncroton/Data/IDataEmail.php
+++ b/lib/ext/Syncroton/Data/IDataEmail.php
@@ -1,44 +1,46 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Data
* @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>
*/
/**
* interface for extended email backend
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Data
*/
interface Syncroton_Data_IDataEmail
{
/**
* send an email
*
* @param resource $inputStream
* @param boolean $saveInSent
*/
public function sendEmail($inputStream, $saveInSent);
/**
* forward an email
*
* @param string|array $source is either a string(LongId) or an array with following properties collectionId, itemId and instanceId
* @param string $inputStream
* @param string $saveInSent
*/
public function forwardEmail($source, $inputStream, $saveInSent, $replaceMime);
/**
* reply to an email
*
* @param string|array $source is either a string(LongId) or an array with following properties collectionId, itemId and instanceId
* @param string $inputStream
* @param string $saveInSent
*/
public function replyEmail($source, $inputStream, $saveInSent, $replaceMime);
}
diff --git a/lib/ext/Syncroton/Data/IDataSearch.php b/lib/ext/Syncroton/Data/IDataSearch.php
index 5546f8d..987faa4 100644
--- a/lib/ext/Syncroton/Data/IDataSearch.php
+++ b/lib/ext/Syncroton/Data/IDataSearch.php
@@ -1,29 +1,30 @@
<?php
-
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Data
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @copyright Copyright (c) 2012 Kolab SYstems AG (http://www.kolabsys.com)
* @author Lars Kneschke <l.kneschke@metaways.de>
* @author Aleksander Machniak <machniak@kolabsys.com>
*/
/**
* class to handle ActiveSync Search command
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Data
*/
interface Syncroton_Data_IDataSearch
{
/**
* Search command handler
*
* @param Syncroton_Model_StoreRequest $store Search query parameters
*
* @return Syncroton_Model_StoreResponse
*/
public function search(Syncroton_Model_StoreRequest $store);
}
diff --git a/lib/ext/Syncroton/Data/Tasks.php b/lib/ext/Syncroton/Data/Tasks.php
index a0c4756..e44feaf 100644
--- a/lib/ext/Syncroton/Data/Tasks.php
+++ b/lib/ext/Syncroton/Data/Tasks.php
@@ -1,24 +1,25 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Data
* @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
+ * @package Syncroton
+ * @subpackage Data
*/
-
class Syncroton_Data_Tasks extends Syncroton_Data_AData
{
protected $_supportedFolderTypes = array(
Syncroton_Command_FolderSync::FOLDERTYPE_TASK,
Syncroton_Command_FolderSync::FOLDERTYPE_TASK_USER_CREATED
);
}
diff --git a/lib/ext/Syncroton/Model/Device.php b/lib/ext/Syncroton/Model/Device.php
index a46ef2d..342c1fa 100644
--- a/lib/ext/Syncroton/Model/Device.php
+++ b/lib/ext/Syncroton/Model/Device.php
@@ -1,46 +1,53 @@
<?php
-
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage 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
+ * @package Syncroton
+ * @subpackage Model
*/
class Syncroton_Model_Device extends Syncroton_Model_AEntry implements Syncroton_Model_IDevice
{
const TYPE_IPHONE = 'iphone';
const TYPE_WEBOS = 'webos';
const TYPE_ANDROID = 'android';
const TYPE_ANDROID_40 = 'android40';
const TYPE_SMASUNGGALAXYS2 = 'samsunggti9100'; // Samsung Galaxy S-3
+ const TYPE_BLACKBERRY = 'blackberry';
/**
* Returns major firmware version of this device
*
* @return int/string
*/
public function getMajorVersion()
{
- switch ($this->devicetype) {
+ switch (strtolower($this->devicetype)) {
+ case Syncroton_Model_Device::TYPE_BLACKBERRY:
+ if (preg_match('/(.+)\/(.+)/', $this->useragent, $matches)) {
+ list(, $name, $version) = $matches;
+ return $version;
+ }
+ break;
+
case Syncroton_Model_Device::TYPE_IPHONE:
if (preg_match('/(.+)\/(\d+)\.(\d+)/', $this->useragent, $matches)) {
list(, $name, $majorVersion, $minorVersion) = $matches;
return $majorVersion;
}
break;
- default:
- break;
}
return 0;
}
}
diff --git a/lib/ext/Syncroton/Model/DeviceInformation.php b/lib/ext/Syncroton/Model/DeviceInformation.php
index 0a5c516..d1c8fff 100644
--- a/lib/ext/Syncroton/Model/DeviceInformation.php
+++ b/lib/ext/Syncroton/Model/DeviceInformation.php
@@ -1,40 +1,42 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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 device information
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Model
* @property string friendlyName
* @property string iMEI
* @property string mobileOperator
* @property string model
* @property string oS
* @property string oSLanguage
* @property string phoneNumber
*/
class Syncroton_Model_DeviceInformation extends Syncroton_Model_AXMLEntry
{
protected $_xmlBaseElement = 'Set';
protected $_properties = array(
'Settings' => array(
'enableOutboundSMS' => array('type' => 'number'),
'friendlyName' => array('type' => 'string'),
'iMEI' => array('type' => 'string'),
'mobileOperator' => array('type' => 'string'),
'model' => array('type' => 'string'),
'oS' => array('type' => 'string'),
'oSLanguage' => array('type' => 'string'),
'phoneNumber' => array('type' => 'string')
),
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/Email.php b/lib/ext/Syncroton/Model/Email.php
index 29fd495..cd448c8 100644
--- a/lib/ext/Syncroton/Model/Email.php
+++ b/lib/ext/Syncroton/Model/Email.php
@@ -1,88 +1,90 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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 email
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Model
* @property array attachments
* @property string contentType
* @property array flag
* @property Syncroton_Model_EmailBody body
* @property array cc
* @property array to
* @property int lastVerbExecuted
* @property DateTime lastVerbExecutionTime
* @property int read
*/
class Syncroton_Model_Email extends Syncroton_Model_AXMLEntry
{
const LASTVERB_UNKNOWN = 0;
const LASTVERB_REPLYTOSENDER = 1;
const LASTVERB_REPLYTOALL = 2;
const LASTVERB_FORWARD = 3;
protected $_xmlBaseElement = 'ApplicationData';
protected $_properties = array(
'AirSyncBase' => array(
'attachments' => array('type' => 'container', 'childElement' => 'attachment', 'class' => 'Syncroton_Model_EmailAttachment'),
'contentType' => array('type' => 'string'),
'body' => array('type' => 'container', 'class' => 'Syncroton_Model_EmailBody'),
'nativeBodyType' => array('type' => 'number'),
),
'Email' => array(
'busyStatus' => array('type' => 'number'),
'categories' => array('type' => 'container', 'childElement' => 'category', 'supportedSince' => '14.0'),
'cc' => array('type' => 'string'),
'completeTime' => array('type' => 'datetime'),
'contentClass' => array('type' => 'string'),
'dateReceived' => array('type' => 'datetime'),
'disallowNewTimeProposal' => array('type' => 'number'),
'displayTo' => array('type' => 'string'),
'dTStamp' => array('type' => 'datetime'),
'endTime' => array('type' => 'datetime'),
'flag' => array('type' => 'container', 'class' => 'Syncroton_Model_EmailFlag'),
'from' => array('type' => 'string'),
'globalObjId' => array('type' => 'string'),
'importance' => array('type' => 'number'),
'instanceType' => array('type' => 'number'),
'internetCPID' => array('type' => 'string'),
'location' => array('type' => 'string'),
'meetingRequest' => array('type' => 'container', 'class' => 'Syncroton_Model_EmailMeetingRequest'),
'messageClass' => array('type' => 'string'),
'organizer' => array('type' => 'string'),
'read' => array('type' => 'number'),
'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'),
),
'Email2' => array(
'accountId' => array('type' => 'string', 'supportedSince' => '14.1'),
'conversationId' => array('type' => 'byteArray', 'supportedSince' => '14.0'),
'conversationIndex' => array('type' => 'byteArray', 'supportedSince' => '14.0'),
'lastVerbExecuted' => array('type' => 'number', 'supportedSince' => '14.0'),
'lastVerbExecutionTime' => array('type' => 'datetime', 'supportedSince' => '14.0'),
'meetingMessageType' => array('type' => 'number', 'supportedSince' => '14.1'),
'receivedAsBcc' => array('type' => 'number', 'supportedSince' => '14.0'),
'sender' => array('type' => 'string', 'supportedSince' => '14.0'),
'umCallerID' => array('type' => 'string', 'supportedSince' => '14.0'),
'umUserNotes' => array('type' => 'string', 'supportedSince' => '14.0'),
),
);
}
diff --git a/lib/ext/Syncroton/Model/EmailAttachment.php b/lib/ext/Syncroton/Model/EmailAttachment.php
index 48d9866..bea0c1e 100644
--- a/lib/ext/Syncroton/Model/EmailAttachment.php
+++ b/lib/ext/Syncroton/Model/EmailAttachment.php
@@ -1,41 +1,43 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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 event
*
- * @package Model
+ * @package Syncroton
+ * @subpackage 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_AXMLEntry
{
protected $_xmlBaseElement = 'Attachment';
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', 'supportedSince' => '14.0'),
'umAttOrder' => array('type' => 'number', 'supportedSince' => '14.0'),
),
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/EmailBody.php b/lib/ext/Syncroton/Model/EmailBody.php
index 3d465b8..b4d29ab 100644
--- a/lib/ext/Syncroton/Model/EmailBody.php
+++ b/lib/ext/Syncroton/Model/EmailBody.php
@@ -1,42 +1,44 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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 AirSyncBase:Body
*
- * @package Model
+ * @package Syncroton
+ * @subpackage 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_AXMLEntry
{
const TYPE_PLAINTEXT = 1;
const TYPE_HTML = 2;
const TYPE_RTF = 3;
const TYPE_MIME = 4;
protected $_xmlBaseElement = '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', 'supportedSince' => '14.0'),
),
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/EmailFlag.php b/lib/ext/Syncroton/Model/EmailFlag.php
index 258fd46..685623e 100644
--- a/lib/ext/Syncroton/Model/EmailFlag.php
+++ b/lib/ext/Syncroton/Model/EmailFlag.php
@@ -1,58 +1,60 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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)
* @copyright Copyright (c) 2012-2012 Kolab Systems AG (http://www.kolabsys.com)
* @author Lars Kneschke <l.kneschke@metaways.de>
* @author Aleksander Machniak <machniak@kolabsys.com>
*/
/**
* class to handle ActiveSync Flag element
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Model
* @property DateTime CompleteTime
* @property DateTime DateCompleted
* @property DateTime DueDate
* @property string FlagType
* @property DateTime OrdinalDate
* @property int ReminderSet
* @property DateTime ReminderTime
* @property DateTime StartDate
* @property string Status
* @property string Subject
* @property string SubOrdinalDate
* @property DateTime UtcDueDate
* @property DateTime UtcStartDate
*/
class Syncroton_Model_EmailFlag extends Syncroton_Model_AXMLEntry
{
const STATUS_CLEARED = 0;
const STATUS_COMPLETE = 1;
const STATUS_ACTIVE = 2;
protected $_xmlBaseElement = 'Flag';
protected $_properties = array(
'Email' => array(
'completeTime' => array('type' => 'datetime'),
'flagType' => array('type' => 'string'),
'status' => array('type' => 'number'),
),
'Tasks' => array(
'dateCompleted' => array('type' => 'datetime'),
'dueDate' => array('type' => 'datetime'),
'ordinalDate' => array('type' => 'datetime'),
'reminderSet' => array('type' => 'number'),
'reminderTime' => array('type' => 'datetime'),
'startDate' => array('type' => 'datetime'),
'subject' => array('type' => 'string'),
'subOrdinalDate' => array('type' => 'string'),
'utcStartDate' => array('type' => 'datetime'),
'utcDueDate' => array('type' => 'datetime'),
),
);
}
diff --git a/lib/ext/Syncroton/Model/Event.php b/lib/ext/Syncroton/Model/Event.php
index 0fccd2a..79686a9 100644
--- a/lib/ext/Syncroton/Model/Event.php
+++ b/lib/ext/Syncroton/Model/Event.php
@@ -1,125 +1,127 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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 event
*
- * @package Model
+ * @package Syncroton
+ * @subpackage 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_AXMLEntry
{
/**
* 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';
protected $_properties = array(
'AirSyncBase' => array(
'body' => array('type' => 'container', 'class' => 'Syncroton_Model_EmailBody')
),
'Calendar' => array(
'allDayEvent' => array('type' => 'number'),
'appointmentReplyTime' => array('type' => 'datetime'),
'attendees' => array('type' => 'container', 'childElement' => 'attendee', 'class' => 'Syncroton_Model_EventAttendee'),
'busyStatus' => array('type' => 'number'),
'categories' => array('type' => 'container', 'childElement' => 'category'),
'disallowNewTimeProposal' => array('type' => 'number'),
'dtStamp' => array('type' => 'datetime'),
'endTime' => array('type' => 'datetime'),
'exceptions' => array('type' => 'container', 'childElement' => 'exception', 'class' => 'Syncroton_Model_EventException'),
'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'),
'sensitivity' => array('type' => 'number'),
'startTime' => array('type' => 'datetime'),
'subject' => array('type' => 'string'),
'timezone' => array('type' => 'timezone'),
'uID' => array('type' => 'string'),
)
);
/**
* (non-PHPdoc)
* @see Syncroton_Model_IEntry::appendXML()
* @todo handle Attendees element
*/
public function appendXML(DOMElement $domParrent, Syncroton_Model_IDevice $device)
{
parent::appendXML($domParrent, $device);
$exceptionElements = $domParrent->getElementsByTagName('Exception');
$parentFields = array('AllDayEvent'/*, 'Attendees'*/, 'Body', 'BusyStatus'/*, 'Categories'*/, 'DtStamp', 'EndTime', 'Location', 'MeetingStatus', 'Reminder', 'ResponseType', 'Sensitivity', 'StartTime', 'Subject');
if ($exceptionElements->length > 0) {
$mainEventElement = $exceptionElements->item(0)->parentNode->parentNode;
foreach ($mainEventElement->childNodes as $childNode) {
if (in_array($childNode->localName, $parentFields)) {
foreach ($exceptionElements as $exception) {
$elementsToLeftOut = $exception->getElementsByTagName($childNode->localName);
foreach ($elementsToLeftOut as $elementToLeftOut) {
if ($elementToLeftOut->nodeValue == $childNode->nodeValue) {
$exception->removeChild($elementToLeftOut);
}
}
}
}
}
}
}
/**
* some elements of an exception can be left out, if they have the same value
* like the main event
*
* this function copies these elements to the exception for backends which need
* this elements in the exceptions too. Tine 2.0 needs this for example.
*/
public function copyFieldsFromParent()
{
if (isset($this->_elements['exceptions']) && is_array($this->_elements['exceptions'])) {
foreach ($this->_elements['exceptions'] as $exception) {
// no need to update deleted exceptions
if ($exception->deleted == 1) {
continue;
}
$parentFields = array('allDayEvent', 'attendees', 'body', 'busyStatus', 'categories', 'dtStamp', 'endTime', 'location', 'meetingStatus', 'reminder', 'responseType', 'sensitivity', 'startTime', 'subject');
foreach ($parentFields as $field) {
if (!isset($exception->$field) && isset($this->_elements[$field])) {
$exception->$field = $this->_elements[$field];
}
}
}
}
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/EventAttendee.php b/lib/ext/Syncroton/Model/EventAttendee.php
index 82257db..8803427 100644
--- a/lib/ext/Syncroton/Model/EventAttendee.php
+++ b/lib/ext/Syncroton/Model/EventAttendee.php
@@ -1,51 +1,53 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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 event
*
- * @package Model
+ * @package Syncroton
+ * @subpackage 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_AXMLEntry
{
protected $_xmlBaseElement = 'Attendee';
/**
* 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;
protected $_properties = array(
'Calendar' => array(
'attendeeStatus' => array('type' => 'number'),
'attendeeType' => array('type' => 'number'),
'email' => array('type' => 'string'),
'name' => array('type' => 'string'),
)
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/EventException.php b/lib/ext/Syncroton/Model/EventException.php
index 63ae15e..06aece9 100644
--- a/lib/ext/Syncroton/Model/EventException.php
+++ b/lib/ext/Syncroton/Model/EventException.php
@@ -1,52 +1,54 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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 event
*
- * @package Model
+ * @package Syncroton
+ * @subpackage 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_AXMLEntry
{
protected $_xmlBaseElement = 'Exception';
protected $_dateTimeFormat = "Ymd\THis\Z";
protected $_properties = array(
'AirSyncBase' => array(
'body' => array('type' => 'container', 'class' => 'Syncroton_Model_EmailBody')
),
'Calendar' => array(
'allDayEvent' => array('type' => 'number'),
'appointmentReplyTime' => array('type' => 'datetime'),
'attendees' => array('type' => 'container', 'childElement' => 'attendee', 'class' => 'Syncroton_Model_EventAttendee'),
'busyStatus' => array('type' => 'number'),
'categories' => array('type' => 'container', 'childElement' => 'category'),
'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 3fe0c94..ac53984 100644
--- a/lib/ext/Syncroton/Model/EventRecurrence.php
+++ b/lib/ext/Syncroton/Model/EventRecurrence.php
@@ -1,70 +1,72 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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 event
*
- * @package Model
+ * @package Syncroton
+ * @subpackage 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_AXMLEntry
{
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;
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'),
)
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/FileReference.php b/lib/ext/Syncroton/Model/FileReference.php
index b54bb7d..a34d37c 100644
--- a/lib/ext/Syncroton/Model/FileReference.php
+++ b/lib/ext/Syncroton/Model/FileReference.php
@@ -1,44 +1,46 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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 event
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Model
* @property string ContentType
* @property string Data
*/
class Syncroton_Model_FileReference extends Syncroton_Model_AXMLEntry
{
protected $_xmlBaseElement = 'ApplicationData';
protected $_properties = array(
'AirSyncBase' => array(
'contentType' => array('type' => 'string'),
),
'ItemOperations' => array(
'data' => array('type' => 'string', 'encoding' => 'base64'),
'part' => array('type' => 'number')
)
);
/**
*
* @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 9655f9a..4da0a97 100644
--- a/lib/ext/Syncroton/Model/Folder.php
+++ b/lib/ext/Syncroton/Model/Folder.php
@@ -1,38 +1,39 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage 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
+ * @package Syncroton
+ * @subpackage Model
*/
-
class Syncroton_Model_Folder extends Syncroton_Model_AXMLEntry implements Syncroton_Model_IFolder
{
protected $_xmlBaseElement = array('FolderUpdate', 'FolderCreate');
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'),
'ownerId' => array('type' => 'string'),
'class' => array('type' => 'string'),
'creationTime' => array('type' => 'datetime'),
'lastfiltertype' => array('type' => 'number')
),
);
}
diff --git a/lib/ext/Syncroton/Model/IContent.php b/lib/ext/Syncroton/Model/IContent.php
index 2ce315a..62eab66 100644
--- a/lib/ext/Syncroton/Model/IContent.php
+++ b/lib/ext/Syncroton/Model/IContent.php
@@ -1,28 +1,28 @@
<?php
-
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage 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
+ * @package Syncroton
+ * @subpackage Model
* @property string id
* @property string device_id
* @property string folder_id
* @property string contentid
* @property DateTime creation_time
* @property string creation_synckey
* @property string is_deleted
*/
-
interface Syncroton_Model_IContent
{
}
diff --git a/lib/ext/Syncroton/Model/IDevice.php b/lib/ext/Syncroton/Model/IDevice.php
index 944a1f7..9765345 100644
--- a/lib/ext/Syncroton/Model/IDevice.php
+++ b/lib/ext/Syncroton/Model/IDevice.php
@@ -1,51 +1,52 @@
<?php
-
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage 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
+ * @package Syncroton
+ * @subpackage Model
* @property string id
* @property string deviceid
* @property string devicetype
* @property string policykey
* @property string policyId
* @property string ownerId
* @property string acsversion
* @property string pingfolder
* @property string pinglifetime
* @property string remotewipe
* @property string useragent
* @property string imei
* @property string model
* @property string friendlyname
* @property string os
* @property string oslanguage
* @property string phonenumber
* @property string pinglifetime
* @property string pingfolder
* @property string contactsfilter_id
* @property string calendarfilter_id
* @property string tasksfilter_id
* @property string emailfilter_id
* @property string lastsynccollection
+ * @property DateTime lastping
*/
interface Syncroton_Model_IDevice extends Syncroton_Model_IEntry
{
/**
* Returns major firmware version of this device
*
* @return int/string
*/
public function getMajorVersion();
-
}
diff --git a/lib/ext/Syncroton/Model/IEntry.php b/lib/ext/Syncroton/Model/IEntry.php
index c2c9ce7..158f389 100644
--- a/lib/ext/Syncroton/Model/IEntry.php
+++ b/lib/ext/Syncroton/Model/IEntry.php
@@ -1,41 +1,42 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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 Model
+ * @package Syncroton
+ * @subpackage Model
* @property string class
* @property string collectionId
* @property bool deletesAsMoves
* @property bool getChanges
* @property string syncKey
* @property int windowSize
*/
-
interface Syncroton_Model_IEntry
{
/**
*
* @param unknown_type $properties
*/
public function __construct($properties = null);
/**
* return true if data have got changed after initial data got loaded via constructor
*/
public function isDirty();
/**
*
* @param array $properties
*/
public function setFromArray(array $properties);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/IFolder.php b/lib/ext/Syncroton/Model/IFolder.php
index f27f523..113887d 100644
--- a/lib/ext/Syncroton/Model/IFolder.php
+++ b/lib/ext/Syncroton/Model/IFolder.php
@@ -1,31 +1,30 @@
<?php
-
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage 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
+ * @package Syncroton
+ * @subpackage Model
* @property string id
* @property string deviceId
* @property string class
* @property string serverId
* @property string parentId
* @property string displayName
* @property string creationTime
* @property string lastfiltertype
* @property string type
*/
-
interface Syncroton_Model_IFolder
{
-
}
diff --git a/lib/ext/Syncroton/Model/IPolicy.php b/lib/ext/Syncroton/Model/IPolicy.php
index b6929d3..999ecc5 100644
--- a/lib/ext/Syncroton/Model/IPolicy.php
+++ b/lib/ext/Syncroton/Model/IPolicy.php
@@ -1,32 +1,32 @@
<?php
-
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage 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
+ * @package Syncroton
+ * @subpackage Model
* @property string id
* @property string deviceid
* @property string devicetype
* @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_IPolicy
{
}
diff --git a/lib/ext/Syncroton/Model/ISyncState.php b/lib/ext/Syncroton/Model/ISyncState.php
index cd1fc2a..133af6c 100644
--- a/lib/ext/Syncroton/Model/ISyncState.php
+++ b/lib/ext/Syncroton/Model/ISyncState.php
@@ -1,25 +1,26 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage 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
+ * @package Syncroton
+ * @subpackage Model
* @property string device_id
* @property string type
* @property string counter
* @property DateTime lastsync
* @property string pendingdata
*/
-
interface Syncroton_Model_ISyncState
{
}
diff --git a/lib/ext/Syncroton/Model/IXMLEntry.php b/lib/ext/Syncroton/Model/IXMLEntry.php
index 7b3ad0f..adc18cd 100644
--- a/lib/ext/Syncroton/Model/IXMLEntry.php
+++ b/lib/ext/Syncroton/Model/IXMLEntry.php
@@ -1,39 +1,40 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2013 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync contact
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Model
*/
-
interface Syncroton_Model_IXMLEntry extends Syncroton_Model_IEntry
{
/**
*
* @param DOMElement $_domParrent
* @param Syncroton_Model_IDevice $device
*/
public function appendXML(DOMElement $_domParrent, Syncroton_Model_IDevice $device);
/**
* return array of valid properties
*
* @return array
*/
public function getProperties();
/**
*
* @param SimpleXMLElement $xmlCollection
* @throws InvalidArgumentException
*/
public function setFromSimpleXMLElement(SimpleXMLElement $properties);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/MeetingResponse.php b/lib/ext/Syncroton/Model/MeetingResponse.php
index fded62d..0a8d321 100644
--- a/lib/ext/Syncroton/Model/MeetingResponse.php
+++ b/lib/ext/Syncroton/Model/MeetingResponse.php
@@ -1,46 +1,47 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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 MeetingResponse request
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Model
* @property int userResponse
* @property string collectionId
* @property string calendarId
* @property string requestId
* @property string instanceId
* @property string longId
*/
-
class Syncroton_Model_MeetingResponse extends Syncroton_Model_AXMLEntry
{
protected $_xmlBaseElement = 'Request';
/**
* attendee status
*/
const RESPONSE_ACCEPTED = 1;
const RESPONSE_TENTATIVE = 2;
const RESPONSE_DECLINED = 3;
protected $_properties = array(
'MeetingResponse' => array(
'userResponse' => array('type' => 'number'),
'collectionId' => array('type' => 'string'),
'calendarId' => array('type' => 'string'),
'requestId' => array('type' => 'string'),
'instanceId' => array('type' => 'datetime'),
),
'Search' => array(
'longId' => array('type' => 'string')
)
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/Policy.php b/lib/ext/Syncroton/Model/Policy.php
index 9031d51..41ae331 100644
--- a/lib/ext/Syncroton/Model/Policy.php
+++ b/lib/ext/Syncroton/Model/Policy.php
@@ -1,75 +1,76 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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 Sync command
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Model
*/
-
class Syncroton_Model_Policy extends Syncroton_Model_AXMLEntry 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/SendMail.php b/lib/ext/Syncroton/Model/SendMail.php
index f8641fd..337a7be 100644
--- a/lib/ext/Syncroton/Model/SendMail.php
+++ b/lib/ext/Syncroton/Model/SendMail.php
@@ -1,33 +1,34 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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)
* @copyright Copyright (c) 2012-2012 Kolab SYstems AG (http://www.kolabsys.com)
* @author Lars Kneschke <l.kneschke@metaways.de>
* @author Aleksander Machniak <machniak@kolabsys.com>
*/
/**
* Class to handle ActiveSync SendMail element
*
* @package Syncroton
* @subpackage Model
*/
class Syncroton_Model_SendMail extends Syncroton_Model_AXMLEntry
{
protected $_properties = array(
'ComposeMail' => array(
'accountId' => array('type' => 'string'),
'clientId' => array('type' => 'string'),
'mime' => array('type' => 'byteArray'),
'saveInSentItems' => array('type' => 'string'),
'status' => array('type' => 'number'),
),
'RightsManagement' => array(
'templateID' => array('type' => 'string'),
)
);
}
diff --git a/lib/ext/Syncroton/Model/SmartForward.php b/lib/ext/Syncroton/Model/SmartForward.php
index 5d34c9e..a403824 100644
--- a/lib/ext/Syncroton/Model/SmartForward.php
+++ b/lib/ext/Syncroton/Model/SmartForward.php
@@ -1,35 +1,36 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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)
* @copyright Copyright (c) 2012-2012 KolabSYstems AG (http://www.kolabsys.com)
* @author Lars Kneschke <l.kneschke@metaways.de>
* @author Aleksander Machniak <machniak@kolabsys.com>
*/
/**
* Class to handle ActiveSync SmartForward element
*
* @package Syncroton
* @subpackage Model
*/
class Syncroton_Model_SmartForward extends Syncroton_Model_AXMLEntry
{
protected $_properties = array(
'ComposeMail' => array(
'accountId' => array('type' => 'string'),
'clientId' => array('type' => 'string'),
'mime' => array('type' => 'byteArray'),
'replaceMime' => array('type' => 'string'),
'saveInSentItems' => array('type' => 'string'),
'source' => array('type' => 'container'), // or string
'status' => array('type' => 'number'),
),
'RightsManagement' => array(
'templateID' => array('type' => 'string'),
)
);
}
diff --git a/lib/ext/Syncroton/Model/SmartReply.php b/lib/ext/Syncroton/Model/SmartReply.php
index 856872c..f8493a5 100644
--- a/lib/ext/Syncroton/Model/SmartReply.php
+++ b/lib/ext/Syncroton/Model/SmartReply.php
@@ -1,35 +1,36 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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)
* @copyright Copyright (c) 2012-2012 Kolab Systems AG (http://www.kolabsys.com)
* @author Lars Kneschke <l.kneschke@metaways.de>
* @author Aleksander Machniak <machniak@kolabsys.com>
*/
/**
* Class to handle ActiveSync SmartReply element
*
* @package Syncroton
* @subpackage Model
*/
class Syncroton_Model_SmartReply extends Syncroton_Model_AXMLEntry
{
protected $_properties = array(
'ComposeMail' => array(
'accountId' => array('type' => 'string'),
'clientId' => array('type' => 'string'),
'mime' => array('type' => 'byteArray'),
'replaceMime' => array('type' => 'string'),
'saveInSentItems' => array('type' => 'string'),
'source' => array('type' => 'container'), // or string
'status' => array('type' => 'number'),
),
'RightsManagement' => array(
'templateID' => array('type' => 'string'),
)
);
}
diff --git a/lib/ext/Syncroton/Model/StoreRequest.php b/lib/ext/Syncroton/Model/StoreRequest.php
index f013b33..c6a158f 100644
--- a/lib/ext/Syncroton/Model/StoreRequest.php
+++ b/lib/ext/Syncroton/Model/StoreRequest.php
@@ -1,245 +1,247 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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)
* @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
+ * @package Syncroton
+ * @subpackage 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;
}
} 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;
}
if (isset($xmlStore->Query->And->ConversationId)) {
$this->_store['query']['and']['conversationId'] = (string) $xmlStore->Query->And->ConversationId;
}
// Protocol specification defines Value as string and DateReceived as datetime, but
// PocketPC device I tested sends XML as follows:
// <GreaterThan>
// <DateReceived>
// <Value>2012-08-02T16:54:11.000Z</Value>
// </GreaterThan>
if (isset($xmlStore->Query->And->GreaterThan)) {
if (isset($xmlStore->Query->And->GreaterThan->Value)) {
$value = (string) $xmlStore->Query->And->GreaterThan->Value;
$this->_store['query']['and']['greaterThan']['value'] = new DateTime($value, new DateTimeZone('UTC'));
}
$email = $xmlStore->Query->And->GreaterThan->children('uri:Email');
if (isset($email->DateReceived)) {
$this->_store['query']['and']['greaterThan']['dateReceived'] = true;
}
}
if (isset($xmlStore->Query->And->LessThan)) {
if (isset($xmlStore->Query->And->LessThan->Value)) {
$value = (string) $xmlStore->Query->And->LessThan->Value;
$this->_store['query']['and']['lessThan']['value'] = new DateTime($value, new DateTimeZone('UTC'));
}
$email = $xmlStore->Query->And->LessThan->children('uri:Email');
if (isset($email->DateReceived)) {
$this->_store['query']['and']['leasThan']['dateReceived'] = true;
}
}
$airSync = $xmlStore->Query->And->children('uri:AirSync');
foreach ($airSync as $name => $value) {
if ($name == 'Class') {
$this->_store['query']['and']['classes'][] = (string) $value;
} 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 {
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/StoreResponseResult.php b/lib/ext/Syncroton/Model/StoreResponseResult.php
index e2deb91..2405d54 100644
--- a/lib/ext/Syncroton/Model/StoreResponseResult.php
+++ b/lib/ext/Syncroton/Model/StoreResponseResult.php
@@ -1,31 +1,32 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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 Search/Response/Store/Result elements
*
* @package Syncroton
* @subpackage Model
*/
class Syncroton_Model_StoreResponseResult extends Syncroton_Model_AXMLEntry
{
protected $_xmlBaseElement = 'Result';
protected $_properties = array(
'AirSync' => array(
'class' => array('type' => 'string'),
'collectionId' => array('type' => 'string'),
),
'Search' => array(
'longId' => array('type' => 'string', 'supportedSince' => '2.5'),
'properties' => array('type' => 'container', 'supportedSince' => '2.5'),
)
);
}
diff --git a/lib/ext/Syncroton/Model/SyncCollection.php b/lib/ext/Syncroton/Model/SyncCollection.php
index 5976b02..1eaaa07 100644
--- a/lib/ext/Syncroton/Model/SyncCollection.php
+++ b/lib/ext/Syncroton/Model/SyncCollection.php
@@ -1,317 +1,319 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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 Sync collection
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Model
* @property string class
* @property string collectionId
* @property bool deletesAsMoves
* @property bool getChanges
* @property string syncKey
* @property int windowSize
*/
class Syncroton_Model_SyncCollection extends Syncroton_Model_AXMLEntry
{
protected $_elements = array(
'syncState' => null,
'folder' => null
);
protected $_xmlCollection;
protected $_xmlBaseElement = 'Collection';
public function __construct($properties = null)
{
if ($properties instanceof SimpleXMLElement) {
$this->setFromSimpleXMLElement($properties);
} elseif (is_array($properties)) {
$this->setFromArray($properties);
}
if (!isset($this->_elements['options'])) {
$this->_elements['options'] = array();
}
if (!isset($this->_elements['options']['filterType'])) {
$this->_elements['options']['filterType'] = Syncroton_Command_Sync::FILTER_NOTHING;
}
if (!isset($this->_elements['options']['mimeSupport'])) {
$this->_elements['options']['mimeSupport'] = Syncroton_Command_Sync::MIMESUPPORT_DONT_SEND_MIME;
}
if (!isset($this->_elements['options']['mimeTruncation'])) {
$this->_elements['options']['mimeTruncation'] = Syncroton_Command_Sync::TRUNCATE_NOTHING;
}
if (!isset($this->_elements['options']['bodyPreferences'])) {
$this->_elements['options']['bodyPreferences'] = array();
}
}
/**
* 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) {
return false;
}
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) {
return false;
}
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) {
return false;
}
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) {
return false;
}
return isset($this->_xmlCollection->Commands->Fetch);
}
/**
* this functions does not only set from SimpleXMLElement but also does merge from SimpleXMLElement
* to support partial sync requests
*
* @param SimpleXMLElement $properties
* @throws InvalidArgumentException
*/
public function setFromSimpleXMLElement(SimpleXMLElement $properties)
{
if (!in_array($properties->getName(), (array) $this->_xmlBaseElement)) {
throw new InvalidArgumentException('Unexpected element name: ' . $properties->getName());
}
$this->_xmlCollection = $properties;
if (isset($properties->CollectionId)) {
$this->_elements['collectionId'] = (string)$properties->CollectionId;
}
if (isset($properties->SyncKey)) {
$this->_elements['syncKey'] = (int)$properties->SyncKey;
}
if (isset($properties->Class)) {
$this->_elements['class'] = (string)$properties->Class;
} elseif (!array_key_exists('class', $this->_elements)) {
$this->_elements['class'] = null;
}
if (isset($properties->WindowSize)) {
$this->_elements['windowSize'] = (string)$properties->WindowSize;
} elseif (!array_key_exists('windowSize', $this->_elements)) {
$this->_elements['windowSize'] = 100;
}
if (isset($properties->DeletesAsMoves)) {
if ((string)$properties->DeletesAsMoves === '0') {
$this->_elements['deletesAsMoves'] = false;
} else {
$this->_elements['deletesAsMoves'] = true;
}
} elseif (!array_key_exists('deletesAsMoves', $this->_elements)) {
$this->_elements['deletesAsMoves'] = true;
}
if (isset($properties->ConversationMode)) {
if ((string)$properties->ConversationMode === '0') {
$this->_elements['conversationMode'] = false;
} else {
$this->_elements['conversationMode'] = true;
}
} elseif (!array_key_exists('conversationMode', $this->_elements)) {
$this->_elements['conversationMode'] = true;
}
if (isset($properties->GetChanges)) {
if ((string)$properties->GetChanges === '0') {
$this->_elements['getChanges'] = false;
} else {
$this->_elements['getChanges'] = true;
}
} elseif (!array_key_exists('getChanges', $this->_elements)) {
$this->_elements['getChanges'] = true;
}
if (isset($properties->Supported)) {
// @todo collect supported elements
}
// process options
if (isset($properties->Options)) {
$this->_elements['options'] = array();
// optional parameters
if (isset($properties->Options->FilterType)) {
$this->_elements['options']['filterType'] = (int)$properties->Options->FilterType;
}
if (isset($properties->Options->MIMESupport)) {
$this->_elements['options']['mimeSupport'] = (int)$properties->Options->MIMESupport;
}
if (isset($properties->Options->MIMETruncation)) {
$this->_elements['options']['mimeTruncation'] = (int)$properties->Options->MIMETruncation;
}
if (isset($properties->Options->Class)) {
$this->_elements['options']['class'] = (string)$properties->Options->Class;
}
// try to fetch element from AirSyncBase:BodyPreference
$airSyncBase = $properties->Options->children('uri:AirSyncBase');
if (isset($airSyncBase->BodyPreference)) {
foreach ($airSyncBase->BodyPreference as $bodyPreference) {
$type = (int) $bodyPreference->Type;
$this->_elements['options']['bodyPreferences'][$type] = array(
'type' => $type
);
// optional
if (isset($bodyPreference->TruncationSize)) {
$this->_elements['options']['bodyPreferences'][$type]['truncationSize'] = (int) $bodyPreference->TruncationSize;
}
// optional
if (isset($bodyPreference->Preview)) {
$this->_elements['options']['bodyPreferences'][$type]['preview'] = (int) $bodyPreference->Preview;
}
}
}
if (isset($airSyncBase->BodyPartPreference)) {
// process BodyPartPreference elements
}
}
}
public function toArray()
{
$result = array();
foreach (array('syncKey', 'collectionId', 'deletesAsMoves', 'conversationMode', 'getChanges', 'windowSize', 'class', 'options') as $key) {
if (isset($this->$key)) {
$result[$key] = $this->$key;
}
}
return $result;
}
public function &__get($name)
{
if (array_key_exists($name, $this->_elements)) {
return $this->_elements[$name];
}
echo $name . PHP_EOL;
return null;
}
public function __set($name, $value)
{
$this->_elements[$name] = $value;
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/SyncState.php b/lib/ext/Syncroton/Model/SyncState.php
index 398f3a7..41e64fb 100644
--- a/lib/ext/Syncroton/Model/SyncState.php
+++ b/lib/ext/Syncroton/Model/SyncState.php
@@ -1,21 +1,21 @@
<?php
-
/**
* Syncroton
*
- * @package Model
+ * @package Syncroton
+ * @subpackage 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
+ * @package Syncroton
+ * @subpackage Model
*/
-
class Syncroton_Model_SyncState extends Syncroton_Model_AEntry implements Syncroton_Model_ISyncState
{
}
diff --git a/lib/ext/Syncroton/Model/Task.php b/lib/ext/Syncroton/Model/Task.php
index 8ab473f..b70f569 100644
--- a/lib/ext/Syncroton/Model/Task.php
+++ b/lib/ext/Syncroton/Model/Task.php
@@ -1,46 +1,48 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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 task
*
- * @package Model
+ * @package Syncroton
+ * @subpackage 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_AXMLEntry
{
protected $_xmlBaseElement = 'ApplicationData';
protected $_properties = array(
'AirSyncBase' => array(
'body' => array('type' => 'container', 'class' => 'Syncroton_Model_EmailBody')
),
'Tasks' => array(
'categories' => array('type' => 'container', 'childElement' => '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'),
)
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/TaskRecurrence.php b/lib/ext/Syncroton/Model/TaskRecurrence.php
index 116ce14..0361b72 100644
--- a/lib/ext/Syncroton/Model/TaskRecurrence.php
+++ b/lib/ext/Syncroton/Model/TaskRecurrence.php
@@ -1,66 +1,67 @@
<?php
/**
* Syncroton
*
- * @package Model
+ * @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 event
*
- * @package Model
+ * @package Syncroton
+ * @subpackage Model
* @property string class
* @property string collectionId
* @property bool deletesAsMoves
* @property bool getChanges
* @property string syncKey
* @property int windowSize
*/
-
class Syncroton_Model_TaskRecurrence extends Syncroton_Model_AXMLEntry
{
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;
protected $_properties = array(
'Tasks' => array(
'calendarType' => array('type' => 'number'),
'dayOfMonth' => array('type' => 'number'),
'dayOfWeek' => array('type' => 'number'),
'deadOccur' => array('type' => 'number'),
'firstDayOfWeek' => array('type' => 'number'),
'interval' => array('type' => 'number'),
'isLeapMonth' => array('type' => 'number'),
'monthOfYear' => array('type' => 'number'),
'occurrences' => array('type' => 'number'),
'regenerate' => array('type' => 'number'),
'start' => array('type' => 'datetime'),
'type' => array('type' => 'number'),
'until' => array('type' => 'datetime'),
'weekOfMonth' => array('type' => 'number'),
)
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Registry.php b/lib/ext/Syncroton/Registry.php
index 1495f8f..5f9ac46 100644
--- a/lib/ext/Syncroton/Registry.php
+++ b/lib/ext/Syncroton/Registry.php
@@ -1,422 +1,420 @@
<?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
+ * @package Syncroton
* @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
+ * @package Syncroton
* @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 DEFAULT_POLICY = 'default_policy';
const PING_TIMEOUT = 'ping_timeout';
const QUIET_TIME = 'quiet_time';
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);
}
/**
* return ping timeout
*
* sleep "ping timeout" seconds between folder checks in Ping and Sync command
*
* @return int
*/
public static function getPingTimeout()
{
if (!self::isRegistered(self::PING_TIMEOUT)) {
return 60;
}
return self::get(self::PING_TIMEOUT);
}
/**
* 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);
}
/**
* return quiet time
*
* don't check folders if last sync was "quiet time" seconds ago
*
* @return int
*/
public static function getQuietTime()
{
if (!self::isRegistered(self::QUIET_TIME)) {
return 180;
}
return self::get(self::QUIET_TIME);
}
/**
* 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 7b681a0..114db4d 100644
--- a/lib/ext/Syncroton/Server.php
+++ b/lib/ext/Syncroton/Server.php
@@ -1,417 +1,445 @@
<?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
{
const PARAMETER_ATTACHMENTNAME = 0;
const PARAMETER_COLLECTIONID = 1;
const PARAMETER_ITEMID = 3;
const PARAMETER_OPTIONS = 7;
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();
$this->_sendHeaders($command->getHeaders());
}
protected function _sendHeaders(array $headers)
{
foreach ($headers as $name => $value) {
header($name . ': ' . $value);
}
}
/**
* handle post request
*/
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' || $requestParameters['contentType'] == 'application/vnd.ms-sync') {
// decode wbxml request
try {
$decoder = new Syncroton_Wbxml_Decoder($this->_body);
$requestBody = $decoder->decode();
if ($this->_logger instanceof Zend_Log) {
$requestBody->formatOutput = true;
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " xml request:\n" . $requestBody->saveXML());
}
} catch(Syncroton_Wbxml_Exception_UnexpectedEndOfFile $e) {
$requestBody = NULL;
}
} else {
$requestBody = $this->_body;
}
header("MS-Server-ActiveSync: 14.00.0536.000");
// avoid sending HTTP header "Content-Type: text/html" for empty sync responses
ini_set('default_mimetype', null);
try {
$command = new $className($requestBody, $device, $requestParameters);
$command->handle();
$response = $command->getResponse();
} 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");
if (version_compare($device->acsversion, '14.0', '>=')) {
$response = $sepn->domDocument;
} else {
// pre 14.0 method
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->crit(__METHOD__ . '::' . __LINE__ . " " . $e->getTraceAsString());
header("HTTP/1.1 500 Internal server error");
return;
}
if ($response instanceof DOMDocument) {
if ($this->_logger instanceof Zend_Log) {
- $response->formatOutput = true;
- $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " xml response:\n" . $response->saveXML());
- $response->formatOutput = false;
+ $this->_logDomDocument(Zend_Log::DEBUG, $response, __METHOD__, __LINE__);
}
if (isset($command) && $command instanceof Syncroton_Command_ICommand) {
$this->_sendHeaders($command->getHeaders());
}
$outputStream = fopen("php://temp", 'r+');
$encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3);
try {
$encoder->encode($response);
} catch (Syncroton_Wbxml_Exception $swe) {
if ($this->_logger instanceof Zend_Log) {
$this->_logger->err(__METHOD__ . '::' . __LINE__ . " Could not encode output: " . $swe);
- $this->_logger->err(__METHOD__ . '::' . __LINE__ . " xml response:\n" . $response->saveXML());
+ $this->_logDomDocument(Zend_Log::ERR, $response, __METHOD__, __LINE__);
}
header("HTTP/1.1 500 Internal server error");
return;
}
if ($requestParameters['acceptMultipart'] == true) {
$parts = $command->getParts();
// output multipartheader
$bodyPartCount = 1 + count($parts);
// number of parts (4 bytes)
$header = pack('i', $bodyPartCount);
$partOffset = 4 + (($bodyPartCount * 2) * 4);
// wbxml body start and length
$streamStat = fstat($outputStream);
$header .= pack('ii', $partOffset, $streamStat['size']);
$partOffset += $streamStat['size'];
// calculate start and length of parts
foreach ($parts as $partId => $partStream) {
rewind($partStream);
$streamStat = fstat($partStream);
// part start and length
$header .= pack('ii', $partOffset, $streamStat['size']);
$partOffset += $streamStat['size'];
}
echo $header;
}
-
+
// output body
rewind($outputStream);
fpassthru($outputStream);
// output multiparts
if (isset($parts)) {
foreach ($parts as $partStream) {
rewind($partStream);
fpassthru($partStream);
}
}
}
- }
+ }
+
+ /**
+ * write (possible big) DOMDocument in smaller chunks to log file
+ *
+ * @param unknown $priority
+ * @param DOMDocument $dom
+ * @param string $method
+ * @param string $method
+ */
+ protected function _logDomDocument($priority, DOMDocument $dom, $method, $method)
+ {
+ $loops = 0;
+
+ $tempStream = fopen('php://temp/maxmemory:5242880', 'r+');
+
+ $dom->formatOutput = true;
+ fwrite($tempStream, $dom->saveXML());
+ $dom->formatOutput = false;
+
+ rewind($tempStream);
+
+ // log data in 1MByte chunks
+ while (!feof($tempStream)) {
+ $this->_logger->log($method . '::' . $method . " xml response($loops):\n" . fread($tempStream, 1048576), $priority);
+
+ $loops++;
+ }
+
+ fclose($tempStream);
+ }
/**
* return request params
*
* @return array
*/
protected function _getRequestParameters(Zend_Controller_Request_Http $request)
{
if (strpos($request->getRequestUri(), '&') === false) {
$commands = array(
0 => 'Sync',
1 => 'SendMail',
2 => 'SmartForward',
3 => 'SmartReply',
4 => 'GetAttachment',
9 => 'FolderSync',
10 => 'FolderCreate',
11 => 'FolderDelete',
12 => 'FolderUpdate',
13 => 'MoveItems',
14 => 'GetItemEstimate',
15 => 'MeetingResponse',
16 => 'Search',
17 => 'Settings',
18 => 'Ping',
19 => 'ItemOperations',
20 => 'Provision',
21 => 'ResolveRecipients',
22 => 'ValidateCert'
);
$requestParameters = substr($request->getRequestUri(), strpos($request->getRequestUri(), '?'));
$stream = fopen("php://temp", 'r+');
fwrite($stream, base64_decode($requestParameters));
rewind($stream);
// unpack the first 4 bytes
$unpacked = unpack('CprotocolVersion/Ccommand/vlocale', fread($stream, 4));
// 140 => 14.0
$protocolVersion = substr($unpacked['protocolVersion'], 0, -1) . '.' . substr($unpacked['protocolVersion'], -1);
$command = $commands[$unpacked['command']];
$locale = $unpacked['locale'];
// unpack deviceId
$length = ord(fread($stream, 1));
if ($length > 0) {
$toUnpack = fread($stream, $length);
$unpacked = unpack("H" . ($length * 2) . "string", $toUnpack);
$deviceId = $unpacked['string'];
}
// unpack policyKey
$length = ord(fread($stream, 1));
if ($length > 0) {
$unpacked = unpack('Vstring', fread($stream, $length));
$policyKey = $unpacked['string'];
}
// unpack device type
$length = ord(fread($stream, 1));
if ($length > 0) {
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$deviceType = $unpacked['string'];
}
while (! feof($stream)) {
$tag = ord(fread($stream, 1));
$length = ord(fread($stream, 1));
switch ($tag) {
case self::PARAMETER_ATTACHMENTNAME:
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$attachmentName = $unpacked['string'];
break;
case self::PARAMETER_COLLECTIONID:
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$collectionId = $unpacked['string'];
break;
case self::PARAMETER_ITEMID:
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$itemId = $unpacked['string'];
break;
case self::PARAMETER_OPTIONS:
$options = ord(fread($stream, 1));
$saveInSent = !!($options & 0x01);
$acceptMultiPart = !!($options & 0x02);
break;
default:
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " found unhandled command parameters");
}
}
$result = array(
'protocolVersion' => $protocolVersion,
'command' => $command,
'deviceId' => $deviceId,
'deviceType' => isset($deviceType) ? $deviceType : null,
'policyKey' => isset($policyKey) ? $policyKey : null,
'saveInSent' => isset($saveInSent) ? $saveInSent : false,
'collectionId' => isset($collectionId) ? $collectionId : null,
'itemId' => isset($itemId) ? $itemId : null,
'attachmentName' => isset($attachmentName) ? $attachmentName : null,
'acceptMultipart' => isset($acceptMultiPart) ? $acceptMultiPart : false
);
} else {
$result = array(
'protocolVersion' => $request->getServer('HTTP_MS_ASPROTOCOLVERSION'),
'command' => $request->getQuery('Cmd'),
'deviceId' => $request->getQuery('DeviceId'),
'deviceType' => $request->getQuery('DeviceType'),
'policyKey' => $request->getServer('HTTP_X_MS_POLICYKEY'),
'saveInSent' => $request->getQuery('SaveInSent') == 'T',
'collectionId' => $request->getQuery('CollectionId'),
'itemId' => $request->getQuery('ItemId'),
'attachmentName' => $request->getQuery('AttachmentName'),
'acceptMultipart' => $request->getServer('HTTP_MS_ASACCEPTMULTIPART') == 'T'
);
}
$result['userAgent'] = $request->getServer('HTTP_USER_AGENT', $result['deviceType']);
$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'];
if ($device->isDirty()) {
$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'],
'policyId' => Syncroton_Registry::isRegistered(Syncroton_Registry::DEFAULT_POLICY) ? Syncroton_Registry::get(Syncroton_Registry::DEFAULT_POLICY) : null
)));
}
return $device;
}
}
diff --git a/lib/ext/Syncroton/Wbxml/Abstract.php b/lib/ext/Syncroton/Wbxml/Abstract.php
index bec61f1..1ffa640 100644
--- a/lib/ext/Syncroton/Wbxml/Abstract.php
+++ b/lib/ext/Syncroton/Wbxml/Abstract.php
@@ -1,255 +1,256 @@
<?php
/**
* Syncroton
*
* @package Wbxml
* @subpackage Wbxml
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2009 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
* @version $Id:Abstract.php 4968 2008-10-17 09:09:33Z l.kneschke@metaways.de $
*/
/**
* class documentation
*
* @package Wbxml
* @subpackage Wbxml
*/
abstract class Syncroton_Wbxml_Abstract
{
/**
* stream containing the wbxml encoded data
*
* @var resource
*/
protected $_stream;
/**
* the wbxml version
*
* @var string
*/
protected $_version;
/**
* the Document Public Identifier
*
* @var string
*/
protected $_dpi;
/**
* the current active dtd
*
* @var Syncroton_Wbxml_Dtd_Syncml_Abstract
*/
protected $_dtd;
/**
* the charSet used in the wbxml file
*
* @var string
*/
protected $_charSet;
/**
* currently active code page
*
* @var array
*/
protected $_codePage;
/**
* see section 5.5
*
*/
const DPI_WELLKNOWN = 'WELLKNOWN';
/**
* see section 5.5
*
*/
const DPI_STRINGTABLE = 'STRINGTABLE';
const SWITCH_PAGE = 0x00;
const END = 0x01;
const ENTITY = 0x02;
const STR_I = 0x03;
const LITERAL = 0x04;
const EXT_I_0 = 0x40;
const EXT_I_1 = 0x41;
const EXT_I_2 = 0x42;
const PI = 0x43;
const LITERAL_C = 0x44;
const EXT_T_0 = 0x80;
const EXT_T_1 = 0x81;
const EXT_T_2 = 0x82;
const STR_T = 0x83;
const LITERAL_A = 0x84;
const EXT_0 = 0xC0;
const EXT_1 = 0xC1;
const EXT_2 = 0xC2;
const OPAQUE = 0xC3;
const LITERAL_AC = 0xC4;
/**
* the real name for this DPI is "unknown"
* But Microsoft is using them for their ActiveSync stuff
* instead defining their own DPI like the sycnml creators did
*
*/
const DPI_1 = '-//AIRSYNC//DTD AirSync//EN';
/**
* return wellknown identifiers
*
* @param integer $_uInt
* @todo add well known identifiers from section 7.2
* @return string
*/
public function getDPI($_uInt = 0)
{
if(!defined('Syncroton_Wbxml_Abstract::DPI_' . $_uInt)) {
throw new Syncroton_Wbxml_Exception('unknown wellknown identifier: ' . $_uInt);
}
$dpi = constant('Syncroton_Wbxml_Abstract::DPI_' . $_uInt);
return $dpi;
}
/**
* return multibyte integer
*
* @return integer
*/
protected function _getMultibyteUInt()
{
$uInt = 0;
do {
$byte = $this->_getByte();
$uInt <<= 7;
$uInt += ($byte & 127);
} while (($byte & 128) != 0);
return $uInt;
}
protected function _getByte()
{
$byte = fread($this->_stream, 1);
if($byte === false) {
throw new Syncroton_Wbxml_Exception("failed reading one byte");
}
return ord($byte);
}
protected function _getOpaque($_length)
{
$string = '';
// it might happen that not complete data is read from stream.
// loop until all data is read or EOF
while ($_length) {
$chunk = fread($this->_stream, $_length);
if ($chunk === false) {
throw new Syncroton_Wbxml_Exception("failed reading opaque data");
}
if ($len = strlen($chunk)) {
$string .= $chunk;
$_length -= $len;
}
- else if (feof($this->_stream)) {
+
+ if (feof($this->_stream)) {
break;
}
}
return $string;
}
/**
* get a 0 terminated string
*
* @return string
*/
protected function _getTerminatedString()
{
$string = '';
while (($byte = $this->_getByte()) != 0) {
$string .= chr($byte);
}
return $string;
}
protected function _writeByte($_byte)
{
fwrite($this->_stream, chr($_byte));
}
protected function _writeMultibyteUInt($_integer)
{
$multibyte = NULL;
$remainder = $_integer;
do {
$byte = ($remainder & 127);
$remainder >>= 7;
if($multibyte === NULL) {
$multibyte = chr($byte);
} else {
$multibyte = chr($byte | 128) . $multibyte;
}
} while ($remainder != 0);
fwrite($this->_stream, $multibyte);
}
protected function _writeString($_string)
{
fwrite($this->_stream, $_string);
}
/**
* write opaque string to stream
*
* @param string|resource $_string
* @throws Syncroton_Wbxml_Exception
*/
protected function _writeOpaqueString($_string)
{
if (is_resource($_string)) {
$stream = $_string;
} else {
$stream = fopen("php://temp", 'r+');
fwrite($stream, $_string);
}
$length = ftell($stream);
rewind($stream);
$this->_writeByte(Syncroton_Wbxml_Abstract::OPAQUE);
$this->_writeMultibyteUInt($length);
$writenBytes = stream_copy_to_stream($stream, $this->_stream);
if($writenBytes !== $length) {
throw new Syncroton_Wbxml_Exception('blow');
}
fclose($stream);
}
protected function _writeTerminatedString($_string)
{
$this->_writeByte(Syncroton_Wbxml_Abstract::STR_I);
fwrite($this->_stream, $_string);
fwrite($this->_stream, chr(0));
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Wbxml/Decoder.php b/lib/ext/Syncroton/Wbxml/Decoder.php
index 130d84f..8b845e4 100644
--- a/lib/ext/Syncroton/Wbxml/Decoder.php
+++ b/lib/ext/Syncroton/Wbxml/Decoder.php
@@ -1,303 +1,306 @@
<?php
/**
* Syncroton
*
* @package Wbxml
* @subpackage Wbxml
* @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
* @copyright Copyright (c) 2008-2009 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
* @version $Id:Decoder.php 4968 2008-10-17 09:09:33Z l.kneschke@metaways.de $
*/
/**
* class to convert WBXML to XML
*
* @package Wbxml
* @subpackage Wbxml
*/
class Syncroton_Wbxml_Decoder extends Syncroton_Wbxml_Abstract
{
/**
* type of Document Public Identifier
*
* @var string the type can be Syncroton_Wbxml_Abstract::DPI_STRINGTABLE or Syncroton_Wbxml_Abstract::DPI_WELLKNOWN
*/
protected $_dpiType;
/**
* the string table
*
* @var array
*/
protected $_stringTable = array();
/**
* the xml document
*
* @var DOMDocument
*/
protected $_dom;
/**
* the main name space / aka the namespace of first tag
*
* @var string
*/
protected $_mainNameSpace;
/**
* the constructor will try to read all data until the first tag
*
* @param resource $_stream
*/
public function __construct($_stream, $_dpi = NULL)
{
if(!is_resource($_stream) || get_resource_type($_stream) != 'stream') {
throw new Syncroton_Wbxml_Exception('$_stream must be a stream');
}
if($_dpi !== NULL) {
$this->_dpi = $_dpi;
}
$this->_stream = $_stream;
$this->_version = $this->_getByte();
if(feof($this->_stream)) {
throw new Syncroton_Wbxml_Exception_UnexpectedEndOfFile();
}
$this->_getDPI();
$this->_getCharset();
$this->_getStringTable();
// resolve DPI as we have read the stringtable now
// this->_dpi contains the string table index
if($this->_dpiType === Syncroton_Wbxml_Abstract::DPI_STRINGTABLE) {
$this->_dpi = $this->_stringTable[$this->_dpi];
}
#$this->_dtd = Syncroton_Wbxml_Dtd_Factory::factory($this->_dpi);
$this->_dtd = Syncroton_Wbxml_Dtd_Factory::factory(Syncroton_Wbxml_Dtd_Factory::ACTIVESYNC);
}
/**
* return the Document Public Identifier
*
* @param integer $_uInt unused param, needed to satisfy abstract class method signature
* @return string
*/
public function getDPI($_uInt = 0)
{
return $this->_dpi;
}
/**
* return the wbxml version
*
* @return string
*/
public function getVersion()
{
return $this->_version;
}
/**
* decodes the tags
*
* @return DOMDocument the decoded xml
*/
public function decode()
{
$openTags = NULL;
$node = NULL;
$this->_codePage = $this->_dtd->getCurrentCodePage();
while (!feof($this->_stream)) {
$byte = $this->_getByte();
switch($byte) {
case Syncroton_Wbxml_Abstract::END:
$node = $node->parentNode;
$openTags--;
break;
case Syncroton_Wbxml_Abstract::OPAQUE:
$length = $this->_getMultibyteUInt();
if($length > 0) {
- // @TODO: handle big data with streams
- // E.g. in SendMail command "opaqued" <Mime> contains full email body
$opaque = $this->_getOpaque($length);
try {
// let see if we can decode it. maybe the opaque data is wbxml encoded content
$opaqueDataStream = fopen("php://temp", 'r+');
fputs($opaqueDataStream, $opaque);
rewind($opaqueDataStream);
$opaqueContentDecoder = new Syncroton_Wbxml_Decoder($opaqueDataStream);
$dom = $opaqueContentDecoder->decode();
fclose($opaqueDataStream);
foreach($dom->childNodes as $newNode) {
if($newNode instanceof DOMElement) {
$newNode = $this->_dom->importNode($newNode, true);
$node->appendChild($newNode);
}
}
} catch (Exception $e) {
// if not, just treat it as a string
$node->appendChild($this->_dom->createTextNode($opaque));
}
}
break;
case Syncroton_Wbxml_Abstract::STR_I:
$string = $this->_getTerminatedString();
$node->appendChild($this->_dom->createTextNode($string));
break;
case Syncroton_Wbxml_Abstract::SWITCH_PAGE:
$page = $this->_getByte();
$this->_codePage = $this->_dtd->switchCodePage($page);
#echo "switched to codepage $page\n";
break;
default:
$tagHasAttributes = (($byte & 0x80) != 0);
$tagHasContent = (($byte & 0x40) != 0);
// get rid of bit 7+8
$tagHexCode = $byte & 0x3F;
- $tag = $this->_codePage->getTag($tagHexCode);
- $nameSpace = $this->_codePage->getNameSpace();
+ try {
+ $tag = $this->_codePage->getTag($tagHexCode);
+ } catch (Syncroton_Wbxml_Exception $swe) {
+ // tag can not be converted to ASCII name
+ $tag = sprintf('unknown tag 0x%x', $tagHexCode);
+ }
+ $nameSpace = $this->_codePage->getNameSpace();
$codePageName = $this->_codePage->getCodePageName();
#echo "Tag: $nameSpace:$tag\n";
- if($node === NULL) {
+ if ($node === NULL) {
// create the domdocument
- $node = $this->_createDomDocument($nameSpace, $tag);
+ $node = $this->_createDomDocument($nameSpace, $tag);
$newNode = $node->documentElement;
} else {
- if(!$this->_dom->isDefaultNamespace($nameSpace)) {
+ if (!$this->_dom->isDefaultNamespace($nameSpace)) {
$this->_dom->documentElement->setAttribute('xmlns:' . $codePageName, $nameSpace);
}
$newNode = $node->appendChild($this->_dom->createElementNS('uri:' . $codePageName, $tag));
}
- if($tagHasAttributes) {
+ if ($tagHasAttributes) {
$attributes = $this->_getAttributes();
}
- if($tagHasContent == true) {
+ if ($tagHasContent == true) {
$node = $newNode;
$openTags++;
}
break;
}
}
return $this->_dom;
}
/**
* creates the root of the xml document
*
* @return DOMDocument
*/
protected function _createDomDocument($_nameSpace, $_tag)
{
$this->_dom = $this->_dtd->getDomDocument($_nameSpace, $_tag);
return $this->_dom;
}
/**
* read the attributes of the current tag
*
* @todo implement logic
*/
protected function _getAttributes()
{
die("fetching attributes not yet implemented!\n");
}
/**
* get document public identifier
*
* the identifier can be all welknown identifier (see section 7.2) or a string from the stringtable
*/
protected function _getDPI()
{
$uInt = $this->_getMultibyteUInt();
if($uInt == 0) {
// get identifier from stringtable
$this->_dpiType = Syncroton_Wbxml_Abstract::DPI_STRINGTABLE;
// string table identifier, can be resolved only after reading string table
$this->_dpi = $this->_getByte();
} else {
// wellknown identifier
$this->_dpiType = Syncroton_Wbxml_Abstract::DPI_WELLKNOWN;
$this->_dpi = Syncroton_Wbxml_Abstract::getDPI($uInt);
}
}
/**
* see http://www.iana.org/assignments/character-sets (MIBenum)
* 106: UTF-8
*
*/
protected function _getCharset()
{
$uInt = $this->_getMultibyteUInt();
switch($uInt) {
case 106:
$this->_charSet = 'UTF-8';
break;
default:
throw new Syncroton_Wbxml_Exception('unsuported charSet: ' . $uInt);
break;
}
}
/**
* get string table and store strings indexed by start
*
* @todo validate spliting at 0 value
*/
protected function _getStringTable()
{
$length = $this->_getMultibyteUInt();
if($length > 0) {
$rawStringTable = $this->_getOpaque($length);
$index = NULL;
$string = NULL;
for($i = 0; $i < strlen($rawStringTable); $i++) {
if($index === NULL) {
$index = $i;
}
if(ord($rawStringTable[$i]) != 0) {
$string .= $rawStringTable[$i];
}
// either the string has ended or we reached a \0
if($i+1 == strlen($rawStringTable) || ord($rawStringTable[$i]) == 0){
$this->_stringTable[$index] = $string;
$index = NULL;
$string = NULL;
}
}
}
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage0.php b/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage0.php
index 263ff52..e039cee 100644
--- a/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage0.php
+++ b/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage0.php
@@ -1,64 +1,64 @@
<?php
/**
* Syncroton
*
* @package Wbxml
* @subpackage ActiveSync
* @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 documentation
*
* @package Wbxml
* @subpackage ActiveSync
*/
class Syncroton_Wbxml_Dtd_ActiveSync_CodePage0 extends Syncroton_Wbxml_Dtd_ActiveSync_Abstract
{
protected $_codePageNumber = 0;
protected $_codePageName = 'AirSync';
- protected $_tags = array(
+ protected $_tags = array(
'Sync' => 0x05,
'Responses' => 0x06,
'Add' => 0x07,
'Change' => 0x08,
'Delete' => 0x09,
'Fetch' => 0x0a,
'SyncKey' => 0x0b,
'ClientId' => 0x0c,
'ServerId' => 0x0d,
'Status' => 0x0e,
'Collection' => 0x0f,
'Class' => 0x10,
'Version' => 0x11,
'CollectionId' => 0x12,
'GetChanges' => 0x13,
'MoreAvailable' => 0x14,
'WindowSize' => 0x15,
'Commands' => 0x16,
'Options' => 0x17,
'FilterType' => 0x18,
'Truncation' => 0x19,
'RtfTruncation' => 0x1a,
'Conflict' => 0x1b,
'Collections' => 0x1c,
'ApplicationData' => 0x1d,
'DeletesAsMoves' => 0x1e,
'NotifyGUID' => 0x1f,
'Supported' => 0x20,
'SoftDelete' => 0x21,
'MIMESupport' => 0x22,
'MIMETruncation' => 0x23,
'Wait' => 0x24,
'Limit' => 0x25,
'Partial' => 0x26,
'ConversationMode' => 0x27,
'MaxItems' => 0x28,
'HeartbeatInterval' => 0x29
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Wbxml/Encoder.php b/lib/ext/Syncroton/Wbxml/Encoder.php
index 746ea67..f17db83 100644
--- a/lib/ext/Syncroton/Wbxml/Encoder.php
+++ b/lib/ext/Syncroton/Wbxml/Encoder.php
@@ -1,357 +1,369 @@
<?php
/**
* Syncroton
*
* @package Wbxml
* @subpackage Wbxml
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2009 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
* @version $Id:Encoder.php 4968 2008-10-17 09:09:33Z l.kneschke@metaways.de $
*/
/**
* class to convert XML to WBXML
*
* @package Wbxml
* @subpackage Wbxml
*/
class Syncroton_Wbxml_Encoder extends Syncroton_Wbxml_Abstract
{
/**
* stack of dtd objects
*
* @var array
*/
protected $_dtdStack = array();
/**
* stack of stream resources
*
* @var array
*/
protected $_streamStack = array();
/**
* stack of levels when to pop data from the other stacks
*
* @var array
*/
protected $_popStack = array();
/**
* count level of tags
*
* @var string
*/
protected $_level = 0;
/**
* when to take data next time from the different stacks
*
* @var unknown_type
*/
protected $_nextStackPop = NULL;
/**
* collect data trough different calls to _handleCharacters
*
* @var string
*/
protected $_currentTagData = NULL;
/**
* the current tag as read by the parser
*
* @var string
*/
protected $_currentTag = NULL;
/**
* the constructor
*
* @param resource $_stream
* @param string $_charSet
* @param integer $_version
*/
public function __construct($_stream, $_charSet = 'UTF-8', $_version = 2)
{
$this->_stream = $_stream;
$this->_charSet = $_charSet;
$this->_version = $_version;
}
/**
* initialize internal variables and write wbxml header to stream
*
* @param string $_urn
* @todo check if dpi > 0, instead checking the urn
*/
protected function _initialize($_dom)
{
$this->_dtd = Syncroton_Wbxml_Dtd_Factory::factory($_dom->doctype->name);
$this->_codePage = $this->_dtd->getCurrentCodePage();
// the WBXML version
$this->_writeByte($this->_version);
if($this->_codePage->getDPI() === NULL) {
// the document public identifier
$this->_writeMultibyteUInt(1);
} else {
// the document public identifier
// defined in string table
$this->_writeMultibyteUInt(0);
// the offset of the DPI in the string table
$this->_writeByte(0);
}
// write the charSet
$this->_writeCharSet($this->_charSet);
if($this->_codePage->getDPI() === NULL) {
// the length of the string table
$this->_writeMultibyteUInt(0);
} else {
// the length of the string table
$this->_writeMultibyteUInt(strlen($this->_codePage->getDPI()));
// the dpi
$this->_writeString($this->_codePage->getDPI());
}
}
/**
* write charset to stream
*
* @param string $_charSet
* @todo add charset lookup table. currently only utf-8 is supported
*/
protected function _writeCharSet($_charSet)
{
switch(strtoupper($_charSet)) {
case 'UTF-8':
$this->_writeMultibyteUInt(106);
break;
default:
throw new Syncroton_Wbxml_Exception('unsuported charSet ' . strtoupper($_charSet));
break;
}
}
/**
* start encoding of xml to wbxml
*
* @param string $_xml the xml string
* @return resource stream
*/
public function encode(DOMDocument $_dom)
{
$_dom->formatOutput = false;
+ $tempStream = fopen('php://temp/maxmemory:5242880', 'r+');
+ fwrite($tempStream, $_dom->saveXML());
+ rewind($tempStream);
+
$this->_initialize($_dom);
$parser = xml_parser_create_ns($this->_charSet, ';');
xml_set_object($parser, $this);
xml_set_element_handler($parser, '_handleStartTag', '_handleEndTag');
xml_set_character_data_handler($parser, '_handleCharacters');
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
- if (!xml_parse($parser, $_dom->saveXML())) {
- #file_put_contents(tempnam(sys_get_temp_dir(), "xmlerrors"), $_dom->saveXML());
- throw new Syncroton_Wbxml_Exception(sprintf('XML error: %s at line %d',
- xml_error_string(xml_get_error_code($parser)),
- xml_get_current_line_number($parser)
- ));
+ while (!feof($tempStream)) {
+ if (!xml_parse($parser, fread($tempStream, 1048576), feof($tempStream))) {
+ // uncomment to write xml document to file
+ #rewind($tempStream);
+ #$xmlStream = fopen(tempnam(sys_get_temp_dir(), "xmlerrors"), 'r+');
+ #stream_copy_to_stream($tempStream, $xmlStream);
+ #fclose($xmlStream);
+
+ throw new Syncroton_Wbxml_Exception(sprintf('XML error: %s at line %d',
+ xml_error_string(xml_get_error_code($parser)),
+ xml_get_current_line_number($parser)
+ ));
+ }
}
+ fclose($tempStream);
xml_parser_free($parser);
}
/**
* get's called by xml parser when tag starts
*
* @param resource $_parser
* @param string $_tag current tag prefixed with namespace
* @param array $_attributes list of tag attributes
*/
protected function _handleStartTag($_parser, $_tag, $_attributes)
{
$this->_level++;
$this->_currentTagData = null;
// write data for previous tag happens whith <tag1><tag2>
if($this->_currentTag !== NULL) {
$this->_writeTag($this->_currentTag, $this->_attributes, true);
}
list($nameSpace, $this->_currentTag) = explode(';', $_tag);
if($this->_codePage->getNameSpace() != $nameSpace) {
$this->_switchCodePage($nameSpace);
}
$this->_attributes = $_attributes;
}
/**
* strip uri: from nameSpace
*
* @param unknown_type $_nameSpace
* @return unknown
*/
protected function _stripNameSpace($_nameSpace)
{
return substr($_nameSpace, 4);
}
/**
* get's called by xml parser when tag ends
*
* @param resource $_parser
* @param string $_tag current tag prefixed with namespace
*/
protected function _handleEndTag($_parser, $_tag)
{
#echo "$_tag Level: $this->_level == $this->_nextStackPop \n";
if($this->_nextStackPop !== NULL && $this->_nextStackPop == $this->_level) {
#echo "TAG: $_tag\n";
$this->_writeByte(Syncroton_Wbxml_Abstract::END);
$subStream = $this->_stream;
$subStreamLength = ftell($subStream);
$this->_dtd = array_pop($this->_dtdStack);
$this->_stream = array_pop($this->_streamStack);
$this->_nextStackPop = array_pop($this->_popStack);
$this->_codePage = $this->_dtd->getCurrentCodePage();
rewind($subStream);
#while (!feof($subStream)) {$buffer = fgets($subStream, 4096);echo $buffer;}
$this->_writeByte(Syncroton_Wbxml_Abstract::OPAQUE);
$this->_writeMultibyteUInt($subStreamLength);
$writenBytes = stream_copy_to_stream($subStream, $this->_stream);
if($writenBytes !== $subStreamLength) {
//echo "$writenBytes !== $subStreamLength\n";
throw new Syncroton_Wbxml_Exception('blow');
}
fclose($subStream);
#echo "$this->_nextStackPop \n"; exit;
} else {
if ($this->_currentTag !== NULL && $this->_currentTagData !== NULL) {
$this->_writeTag($this->_currentTag, $this->_attributes, true, $this->_currentTagData);
$this->_writeByte(Syncroton_Wbxml_Abstract::END);
} elseif ($this->_currentTag !== NULL && $this->_currentTagData === NULL) {
// for example <UTC/> tag with no data, jumps directly from _handleStartTag to _handleEndTag
$this->_writeTag($this->_currentTag, $this->_attributes);
// no end tag required, tag has no content
} else {
$this->_writeByte(Syncroton_Wbxml_Abstract::END);
}
}
#list($urn, $tag) = explode(';', $_tag); echo "</$tag> ($this->_level)\n";
// reset $this->_currentTag, as tag got writen to stream already
$this->_currentTag = NULL;
$this->_level--;
}
/**
* collects data(value) of tag
* can be called multiple lines if the value contains linebreaks
*
* @param resource $_parser the xml parser
* @param string $_data the data(value) of the tag
*/
protected function _handleCharacters($_parser, $_data)
{
$this->_currentTagData .= $_data;
}
/**
* writes tag with data to stream
*
* @param string $_tag
* @param array $_attributes
* @param bool $_hasContent
* @param string $_data
*/
protected function _writeTag($_tag, $_attributes=NULL, $_hasContent=false, $_data=NULL)
{
if($_hasContent == false && $_data !== NULL) {
throw new Syncroton_Wbxml_Exception('$_hasContent can not be false, when $_data !== NULL');
}
// handle the tag
$identity = $this->_codePage->getIdentity($_tag);
if (is_array($_attributes) && isset($_attributes['uri:Syncroton;encoding'])) {
$encoding = 'opaque';
unset($_attributes['uri:Syncroton;encoding']);
} else {
$encoding = 'termstring';
}
if(!empty($_attributes)) {
$identity |= 0x80;
}
if($_hasContent == true) {
$identity |= 0x40;
}
$this->_writeByte($identity);
// handle the data
if($_data !== NULL) {
if ($encoding == 'opaque') {
$this->_writeOpaqueString(base64_decode($_data));
} else {
$this->_writeTerminatedString($_data);
}
}
$this->_currentTagData = NULL;
}
/**
* switch code page
*
* @param string $_urn
*/
protected function _switchCodePage($_nameSpace)
{
try {
$codePageName = $this->_stripNameSpace($_nameSpace);
if(!defined('Syncroton_Wbxml_Dtd_ActiveSync::CODEPAGE_'. strtoupper($codePageName))) {
throw new Syncroton_Wbxml_Exception('codepage ' . $codePageName . ' not found');
}
// switch to another codepage
// no need to write the wbxml header again
$codePageId = constant('Syncroton_Wbxml_Dtd_ActiveSync::CODEPAGE_'. strtoupper($codePageName));
$this->_codePage = $this->_dtd->switchCodePage($codePageId);
$this->_writeByte(Syncroton_Wbxml_Abstract::SWITCH_PAGE);
$this->_writeByte($codePageId);
} catch (Syncroton_Wbxml_Dtd_Exception_CodePageNotFound $e) {
// switch to another dtd
// need to write the wbxml header again
// put old dtd and stream on stack
$this->_dtdStack[] = $this->_dtd;
$this->_streamStack[] = $this->_stream;
$this->_popStack[] = $this->_nextStackPop;
$this->_nextStackPop = $this->_level;
$this->_stream = fopen("php://temp", 'r+');
$this->_initialize($_urn);
}
}
}
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Fri, Aug 21, 1:24 AM (10 h, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1277353
Default Alt Text
(256 KB)

Event Timeline