Projects
Kolab:16
kolab-syncroton
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 61
View file
kolab-syncroton.spec
Changed
@@ -37,7 +37,7 @@ %global upstream_version 2.4.2 Name: kolab-syncroton -Version: 2.4.2.10 +Version: 2.4.2.13 Release: 1%{?dist} Summary: ActiveSync for Kolab Groupware
View file
debian.changelog
Changed
@@ -1,4 +1,4 @@ -kolab-syncroton (2.4.2.10-0~kolab1) unstable; urgency=low +kolab-syncroton (2.4.2.13-0~kolab1) unstable; urgency=low * Release version 2.4.2
View file
kolab-syncroton-2.4.2.tar.gz/.php-cs-fixer.php
Added
@@ -0,0 +1,47 @@ +<?php + +declare(strict_types=1); + +use PhpCsFixer\Config; +use PhpCsFixer\Finder; + +$finder = Finder::create() + ->in(__DIR__) + ->exclude( + 'lib/ext', + 'lib/plugins', + 'vendor', + ) + ->ignoreDotFiles(false) + ->name('*.php.dist'); + +return (new Config()) + ->setRiskyAllowed(true) + ->setRules( + '@PHP74Migration' => true, + '@PHP74Migration:risky' => true, + '@PHP80Migration' => true, + '@PSR1' => true, + '@PSR12' => true, + + 'concat_space' => + 'spacing' => 'one', + , + + 'declare_strict_types' => false, + 'phpdoc_add_missing_param_annotation' => false, + 'use_arrow_functions' => false, + 'void_return' => false, + + 'yoda_style' => + 'equal' => false, + 'identical' => false, + , + + // TODO - risky + 'no_unset_on_property' => false, + 'random_api_migration' => false, + 'strict_param' => false, + ) + ->setFinder($finder) + ->setCacheFile(sys_get_temp_dir() . '/php-cs-fixer.' . md5(__DIR__) . '.cache');
View file
kolab-syncroton-2.4.2.tar.gz/README.md
Changed
@@ -41,5 +41,33 @@ package as document root as well as the /Microsoft-Server-ActiveSync alias. See docs/kolab-syncroton.conf for an example Apache config. + +FOR DEVELOPERS (TESTING) +======================== + +Steps 1-5 from the installation procedure need to be done. + +To check code quality run: `vendor/bin/phpstan analyse` + +To check code style run: `vendor/bin/php-cs-fixer fix --dry-run --diff --verbose` + +To run the Unit test suite goto `tests/` and run `../vendor/bin/phpunit --testsuite=Unit` + +To run the integration tests you have to follow these steps: + +1. Prepare a test account on the Kolab server and add this to config/config.inc.php: +``` +$config'activesync_test_username' = 'user@domain.tld'; +$config'activesync_test_password' = 'password'; +``` + + Don't forget to configure also `imap_host`, `activesync_dav_server` and `activesync_storage`. + All other options should use the default values. + +2. Start the syncroton server: `php -S localhost:8000` + +3. Goto `tests/` and run `../vendor/bin/phpunit --testsuite=Sync` + + 1: http://getcomposer.org 2: https://github.com/roundcube/roundcubemail/blob/master/program/lib/Roundcube/README.md)
View file
kolab-syncroton-2.4.2.tar.gz/composer.json-dist
Changed
@@ -2,27 +2,28 @@ "name": "kolab/syncroton", "description": "The ActiveSync Service for Kolab", "license": "AGPL-3.0+", - "repositories": - { - "type": "vcs", - "url": "https://git.kolab.org/diffusion/PNL/php-net_ldap.git" - } - , "require": { - "php": ">=5.4.0", + "php": ">=7.2.0", + "kolab/net_ldap3": "dev-master", "pear/pear-core-minimal": "~1.10.1", "pear/net_socket": "~1.2.1", "pear/auth_sasl": "~1.1.0", + "pear/http_request2": "~2.5.0", "pear/net_idna2": "~0.2.0", "pear/mail_mime": "~1.10.0", "pear/net_smtp": "~1.7.1", "pear/net_ldap2": "~2.2.0", "pear/net_sieve": "~1.4.0", - "kolab/net_ldap3": "dev-master", + "roundcube/rtf-html-php": "~2.1", + "sabre/vobject": "~4.5.1", + "zf1s/zend-controller": "~1.12.20", "zf1s/zend-json": "~1.12.20", "zf1s/zend-log": "~1.12.20" }, "require-dev": { - "phpunit/phpunit": "^4.8 || ^5.7 || ^6 || ^7 || ^9" + "friendsofphp/php-cs-fixer": "^3.0", + "guzzlehttp/guzzle": "^7.3.0", + "phpstan/phpstan": "^1.2", + "phpunit/phpunit": "^8 || ^9" } }
View file
kolab-syncroton-2.4.2.tar.gz/config/config.inc.php.dist
Changed
@@ -27,7 +27,7 @@ // List of global addressbooks (GAL) // Note: If empty 'autocomplete_addressbooks' setting will be used -$config'activesync_addressbooks' = array(); +$config'activesync_addressbooks' = ; // ActiveSync => Roundcube contact fields map for GAL search /* Default: array( @@ -67,10 +67,10 @@ // List of Roundcube plugins // WARNING: Not all plugins used in Roundcube can be listed here -$config'activesync_plugins' = array( +$config'activesync_plugins' = 'libcalendaring', - 'libkolab' -); + 'libkolab', +; // Defines for how many seconds we'll sleep between every // action for detecting changes in folders. Default: 60 @@ -106,7 +106,7 @@ // When set to an array folder hierarchies are used on all devices not listed here. // When set to null an old whitelist approach will be used where we do opposite // action and enable folder hierarchies only on device types known to support it. -$config'activesync_multifolder_blacklist' = array(); +$config'activesync_multifolder_blacklist' = ; // Blacklist overwrites for specified object type. If set to an array // it will have a precedence over 'activesync_multifolder_blacklist' list only for that type. @@ -114,7 +114,7 @@ // in that case use $config'activesync_multifolder_blacklist_contact' = array('windowsoutlook'); $config'activesync_multifolder_blacklist_mail' = null; $config'activesync_multifolder_blacklist_event' = null; -$config'activesync_multifolder_blacklist_contact' = array('windowsoutlook'); +$config'activesync_multifolder_blacklist_contact' = 'windowsoutlook'; $config'activesync_multifolder_blacklist_note' = null; $config'activesync_multifolder_blacklist_task' = null;
View file
kolab-syncroton-2.4.2.tar.gz/docs/SQL/mysql.initial.sql
Changed
@@ -59,6 +59,7 @@ `counter` int(11) NOT NULL DEFAULT '0', `lastsync` datetime DEFAULT NULL, `pendingdata` longblob, + `client_id_map` longblob DEFAULT NULL, 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 @@ -123,4 +124,4 @@ PRIMARY KEY(`name`) ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; -INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2014101300'); +INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2023100500');
View file
kolab-syncroton-2.4.2.tar.gz/docs/SQL/oracle
Deleted
-(directory)
View file
kolab-syncroton-2.4.2.tar.gz/docs/SQL/oracle.initial.sql
Deleted
@@ -1,113 +0,0 @@ -CREATE TABLE "syncroton_policy" ( - "id" varchar(40) NOT NULL PRIMARY KEY, - "name" varchar(255) NOT NULL, - "description" varchar(255) DEFAULT NULL, - "policy_key" varchar(64) NOT NULL, - "json_policy" clob DEFAULT NULL -); - -CREATE TABLE "syncroton_device" ( - "id" varchar(40) NOT NULL PRIMARY KEY, - "deviceid" varchar(64) NOT NULL, - "devicetype" varchar(64) NOT NULL, - "owner_id" varchar(40) NOT NULL, - "acsversion" varchar(40) NOT NULL, - "policykey" varchar(64) DEFAULT NULL, - "policy_id" varchar(40) DEFAULT NULL, - "useragent" varchar(255) DEFAULT NULL, - "imei" varchar(255) DEFAULT NULL, - "model" varchar(255) DEFAULT NULL, - "friendlyname" varchar(255) DEFAULT NULL, - "os" varchar(255) DEFAULT NULL, - "oslanguage" varchar(255) DEFAULT NULL, - "phonenumber" varchar(255) DEFAULT NULL, - "pinglifetime" integer DEFAULT NULL, - "remotewipe" integer DEFAULT 0, - "pingfolder" clob DEFAULT NULL, - "lastsynccollection" clob DEFAULT NULL, - "lastping" timestamp DEFAULT NULL, - "contactsfilter_id" varchar(40) DEFAULT NULL, - "calendarfilter_id" varchar(40) DEFAULT NULL, - "tasksfilter_id" varchar(40) DEFAULT NULL, - "emailfilter_id" varchar(40) DEFAULT NULL -); - -CREATE UNIQUE INDEX "syncroton_device_owner_id_idx" ON "syncroton_device" ("owner_id", "deviceid"); - - -CREATE TABLE "syncroton_folder" ( - "id" varchar(40) NOT NULL PRIMARY KEY, - "device_id" varchar(40) NOT NULL - REFERENCES "syncroton_device" ("id") ON DELETE CASCADE, - "class" varchar(64) NOT NULL, - "folderid" varchar(254) NOT NULL, - "parentid" varchar(254) DEFAULT NULL, - "displayname" varchar(254) NOT NULL, - "type" integer NOT NULL, - "creation_time" timestamp NOT NULL, - "lastfiltertype" integer DEFAULT NULL, - "supportedfields" clob DEFAULT NULL -); - -CREATE UNIQUE INDEX "syncroton_folder_device_id_idx" ON "syncroton_folder" ("device_id", "class", "folderid"); - - -CREATE TABLE "syncroton_synckey" ( - "id" varchar(40) NOT NULL PRIMARY KEY, - "device_id" varchar(40) NOT NULL - REFERENCES "syncroton_device" ("id") ON DELETE CASCADE, - "type" varchar(64) DEFAULT NULL, - "counter" integer DEFAULT 0 NOT NULL, - "lastsync" timestamp DEFAULT NULL, - "pendingdata" clob -); - -CREATE UNIQUE INDEX "syncroton_synckey_device_idx" ON "syncroton_synckey" ("device_id", "type", "counter"); - -CREATE TABLE "syncroton_content" ( - "id" varchar(40) NOT NULL PRIMARY KEY, - "device_id" varchar(40) NOT NULL - REFERENCES "syncroton_device" ("id") ON DELETE CASCADE, - "folder_id" varchar(40) NOT NULL, - "contentid" varchar(128) NOT NULL, - "creation_time" timestamp DEFAULT NULL, - "creation_synckey" integer NOT NULL, - "is_deleted" smallint DEFAULT 0 -); - -CREATE UNIQUE INDEX "syncroton_content_device_idx" ON "syncroton_content" ("device_id", "folder_id", "contentid"); - -CREATE TABLE "syncroton_data" ( - "id" varchar(40) NOT NULL PRIMARY KEY, - "class" varchar(40) NOT NULL, - "folder_id" varchar(40) NOT NULL, - "data" clob -); - -CREATE TABLE "syncroton_data_folder" ( - "id" varchar(40) NOT NULL PRIMARY KEY, - "type" integer NOT NULL, - "name" varchar(255) NOT NULL, - "owner_id" varchar(40) NOT NULL, - "parent_id" varchar(40) DEFAULT NULL -); - -CREATE TABLE "syncroton_modseq" ( - "device_id" varchar(40) NOT NULL - REFERENCES "syncroton_device" ("id") ON DELETE CASCADE, - "folder_id" varchar(40) NOT NULL, - "synctime" timestamp NOT NULL, - "data" clob, - PRIMARY KEY ("device_id", "folder_id", "synctime") -); - -CREATE TABLE "syncroton_relations_state" ( - "device_id" varchar(40) NOT NULL - REFERENCES "syncroton_device" ("id") ON DELETE CASCADE, - "folder_id" varchar(40) NOT NULL, - "synctime" timestamp NOT NULL, - "data" clob, - PRIMARY KEY ("device_id", "folder_id", "synctime") -); - -INSERT INTO "system" ("name", "value") VALUES ('syncroton-version', '2014101300');
View file
kolab-syncroton-2.4.2.tar.gz/docs/SQL/oracle/2014101300.sql
Deleted
@@ -1,8 +0,0 @@ -CREATE TABLE "syncroton_relations_state" ( - "device_id" varchar(40) NOT NULL - REFERENCES "syncroton_device" ("id") ON DELETE CASCADE, - "folder_id" varchar(40) NOT NULL, - "synctime" timestamp NOT NULL, - "data" clob, - PRIMARY KEY ("device_id", "folder_id", "synctime") -);
View file
kolab-syncroton-2.4.2.tar.gz/index.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | |
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Backend/IDevice.php
Changed
@@ -20,8 +20,9 @@ interface Syncroton_Backend_IDevice extends Syncroton_Backend_IBackend { /** - * @param unknown_type $userId - * @param unknown_type $deviceId + * @param string $userId + * @param string $deviceId + * * @return Syncroton_Model_IDevice */ public function getUserDevice($userId, $deviceId); @@ -40,7 +41,7 @@ * * @param array $request Oof/Get request data * - * @return Syncroton_Model_Oof Response object or NULL if OOF is not supported + * @return Syncroton_Model_Oof|null Response object or NULL if OOF is not supported * @throws Syncroton_Exception_Status */ public function getOOF($request);
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Backend/ISyncState.php
Changed
@@ -42,7 +42,8 @@ * * @param Syncroton_Model_Device $_deviceId * @param string $_class - * @return array + * + * @return Syncroton_Model_SyncState|false */ public function validate($_deviceId, $_syncKey, $_type); }
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Backend/SyncState.php
Changed
@@ -138,7 +138,8 @@ * * @param Syncroton_Model_IDevice|string $deviceId * @param Syncroton_Model_IFolder|string $folderId - * @return Syncroton_Model_SyncState + * + * @return Syncroton_Model_SyncState|false */ public function validate($deviceId, $folderId, $syncKey) {
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/FolderDelete.php
Changed
@@ -26,11 +26,6 @@ protected $_folder; /** - * @var Syncroton_Model_ISyncState - */ - protected $_syncState; - - /** * parse FolderDelete request */ public function handle()
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/FolderSync.php
Changed
@@ -66,10 +66,9 @@ * @var string */ protected $_syncKey; - + /** - * parse FolderSync request - * + * Parse FolderSync request */ public function handle() {
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/Sync.php
Changed
@@ -99,11 +99,6 @@ */ protected $_moreAvailable = false; - /** - * @var Syncroton_Model_SyncState - */ - protected $_syncState; - protected $_maxWindowSize = 100; protected $_heartbeatInterval = null; @@ -969,6 +964,12 @@ } $totalChanges += $collectionChanges; + + // If the client resent an old sync-key, we should still respond with the latest sync key + if (isset($collectionData->syncState->counterNext)) { + //TODO we're not resending the changes in between, but I'm not sure we have to. + $collectionData->syncState->counter = $collectionData->syncState->counterNext; + } // increase SyncKey if needed if (( @@ -1069,7 +1070,7 @@ if (!empty($clientModifications'added')) { foreach ($clientModifications'added' as $added) { $this->_contentStateBackend->delete($added'contentState'); - $dataController->deleteEntry($collectionData->collectionId, $added'serverId', array()); + $dataController->deleteEntry($collectionData->collectionId, $added'serverId'); } } @@ -1092,7 +1093,7 @@ if (!empty($clientModifications'added')) { foreach ($clientModifications'added' as $added) { $this->_contentStateBackend->delete($added'contentState'); - $dataController->deleteEntry($collectionData->collectionId, $added'serverId', array()); + $dataController->deleteEntry($collectionData->collectionId, $added'serverId'); } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Data/IData.php
Changed
@@ -37,11 +37,11 @@ /** * delete entry in backend * - * @param string $_folderId - * @param string $_serverId - * @param unknown_type $_collectionData + * @param string $_folderId + * @param string $_serverId + * @param ?Syncroton_Model_SyncCollection $_collectionData */ - public function deleteEntry($_folderId, $_serverId, $_collectionData); + public function deleteEntry($_folderId, $_serverId, $_collectionData = null); /** * delete folder @@ -86,7 +86,7 @@ /** * - * @param unknown_type $fileReference + * @param string $fileReference * @return Syncroton_Model_FileReference */ public function getFileReference($fileReference);
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Server.php
Changed
@@ -421,11 +421,9 @@ /** * 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 + * @param string $ownerId + * @param array $requestParameters + * * @return Syncroton_Model_Device */ protected function _getUserDevice($ownerId, $requestParameters) @@ -435,6 +433,7 @@ $device->useragent = $requestParameters'userAgent'; $device->acsversion = $requestParameters'protocolVersion'; + $device->devicetype = $requestParameters'deviceType'; if ($device->isDirty()) { $device = $this->_deviceBackend->update($device);
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/rtf.php
Deleted
@@ -1,705 +0,0 @@ -<?php -/* - This class contains code from rtfclass.php that was written by Markus Fischer and placed by him under - GPLv2 License. - - =======================================NOTES FROM ORIGINAL AUTHOR==================================== - Rich Text Format - Parsing Class - ================================ - - (c) 2000 Markus Fischer - <mfischer@josefine.ben.tuwien.ac.at> - http://josefine.ben.tuwien.ac.at/~mfischer/ - - Latest versions of this class can always be found at - http://josefine.ben.tuwien.ac.at/~mfischer/developing/php/rtf/rtfclass.phps - Testing suite is available at - http://josefine.ben.tuwien.ac.at/~mfischer/developing/php/rtf/ - - License: GPLv2 - - Specification: - http://msdn.microsoft.com/library/default.asp?URL=/library/specs/rtfspec.htm - - General Notes: - ============== - Unknown or unspupported control symbols are silently gnored - - Group stacking is still not supported :( - group stack logic implemented; however not really used yet - ===================================================================================================== - - It was modified by me (Andreas Brodowski) to allow compressed RTF being uncompressed by code I ported from - Java to PHP and adapted according the needs of Z-Push. - - Currently it is being used to detect empty RTF Streams from Nokia Phones in MfE Clients - - It needs to be used by other backend writers that needs to have notes in calendar, appointment or tasks - objects to be written to their databases since devices send them usually in RTF Format... With Zarafa - you can write them directly to DB and Zarafa is doing the conversion job. Other Groupware systems usually - don't have this possibility... - - Aleksander Machniak <machniak@kolabsys.com> fixed some deprecated function usage and some small issues - -*/ - - -class rtf { - var $LZRTF_HDR_DATA = "{\\rtf1\\ansi\\mac\\deff0\\deftab720{\\fonttbl;}{\\f0\\fnil \\froman \\fswiss \\fmodern \\fscript \\fdecor MS Sans SerifSymbolArialTimes New RomanCourier{\\colortbl\\red0\\green0\\blue0\n\r\\par \\pard\\plain\\f0\\fs20\\b\\i\\u\\tab\\tx"; - var $LZRTF_HDR_LEN = 207; - var $CRC32_TABLE = array( 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3, - 0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91, - 0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7, - 0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5, - 0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B, - 0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59, - 0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F, - 0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D, - 0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433, - 0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01, - 0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457, - 0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65, - 0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB, - 0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9, - 0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F, - 0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD, - 0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683, - 0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1, - 0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7, - 0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5, - 0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B, - 0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79, - 0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F, - 0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D, - 0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713, - 0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21, - 0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777, - 0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45, - 0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB, - 0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9, - 0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF, - 0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D, - ); - - var $rtf; // rtf core stream - var $rtf_len; // length in characters of the stream (get performace due avoiding calling strlen everytime) - var $err = array(); // array of error message, no entities on no error - - var $wantXML; // convert to XML - var $wantHTML; // convert to HTML - var $wantASCII; // convert to HTML - - // the only variable which should be accessed from the outside - var $out; // output data stream (depends on which $wantXXXXX is set to true - var $outstyles; // htmlified styles (generated after parsing if wantHTML - var $styles = array(); // if wantHTML, stylesheet definitions are put in here - - // internal parser variables -------------------------------- - // control word variables - var $cword; // holds the current (or last) control word, depending on $cw - var $cw; // are we currently parsing a control word ? - var $cfirst; // could this be the first character ? so watch out for control symbols - - var $flags = array(); // parser flags - var $fonttable = array(); - - var $queue; // every character which is no sepcial char, not belongs to a control word/symbol; is generally considered being 'plain' - - var $stack = array(); // group stack - - /* keywords which don't follw the specification (used by Word '97 - 2000) */ - // not yet used - var $control_exception = array( - "clFitText", - "clftsWidth(-?0-9+)?", - "clNoWrap(-?0-9+)?", - "clwWidth(-?0-9+)?", - "tdfrmtxtBottom(-?0-9+)?", - "tdfrmtxtLeft(-?0-9+)?", - "tdfrmtxtRight(-?0-9+)?", - "tdfrmtxtTop(-?0-9+)?", - "trftsWidthA(-?0-9+)?", - "trftsWidthB(-?0-9+)?", - "trftsWidth(-?0-9+)?", - "trwWithA(-?0-9+)?", - "trwWithB(-?0-9+)?", - "trwWith(-?0-9+)?", - "spectspecifygen(-?0-9+)?", - ); - - var $charset_table = array( - "0" => "ANSI", - "1" => "Default", - "2" => "Symbol", - "77" => "Mac", - "128" => "Shift Jis", - "129" => "Hangul", - "130" => "Johab", - "134" => "GB2312", - "136" => "Big5", - "161" => "Greek", - "162" => "Turkish", - "163" => "Vietnamese", - "177" => "Hebrew", - "178" => "Arabic", - "179" => "Arabic Traditional", - "180" => "Arabic user", - "181" => "Hebrew user", - "186" => "Baltic", - "204" => "Russian", - "222" => "Thai", - "238" => "Eastern European", - "255" => "PC 437", - "255" => "OEM", - ); - - /* note: the only conversion table used */ - var $fontmodifier_table = array( - "bold" => "b", - "italic" => "i", - "underlined" => "u", - "strikethru" => "strike", - ); - - - function __construct() { - $this->rtf_len = 0; - $this->rtf = ''; - - $this->out = ''; - } - - // loadrtf - load the raw rtf data to be converted by this class - // data = the raw rtf - function loadrtf($data) { - if (($this->rtf = $this->uncompress($data))) { - $this->rtf_len = strlen($this->rtf); - }; - if($this->rtf_len == 0) { - debugLog("No data in stream found"); - return false; - }; - return true; - } - - function output($typ) { - switch($typ) { - case "ascii": $this->wantASCII = true; break; - case "xml": $this->wantXML = true; break; - case "html": $this->wantHTML = true; break; - default: break; - } - } - - // uncompress - uncompress compressed rtf data - // src = the compressed raw rtf in LZRTF format - function uncompress($src) { - $header = unpack("LcSize/LuSize/Lmagic/Lcrc32",substr($src,0,16)); - $in = 16; - if ($header'cSize' != strlen($src)-4) { - debugLog("Stream too short"); - return false; - } - - if ($header'crc32' != $this->LZRTFCalcCRC32($src,16,(($header'cSize'+4))-16)) { - debugLog("CRC MISMATCH"); - return false; - } - - if ($header'magic' == 0x414c454d) { // uncompressed RTF - return as is. - $dest = substr($src,$in,$header'uSize'); - } else if ($header'magic' == 0x75465a4c) { // compressed RTF - uncompress. - $dst = $this->LZRTF_HDR_DATA; - $out = $this->LZRTF_HDR_LEN; - $oblen = $this->LZRTF_HDR_LEN + $header'uSize'; - $flagCount = 0; - $flags = 0; - while ($out<$oblen) { - $flags = ($flagCount++ % 8 == 0) ? ord($src$in++) : $flags >> 1; - if (($flags & 1) == 1) { - $offset = ord($src$in++); - $length = ord($src$in++); - $offset = ($offset << 4) | ($length >> 4); - $length = ($length & 0xF) + 2; - $offset = (int)($out / 4096) * 4096 + $offset; - if ($offset >= $out) $offset -= 4096; - $end = $offset + $length; - while ($offset < $end) { - $dst$out++ = $dst$offset++; - }; - } else { - $dst$out++ = $src$in++; - } - } - $src = $dst; - $dest = substr($src,$this->LZRTF_HDR_LEN,$header'uSize'); - } else { // unknown magic - returfn false (please report if this ever happens) - debugLog("Unknown Magic"); - return false; - } - - return $dest; - } - - // LZRTFCalcCRC32 - calculates the CRC32 of the LZRTF data part - // buf = the whole rtf data part - // off = start point of crc calculation - // len = length of data to calculate CRC for - // function is necessary since in RTF there is no XOR 0xffffffff being done (said to be 0x00 unsafe CRC32 calculation - function LZRTFCalcCRC32($buf, $off, $len) { - $c=0; - $end = $off + $len; - for($i=$off;$i < $end;$i++) { - $c=$this->CRC32_TABLE($c ^ ord($buf$i)) & 0xFF ^ (($c >> 8) & 0x00ffffff); - } - return $c; - } - - function parserInit() { /* Default values according to the specs */ - $this->flags = array( - "fontsize" => 24, - "beginparagraph" => true, - ); - } - - function parseControl($control, $parameter) { - switch ($control) { - case "fonttbl": // font table definition start - $this->flags"fonttbl" = true; // signal fonttable control words they are allowed to behave as expected - break; - case "f": // define or set font - if($this->flags"fonttbl") { // if its set, the fonttable definition is written to; else its read from - $this->flags"fonttbl_current_write" = $parameter; - } else { - $this->flags"fonttbl_current_read" = $parameter; - } - break; - case "fcharset": // this is for preparing flushQueue; it then moves the Queue to $this->fonttable .. instead to formatted output - $this->flags"fonttbl_want_fcharset" = $parameter; - break; - case "fs": // sets the current fontsize; is used by stylesheets (which are therefore generated on the fly - $this->flags"fontsize" = $parameter; - break; - - case "qc": // handle center alignment - $this->flags"alignment" = "center"; - break; - case "qr": // handle right alignment - $this->flags"alignment" = "right"; - break; - - case "pard": // reset paragraph settings (only alignment) - $this->flags"alignment" = ""; - break; - case "par": // define new paragraph (for now, thats a simple break in html) begin new line - if($this->wantHTML) { - $this->out .= "</div>"; - } - else if($this->wantASCII) { - $this->out .= "\n"; - } - $this->flags"beginparagraph" = true; - break; - case "bnone": // bold - $parameter = "0"; - case "b": - // haven'y yet figured out WHY I need a (string)-cast here ... hm - if((string)$parameter == "0") - $this->flags"bold" = false; - else - $this->flags"bold" = true; - break; - case "ulnone": // underlined - $parameter = "0"; - case "ul": - if((string)$parameter == "0") - $this->flags"underlined" = false; - else - $this->flags"underlined" = true; - break; - case "inone": // italic - $parameter = "0"; - case "i": - if((string)$parameter == "0") - $this->flags"italic" = false; - else - $this->flags"italic" = true; - break; - case "strikenone": // strikethru - $parameter = "0"; - case "strike": - if((string)$parameter == "0") - $this->flags"strikethru" = false; - else - $this->flags"strikethru" = true; - break; - case "plain": // reset all font modifiers and fontsize to 12 - $this->flags"bold" = false; - $this->flags"italic" = false; - $this->flags"underlined" = false; - $this->flags"strikethru" = false; - $this->flags"fontsize" = 12; - - $this->flags"subscription" = false; - $this->flags"superscription" = false; - break; - case "subnone": // subscription - $parameter = "0"; - case "sub": - if((string)$parameter == "0") - $this->flags"subscription" = false; - else - $this->flags"subscription" = true; - break; - case "supernone": // superscription - $parameter = "0"; - case "super": - if((string)$parameter == "0") - $this->flags"superscription" = false; - else - $this->flags"superscription" = true; - break; - } - } - - /* - Dispatch the control word to the output stream - */ - - function flushControl() { - if(preg_match("/^(A-Za-z+)(-?0-9*) ?$/", $this->cword, $match)) { - $this->parseControl($match1, $match2); - if($this->wantXML) { - $this->out.="<control word=\"".$match1."\""; - if(strlen($match2) > 0) - $this->out.=" param=\"".$match2."\""; - $this->out.="/>"; - } - } - } - - /* - If output stream supports comments, dispatch it - */ - - function flushComment($comment) { - if($this->wantXML || $this->wantHTML) { - $this->out.="<!-- ".$comment." -->"; - } - } - - /* - Dispatch start/end of logical rtf groups (not every output type needs it; merely debugging purpose) - */ - - function flushGroup($state) { - if($state == "open") { /* push onto the stack */ - array_push($this->stack, $this->flags); - - if($this->wantXML) - $this->out.="<group>"; - } - if($state == "close") { /* pop from the stack */ - $this->flags = array_pop($this->stack); - - $this->flags"fonttbl_current_write" = ""; // on group close, no more fontdefinition will be written to this id - // this is not really the right way to do it ! - // of course a '}' not necessarily donates a fonttable end; a fonttable - // group at least *can* contain sub-groups - // therefore an stacked approach is heavily needed - $this->flags"fonttbl" = false; // no matter what you do, if a group closes, its fonttbl definition is closed too - - if($this->wantXML) - $this->out.="</group>"; - } - } - - function flushHead() { - if($this->wantXML) - $this->out.="<rtf>"; - } - - function flushBottom() { - if($this->wantXML) - $this->out.="</rtf>"; - } - - function checkHtmlSpanContent($command) { - reset($this->fontmodifier_table); - while(list($rtf, $html) = each($this->fontmodifier_table)) { - if($this->flags$rtf == true) { - if($command == "start") - $this->out .= "<".$html.">"; - else - $this->out .= "</".$html.">"; - } - } - } - - /* - flush text in queue - */ - function flushQueue() { - if(strlen($this->queue)) { - // processing logic - if (isset($this->flags"fonttbl_want_fcharset") && - preg_match("/^0-9+$/", $this->flags"fonttbl_want_fcharset")) { - $this->fonttable$this->flags"fonttbl_want_fcharset""charset" = $this->queue; - $this->flags"fonttbl_want_fcharset" = ""; - $this->queue = ""; - } - - // output logic - if (strlen($this->queue)) { - /* - Everything which passes this is (or, at leat, *should*) be only outputted plaintext - Thats why we can safely add the css-stylesheet when using wantHTML - */ - if($this->wantXML) - $this->out.= "<plain>".$this->queue."</plain>"; - else if($this->wantHTML) { - // only output html if a valid (for now, just numeric;) fonttable is given - if(preg_match("/^0-9+$/", $this->flags"fonttbl_current_read")) { - if($this->flags"beginparagraph" == true) { - $this->flags"beginparagraph" = false; - $this->out .= "<div align=\""; - switch($this->flags"alignment") { - case "right": - $this->out .= "right"; - break; - case "center": - $this->out .= "center"; - break; - case "left": - default: - $this->out .= "left"; - } - $this->out .= "\">"; - } - - /* define new style for that span */ - $this->styles"f".$this->flags"fonttbl_current_read"."s".$this->flags"fontsize" = "font-family:".$this->fonttable$this->flags"fonttbl_current_read""charset"." font-size:".$this->flags"fontsize".";"; - /* write span start */ - $this->out .= "<span class=\"f".$this->flags"fonttbl_current_read"."s".$this->flags"fontsize"."\">"; - - /* check if the span content has a modifier */ - $this->checkHtmlSpanContent("start"); - /* write span content */ - $this->out .= $this->queue; - /* close modifiers */ - $this->checkHtmlSpanContent("stop"); - /* close span */ - $this->out .= "</span>"; - } - } - $this->queue = ""; - } - } - } - - /* - handle special charactes like \'ef - */ - - function flushSpecial($special) { - if(strlen($special) == 2) { - if($this->wantASCII) - $this->out .= chr(hexdec('0x'.$special)); - else if($this->wantXML) - $this->out .= "<special value=\"".$special."\"/>"; - else if($this->wantHTML){ - $this->out .= "<special value=\"".$special."\"/>"; - switch($special) { - case "c1": $this->out .= "Á"; break; - case "e1": $this->out .= "á"; break; - case "c0": $this->out .= "À"; break; - case "e0": $this->out .= "à"; break; - case "c9": $this->out .= "É"; break; - case "e9": $this->out .= "é"; break; - case "c8": $this->out .= "È"; break; - case "e8": $this->out .= "è"; break; - case "cd": $this->out .= "Í"; break; - case "ed": $this->out .= "í"; break; - case "cc": $this->out .= "Ì"; break; - case "ec": $this->out .= "ì"; break; - case "d3": $this->out .= "Ó"; break; - case "f3": $this->out .= "ó"; break; - case "d2": $this->out .= "Ò"; break; - case "f2": $this->out .= "ò"; break; - case "da": $this->out .= "Ú"; break; - case "fa": $this->out .= "ú"; break; - case "d9": $this->out .= "Ù"; break; - case "f9": $this->out .= "ù"; break; - case "80": $this->out .= "€"; break; - case "d1": $this->out .= "Ñ"; break; - case "f1": $this->out .= "ñ"; break; - case "c7": $this->out .= "Ç"; break; - case "e7": $this->out .= "ç"; break; - case "dc": $this->out .= "Ü"; break; - case "fc": $this->out .= "ü"; break; - case "bf": $this->out .= "¿"; break; - case "a1": $this->out .= "¡"; break; - case "b7": $this->out .= "·"; break; - case "a9": $this->out .= "©"; break; - case "ae": $this->out .= "®"; break; - case "ba": $this->out .= "º"; break; - case "aa": $this->out .= "ª"; break; - case "b2": $this->out .= "²"; break; - case "b3": $this->out .= "³"; break; - } - } - } - } - - /* - Output errors at end - */ - function flushErrors() { - if(count($this->err) > 0) { - if($this->wantXML) { - $this->out .= "<errors>"; - while(list($num,$value) = each($this->err)) { - $this->out .= "<message>".$value."</message>"; - } - $this->out .= "</errors>"; - } - } - } - - function makeStyles() { - $this->outstyles = "<style type=\"text/css\"><!--\n"; - reset($this->styles); - while(list($stylename, $styleattrib) = each($this->styles)) { - $this->outstyles .= ".".$stylename." { ".$styleattrib." }\n"; - } - $this->outstyles .= "--></style>\n"; - } - - function parse() { - - $this->parserInit(); - - $i = 0; - $this->cw = false; // flag if control word is currently parsed - $this->cfirst = false; // first control character ? - $this->cword = ""; // last or current control word (depends on $this->cw - $this->queue = ""; // plain text data found during parsing - - $this->flushHead(); - - while($i < $this->rtf_len) { - switch($this->rtf$i) { - case "{": - if($this->cw) { - $this->flushControl(); - $this->cw = false; - $this->cfirst = false; - } else - $this->flushQueue(); - - $this->flushGroup("open"); - break; - case "}": - if($this->cw) { - $this->flushControl(); - $this->cw = false; - $this->cfirst = false; - } else - $this->flushQueue(); - - $this->flushGroup("close"); - break; - case "\\": - if($this->cfirst) { // catches '\\' - $this->queue .= "\\"; // replaced single quotes - $this->cfirst = false; - $this->cw = false; - break; - } - if($this->cw) { - $this->flushControl(); - } else - $this->flushQueue(); - $this->cw = true; - $this->cfirst = true; - $this->cword = ""; - break; - default: - if((ord($this->rtf$i) == 10) || (ord($this->rtf$i) == 13)) break; // eat line breaks - if($this->cw) { // active control word ? - /* - Watch the RE: there's an optional space at the end which IS part of - the control word (but actually its ignored by flushControl) - */ - if(preg_match("/^a-zA-Z0-9-?$/", $this->rtf$i)) { // continue parsing - $this->cword .= $this->rtf$i; - $this->cfirst = false; - } else { - /* - Control word could be a 'control symbol', like \~ or \* etc. - */ - $specialmatch = false; - if($this->cfirst) { - if($this->rtf$i == '\'') { // expect to get some special chars - $this->flushQueue(); - $this->flushSpecial($this->rtf$i+1.$this->rtf$i+2); - $i+=2; - $specialmatch = true; - $this->cw = false; - $this->cfirst = false; - $this->cword = ""; - } else - if(preg_match("/^{}\*$/", $this->rtf$i)) { - $this->flushComment("control symbols not yet handled"); - $specialmatch = true; - } - $this->cfirst = false; - } else { - if($this->rtf$i == ' ') { // space delimtes control words, so just discard it and flush the controlword - $this->cw = false; - $this->flushControl(); - break; - } - } - if(!$specialmatch) { - $this->flushControl(); - $this->cw = false; - $this->cfirst = false; - /* - The current character is a delimeter, but is NOT - part of the control word so we hop one step back - in the stream and process it again - */ - $i--; - } - } - } else { - // < and > need translation before putting into queue when XML or HTML is wanted - if(($this->wantHTML) || ($this->wantXML)) { - switch($this->rtf$i) { - case "<": - $this->queue .= "<"; - break; - case ">": - $this->queue .= ">"; - break; - default: - $this->queue .= $this->rtf$i; - break; - } - } else - $this->queue .= $this->rtf$i; - } - - } - $i++; - } - $this->flushQueue(); - $this->flushErrors(); - $this->flushBottom(); - - if($this->wantHTML) { - $this->makeStyles(); - } - } -}
View file
kolab-syncroton-2.4.2.tar.gz/lib/init.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | |
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -28,28 +28,23 @@ */ class kolab_sync extends rcube { - /** - * Application name - * - * @var string - */ + /** @var string Application name */ public $app_name = 'ActiveSync for Kolab'; // no double quotes inside - /** - * Current user - * - * @var rcube_user - */ - public $user; - + /** @var string|null Request user name */ public $username; + + /** @var string|null Request user password */ public $password; - public $task = null; + public $task; + protected $per_user_log_dir; + protected $log_dir; + protected $logger; - const CHARSET = 'UTF-8'; - const VERSION = "2.4.2"; + public const CHARSET = 'UTF-8'; + public const VERSION = "2.4.2"; /** @@ -60,7 +55,7 @@ * * @return kolab_sync The one and only instance */ - static function get_instance($mode = 0, $env = '') + public static function get_instance($mode = 0, $env = '') { if (!self::$instance || !is_a(self::$instance, 'kolab_sync')) { self::$instance = new kolab_sync(); @@ -77,15 +72,16 @@ public function startup() { // Initialize Syncroton Logger - $debug_mode = $this->config->get('activesync_debug') ? kolab_sync_logger::DEBUG : kolab_sync_logger::WARN; - $this->logger = new kolab_sync_logger($debug_mode); + $debug_mode = $this->config->get('activesync_debug') ? kolab_sync_logger::DEBUG : kolab_sync_logger::WARN; + $this->logger = new kolab_sync_logger($debug_mode); + $this->log_dir = $this->config->get('log_dir'); // Get list of plugins // WARNING: We can use only plugins that are prepared for this - // e.g. are not using output or rcmail objects or - // doesn't throw errors when using them - $plugins = (array)$this->config->get('activesync_plugins', array('kolab_auth')); - $plugins = array_unique(array_merge($plugins, array('libkolab', 'libcalendaring'))); + // e.g. are not using output or rcmail objects and + // do not throw errors when using them + $plugins = (array)$this->config->get('activesync_plugins', 'kolab_auth'); + $plugins = array_unique(array_merge($plugins, 'libkolab', 'libcalendaring')); // Initialize/load plugins $this->plugins = kolab_sync_plugin_api::get_instance(); @@ -118,7 +114,7 @@ } if (isset($basicAuthData) && !empty($basicAuthData)) { - list($_SERVER'PHP_AUTH_USER', $_SERVER'PHP_AUTH_PW') = explode(":", $basicAuthData); + $_SERVER'PHP_AUTH_USER', $_SERVER'PHP_AUTH_PW' = explode(":", $basicAuthData); } } @@ -132,34 +128,37 @@ } } + // Set log directory per-user + $this->set_log_dir($_SERVER'PHP_AUTH_USER'); + // Authenticate the user $userid = $this->authenticate($_SERVER'PHP_AUTH_USER', $_SERVER'PHP_AUTH_PW'); } if (empty($userid)) { - header('WWW-Authenticate: Basic realm="' . $this->app_name .'"'); + header('WWW-Authenticate: Basic realm="' . $this->app_name . '"'); header('HTTP/1.1 401 Unauthorized'); exit; } - $this->plugins->exec_hook('ready', array('task' => 'syncroton')); + $this->plugins->exec_hook('ready', 'task' => 'syncroton'); - // Set log directory per-user + // Set log directory per-user (again, in case the username changed above) $this->set_log_dir(); // Save user password for Roundcube Framework $this->password = $_SERVER'PHP_AUTH_PW'; // Register Syncroton backends/callbacks - Syncroton_Registry::set(Syncroton_Registry::LOGGERBACKEND, $this->logger); - Syncroton_Registry::set(Syncroton_Registry::DATABASE, $this->get_dbh()); - Syncroton_Registry::set(Syncroton_Registry::TRANSACTIONMANAGER, kolab_sync_transaction_manager::getInstance()); - Syncroton_Registry::set(Syncroton_Registry::DEVICEBACKEND, new kolab_sync_backend_device); - Syncroton_Registry::set(Syncroton_Registry::FOLDERBACKEND, new kolab_sync_backend_folder); - Syncroton_Registry::set(Syncroton_Registry::SYNCSTATEBACKEND, new kolab_sync_backend_state); - Syncroton_Registry::set(Syncroton_Registry::CONTENTSTATEBACKEND, new kolab_sync_backend_content); - Syncroton_Registry::set(Syncroton_Registry::POLICYBACKEND, new kolab_sync_backend_policy); - Syncroton_Registry::set(Syncroton_Registry::SLEEP_CALLBACK, array($this, 'sleep')); + Syncroton_Registry::set(Syncroton_Registry::LOGGERBACKEND, $this->logger); + Syncroton_Registry::set(Syncroton_Registry::DATABASE, $this->get_dbh()); + Syncroton_Registry::set(Syncroton_Registry::TRANSACTIONMANAGER, kolab_sync_transaction_manager::getInstance()); + Syncroton_Registry::set(Syncroton_Registry::DEVICEBACKEND, new kolab_sync_backend_device()); + Syncroton_Registry::set(Syncroton_Registry::FOLDERBACKEND, new kolab_sync_backend_folder()); + Syncroton_Registry::set(Syncroton_Registry::SYNCSTATEBACKEND, new kolab_sync_backend_state()); + Syncroton_Registry::set(Syncroton_Registry::CONTENTSTATEBACKEND, new kolab_sync_backend_content()); + Syncroton_Registry::set(Syncroton_Registry::POLICYBACKEND, new kolab_sync_backend_policy()); + Syncroton_Registry::set(Syncroton_Registry::SLEEP_CALLBACK, $this, 'sleep'); Syncroton_Registry::setContactsDataClass('kolab_sync_data_contacts'); Syncroton_Registry::setCalendarDataClass('kolab_sync_data_calendar'); @@ -169,9 +168,9 @@ Syncroton_Registry::setGALDataClass('kolab_sync_data_gal'); // Configuration - Syncroton_Registry::set(Syncroton_Registry::PING_TIMEOUT, (int) $this->config->get('activesync_ping_timeout', 60)); - Syncroton_Registry::set(Syncroton_Registry::PING_INTERVAL, (int) $this->config->get('activesync_ping_interval', 15 * 60)); - Syncroton_Registry::set(Syncroton_Registry::QUIET_TIME, (int) $this->config->get('activesync_quiet_time', 3 * 60)); + Syncroton_Registry::set(Syncroton_Registry::PING_TIMEOUT, (int) $this->config->get('activesync_ping_timeout', 60)); + Syncroton_Registry::set(Syncroton_Registry::PING_INTERVAL, (int) $this->config->get('activesync_ping_interval', 15 * 60)); + Syncroton_Registry::set(Syncroton_Registry::QUIET_TIME, (int) $this->config->get('activesync_quiet_time', 3 * 60)); Syncroton_Registry::set(Syncroton_Registry::MAX_COLLECTIONS, (int) $this->config->get('activesync_max_folders', 100)); // Run Syncroton @@ -186,7 +185,7 @@ * @param string $username User name * @param string $password User password * - * @param int User ID + * @return null|int User ID */ public function authenticate($username, $password) { @@ -196,44 +195,42 @@ $cache_key = sha1($username . '::' . $host); if (!$cache || !($auth = $cache->get($cache_key))) { - $auth = $this->plugins->exec_hook('authenticate', array( + $auth = $this->plugins->exec_hook('authenticate', 'host' => $host, 'user' => $username, 'pass' => $password, - )); + ); if (!$auth'abort' && $cache) { - $cache->set($cache_key, array( + $cache->set($cache_key, 'user' => $auth'user', 'host' => $auth'host', - )); + ); } // LDAP server failure... send 503 error - if ($auth'kolab_ldap_error' ?? null) { + if (!empty($auth'kolab_ldap_error')) { self::server_error(); } // Close LDAP connection from kolab_auth plugin if (class_exists('kolab_auth', false)) { - if (method_exists('kolab_auth', 'ldap_close')) { - kolab_auth::ldap_close(); - } + kolab_auth::ldap_close(); } - } - else { + } else { $auth'pass' = $password; } $err = null; // Authenticate - get Roundcube user ID - if (!($auth'abort' ?? false) && ($userid = $this->login($auth'user', $auth'pass', $auth'host', $err))) { + if (empty($auth'abort') && ($userid = $this->login($auth'user', $auth'pass', $auth'host', $err))) { // set real username $this->username = $auth'user'; return $userid; } - else if ($err) { + + if ($err) { $err_str = $this->get_storage()->get_error_str(); } @@ -241,17 +238,18 @@ kolab_auth::log_login_error($auth'user', !empty($err_str) ? $err_str : $err); } - $this->plugins->exec_hook('login_failed', array( + $this->plugins->exec_hook('login_failed', 'host' => $auth'host', 'user' => $auth'user', - )); + ); // IMAP server failure... send 503 error if ($err == rcube_imap_generic::ERROR_BAD) { self::server_error(); } - } + return null; + } /** * Storage host selection @@ -259,10 +257,10 @@ private function select_host($username) { // Get IMAP host - $host = $this->config->get('default_host'); + $host = $this->config->get('imap_host', $this->config->get('default_host')); if (is_array($host)) { - list($user, $domain) = explode('@', $username); + $user, $domain = explode('@', $username); // try to select host by mail domain if (!empty($domain)) { @@ -270,8 +268,7 @@ if (is_array($mail_domains) && in_array_nocase($domain, $mail_domains)) { $host = $storage_host; break; - } - else if (stripos($storage_host, $domain) !== false || stripos(strval($mail_domains), $domain) !== false) { + } elseif (stripos($storage_host, $domain) !== false || stripos(strval($mail_domains), $domain) !== false) { $host = is_numeric($storage_host) ? $mail_domains : $storage_host; break; } @@ -280,8 +277,8 @@ // take the first entry if $host is not found if (is_array($host)) { - list($key, $val) = each($host); - $host = is_numeric($key) ? $val : $key; + $key = key($host); + $host = is_numeric($key) ? $host$key : $key; } } @@ -307,11 +304,10 @@ $ssl = null; if (!empty($a_host'host')) { $host = $a_host'host'; - $ssl = (isset($a_host'scheme') && in_array($a_host'scheme', array('ssl','imaps','tls'))) ? $a_host'scheme' : null; + $ssl = (isset($a_host'scheme') && in_array($a_host'scheme', 'ssl','imaps','tls')) ? $a_host'scheme' : null; if (!empty($a_host'port')) { $port = $a_host'port'; - } - else if ($ssl && $ssl != 'tls' && (!$default_port || $default_port == 143)) { + } elseif ($ssl && $ssl != 'tls' && (!$default_port || $default_port == 143)) { $port = 993; } } @@ -325,10 +321,9 @@ if ($login_lc) { if ($login_lc == 2 || $login_lc === true) { $username = mb_strtolower($username); - } - else if (strpos($username, '@')) { + } elseif (strpos($username, '@')) { // lowercase domain name - list($local, $domain) = explode('@', $username); + $local, $domain = explode('@', $username); $username = $local . '@' . mb_strtolower($domain); } } @@ -357,18 +352,17 @@ $user = rcube_user::create($username, $host); if (!$user) { - self::raise_error(array( + self::raise_error( 'code' => 620, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Failed to create a user record", - ), true, false); + , true, false); return null; } - } - else { - self::raise_error(array( + } else { + self::raise_error( 'code' => 620, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Access denied for new user $username. 'auto_create_user' is disabled", - ), true, false); + , true, false); return null; } } @@ -391,15 +385,35 @@ /** + * Initializes and returns the storage backend object + */ + public static function storage() + { + $class = 'kolab_sync_storage'; + $self = self::get_instance(); + + if (($name = $self->config->get('activesync_storage')) && $name != 'kolab') { + $class .= '_' . strtolower($name); + } + + return $class::get_instance(); + } + + + /** * Set logging directory per-user */ - protected function set_log_dir() + protected function set_log_dir($username = null) { - if (empty($this->username)) { + if (empty($username)) { + $username = $this->username; + } + + if (empty($username)) { return; } - $this->logger->set_username($this->username); + $this->logger->set_username($username); $user_debug = (bool) $this->config->get('per_user_logging'); @@ -407,8 +421,7 @@ return; } - $log_dir = $this->config->get('log_dir'); - $log_dir .= DIRECTORY_SEPARATOR . $this->username; + $log_dir = $this->log_dir . DIRECTORY_SEPARATOR . $username; // No automatically creating any log directories if (!is_dir($log_dir)) { @@ -420,8 +433,7 @@ if (!empty($_GET'DeviceId')) { $deviceId = $_GET'DeviceId'; - } - else if ( + } elseif ( !empty($_SERVER'QUERY_STRING') && strpos($_SERVER'QUERY_STRING', '&') == false && ($query = base64_decode($_SERVER'QUERY_STRING')) @@ -470,6 +482,10 @@ */ public static function server_error() { + if (php_sapi_name() == 'cli') { + throw new Exception("LDAP/IMAP error on authentication"); + } + header("HTTP/1.1 503 Service Temporarily Unavailable"); header("Retry-After: 120"); exit; @@ -490,22 +506,27 @@ if ($this->config->get('devel_mode') || $this->config->get('performance_stats')) { // we have to disable per_user_logging to make sure stats end up in the main console log $this->config->set('per_user_logging', false); + $this->config->set('log_dir', $this->log_dir); // make sure logged numbers use unified format setlocale(LC_NUMERIC, 'en_US.utf8', 'en_US.UTF-8', 'en_US', 'C'); - if (function_exists('memory_get_usage')) + $mem = ''; + if (function_exists('memory_get_usage')) { $mem = round(memory_get_usage() / 1048576, 1); - if (function_exists('memory_get_peak_usage')) + } + if (function_exists('memory_get_peak_usage')) { $mem .= '/' . round(memory_get_peak_usage() / 1048576, 1); + } - $query = $_SERVER'QUERY_STRING'; + $query = $_SERVER'QUERY_STRING' ?? ''; $log = $query . ($mem ? ($query ? ' ' : '') . "$mem" : ''); - if (defined('KOLAB_SYNC_START')) + if (defined('KOLAB_SYNC_START')) { self::print_timer(KOLAB_SYNC_START, $log); - else + } else { self::console($log); + } } } @@ -526,7 +547,10 @@ $book->close(); } - kolab_sync_data_gal::$address_books = array(); + kolab_sync_data_gal::$address_books = ; } + + // Reset internal cache of the storage class + self::storage()->reset(); } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_backend.php
Deleted
@@ -1,1057 +0,0 @@ -<?php - -/** - +--------------------------------------------------------------------------+ - | Kolab Sync (ActiveSync for Kolab) | - | | - | Copyright (C) 2011-2012, Kolab Systems AG <contact@kolabsys.com> | - | | - | This program is free software: you can redistribute it and/or modify | - | it under the terms of the GNU Affero General Public License as published | - | by the Free Software Foundation, either version 3 of the License, or | - | (at your option) any later version. | - | | - | This program is distributed in the hope that it will be useful, | - | but WITHOUT ANY WARRANTY; without even the implied warranty of | - | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | - | GNU Affero General Public License for more details. | - | | - | You should have received a copy of the GNU Affero General Public License | - | along with this program. If not, see <http://www.gnu.org/licenses/> | - +--------------------------------------------------------------------------+ - | Author: Aleksander Machniak <machniak@kolabsys.com> | - +--------------------------------------------------------------------------+ -*/ - -class kolab_sync_backend -{ - /** - * Singleton instace of kolab_sync_backend - * - * @var kolab_sync_backend - */ - static protected $instance; - - protected $storage; - protected $folder_meta; - protected $folder_uids; - protected $root_meta; - - static protected $types = array( - 1 => '', - 2 => 'mail.inbox', - 3 => 'mail.drafts', - 4 => 'mail.wastebasket', - 5 => 'mail.sentitems', - 6 => 'mail.outbox', - 7 => 'task.default', - 8 => 'event.default', - 9 => 'contact.default', - 10 => 'note.default', - 11 => 'journal.default', - 12 => 'mail', - 13 => 'event', - 14 => 'contact', - 15 => 'task', - 16 => 'journal', - 17 => 'note', - ); - - static protected $classes = array( - Syncroton_Data_Factory::CLASS_CALENDAR => 'event', - Syncroton_Data_Factory::CLASS_CONTACTS => 'contact', - Syncroton_Data_Factory::CLASS_EMAIL => 'mail', - Syncroton_Data_Factory::CLASS_NOTES => 'note', - Syncroton_Data_Factory::CLASS_TASKS => 'task', - ); - - const ROOT_MAILBOX = 'INBOX'; -// const ROOT_MAILBOX = ''; - const ASYNC_KEY = '/private/vendor/kolab/activesync'; - const UID_KEY = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; - - - /** - * This implements the 'singleton' design pattern - * - * @return kolab_sync_backend The one and only instance - */ - static function get_instance() - { - if (!self::$instance) { - self::$instance = new kolab_sync_backend; - self::$instance->startup(); // init AFTER object was linked with self::$instance - } - - return self::$instance; - } - - - /** - * Class initialization - */ - public function startup() - { - $this->storage = rcube::get_instance()->get_storage(); - - // @TODO: reset cache? if we do this for every request the cache would be useless - // There's no session here - //$this->storage->clear_cache('mailboxes.', true); - - // set additional header used by libkolab - $this->storage->set_options(array( - // @TODO: there can be Roundcube plugins defining additional headers, - // we maybe would need to add them here - 'fetch_headers' => 'X-KOLAB-TYPE X-KOLAB-MIME-VERSION', - 'skip_deleted' => true, - 'threading' => false, - )); - - // Disable paging - $this->storage->set_pagesize(999999); - } - - - /** - * List known devices - * - * @return array Device list as hash array - */ - public function devices_list() - { - if ($this->root_meta === null) { - // @TODO: consider server annotation instead of INBOX - if ($meta = $this->storage->get_metadata(self::ROOT_MAILBOX, self::ASYNC_KEY)) { - $this->root_meta = $this->unserialize_metadata($metaself::ROOT_MAILBOXself::ASYNC_KEY); - } - else { - $this->root_meta = array(); - } - } - - if (!empty($this->root_meta'DEVICE') && is_array($this->root_meta'DEVICE')) { - return $this->root_meta'DEVICE'; - } - - return array(); - } - - - /** - * Get list of folders available for sync - * - * @param string $deviceid Device identifier - * @param string $type Folder type - * @param bool $flat_mode Enables flat-list mode - * - * @return array|bool List of mailbox folders, False on backend failure - */ - public function folders_list($deviceid, $type, $flat_mode = false) - { - // get all folders of specified type - $folders = kolab_storage::list_folders('', '*', $type, false, $typedata); - - // get folders activesync config - $folderdata = $this->folder_meta(); - - if (!is_array($folders) || !is_array($folderdata)) { - return false; - } - - $folders_list = array(); - - // check if folders are "subscribed" for activesync - foreach ($folderdata as $folder => $meta) { - if (empty($meta'FOLDER') || empty($meta'FOLDER'$deviceid) - || empty($meta'FOLDER'$deviceid'S') - ) { - continue; - } - - // force numeric folder name to be a string (T1283) - $folder = (string) $folder; - - if (!empty($type) && !in_array($folder, $folders)) { - continue; - } - - // Activesync folder identifier (serverId) - $folder_type = !empty($typedata$folder) ? $typedata$folder : 'mail'; - $folder_id = self::folder_id($folder, $folder_type); - - $folders_list$folder_id = $this->folder_data($folder, $folder_type); - } - - if ($flat_mode) { - $folders_list = $this->folders_list_flat($folders_list, $type, $typedata); - } - - return $folders_list; - } - - /** - * Converts list of folders to a "flat" list - */ - private function folders_list_flat($folders, $type, $typedata) - { - $delim = $this->storage->get_hierarchy_delimiter(); - - foreach ($folders as $idx => $folder) { - if ($folder'parentId') { - // for non-mail folders we make the list completely flat - if ($type != 'mail') { - $display_name = kolab_storage::object_name($folder'imap_name'); - $display_name = html_entity_decode($display_name, ENT_COMPAT, RCUBE_CHARSET); - - $folders$idx'parentId' = 0; - $folders$idx'displayName' = $display_name; - } - // for mail folders we modify only folders with non-existing parents - else if (!isset($folders$folder'parentId')) { - $items = explode($delim, $folder'imap_name'); - $parent = 0; - - // find existing parent - while (count($items) > 0) { - array_pop($items); - - $parent_name = implode($delim, $items); - $parent_type = !empty($typedata$parent_name) ? $typedata$parent_name : 'mail'; - $parent_id = self::folder_id($parent_name, $parent_type); - - if (isset($folders$parent_id)) { - $parent = $parent_id; - break; - } - } - - if (!$parent) { - $display_name = kolab_storage::object_name($folder'imap_name'); - $display_name = html_entity_decode($display_name, ENT_COMPAT, RCUBE_CHARSET); - } - else { - $parent_name = $folders$parent_id'imap_name'; - $display_name = substr($folder'imap_name', strlen($parent_name)+1); - $display_name = rcube_charset::convert($display_name, 'UTF7-IMAP'); - $display_name = str_replace($delim, ' » ', $display_name); - } - - $folders$idx'parentId' = $parent; - $folders$idx'displayName' = $display_name; - } - } - } - - return $folders; - } - - /** - * Getter for folder metadata - * - * @return array|bool Hash array with meta data for each folder, False on backend failure - */ - public function folder_meta() - { - if (!isset($this->folder_meta)) { - // get folders activesync config - $folderdata = $this->storage->get_metadata("*", self::ASYNC_KEY); - - if (!is_array($folderdata)) { - return $this->folder_meta = false; - } - - $this->folder_meta = array(); - - foreach ($folderdata as $folder => $meta) { - if ($asyncdata = $metaself::ASYNC_KEY) { - if ($metadata = $this->unserialize_metadata($asyncdata)) { - $this->folder_meta$folder = $metadata; - } - } - } - } - - return $this->folder_meta; - } - - - /** - * Creates folder and subscribes to the device - * - * @param string $name Folder name (UTF7-IMAP) - * @param int $type Folder (ActiveSync) type - * @param string $deviceid Device identifier - * - * @return bool True on success, False on failure - */ - public function folder_create($name, $type, $deviceid) - { - if ($this->storage->folder_exists($name)) { - $created = true; - } - else { - $type = self::type_activesync2kolab($type); - $created = kolab_storage::folder_create($name, $type, true); - } - - if ($created) { - // Set ActiveSync subscription flag - $this->folder_set($name, $deviceid, 1); - - return true; - } - - return false; - } - - - /** - * Renames a folder - * - * @param string $old_name Old folder name (UTF7-IMAP) - * @param string $new_name New folder name (UTF7-IMAP) - * @param int $type Folder (ActiveSync) type - * - * @return bool True on success, False on failure - */ - public function folder_rename($old_name, $new_name, $type) - { - $this->folder_meta = null; - - $type = self::type_activesync2kolab($type); - - // don't use kolab_storage for moving mail folders - if (preg_match('/^mail/', $type)) { - return $this->storage->rename_folder($old_name, $new_name); - } - else { - return kolab_storage::folder_rename($old_name, $new_name); - } - } - - - /** - * Deletes folder - * - * @param string $name Folder name (UTF7-IMAP) - * @param string $deviceid Device identifier - * - */ - public function folder_delete($name, $deviceid) - { - unset($this->folder_meta$name); - - return kolab_storage::folder_delete($name); - } - - - /** - * Sets ActiveSync subscription flag on a folder - * - * @param string $name Folder name (UTF7-IMAP) - * @param string $deviceid Device identifier - * @param int $flag Flag value (0|1|2) - */ - public function folder_set($name, $deviceid, $flag) - { - if (empty($deviceid)) { - return false; - } - - // get folders activesync config - $metadata = $this->folder_meta(); - - if (!is_array($metadata)) { - return false; - } - - $metadata = isset($metadata$name) ? $metadata$name : array(); - - if ($flag) { - if (empty($metadata)) { - $metadata = array(); - } - - if (empty($metadata'FOLDER')) { - $metadata'FOLDER' = array(); - } - - if (empty($metadata'FOLDER'$deviceid)) { - $metadata'FOLDER'$deviceid = array(); - } - - // Z-Push uses: - // 1 - synchronize, no alarms - // 2 - synchronize with alarms - $metadata'FOLDER'$deviceid'S' = $flag; - } - else { - unset($metadata'FOLDER'$deviceid'S'); - - if (empty($metadata'FOLDER'$deviceid)) { - unset($metadata'FOLDER'$deviceid); - } - - if (empty($metadata'FOLDER')) { - unset($metadata'FOLDER'); - } - - if (empty($metadata)) { - $metadata = null; - } - } - - // Return if nothing's been changed - if (!self::data_array_diff(isset($this->folder_meta$name) ? $this->folder_meta$name : null, $metadata)) { - return true; - } - - $this->folder_meta$name = $metadata; - - return $this->storage->set_metadata($name, array( - self::ASYNC_KEY => $this->serialize_metadata($metadata))); - } - - - public function device_get($id) - { - $devices_list = $this->devices_list(); - return $devices_list$id ?? null; - } - - /** - * Registers new device on server - * - * @param array $device Device data - * @param string $id Device ID - * - * @return bool True on success, False on failure - */ - public function device_create($device, $id) - { - // Fill local cache - $this->devices_list(); - - // Some devices create dummy devices with name "validate" (#1109) - // This device entry is used in two initial requests, but later - // the device registers a real name. We can remove this dummy entry - // on new device creation - $this->device_delete('validate'); - - // Old Kolab_ZPush device parameters - // MODE: -1 | 0 | 1 (not set | flatmode | foldermode) - // TYPE: device type string - // ALIAS: user-friendly device name - - // Syncroton (kolab_sync_backend_device) uses - // ID: internal identifier in syncroton database - // TYPE: device type string - // ALIAS: user-friendly device name - - $metadata = $this->root_meta; - $metadata'DEVICE'$id = $device; - $metadata = array(self::ASYNC_KEY => $this->serialize_metadata($metadata)); - - $result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata); - - if ($result) { - // Update local cache - $this->root_meta'DEVICE'$id = $device; - - // subscribe default set of folders - $this->device_init_subscriptions($id); - } - - return $result; - } - - /** - * Device update. - * - * @param array $device Device data - * @param string $id Device ID - * - * @return bool True on success, False on failure - */ - public function device_update($device, $id) - { - $devices_list = $this->devices_list(); - $old_device = $devices_list$id; - - if (!$old_device) { - return false; - } - - // Do nothing if nothing is changed - if (!self::data_array_diff($old_device, $device)) { - return true; - } - - $device = array_merge($old_device, $device); - - $metadata = $this->root_meta; - $metadata'DEVICE'$id = $device; - $metadata = array(self::ASYNC_KEY => $this->serialize_metadata($metadata)); - - $result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata); - - if ($result) { - // Update local cache - $this->root_meta'DEVICE'$id = $device; - } - - return $result; - } - - - /** - * Device delete. - * - * @param string $id Device ID - * - * @return bool True on success, False on failure - */ - public function device_delete($id) - { - $device = $this->device_get($id); - - if (!$device) { - return false; - } - - unset($this->root_meta'DEVICE'$id, $this->root_meta'FOLDER'$id); - - if (empty($this->root_meta'DEVICE')) { - unset($this->root_meta'DEVICE'); - } - if (empty($this->root_meta'FOLDER')) { - unset($this->root_meta'FOLDER'); - } - - $metadata = $this->serialize_metadata($this->root_meta); - $metadata = array(self::ASYNC_KEY => $metadata); - - // update meta data - $result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata); - - if ($result) { - // remove device annotation for every folder - foreach ($this->folder_meta() as $folder => $meta) { - // skip root folder (already handled above) - if ($folder == self::ROOT_MAILBOX) - continue; - - if (!empty($meta'FOLDER') && isset($meta'FOLDER'$id)) { - unset($meta'FOLDER'$id); - - if (empty($meta'FOLDER')) { - unset($this->folder_meta$folder'FOLDER'); - unset($meta'FOLDER'); - } - if (empty($meta)) { - unset($this->folder_meta$folder); - $meta = null; - } - - $metadata = array(self::ASYNC_KEY => $this->serialize_metadata($meta)); - $res = $this->storage->set_metadata($folder, $metadata); - - if ($res && $meta) { - $this->folder_meta$folder = $meta; - } - } - } - } - - return $result; - } - - /** - * Subscribe default set of folders on device registration - */ - private function device_init_subscriptions($deviceid) - { - // INBOX always exists - $this->folder_set('INBOX', $deviceid, 1); - - $supported_types = array( - 'mail.drafts', - 'mail.wastebasket', - 'mail.sentitems', - 'mail.outbox', - 'event.default', - 'contact.default', - 'note.default', - 'task.default', - 'event', - 'contact', - 'note', - 'task', - 'event.confidential', - 'event.private', - 'task.confidential', - 'task.private', - ); - - // This default set can be extended by adding following values: - $modes = array( - 'SUB_PERSONAL' => 1, // all subscribed folders in personal namespace - 'ALL_PERSONAL' => 2, // all folders in personal namespace - 'SUB_OTHER' => 4, // all subscribed folders in other users namespace - 'ALL_OTHER' => 8, // all folders in other users namespace - 'SUB_SHARED' => 16, // all subscribed folders in shared namespace - 'ALL_SHARED' => 32, // all folders in shared namespace - ); - - $rcube = rcube::get_instance(); - $config = $rcube->config; - $mode = (int) $config->get('activesync_init_subscriptions'); - $folders = array(); - - // Subscribe to default folders - $foldertypes = kolab_storage::folders_typedata(); - - if (!empty($foldertypes)) { - $_foldertypes = array_intersect($foldertypes, $supported_types); - - // get default folders - foreach ($_foldertypes as $folder => $type) { - // only personal folders - if ($this->storage->folder_namespace($folder) == 'personal') { - $flag = preg_match('/^(event|task)/', $type) ? 2 : 1; - $this->folder_set($folder, $deviceid, $flag); - $folders = $folder; - } - } - } - - // we're in default mode, exit - if (!$mode) { - return; - } - - // below we support additionally all mail folders - $supported_types = 'mail'; - $supported_types = 'mail.junkemail'; - - // get configured special folders - $special_folders = array(); - $map = array( - 'drafts' => 'mail.drafts', - 'junk' => 'mail.junkemail', - 'sent' => 'mail.sentitems', - 'trash' => 'mail.wastebasket', - ); - - foreach ($map as $folder => $type) { - if ($folder = $config->get($folder . '_mbox')) { - $special_folders$folder = $type; - } - } - - // get folders list(s) - if (($mode & $modes'ALL_PERSONAL') || ($mode & $modes'ALL_OTHER') || ($mode & $modes'ALL_SHARED')) { - $all_folders = $this->storage->list_folders(); - if (($mode & $modes'SUB_PERSONAL') || ($mode & $modes'SUB_OTHER') || ($mode & $modes'SUB_SHARED')) { - $subscribed_folders = $this->storage->list_folders_subscribed(); - } - } - else { - $all_folders = $this->storage->list_folders_subscribed(); - } - - foreach ($all_folders as $folder) { - // folder already subscribed - if (in_array($folder, $folders)) { - continue; - } - - $type = ($foldertypes$folder ?? null) ?: 'mail'; - if ($type == 'mail' && isset($special_folders$folder)) { - $type = $special_folders$folder; - } - - if (!in_array($type, $supported_types)) { - continue; - } - - $ns = strtoupper($this->storage->folder_namespace($folder)); - - // subscribe the folder according to configured mode - // and folder namespace/subscription status - if (($mode & $modes"ALL_$ns") - || (($mode & $modes"SUB_$ns") - && (!isset($subscribed_folders) || in_array($folder, $subscribed_folders))) - ) { - $flag = preg_match('/^(event|task)/', $type) ? 2 : 1; - $this->folder_set($folder, $deviceid, $flag); - } - } - } - - /** - * Helper method to decode saved IMAP metadata - */ - private function unserialize_metadata($str) - { - if (!empty($str)) { - // Support old Z-Push annotation format - if ($str0 != '{') { - $str = base64_decode($str); - } - $data = json_decode($str, true); - return $data; - } - - return null; - } - - /** - * Helper method to encode IMAP metadata for saving - */ - private function serialize_metadata($data) - { - if (!empty($data) && is_array($data)) { - $data = json_encode($data); -// $data = base64_encode($data); - return $data; - } - - return null; - } - - /** - * Returns Kolab folder type for specified ActiveSync type ID - */ - public static function type_activesync2kolab($type) - { - if (!empty(self::$types$type)) { - return self::$types$type; - } - - return ''; - } - - /** - * Returns ActiveSync folder type for specified Kolab type - */ - public static function type_kolab2activesync($type) - { - $type = preg_replace('/\.(confidential|private)$/i', '', $type); - - if ($key = array_search($type, self::$types)) { - return $key; - } - - return key(self::$types); - } - - /** - * Returns Kolab folder type for specified ActiveSync class name - */ - public static function class_activesync2kolab($class) - { - if (!empty(self::$classes$class)) { - return self::$classes$class; - } - - return ''; - } - - /** - * Returns folder data in Syncroton format - */ - private function folder_data($folder, $type) - { - // Folder name parameters - $delim = $this->storage->get_hierarchy_delimiter(); - $items = explode($delim, $folder); - $name = array_pop($items); - - // Folder UID - $folder_id = $this->folder_id($folder, $type); - - // Folder type - if (strcasecmp($folder, 'INBOX') === 0) { - // INBOX is always inbox, prevent from issues related with a change of - // folder type annotation (it can be initially unset). - $type = 2; - } - else { - $type = self::type_kolab2activesync($type); - - // fix type, if there's no type annotation it's detected as UNKNOWN we'll use 'mail' (12) - if ($type == 1) { - $type = 12; - } - } - - // Syncroton folder data array - return array( - 'serverId' => $folder_id, - 'parentId' => count($items) ? self::folder_id(implode($delim, $items)) : 0, - 'displayName' => rcube_charset::convert($name, 'UTF7-IMAP', kolab_sync::CHARSET), - 'type' => $type, - // for internal use - 'imap_name' => $folder, - ); - } - - /** - * Builds folder ID based on folder name - */ - public function folder_id($name, $type = null) - { - // ActiveSync expects folder identifiers to be max.64 characters - // So we can't use just folder name - - $name = (string) $name; - - if ($name === '') { - return null; - } - - if (isset($this->folder_uids$name)) { - return $this->folder_uids$name; - } - -/* - @TODO: For now uniqueid annotation doesn't work, we will create UIDs by ourselves. - There's one inconvenience of this solution: folder name/type change - would be handled in ActiveSync as delete + create. - - // get folders unique identifier - $folderdata = $this->storage->get_metadata($name, self::UID_KEY); - - if ($folderdata && !empty($folderdata$name)) { - $uid = $folderdata$nameself::UID_KEY; - return $this->folder_uids$name = $uid; - } -*/ - if (strcasecmp($name, 'INBOX') === 0) { - // INBOX is always inbox, prevent from issues related with a change of - // folder type annotation (it can be initially unset). - $type = 'mail.inbox'; - } - else { - if ($type === null) { - $type = kolab_storage::folder_type($name); - } - - if ($type != null) { - $type = preg_replace('/\.(confidential|private)$/i', '', $type); - } - } - - // Add type to folder UID hash, so type change can be detected by Syncroton - $uid = $name . '!!' . $type; - $uid = md5($uid); - - return $this->folder_uids$name = $uid; - } - - /** - * Returns IMAP folder name - * - * @param string $id Folder identifier - * @param string $deviceid Device dentifier - * - * @return string Folder name (UTF7-IMAP) - */ - public function folder_id2name($id, $deviceid) - { - // check in cache first - if (!empty($this->folder_uids)) { - if (($name = array_search($id, $this->folder_uids)) !== false) { - return $name; - } - } - -/* - @TODO: see folder_id() - - // get folders unique identifier - $folderdata = $this->storage->get_metadata('*', self::UID_KEY); - - foreach ((array)$folderdata as $folder => $data) { - if (!empty($dataself::UID_KEY)) { - $uid = $dataself::UID_KEY; - $this->folder_uids$folder = $uid; - if ($uid == $id) { - $name = $folder; - } - } - } -*/ - // get all folders of specified type - $folderdata = $this->folder_meta(); - - if (!is_array($folderdata) || $id === null) { - return null; - } - - // check if folders are "subscribed" for activesync - foreach ($folderdata as $folder => $meta) { - if (empty($meta'FOLDER') || empty($meta'FOLDER'$deviceid) - || empty($meta'FOLDER'$deviceid'S') - ) { - continue; - } - - if ($uid = self::folder_id($folder)) { - $this->folder_uids$folder = $uid; - } - - if ($uid === $id) { - $name = $folder; - } - } - - return $name; - } - - /** - */ - public function modseq_set($deviceid, $folderid, $synctime, $data) - { - $synctime = $synctime->format('Y-m-d H:i:s'); - $rcube = rcube::get_instance(); - $db = $rcube->get_dbh(); - $old_data = $this->modseq$folderid$synctime ?? null; - - if (empty($old_data)) { - $this->modseq$folderid$synctime = $data; - $data = json_encode($data); - - $db->set_option('ignore_key_errors', true); - $db->query("INSERT INTO `syncroton_modseq` (`device_id`, `folder_id`, `synctime`, `data`)" - ." VALUES (?, ?, ?, ?)", - $deviceid, $folderid, $synctime, $data); - $db->set_option('ignore_key_errors', false); - } - } - - public function modseq_get($deviceid, $folderid, $synctime) - { - $synctime = $synctime->format('Y-m-d H:i:s'); - - if (empty($this->modseq$folderid$synctime)) { - $this->modseq$folderid = array(); - - $rcube = rcube::get_instance(); - $db = $rcube->get_dbh(); - - $db->limitquery("SELECT `data`, `synctime` FROM `syncroton_modseq`" - ." WHERE `device_id` = ? AND `folder_id` = ? AND `synctime` <= ?" - ." ORDER BY `synctime` DESC", - 0, 1, $deviceid, $folderid, $synctime); - - if ($row = $db->fetch_assoc()) { - $synctime = $row'synctime'; - // @TODO: make sure synctime from sql is in "Y-m-d H:i:s" format - $this->modseq$folderid$synctime = json_decode($row'data', true); - } - - // Cleanup: remove all records except the current one - $db->query("DELETE FROM `syncroton_modseq`" - ." WHERE `device_id` = ? AND `folder_id` = ? AND `synctime` <> ?", - $deviceid, $folderid, $synctime); - } - - return @$this->modseq$folderid$synctime; - } - - /** - * Set state of relation objects at specified point in time - */ - public function relations_state_set($deviceid, $folderid, $synctime, $relations) - { - $synctime = $synctime->format('Y-m-d H:i:s'); - $rcube = rcube::get_instance(); - $db = $rcube->get_dbh(); - $old_data = $this->relations$folderid$synctime ?? null; - - if (empty($old_data)) { - $this->relations$folderid$synctime = $relations; - $data = rcube_charset::clean(json_encode($relations)); - - $db->set_option('ignore_key_errors', true); - $db->query("INSERT INTO `syncroton_relations_state`" - ." (`device_id`, `folder_id`, `synctime`, `data`)" - ." VALUES (?, ?, ?, ?)", - $deviceid, $folderid, $synctime, $data); - $db->set_option('ignore_key_errors', false); - } - } - - /** - * Get state of relation objects at specified point in time - */ - public function relations_state_get($deviceid, $folderid, $synctime) - { - $synctime = $synctime->format('Y-m-d H:i:s'); - - if (empty($this->relations$folderid$synctime)) { - $this->relations$folderid = array(); - - $rcube = rcube::get_instance(); - $db = $rcube->get_dbh(); - - $db->limitquery("SELECT `data`, `synctime` FROM `syncroton_relations_state`" - ." WHERE `device_id` = ? AND `folder_id` = ? AND `synctime` <= ?" - ." ORDER BY `synctime` DESC", - 0, 1, $deviceid, $folderid, $synctime); - - if ($row = $db->fetch_assoc()) { - $synctime = $row'synctime'; - // @TODO: make sure synctime from sql is in "Y-m-d H:i:s" format - $this->relations$folderid$synctime = json_decode($row'data', true); - } - - // Cleanup: remove all records except the current one - $db->query("DELETE FROM `syncroton_relations_state`" - ." WHERE `device_id` = ? AND `folder_id` = ? AND `synctime` <> ?", - $deviceid, $folderid, $synctime); - } - - return @$this->relations$folderid$synctime; - } - - /** - * Return last storage error - */ - public static function last_error() - { - return kolab_storage::$last_error; - } - - /** - * Compares two arrays - * - * @param array $array1 - * @param array $array2 - * - * @return bool True if arrays differs, False otherwise - */ - private static function data_array_diff($array1, $array2) - { - if (!is_array($array1) || !is_array($array2)) { - return $array1 != $array2; - } - - if (count($array1) != count($array2)) { - return true; - } - - foreach ($array1 as $key => $val) { - if (!array_key_exists($key, $array2)) { - return true; - } - if ($val !== $array2$key) { - return true; - } - } - - return false; - } -}
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_backend_common.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -61,13 +61,13 @@ * * @var array */ - protected $cache = array(); + protected $cache = ; /** * Constructor */ - function __construct() + public function __construct() { $this->db = rcube::get_instance()->get_dbh(); @@ -79,9 +79,9 @@ /** * Creates new Syncroton object in database * - * @param Syncroton_Model_* $object Object + * @param object $object Object * - * @return Syncroton_Model_* Object + * @return object Object * @throws InvalidArgumentException|Syncroton_Exception_DeadlockDetected|Exception */ public function create($object) @@ -91,15 +91,16 @@ } $data = $this->object_to_array($object); - $cols = array(); + $cols = ; - $data'id' = $object->id = sha1(mt_rand(). microtime()); + $data'id' = $object->id = sha1(mt_rand() . microtime()); foreach (array_keys($data) as $key) { $cols = $this->db->quote_identifier($key); } - $result = $this->db->query('INSERT INTO `' . $this->table_name . '`' . ' (' . implode(', ', $cols) . ')' + $result = $this->db->query( + 'INSERT INTO `' . $this->table_name . '`' . ' (' . implode(', ', $cols) . ')' . ' VALUES(' . implode(', ', array_fill(0, count($cols), '?')) . ')', array_values($data) ); @@ -119,16 +120,17 @@ /** * Returns Syncroton data object * - * @param string $id + * @param string $id + * * @throws Syncroton_Exception_NotFound - * @return Syncroton_Model_* + * @return object */ public function get($id) { $id = $id instanceof $this->interface_name ? $id->id : $id; if ($id) { - $select = $this->db->query('SELECT * FROM `' . $this->table_name . '` WHERE `id` = ?', array($id)); + $select = $this->db->query('SELECT * FROM `' . $this->table_name . '` WHERE `id` = ?', $id); $data = $this->db->fetch_assoc($select); } @@ -142,7 +144,7 @@ /** * Deletes Syncroton data object * - * @param string|Syncroton_Model_* $id Object or identifier + * @param string|object $id Object or identifier * * @return bool True on success, False on failure * @throws Syncroton_Exception_DeadlockDetected|Exception @@ -155,14 +157,14 @@ return false; } - $result = $this->db->query('DELETE FROM `' . $this->table_name .'` WHERE `id` = ?', array($id)); + $result = $this->db->query('DELETE FROM `' . $this->table_name . '` WHERE `id` = ?', $id); if ($err = $this->db->is_error($result)) { $err = "Failed to delete instance of {$this->interface_name}: {$err}"; if ($this->db->error_info()0 == '40001') { throw new Syncroton_Exception_DeadlockDetected($err); } else { - throw new Exception($rr); + throw new Exception($err); } } @@ -172,9 +174,9 @@ /** * Updates Syncroton data object * - * @param Syncroton_Model_* $object + * @param object $object * - * @return Syncroton_Model_* Object + * @return object Object * @throws InvalidArgumentException|Syncroton_Exception_DeadlockDetected|Exception */ public function update($object) @@ -184,7 +186,7 @@ } $data = $this->object_to_array($object); - $set = array(); + $set = ; foreach (array_keys($data) as $key) { $set = $this->db->quote_identifier($key) . ' = ?'; @@ -215,6 +217,7 @@ public function userAccounts($device) { // this method is overwritten by kolab_sync_backend class + return ; } /** @@ -240,7 +243,7 @@ */ protected function object_to_array($object) { - $data = array(); + $data = ; foreach ($object as $key => $value) { if ($value instanceof DateTime) {
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_backend_content.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -42,7 +42,7 @@ { $id = $id instanceof Syncroton_Model_IContent ? $id->id : $id; - $result = $this->db->query("UPDATE `{$this->table_name}` SET `is_deleted` = 1 WHERE `id` = ?", array($id)); + $result = $this->db->query("UPDATE `{$this->table_name}` SET `is_deleted` = 1 WHERE `id` = ?", $id); if ($result = (bool) $this->db->affected_rows($result)) { unset($this->cache'content_folderstate'); @@ -107,7 +107,7 @@ } $select = $this->db->query("SELECT `contentid` FROM `{$this->table_name}` WHERE " . implode(' AND ', $where)); - $result = array(); + $result = ; while ($state = $this->db->fetch_assoc($select)) { $result = $state'contentid';
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_backend_device.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -32,9 +32,9 @@ protected $interface_name = 'Syncroton_Model_IDevice'; /** - * Kolab Sync backend + * Kolab Sync storage backend * - * @var kolab_sync_backend + * @var kolab_sync_storage */ protected $backend; @@ -45,7 +45,7 @@ public function __construct() { parent::__construct(); - $this->backend = kolab_sync_backend::get_instance(); + $this->backend = kolab_sync::storage(); } /** @@ -60,11 +60,11 @@ $device = parent::create($device); // Create device entry in kolab backend - $created = $this->backend->device_create(array( + $created = $this->backend->device_create( 'ID' => $device->id, 'TYPE' => $device->devicetype, 'ALIAS' => $device->friendlyname, - ), $device->deviceid); + , $device->deviceid); if (!$created) { throw new Syncroton_Exception_NotFound('Device creation failed'); @@ -135,7 +135,8 @@ $engine = kolab_sync::get_instance(); $identities = $engine->user->list_identities(); $email = $engine->get_user_email(); - $addresses = array(); + $addresses = ; + $displayname = null; // read email addresses and display name (default ident comes first) foreach ((array)$identities as $ident) { @@ -147,20 +148,20 @@ } if (empty($displayname) && empty($email) && empty($addresses)) { - return array(); + return ; } - $account = new Syncroton_Model_Account; + $account = new Syncroton_Model_Account(); if ($email) { - $addresses = array_diff($addresses, array($email)); + $addresses = array_diff($addresses, $email); } $account->userDisplayName = $displayname; $account->primaryAddress = $email; $account->addresses = array_unique($addresses); - return array($account); + return $account; } /** @@ -168,14 +169,14 @@ * * @param array $request Oof/Get request data * - * @return Syncroton_Model_Oof Response object or NULL if OOF is not supported + * @return Syncroton_Model_Oof|null Response object or NULL if OOF is not supported * @throws Syncroton_Exception_Status */ public function getOOF($request) { $vacation_engine = $this->vacation_engine(); if (!$vacation_engine) { - return; + return null; } $vacation = $vacation_engine->get_vacation(); @@ -183,8 +184,7 @@ if (!$vacation'enabled') { $status = Syncroton_Model_Oof::STATUS_DISABLED; $vacation'start' = $vacation'end' = null; - } - else if ($vacation'start' || $vacation'end') { + } elseif ($vacation'start' || $vacation'end') { // in Activesync both or none time are required if (!$vacation'start' && $vacation'end') { $vacation'start' = new DateTime('1970-01-01', new DateTimeZone('UTC')); @@ -202,15 +202,14 @@ } $status = Syncroton_Model_Oof::STATUS_TIME_BASED; - } - else { + } else { $status = Syncroton_Model_Oof::STATUS_GLOBAL; } $message = null; if ($vacation'message') { - $message = array(); + $message = ; // convert message format, Roundcube supports plain text only if ($request'bodyType' == 'HTML') { @@ -218,22 +217,22 @@ $vacation'message' = $text2html->get_html(); } - foreach (array('Internal', 'ExternalKnown', 'ExternalUnknown') as $type) { - $message = new Syncroton_Model_OofMessage(array( + foreach ('Internal', 'ExternalKnown', 'ExternalUnknown' as $type) { + $message = new Syncroton_Model_OofMessage( "appliesTo$type" => true, 'enabled' => 1, 'bodyType' => 'Text', 'replyMessage' => rcube_charset::clean($vacation'message'), - )); + ); } } - return new Syncroton_Model_Oof(array( + return new Syncroton_Model_Oof( 'oofState' => $status, 'startTime' => $vacation'start', 'endTime' => $vacation'end', 'oofMessage' => $message, - )); + ); } /** @@ -261,8 +260,7 @@ if (empty($vacation'start') || empty($vacation'end')) { throw new Syncroton_Exception_Status_Settings(Syncroton_Exception_Status_Settings::INVALID_ARGUMENTS); } - } - else { + } else { $vacation'start' = $vacation'end' = null; } @@ -291,7 +289,7 @@ $vacation_engine->set_vacation($vacation); } // disable out-of-office - else if (isset($request->oofState)) { + elseif (isset($request->oofState)) { if ($vacation'enabled') { $vacation'enabled' = false; @@ -310,7 +308,7 @@ if (class_exists('managesieve')) { $plugin = $engine->plugins->get_plugin('managesieve'); - $vacation = $plugin->get_engine('vacation'); + $vacation = $plugin->get_engine('vacation'); // @phpstan-ignore-line if ($vacation->connect($engine->username, $engine->password)) { throw new Exception("Connection to managesieve server failed");
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_backend_folder.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -58,10 +58,10 @@ $device_id = $deviceid instanceof Syncroton_Model_IDevice ? $deviceid->id : $deviceid; $where = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); - $where = $this->db->quote_identifier('class') . ' = ' . $this->db->quote($class); + $where = $this->db->quote_identifier('class') . ' = ' . $this->db->quote($class); - $select = $this->db->query('SELECT * FROM `' . $this->table_name .'` WHERE ' . implode(' AND ', $where)); - $result = array(); + $select = $this->db->query('SELECT * FROM `' . $this->table_name . '` WHERE ' . implode(' AND ', $where)); + $result = ; while ($folder = $this->db->fetch_assoc($select)) { $result$folder'folderid' = $this->get_object($folder); @@ -83,7 +83,7 @@ $device_id = $deviceid instanceof Syncroton_Model_IDevice ? $deviceid->id : $deviceid; $where = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); - $where = $this->db->quote_identifier('folderid') . ' = ' . $this->db->quote($folderid); + $where = $this->db->quote_identifier('folderid') . ' = ' . $this->db->quote($folderid); $select = $this->db->query('SELECT * FROM `' . $this->table_name . '` WHERE ' . implode(' AND ', $where)); $folder = $this->db->fetch_assoc($select); @@ -107,15 +107,15 @@ $timestamp = new DateTime('now', new DateTimeZone('utc')); $client_crc = ''; $server_crc = ''; - $client_folders = array(); - $server_folders = array(); - $folder_classes = array( + $client_folders = ; + $server_folders = ; + $folder_classes = Syncroton_Data_Factory::CLASS_CALENDAR, Syncroton_Data_Factory::CLASS_CONTACTS, Syncroton_Data_Factory::CLASS_EMAIL, Syncroton_Data_Factory::CLASS_NOTES, - Syncroton_Data_Factory::CLASS_TASKS - ); + Syncroton_Data_Factory::CLASS_TASKS, + ; // Reset imap cache so we work with up-to-date folders list rcube::get_instance()->get_storage()->clear_cache('mailboxes', true); @@ -125,8 +125,7 @@ // retrieve all folders available in data backend $dataController = Syncroton_Data_Factory::factory($class, $device, $timestamp); $server_folders = array_merge($server_folders, $dataController->getAllFolders()); - } - catch (Exception $e) { + } catch (Exception $e) { rcube::raise_error($e, true, false); // This is server error, returning True might cause infinite sync loops return false; @@ -164,15 +163,10 @@ case 'displayName': case 'parentId': return strtolower($string); - break; - case 'serverId': return 'folderid'; - break; - default: return parent::from_camelcase($string); - break; } } @@ -185,19 +179,12 @@ switch ($string) { case 'displayname': return 'displayName'; - break; - case 'parentid': return 'parentId'; - break; - case 'folderid': return 'serverId'; - break; - default: return parent::to_camelcase($string, $ucFirst); - break; } } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_backend_policy.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | |
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_backend_state.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -41,6 +41,7 @@ */ public function create($object, $keep_previous_state = true) { + unset($object->counterNext); $object = parent::create($object); if ($keep_previous_state !== true) { @@ -57,9 +58,9 @@ protected function _deleteOtherStates(Syncroton_Model_ISyncState $state) { // remove all other synckeys - $where = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($state->deviceId); - $where = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($state->type); - $where = $this->db->quote_identifier('counter') . ' <> ' . $this->db->quote($state->counter); + $where = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($state->deviceId); + $where = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($state->type); + $where = $this->db->quote_identifier('counter') . ' <> ' . $this->db->quote($state->counter); $this->db->query("DELETE FROM `{$this->table_name}` WHERE " . implode(' AND ', $where)); } @@ -109,7 +110,7 @@ $folder_id = $folderid instanceof Syncroton_Model_IFolder ? $folderid->id : $folderid; $where = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); - $where = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($folder_id); + $where = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($folder_id); $select = $this->db->limitquery("SELECT * FROM `{$this->table_name}` WHERE " . implode(' AND ', $where) . " ORDER BY `counter` DESC", 0, 1); @@ -135,7 +136,7 @@ $folder_id = $folderid instanceof Syncroton_Model_IFolder ? $folderid->id : $folderid; $where = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); - $where = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($folder_id); + $where = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($folder_id); $this->db->query("DELETE FROM `{$this->table_name}` WHERE " . implode(' AND ', $where)); } @@ -147,19 +148,19 @@ * @param Syncroton_Model_IFolder|string $folderid Folder object or identifier * @param int $sync_key State key * - * @return Syncroton_Model_SyncState + * @return Syncroton_Model_SyncState|false */ public function validate($deviceid, $folderid, $sync_key) { $device_id = $deviceid instanceof Syncroton_Model_IDevice ? $deviceid->id : $deviceid; $folder_id = $folderid instanceof Syncroton_Model_IFolder ? $folderid->id : $folderid; - $states = array(); + $states = ; // get sync data // we'll get all records, thanks to this we'll be able to // skip _deleteOtherStates() call below (one DELETE query less) $where'device_id' = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); - $where'type' = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($folder_id); + $where'type' = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($folder_id); $select = $this->db->query("SELECT * FROM `{$this->table_name}` WHERE " . implode(' AND ', $where)); @@ -175,20 +176,20 @@ $state = $states$sync_key; $next = max(array_keys($states)); - $where = array(); - $where'device_id' = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); - $where'folder_id' = $this->db->quote_identifier('folder_id') . ' = ' . $this->db->quote($folder_id); + $where = ; + $where'device_id' = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); + $where'folder_id' = $this->db->quote_identifier('folder_id') . ' = ' . $this->db->quote($folder_id); $where'is_deleted' = $this->db->quote_identifier('is_deleted') . ' = 1'; // found more recent synckey => the last sync response was not received by the client if ($next > $sync_key) { // We store the clientIdMap with the "next" sync state, so we need to copy it back. $state->clientIdMap = $states$next->clientIdMap; - } - else { + $state->counterNext = $next; + } else { // finally delete all entries marked for removal in syncroton_content table $retryCounter = 0; - while(True) { + while (true) { $result = $this->db->query("DELETE FROM `syncroton_content` WHERE " . implode(' AND ', $where)); if ($this->db->is_error($result)) { $retryCounter++; @@ -218,8 +219,8 @@ $folder_id = $folderid instanceof Syncroton_Model_IFolder ? $folderid->id : $folderid; $where'device_id' = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); - $where'type' = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($folder_id); - $where'counter' = $this->db->quote_identifier('counter') . ' > ' . $this->db->quote($sync_key); + $where'type' = $this->db->quote_identifier('type') . ' = ' . $this->db->quote($folder_id); + $where'counter' = $this->db->quote_identifier('counter') . ' > ' . $this->db->quote($sync_key); $select = $this->db->query("SELECT id FROM `{$this->table_name}` WHERE " . implode(' AND ', $where)); return $this->db->num_rows($select) > 0;
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_body_converter.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -60,14 +60,14 @@ // ActiveSync types: TYPE_PLAINTEXT, TYPE_HTML, TYPE_RTF, TYPE_MIME switch ($this->type) { - case Syncroton_Model_EmailBody::TYPE_PLAINTEXT: - return $this->convert_text_plain($type); - case Syncroton_Model_EmailBody::TYPE_HTML: - return $this->convert_text_html($type); - case Syncroton_Model_EmailBody::TYPE_RTF: - return $this->convert_rtf($type); - default: - return $this->text; + case Syncroton_Model_EmailBody::TYPE_PLAINTEXT: + return $this->convert_plain($type); + case Syncroton_Model_EmailBody::TYPE_HTML: + return $this->convert_html($type); + case Syncroton_Model_EmailBody::TYPE_RTF: + return $this->convert_rtf($type); + default: + return $this->text; } } @@ -79,16 +79,16 @@ * * @return string Body value */ - protected function convert_text_plain($type) + protected function convert_plain($type) { $data = $this->text; switch ($type) { - case Syncroton_Model_EmailBody::TYPE_HTML: - return '<pre>' . htmlspecialchars($data, ENT_COMPAT, kolab_sync::CHARSET) . '</pre>'; - case Syncroton_Model_EmailBody::TYPE_RTF: - // @TODO - return ''; + case Syncroton_Model_EmailBody::TYPE_HTML: + return '<pre>' . htmlspecialchars($data, ENT_COMPAT, kolab_sync::CHARSET) . '</pre>'; + case Syncroton_Model_EmailBody::TYPE_RTF: + // @TODO + return ''; } return $data; @@ -102,17 +102,17 @@ * * @return string Body value */ - protected function convert_text_html($type) + protected function convert_html($type) { switch ($type) { - case Syncroton_Model_EmailBody::TYPE_PLAINTEXT: - $txt = new rcube_html2text($this->text, false, true); - return $txt->get_text(); - case Syncroton_Model_EmailBody::TYPE_RTF: - // @TODO - return ''; - case Syncroton_Model_EmailBody::TYPE_MIME: - return ''; + case Syncroton_Model_EmailBody::TYPE_PLAINTEXT: + $txt = new rcube_html2text($this->text, false, true); + return $txt->get_text(); + case Syncroton_Model_EmailBody::TYPE_RTF: + // @TODO + return ''; + case Syncroton_Model_EmailBody::TYPE_MIME: + return ''; } return $this->text; @@ -128,20 +128,28 @@ */ protected function convert_rtf($type) { - $rtf = new rtf(); - $rtf->loadrtf($this->text); - switch ($type) { - case Syncroton_Model_EmailBody::TYPE_PLAINTEXT: - $rtf->output('ascii'); - $rtf->parse(); - return $rtf->out; - case Syncroton_Model_EmailBody::TYPE_HTML: - // @TODO: Conversion to HTML is broken, - // convert to text and use <PRE> tags - $rtf->output('ascii'); - $rtf->parse(); - return '<pre>' . trim($rtf->out) . '</pre>'; + case Syncroton_Model_EmailBody::TYPE_PLAINTEXT: + try { + $document = new RtfHtmlPhp\Document($this->text); + $formatter = new RtfHtmlPhp\Html\HtmlFormatter(RCUBE_CHARSET); + $txt = new rcube_html2text($formatter->format($document), false, true); + return $txt->get_text(); + } catch (Exception $e) { + $logger = Syncroton_Registry::get('loggerBackend'); + $logger->warn("Failed to convert RTF content"); + return ''; + } + case Syncroton_Model_EmailBody::TYPE_HTML: + try { + $document = new RtfHtmlPhp\Document($this->text); + $formatter = new RtfHtmlPhp\Html\HtmlFormatter(RCUBE_CHARSET); + return $formatter->format($document); + } catch (Exception $e) { + $logger = Syncroton_Registry::get('loggerBackend'); + $logger->warn("Failed to convert RTF content"); + return ''; + } } return $this->text;
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_data.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -31,11 +31,18 @@ /** * ActiveSync protocol version * - * @var int + * @var float */ protected $asversion = 0; /** + * The storage backend + * + * @var kolab_sync_storage + */ + protected $backend; + + /** * information about the current device * * @var Syncroton_Model_IDevice @@ -71,6 +78,13 @@ protected $defaultFolder; /** + * default root folder + * + * @var string + */ + protected $defaultRootFolder; + + /** * type of user created folders * * @var int @@ -78,24 +92,17 @@ protected $folderType; /** - * Internal cache for kolab_storage folder objects + * Internal cache for storage folders list * * @var array */ - protected $folders = array(); + protected $folders = ; /** - * Internal cache for IMAP folders list + * Logger instance. * - * @var array + * @var kolab_sync_logger */ - protected $imap_folders = array(); - - /** - * Logger instance. - * - * @var kolab_sync_logger - */ protected $logger; /** @@ -110,7 +117,7 @@ * * @var array */ - protected $ext_devices = array( + protected $ext_devices = 'iphone', 'ipad', 'thundertine', @@ -118,47 +125,50 @@ 'wp', 'wp8', 'playbook', - ); + ; - const RESULT_OBJECT = 0; - const RESULT_UID = 1; - const RESULT_COUNT = 2; + protected $lastsync_folder = null; + protected $lastsync_time = null; + + public const RESULT_OBJECT = 0; + public const RESULT_UID = 1; + public const RESULT_COUNT = 2; /** * Recurrence types */ - const RECUR_TYPE_DAILY = 0; // Recurs daily. - const RECUR_TYPE_WEEKLY = 1; // Recurs weekly - const RECUR_TYPE_MONTHLY = 2; // Recurs monthly - const RECUR_TYPE_MONTHLY_DAYN = 3; // Recurs monthly on the nth day - const RECUR_TYPE_YEARLY = 5; // Recurs yearly - const RECUR_TYPE_YEARLY_DAYN = 6; // Recurs yearly on the nth day + public const RECUR_TYPE_DAILY = 0; // Recurs daily. + public const RECUR_TYPE_WEEKLY = 1; // Recurs weekly + public const RECUR_TYPE_MONTHLY = 2; // Recurs monthly + public const RECUR_TYPE_MONTHLY_DAYN = 3; // Recurs monthly on the nth day + public const RECUR_TYPE_YEARLY = 5; // Recurs yearly + public const RECUR_TYPE_YEARLY_DAYN = 6; // Recurs yearly on the nth day /** * Day of week constants */ - const RECUR_DOW_SUNDAY = 1; - const RECUR_DOW_MONDAY = 2; - const RECUR_DOW_TUESDAY = 4; - const RECUR_DOW_WEDNESDAY = 8; - const RECUR_DOW_THURSDAY = 16; - const RECUR_DOW_FRIDAY = 32; - const RECUR_DOW_SATURDAY = 64; - const RECUR_DOW_LAST = 127; // The last day of the month. Used as a special value in monthly or yearly recurrences. + public const RECUR_DOW_SUNDAY = 1; + public const RECUR_DOW_MONDAY = 2; + public const RECUR_DOW_TUESDAY = 4; + public const RECUR_DOW_WEDNESDAY = 8; + public const RECUR_DOW_THURSDAY = 16; + public const RECUR_DOW_FRIDAY = 32; + public const RECUR_DOW_SATURDAY = 64; + public const RECUR_DOW_LAST = 127; // The last day of the month. Used as a special value in monthly or yearly recurrences. /** * Mapping of recurrence types * * @var array */ - protected $recurTypeMap = array( + protected $recurTypeMap = self::RECUR_TYPE_DAILY => 'DAILY', self::RECUR_TYPE_WEEKLY => 'WEEKLY', self::RECUR_TYPE_MONTHLY => 'MONTHLY', self::RECUR_TYPE_MONTHLY_DAYN => 'MONTHLY', self::RECUR_TYPE_YEARLY => 'YEARLY', self::RECUR_TYPE_YEARLY_DAYN => 'YEARLY', - ); + ; /** * Mapping of weekdays @@ -166,7 +176,7 @@ * * @var array */ - protected $recurDayMap = array( + protected $recurDayMap = 'SU' => self::RECUR_DOW_SUNDAY, 'MO' => self::RECUR_DOW_MONDAY, 'TU' => self::RECUR_DOW_TUESDAY, @@ -174,7 +184,7 @@ 'TH' => self::RECUR_DOW_THURSDAY, 'FR' => self::RECUR_DOW_FRIDAY, 'SA' => self::RECUR_DOW_SATURDAY, - ); + ; /** @@ -185,10 +195,10 @@ */ public function __construct(Syncroton_Model_IDevice $device, DateTime $syncTimeStamp) { - $this->backend = kolab_sync_backend::get_instance(); + $this->backend = kolab_sync::storage(); $this->device = $device; $this->asversion = floatval($device->acsversion); - $this->syncTimeStamp = $syncTimeStamp; + $this->syncTimeStamp = $this->backend->syncTimeStamp = $syncTimeStamp; $this->logger = Syncroton_Registry::get(Syncroton_Registry::LOGGERBACKEND); $this->defaultRootFolder = $this->defaultFolder . '::Syncroton'; @@ -197,8 +207,7 @@ try { $this->timezone = rcube::get_instance()->config->get('timezone', 'GMT'); kolab_format::$timezone = new DateTimeZone($this->timezone); - } - catch (Exception $e) { + } catch (Exception $e) { //rcube::raise_error($e, true); $this->timezone = 'GMT'; kolab_format::$timezone = new DateTimeZone('GMT'); @@ -212,15 +221,14 @@ */ public function getAllFolders() { - $list = array(); + $list = ; // device supports multiple folders ? if ($this->isMultiFolder()) { // get the folders the user has access to $list = $this->listFolders(); - } - else if ($default = $this->getDefaultFolder()) { - $list = array($default'serverId' => $default); + } elseif ($default = $this->getDefaultFolder()) { + $list = $default'serverId' => $default; } // getAllFolders() is called only in FolderSync @@ -246,7 +254,9 @@ */ public function getChangedFolders(DateTime $startTimeStamp, DateTime $endTimeStamp) { - return array(); + // FIXME/TODO: Can we get mtime of a DAV folder? + // Without this, we have a problem if folder ID does not change on rename + return ; } /** @@ -289,8 +299,7 @@ // Return first on the list if there's no default if (empty($default)) { - $key = array_shift(array_keys($folders)); - $default = $folders$key; + $default = array_first($folders); // make sure the type is default here $default'type' = $this->defaultFolderType; } @@ -308,47 +317,16 @@ */ public function createFolder(Syncroton_Model_IFolder $folder) { - $parentid = $folder->parentId; - $type = $folder->type; - $display_name = $folder->displayName; - $parent = null; - - if ($parentid) { - $parent = $this->backend->folder_id2name($parentid, $this->device->deviceid); - - if ($parent === null) { - throw new Syncroton_Exception_Status_FolderCreate(Syncroton_Exception_Status_FolderCreate::PARENT_NOT_FOUND); - } - } - - $name = rcube_charset::convert($display_name, kolab_sync::CHARSET, 'UTF7-IMAP'); - - if ($parent !== null) { - $rcube = rcube::get_instance(); - $storage = $rcube->get_storage(); - $delim = $storage->get_hierarchy_delimiter(); - $name = $parent . $delim . $name; - } - - // Create IMAP folder - $result = $this->backend->folder_create($name, $type, $this->device->deviceid); + $result = $this->backend->folder_create($folder->displayName, $folder->type, $this->device->deviceid, $folder->parentId); if ($result) { - $folder->serverId = $this->backend->folder_id($name); + $folder->serverId = $result; return $folder; } - $errno = Syncroton_Exception_Status_FolderCreate::UNKNOWN_ERROR; - - // Special case when client tries to create a subfolder of INBOX - // which is not possible on Cyrus-IMAP (T2223) - if ($parent == 'INBOX' && stripos($this->backend->last_error(), 'invalid') !== false) { - $errno = Syncroton_Exception_Status_FolderCreate::SPECIAL_FOLDER; - } - // Note: Looks like Outlook 2013 ignores any errors on FolderCreate command - throw new Syncroton_Exception_Status_FolderCreate($errno); + throw new Syncroton_Exception_Status_FolderCreate(Syncroton_Exception_Status_FolderCreate::UNKNOWN_ERROR); } /** @@ -356,32 +334,7 @@ */ public function updateFolder(Syncroton_Model_IFolder $folder) { - $parentid = $folder->parentId; - $type = $folder->type; - $display_name = $folder->displayName; - $old_name = $this->backend->folder_id2name($folder->serverId, $this->device->deviceid); - - if ($parentid) { - $parent = $this->backend->folder_id2name($parentid, $this->device->deviceid); - } - - $name = rcube_charset::convert($display_name, kolab_sync::CHARSET, 'UTF7-IMAP'); - - if ($parent !== null) { - $rcube = rcube::get_instance(); - $storage = $rcube->get_storage(); - $delim = $storage->get_hierarchy_delimiter(); - $name = $parent . $delim . $name; - } - - // Rename/move IMAP folder - if ($name == $old_name) { - $result = true; - // @TODO: folder type change? - } - else { - $result = $this->backend->folder_rename($old_name, $name, $type); - } + $result = $this->backend->folder_rename($folder->serverId, $this->device->deviceid, $folder->displayName, $folder->parentId); if ($result) { return $folder; @@ -399,53 +352,28 @@ $folder = $folder->serverId; } - $name = $this->backend->folder_id2name($folder, $this->device->deviceid); - // @TODO: throw exception - return $this->backend->folder_delete($name, $this->device->deviceid); + return $this->backend->folder_delete($folder, $this->device->deviceid); } /** * Empty folder (remove all entries and optionally subfolders) * - * @param string $folderId Folder identifier + * @param string $folderid Folder identifier * @param array $options Options */ public function emptyFolderContents($folderid, $options) { - $folders = $this->extractFolders($folderid); - - foreach ($folders as $folderid) { - $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid); - $folder = $this->getFolderObject($foldername); - - if (!$folder || !$folder->valid) { + // ActiveSync spec.: Clients use EmptyFolderContents to empty the Deleted Items folder. + // The client can clear out all items in the Deleted Items folder when the user runs out of storage quota + // (indicated by the return of an MailboxQuotaExceeded (113) status code from the server. + // FIXME: Does that mean we don't need this to work on any other folder? + // TODO: Respond with MailboxQuotaExceeded status. Where exactly? + + foreach ($this->extractFolders($folderid) as $folderid) { + if (!$this->backend->folder_empty($folderid, $this->device->deviceid, !empty($options'deleteSubFolders'))) { throw new Syncroton_Exception_Status_ItemOperations(Syncroton_Exception_Status_ItemOperations::ITEM_SERVER_ERROR); } - - // Remove all entries - $folder->delete_all(); - - // Remove subfolders - if (!empty($options'deleteSubFolders')) { - $list = $this->listFolders($folderid); - - if (!is_array($list)) { - throw new Syncroton_Exception_Status_ItemOperations(Syncroton_Exception_Status_ItemOperations::ITEM_SERVER_ERROR); - } - - foreach ($list as $folderid => $folder) { - $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid); - $folder = $this->getFolderObject($foldername); - - if (!$folder || !$folder->valid) { - throw new Syncroton_Exception_Status_ItemOperations(Syncroton_Exception_Status_ItemOperations::ITEM_SERVER_ERROR); - } - - // Remove all entries - $folder->delete_all(); - } - } } } @@ -461,23 +389,16 @@ */ public function moveItem($srcFolderId, $serverId, $dstFolderId) { - $item = $this->getObject($srcFolderId, $serverId, $folder); + // TODO: Optimize, we just need to find the folder ID and UID, we do not need to "fetch" it. + $item = $this->getObject($srcFolderId, $serverId); - if (!$item || !$folder) { + if (!$item) { throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE); } - $dstname = $this->backend->folder_id2name($dstFolderId, $this->device->deviceid); + $uid = $this->backend->moveItem($item'folderId', $this->device->deviceid, $this->modelName, $item'uid', $dstFolderId); - if ($dstname === null) { - throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_DESTINATION); - } - - if (!$folder->move($serverId, $dstname)) { - throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE); - } - - return $item'uid'; + return $this->serverId($uid, $dstFolderId); } /** @@ -491,21 +412,32 @@ public function createEntry($folderId, Syncroton_Model_IEntry $entry) { $entry = $this->toKolab($entry, $folderId); - $entry = $this->createObject($folderId, $entry); - if (empty($entry)) { + if ($folderId == $this->defaultRootFolder) { + $default = $this->getDefaultFolder(); + + if (!is_array($default)) { + throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR); + } + + $folderId = $default'realid' ?? $default'serverId'; + } + + $uid = $this->backend->createItem($folderId, $this->device->deviceid, $this->modelName, $entry); + + if (empty($uid)) { throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR); } - return $entry'_serverId'; + return $this->serverId($uid, $folderId); } /** * update existing entry * - * @param string $folderId - * @param string $serverId - * @param SimpleXMLElement $entry + * @param string $folderId + * @param string $serverId + * @param Syncroton_Model_IEntry $entry * * @return string ID of the updated entry */ @@ -518,39 +450,49 @@ } $entry = $this->toKolab($entry, $folderId, $oldEntry); - $entry = $this->updateObject($folderId, $serverId, $entry); + $uid = $this->backend->updateItem($oldEntry'folderId', $this->device->deviceid, $this->modelName, $oldEntry'uid', $entry); - if (empty($entry)) { + if (empty($uid)) { throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR); } - return $entry'_serverId'; + return $this->serverId($uid, $oldEntry'folderId'); } /** - * delete entry + * Delete entry * - * @param string $folderId - * @param string $serverId - * @param array $collectionData + * @param string $folderId + * @param string $serverId + * @param ?Syncroton_Model_SyncCollection $collectionData */ - public function deleteEntry($folderId, $serverId, $collectionData) + public function deleteEntry($folderId, $serverId, $collectionData = null) { - $deleted = $this->deleteObject($folderId, $serverId); + // TODO: Optimize, we just need to find the folder ID and UID, we do not need to "fetch" it. + $object = $this->getObject($folderId, $serverId); - if (!$deleted) { - throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR); + if ($object) { + $deleted = $this->backend->deleteItem($object'folderId', $this->device->deviceid, $this->modelName, $object'uid'); + + if (!$deleted) { + throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR); + } } } - + /** + * Get attachment data from the server. + * + * @param string $fileReference + * + * @return Syncroton_Model_FileReference + */ public function getFileReference($fileReference) { // to be implemented by Email data class - // @TODO: throw "unimplemented" exception here? + throw new Syncroton_Exception_NotFound('File references not supported'); } - /** * Search for existing entries * @@ -558,108 +500,31 @@ * @param array $filter Search filter * @param int $result_type Type of the result (see RESULT_* constants) * - * @return array|int Search result as count or array of uids/objects + * @return array|int Search result as count or array of uids/objects */ - protected function searchEntries($folderid, $filter = array(), $result_type = self::RESULT_UID) + protected function searchEntries($folderid, $filter = , $result_type = self::RESULT_UID) { - if ($folderid == $this->defaultRootFolder) { - $folders = $this->listFolders(); + $result = $result_type == self::RESULT_COUNT ? 0 : ; + $ts = time(); + $force = $this->lastsync_folder != $folderid || $this->lastsync_time <= $ts - Syncroton_Registry::getPingTimeout(); + $found = false; - if (!is_array($folders)) { - throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); - } - - $folders = array_keys($folders); - } - else { - $folders = array($folderid); - } - - // there's a PHP Warning from kolab_storage if $filter isn't an array - if (empty($filter)) { - $filter = array(); - } - else { - $changed_objects = $this->getChangesByRelations($folderid, $filter); - } - - $result = $result_type == self::RESULT_COUNT ? 0 : array(); - $found = 0; - - foreach ($folders as $folder_id) { - $foldername = $this->backend->folder_id2name($folder_id, $this->device->deviceid); - $folder = $this->getFolderObject($foldername); - - if (!$folder || !$folder->valid) { - throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); - } - - $found++; - $error = false; + foreach ($this->extractFolders($folderid) as $fid) { + $search = $this->backend->searchEntries($fid, $this->device->deviceid, $this->modelName, $filter, $result_type, $force); + $found = true; switch ($result_type) { - case self::RESULT_COUNT: - $count = $folder->count($filter); - - if ($count === null || $count === false) { - $error = true; - } - else { - $result += (int) $count; - } - break; - - case self::RESULT_UID: - $uids = $folder->get_uids($filter); - - if (!is_array($uids)) { - $error = true; - } - else if (!empty($uids)) { - $result = array_merge($result, $this->applyServerId($uids, $folder)); - } - break; - } - - if ($error) { - throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); - } - - // handle tag modifications - if (!empty($changed_objects)) { - // build new filter - // search objects mathing current filter, - // relations may contain members of many types, we need to - // search them by UID in all requested folders to get - // only these with requested type (and that really exist - // in specified folders) - $tag_filter = array(array('uid', '=', $changed_objects)); - foreach ($filter as $f) { - if ($f0 != 'changed') { - $tag_filter = $f; - } - } - - switch ($result_type) { case self::RESULT_COUNT: - // Note: this way we're potentally counting the same objects twice - // I'm not sure if this is a problem, we most likely do not - // need a precise result here - $count = $folder->count($tag_filter); - if ($count !== null && $count !== false) { - $result += (int) $count; - } - + $result += $search; break; case self::RESULT_UID: - $uids = $folder->get_uids($tag_filter); - if (is_array($uids) && !empty($uids)) { - $result = array_unique(array_merge($result, $this->applyServerId($uids, $folder))); + foreach ($search as $idx => $uid) { + $search$idx = $this->serverId($uid, $fid); } + $result = array_unique(array_merge($result, $search)); break; - } } } @@ -667,138 +532,8 @@ throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); } - return $result; - } - - /** - * Detect changes of relation (tag) objects data and assigned objects - * Returns relation member identifiers - */ - protected function getChangesByRelations($folderid, $filter) - { - if (isset($this->tag_categories) && !$this->tag_categories) { - return; - } - - // get period filter, create new objects filter - foreach ($filter as $f) { - if ($f0 == 'changed' && $f1 == '>') { - $since = $f2; - } - } - - // this is not search for changes, do nothing - if (empty($since)) { - return; - } - - // get relations state from the last sync - $last_state = (array) $this->backend->relations_state_get($this->device->id, $folderid, $since); - - // get current relations state - $config = kolab_storage_config::get_instance(); - $default = true; - $filter = array( - array('type', '=', 'relation'), - array('category', '=', 'tag') - ); - - $relations = $config->get_objects($filter, $default, 100); - - $result = array(); - $changed = false; - - // compare states, get members of changed relations - foreach ($relations as $relation) { - $rel_id = $relation'uid'; - - if ($relation'changed') { - $relation'changed'->setTimezone(new DateTimeZone('UTC')); - } - - // last state unknown... - if (empty($last_state$rel_id)) { - // ...get all members - if (!empty($relation'members')) { - $changed = true; - $result = array_merge($result, $relation'members'); - } - } - // last state known, changed tag name... - else if ($last_state$rel_id'name' != $relation'name') { - // ...get all (old and new) members - $members_old = explode("\n", $last_state$rel_id'members'); - $changed = true; - $members = array_unique(array_merge($relation'members', $members_old)); - $result = array_merge($result, $members); - } - // last state known, any other change change... - else if ($last_state$rel_id'changed' < $relation'changed'->format('U')) { - // ...find new and removed members - $members_old = explode("\n", $last_state$rel_id'members'); - $new = array_diff($relation'members', $members_old); - $removed = array_diff($members_old, $relation'members'); - - if (!empty($new) || !empty($removed)) { - $changed = true; - $result = array_merge($result, $new, $removed); - } - } - - unset($last_state$rel_id); - } - - // get members of deleted relations - if (!empty($last_state)) { - $changed = true; - foreach ($last_state as $relation) { - $members = explode("\n", $relation'members'); - $result = array_merge($result, $members); - } - } - - // save current state - if ($changed) { - $data = array(); - foreach ($relations as $relation) { - $data$relation'uid' = array( - 'name' => $relation'name', - 'changed' => $relation'changed'->format('U'), - 'members' => implode("\n", (array)$relation'members'), - ); - } - - $now = new DateTime('now', new DateTimeZone('UTC')); - - $this->backend->relations_state_set($this->device->id, $folderid, $now, $data); - } - - // in mail mode return only message URIs - if ($this->modelName == 'mail') { - // lambda function to skip email members - $filter_func = function($value) { - return strpos($value, 'imap://') === 0; - }; - - $result = array_filter(array_unique($result), $filter_func); - } - // otherwise return only object UIDs - else { - // lambda function to skip email members - $filter_func = function($value) { - return strpos($value, 'urn:uuid:') === 0; - }; - - // lambda function to parse member URI - $member_func = function($value) { - if (strpos($value, 'urn:uuid:') === 0) { - $value = substr($value, 9); - } - return $value; - }; - - $result = array_map($member_func, array_filter(array_unique($result), $filter_func)); - } + $this->lastsync_folder = $folderid; + $this->lastsync_time = $ts; return $result; } @@ -808,12 +543,12 @@ * * @param int $filter_type Filter type * - * @param array Filter query + * @return array Filter query */ protected function filter($filter_type = 0) { // overwrite by child class according to specified type - return array(); + return ; } /** @@ -822,17 +557,17 @@ * @param string $folderId * @param DateTime $start * @param DateTime $end - * @param int $filterType + * @param int $filter_type * * @return array */ public function getChangedEntries($folderId, DateTime $start, DateTime $end = null, $filter_type = null) { $filter = $this->filter($filter_type); - $filter = array('changed', '>', $start); + $filter = 'changed', '>', $start; if ($end) { - $filter = array('changed', '<=', $end); + $filter = 'changed', '<=', $end; } return $this->searchEntries($folderId, $filter, self::RESULT_UID); @@ -844,17 +579,17 @@ * @param string $folderId * @param DateTime $start * @param DateTime $end - * @param int $filterType + * @param int $filter_type * * @return int */ public function getChangedEntriesCount($folderId, DateTime $start, DateTime $end = null, $filter_type = null) { $filter = $this->filter($filter_type); - $filter = array('changed', '>', $start); + $filter = 'changed', '>', $start; if ($end) { - $filter = array('changed', '<=', $end); + $filter = 'changed', '<=', $end; } return $this->searchEntries($folderId, $filter, self::RESULT_COUNT); @@ -863,8 +598,8 @@ /** * get id's of all entries available on the server * - * @param string $folderId - * @param int $filterType + * @param string $folder_id + * @param string $filter_type * * @return array */ @@ -879,8 +614,8 @@ /** * get count of all entries available on the server * - * @param string $folderId - * @param int $filterType + * @param string $folder_id + * @param string $filter_type * * @return int */ @@ -903,6 +638,7 @@ */ public function getCountOfChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState) { + // @phpstan-ignore-next-line $allClientEntries = $contentBackend->getFolderState($this->device, $folder, $syncState->counter); $allServerEntries = $this->getServerEntries($folder->serverId, $folder->lastfiltertype); $changedEntries = $this->getChangedEntriesCount($folder->serverId, $syncState->lastsync, null, $folder->lastfiltertype); @@ -928,6 +664,7 @@ return true; } + // @phpstan-ignore-next-line $allClientEntries = $contentBackend->getFolderState($this->device, $folder, $syncState->counter); // @TODO: Consider looping over all folders here, not in getServerEntries() and @@ -939,8 +676,7 @@ $deletedEntries = array_diff($allClientEntries, $allServerEntries); return count($addedEntries) > 0 || count($deletedEntries) > 0; - } - catch (Exception $e) { + } catch (Exception $e) { // return "no changes" if something failed return false; } @@ -949,147 +685,42 @@ /** * Fetches the entry from the backend */ - protected function getObject($folderid, $entryid, &$folder = null) + protected function getObject($folderid, $entryid) { - $folders = $this->extractFolders($folderid); - - if (empty($folders)) { - return null; - } - - foreach ($folders as $folderid) { - $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid); - $folder = $this->getFolderObject($foldername); - - if ($folder && $folder->valid) { - $crc = null; - $uid = $entryid; - - // See self::serverId() for full explanation - // Use (slower) UID prefix matching... - if (preg_match('/^CRC(0-9A-Fa-f{8})(.+)$/', $uid, $matches)) { - $crc = $matches1; - $uid = $matches2; - - if (strlen($entryid) >= 64) { - foreach ($folder->select(array(array('uid', '~*', $uid))) as $object) { - if (($object'uid' == $uid || strpos($object'uid', $uid) === 0) - && $crc == $this->objectCRC($object'uid', $folder) - ) { - $object'_folderid' = $folderid; - return $object; - } + foreach ($this->extractFolders($folderid) as $fid) { + $crc = null; + $uid = $entryid; + + // See self::serverId() for full explanation + // Use (slower) UID prefix matching... + if (preg_match('/^CRC(0-9A-Fa-f{8})(.+)$/', $uid, $matches)) { + $crc = $matches1; + $uid = $matches2; + + if (strlen($entryid) >= 64) { + $objects = $this->backend->getItemsByUidPrefix($fid, $this->device->deviceid, $this->modelName, $uid); + + foreach ($objects as $object) { + if (($object'uid' === $uid || strpos($object'uid', $uid) === 0) + && $crc == $this->objectCRC($object'uid', $fid) + ) { + $object'folderId' = $fid; + return $object; } - - continue; } - } - - // Or (faster) strict UID matching... - if (($object = $folder->get_object($uid)) - && ($crc === null || $crc == $this->objectCRC($object'uid', $folder)) - ) { - $object'_folderid' = $folderid; - return $object; - } - } - } - } - - /** - * Saves the entry on the backend - */ - protected function createObject($folderid, $data) - { - if ($folderid == $this->defaultRootFolder) { - $default = $this->getDefaultFolder(); - - if (!is_array($default)) { - return null; - } - - $folderid = isset($default'realid') ? $default'realid' : $default'serverId'; - } - - // convert categories into tags, save them after creating an object - if (!empty($data'categories') && isset($this->tag_categories) && $this->tag_categories) { - $tags = $data'categories'; - unset($data'categories'); - } - - $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid); - $folder = $this->getFolderObject($foldername); - - // Set User-Agent for saved objects - $app = kolab_sync::get_instance(); - $app->config->set('useragent', $app->app_name . ' ' . kolab_sync::VERSION); - - if ($folder && $folder->valid && $folder->save($data)) { - if (!empty($tags)) { - $this->setKolabTags($data'uid', $tags); - } - $data'_serverId' = $this->serverId($data'uid', $folder); - - return $data; - } - } - - /** - * Updates the entry on the backend - */ - protected function updateObject($folderid, $entryid, $data) - { - $object = $this->getObject($folderid, $entryid); - - if ($object) { - $folder = $this->getFolderObject($object'_mailbox'); - - // convert categories into tags, save them after updating an object - if (isset($this->tag_categories) && $this->tag_categories && array_key_exists('categories', $data)) { - $tags = (array) $data'categories'; - unset($data'categories'); - } - - // Set User-Agent for saved objects - $app = kolab_sync::get_instance(); - $app->config->set('useragent', $app->app_name . ' ' . kolab_sync::VERSION); - - if ($folder && $folder->valid && $folder->save($data)) { - if (isset($tags)) { - $this->setKolabTags($data'uid', $tags); + continue; } - - $data'_serverId' = $this->serverId($object'uid', $folder); - - return $data; } - } - } - /** - * Removes the entry from the backend - */ - protected function deleteObject($folderid, $entryid) - { - $object = $this->getObject($folderid, $entryid); + // Or (faster) strict UID matching... + $object = $this->backend->getItem($fid, $this->device->deviceid, $this->modelName, $uid); - if ($object) { - $folder = $this->getFolderObject($object'_mailbox'); - - if ($folder && $folder->valid && $folder->delete($object'uid')) { - if (isset($this->tag_categories) && $this->tag_categories) { - $this->setKolabTags($object'uid', null); - } - - return true; + if (!empty($object) && ($crc === null || $crc == $this->objectCRC($object'uid', $fid))) { + $object'folderId' = $fid; + return $object; } - - return false; } - - // object doesn't exist, confirm deletion - return true; } /** @@ -1105,17 +736,16 @@ $folderid = $folderid->serverId; } - if ($folderid == $this->defaultRootFolder) { + if ($folderid === $this->defaultRootFolder) { $folders = $this->listFolders(); if (!is_array($folders)) { - return null; + throw new Syncroton_Exception_NotFound('Folder not found'); } $folders = array_keys($folders); - } - else { - $folders = array($folderid); + } else { + $folders = $folderid; } return $folders; @@ -1130,19 +760,22 @@ */ protected function listFolders($parentid = null) { - if (empty($this->imap_folders)) { - $this->imap_folders = $this->backend->folders_list( - $this->device->deviceid, $this->modelName, $this->isMultiFolder()); + if (empty($this->folders)) { + $this->folders = $this->backend->folders_list( + $this->device->deviceid, + $this->modelName, + $this->isMultiFolder() + ); } - if ($parentid === null || !is_array($this->imap_folders)) { - return $this->imap_folders; + if ($parentid === null || !is_array($this->folders)) { + return $this->folders; } - $folders = array(); - $parents = array($parentid); + $folders = ; + $parents = $parentid; - foreach ($this->imap_folders as $folder_id => $folder) { + foreach ($this->folders as $folder_id => $folder) { if ($folder'parentId' && in_array($folder'parentId', $parents)) { $folders$folder_id = $folder; $parents = $folder_id; @@ -1153,88 +786,37 @@ } /** - * Returns Folder object (uses internal cache) - * - * @param string $name Folder name (UTF7-IMAP) - * - * @return kolab_storage_folder Folder object - */ - protected function getFolderObject($name) - { - if ($name === null || $name === '') { - return null; - } - - if (!isset($this->folders$name)) { - $this->folders$name = kolab_storage::get_folder($name, $this->modelName); - } - - return $this->folders$name; - } - - /** * Returns ActiveSync settings of specified folder * - * @param string $name Folder name (UTF7-IMAP) + * @param string $folderid Folder identifier * * @return array Folder settings */ - protected function getFolderConfig($name) - { - $metadata = $this->backend->folder_meta(); - - if (!is_array($metadata)) { - return array(); - } - - $deviceid = $this->device->deviceid; - $config = $metadata$name'FOLDER'$deviceid; - - return array( - 'ALARMS' => $config'S' == 2, - ); - } - - /** - * Returns real folder name for specified folder ID - */ - protected function getFolderName($folderid) + protected function getFolderConfig($folderid) { if ($folderid == $this->defaultRootFolder) { - $default = $this->getDefaultFolder(); + $default = $this->getDefaultFolder(); if (!is_array($default)) { - return null; + return ; } - $folderid = isset($default'realid') ? $default'realid' : $default'serverId'; + $folderid = $default'realid' ?? $default'serverId'; } - return $this->backend->folder_id2name($folderid, $this->device->deviceid); - } - - /** - * Returns folder ID from Kolab folder object - */ - protected function getFolderId($folder) - { - if (!$this->isMultiFolder()) { - return $this->defaultRootFolder; - } - - return $this->backend->folder_id($folder->get_name(), $folder->get_type()); + return $this->backend->getFolderConfig($folderid, $this->device->deviceid, $this->modelName); } /** * Convert contact from xml to kolab format * - * @param Syncroton_Model_IEntry $data Contact data - * @param string $folderId Folder identifier - * @param array $entry Old Contact data for merge + * @param mixed $data Contact data + * @param string $folderId Folder identifier + * @param array $entry Old Contact data for merge * * @return array */ - abstract function toKolab(Syncroton_Model_IEntry $data, $folderId, $entry = null); + abstract public function toKolab($data, $folderId, $entry = null); /** * Extracts data from kolab data array @@ -1311,7 +893,7 @@ $key_name = $name_items2; if (!isset($data$name)) { - $data$name = array(); + $data$name = ; } foreach ($data$name as $idx => $element) { @@ -1324,7 +906,7 @@ if (!isset($found)) { $data$name = array_values($data$name); $found = count($data$name); - $data$name$found = array('type' => $type); + $data$name$found = 'type' => $type; } $data$name$found$key_name = $value; @@ -1334,7 +916,7 @@ // custom properties if ($count == 2 && $name_items0 == 'x-custom') { - $data'x-custom' = isset($data'x-custom') ? ((array) $data'x-custom') : array(); + $data'x-custom' = isset($data'x-custom') ? ((array) $data'x-custom') : ; foreach ($data'x-custom' as $idx => $val) { if (is_array($val) && $val0 == $name_items1) { $data'x-custom'$idx1 = $value; @@ -1342,7 +924,7 @@ } } - $data'x-custom' = array($name_items1, $value); + $data'x-custom' = $name_items1, $value; return; } @@ -1431,12 +1013,12 @@ /** * Setter for Body attribute according to client version * - * @param string $value Body - * @param array $param Body parameters + * @param string $value Body + * @param array $params Body parameters * * @reurn Syncroton_Model_EmailBody Body element */ - protected function setBody($value, $params = array()) + protected function setBody($value, $params = ) { if (empty($value) && empty($params)) { return; @@ -1504,10 +1086,10 @@ $prefs = $opts'bodyPreferences'; $html_type = Syncroton_Command_Sync::BODY_TYPE_HTML; $type = Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT; - $params = array(); + $params = ; // HTML? check for opening and closing <html> or <body> tags - $is_html = preg_match('/<(html|body)(\s+a-z|>)/', $body, $m) && strpos($body, '</'.$m1.'>') > 0; + $is_html = preg_match('/<(html|body)(\s+a-z|>)/', $body, $m) && strpos($body, '</' . $m1 . '>') > 0; // here we assume that all devices support plain text if ($is_html) { @@ -1546,18 +1128,16 @@ * * @param DateTime|int|string $date Unix timestamp, date (YYYY-MM-DD) or PHP DateTime object * - * @return DateTime Datetime object + * @return DateTime|null Datetime object */ protected static function date_from_kolab($date) { if (!empty($date)) { if (is_numeric($date)) { $date = new DateTime('@' . $date); - } - else if (is_string($date)) { + } elseif (is_string($date)) { $date = new DateTime($date, new DateTimeZone('UTC')); - } - else if ($date instanceof DateTime) { + } elseif ($date instanceof DateTime) { $date = clone $date; $tz = $date->getTimezone(); $tz_name = $tz->getName(); @@ -1567,23 +1147,23 @@ $utc = new DateTimeZone('UTC'); // safe dateonly object conversion to UTC // note: _dateonly flag is set by libkolab e.g. for birthdays - if ($date->_dateonly) { + if (!empty($date->_dateonly)) { // avoid time change $date = new DateTime($date->format('Y-m-d'), $utc); // set time to noon to avoid timezone troubles $date->setTime(12, 0, 0); - } - else { + } else { $date->setTimezone($utc); } } - } - else { + } else { return null; // invalid input } return $date; } + + return null; } /** @@ -1591,72 +1171,75 @@ */ protected function recurrence_from_kolab($collection, $data, &$result, $type = 'Event') { - if (empty($data'recurrence') || !empty($data'recurrence_date')) { + if (empty($data'recurrence') || !empty($data'recurrence_date') || empty($data'recurrence''FREQ')) { return; } - $recurrence = array(); + $recurrence = ; $r = $data'recurrence'; // required fields switch($r'FREQ') { - case 'DAILY': - $recurrence'type' = self::RECUR_TYPE_DAILY; - break; - - case 'WEEKLY': - $recurrence'type' = self::RECUR_TYPE_WEEKLY; - $recurrence'dayOfWeek' = $this->day2bitmask($r'BYDAY'); - break; - - case 'MONTHLY': - if (!empty($r'BYMONTHDAY')) { - // @TODO: ActiveSync doesn't support multi-valued month days, - // should we replicate the recurrence element for each day of month? - $month_day = array_shift(explode(',', $r'BYMONTHDAY')); - $recurrence'type' = self::RECUR_TYPE_MONTHLY; - $recurrence'dayOfMonth' = $month_day; - } - else { - $week = (int) substr($r'BYDAY', 0, -2); - $week = ($week == -1) ? 5 : $week; - $day = substr($r'BYDAY', -2); - $recurrence'type' = self::RECUR_TYPE_MONTHLY_DAYN; - $recurrence'weekOfMonth' = $week; - $recurrence'dayOfWeek' = $this->day2bitmask($day); - } - break; - - case 'YEARLY': - // @TODO: ActiveSync doesn't support multi-valued months, - // should we replicate the recurrence element for each month? - $month = array_shift(explode(',', $r'BYMONTH')); - - if (!empty($r'BYDAY')) { - $week = (int) substr($r'BYDAY', 0, -2); - $week = ($week == -1) ? 5 : $week; - $day = substr($r'BYDAY', -2); - $recurrence'type' = self::RECUR_TYPE_YEARLY_DAYN; - $recurrence'weekOfMonth' = $week; + case 'DAILY': + $recurrence'type' = self::RECUR_TYPE_DAILY; + break; + + case 'WEEKLY': + $day = $r'BYDAY' ?? 0; + if (!$day && (!empty($data'_start') || !empty($data'start'))) { + $days = '', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA','SU'; + $start = $data'_start' ?? $data'start'; + $day = $days$start->format('N'); + } + + $recurrence'type' = self::RECUR_TYPE_WEEKLY; $recurrence'dayOfWeek' = $this->day2bitmask($day); - $recurrence'monthOfYear' = $month; - } - else if (!empty($r'BYMONTHDAY')) { - // @TODO: ActiveSync doesn't support multi-valued month days, - // should we replicate the recurrence element for each day of month? - $month_day = array_shift(explode(',', $r'BYMONTHDAY')); - $recurrence'type' = self::RECUR_TYPE_YEARLY; - $recurrence'dayOfMonth' = $month_day; - $recurrence'monthOfYear' = $month; - } - else { - $recurrence'type' = self::RECUR_TYPE_YEARLY; - $recurrence'monthOfYear' = $month; - } - break; + break; - default: - return; + case 'MONTHLY': + if (!empty($r'BYMONTHDAY')) { + // @TODO: ActiveSync doesn't support multi-valued month days, + // should we replicate the recurrence element for each day of month? + $month_day, = explode(',', $r'BYMONTHDAY'); + $recurrence'type' = self::RECUR_TYPE_MONTHLY; + $recurrence'dayOfMonth' = $month_day; + } elseif (!empty($r'BYDAY')) { + $week = (int) substr($r'BYDAY', 0, -2); + $week = ($week == -1) ? 5 : $week; + $day = substr($r'BYDAY', -2); + $recurrence'type' = self::RECUR_TYPE_MONTHLY_DAYN; + $recurrence'weekOfMonth' = $week; + $recurrence'dayOfWeek' = $this->day2bitmask($day); + } else { + return; + } + break; + + case 'YEARLY': + // @TODO: ActiveSync doesn't support multi-valued months, + // should we replicate the recurrence element for each month? + $month, = explode(',', $r'BYMONTH'); + + if (!empty($r'BYDAY')) { + $week = (int) substr($r'BYDAY', 0, -2); + $week = ($week == -1) ? 5 : $week; + $day = substr($r'BYDAY', -2); + $recurrence'type' = self::RECUR_TYPE_YEARLY_DAYN; + $recurrence'weekOfMonth' = $week; + $recurrence'dayOfWeek' = $this->day2bitmask($day); + $recurrence'monthOfYear' = $month; + } elseif (!empty($r'BYMONTHDAY')) { + // @TODO: ActiveSync doesn't support multi-valued month days, + // should we replicate the recurrence element for each day of month? + $month_day, = explode(',', $r'BYMONTHDAY'); + $recurrence'type' = self::RECUR_TYPE_YEARLY; + $recurrence'dayOfMonth' = $month_day; + $recurrence'monthOfYear' = $month; + } else { + $recurrence'type' = self::RECUR_TYPE_YEARLY; + $recurrence'monthOfYear' = $month; + } + break; } // Skip all empty values (T2519) @@ -1669,8 +1252,7 @@ if (!empty($r'UNTIL')) { $recurrence'until' = self::date_from_kolab($r'UNTIL'); - } - else if (!empty($r'COUNT')) { + } elseif (!empty($r'COUNT')) { $recurrence'occurrences' = $r'COUNT'; } @@ -1703,53 +1285,52 @@ $type = $recurrence->type; switch ($type) { - case self::RECUR_TYPE_DAILY: - break; + case self::RECUR_TYPE_DAILY: + break; - case self::RECUR_TYPE_WEEKLY: - $rrule'BYDAY' = $this->bitmask2day($recurrence->dayOfWeek); - break; + case self::RECUR_TYPE_WEEKLY: + $rrule'BYDAY' = $this->bitmask2day($recurrence->dayOfWeek); + break; - case self::RECUR_TYPE_MONTHLY: - $rrule'BYMONTHDAY' = $recurrence->dayOfMonth; - break; + case self::RECUR_TYPE_MONTHLY: + $rrule'BYMONTHDAY' = $recurrence->dayOfMonth; + break; - case self::RECUR_TYPE_MONTHLY_DAYN: - $week = $recurrence->weekOfMonth; - $day = $recurrence->dayOfWeek; - $byDay = $week == 5 ? -1 : $week; - $byDay .= $this->bitmask2day($day); + case self::RECUR_TYPE_MONTHLY_DAYN: + $week = $recurrence->weekOfMonth; + $day = $recurrence->dayOfWeek; + $byDay = $week == 5 ? -1 : $week; + $byDay .= $this->bitmask2day($day); - $rrule'BYDAY' = $byDay; - break; + $rrule'BYDAY' = $byDay; + break; - case self::RECUR_TYPE_YEARLY: - $rrule'BYMONTH' = $recurrence->monthOfYear; - $rrule'BYMONTHDAY' = $recurrence->dayOfMonth; - break; + case self::RECUR_TYPE_YEARLY: + $rrule'BYMONTH' = $recurrence->monthOfYear; + $rrule'BYMONTHDAY' = $recurrence->dayOfMonth; + break; - case self::RECUR_TYPE_YEARLY_DAYN: - $rrule'BYMONTH' = $recurrence->monthOfYear; + case self::RECUR_TYPE_YEARLY_DAYN: + $rrule'BYMONTH' = $recurrence->monthOfYear; - $week = $recurrence->weekOfMonth; - $day = $recurrence->dayOfWeek; - $byDay = $week == 5 ? -1 : $week; - $byDay .= $this->bitmask2day($day); + $week = $recurrence->weekOfMonth; + $day = $recurrence->dayOfWeek; + $byDay = $week == 5 ? -1 : $week; + $byDay .= $this->bitmask2day($day); - $rrule'BYDAY' = $byDay; - break; + $rrule'BYDAY' = $byDay; + break; } $rrule'FREQ' = $this->recurTypeMap$type; - $rrule'INTERVAL' = isset($recurrence->interval) ? $recurrence->interval : 1; + $rrule'INTERVAL' = $recurrence->interval ?? 1; if (isset($recurrence->until)) { if ($timezone) { $recurrence->until->setTimezone($timezone); } $rrule'UNTIL' = $recurrence->until; - } - else if (!empty($recurrence->occurrences)) { + } elseif (!empty($recurrence->occurrences)) { $rrule'COUNT' = $recurrence->occurrences; } @@ -1770,17 +1351,17 @@ return null; } - $ex_list = array(); + $ex_list = ; // exceptions (modified occurences) if (!empty($data'recurrence''EXCEPTIONS')) { foreach ((array)$data'recurrence''EXCEPTIONS' as $exception) { $exception'_mailbox' = $data'_mailbox'; - $ex = $this->getEntry($collection, $exception, true); + $ex = $this->getEntry($collection, $exception, true); // @phpstan-ignore-line $date = clone ($exception'recurrence_date' ?: $ex'startTime'); - $ex'exceptionStartTime' = self::set_exception_time($date, $data'_start'); + $ex'exceptionStartTime' = self::set_exception_time($date, $data'_start' ?? null); // remove fields not supported by Syncroton_Model_EventException unset($ex'uID'); @@ -1799,10 +1380,10 @@ continue; } - $ex = array( + $ex = 'deleted' => 1, - 'exceptionStartTime' => self::set_exception_time($exception, $data'_start'), - ); + 'exceptionStartTime' => self::set_exception_time($exception, $data'_start' ?? null), + ; $ex_list = new Syncroton_Model_EventException($ex); } @@ -1816,8 +1397,8 @@ */ protected function exceptions_to_kolab($data, &$rrule, $folderid, $timezone = null) { - $rrule'EXDATE' = array(); - $rrule'EXCEPTIONS' = array(); + $rrule'EXDATE' = ; + $rrule'EXCEPTIONS' = ; // handle exceptions from recurrence if (!empty($data->exceptions)) { @@ -1830,13 +1411,12 @@ if ($exception->deleted) { $date->setTime(0, 0, 0); $rrule'EXDATE' = $date; - } - else { - $ex = $this->toKolab($exception, $folderid, null, $timezone); + } else { + $ex = $this->toKolab($exception, $folderid, null, $timezone); // @phpstan-ignore-line $ex'recurrence_date' = $date; - if ($data->allDayEvent) { + if (!empty($data->allDayEvent)) { $ex'allday' = 1; } @@ -1871,32 +1451,6 @@ } /** - * Returns list of tag names assigned to kolab object - */ - protected function getKolabTags($uid, $categories = null) - { - $config = kolab_storage_config::get_instance(); - $tags = $config->get_tags($uid); - $tags = array_filter(array_map(function($v) { return $v'name'; }, $tags)); - - // merge result with old categories - if (!empty($categories)) { - $tags = array_unique(array_merge($tags, (array) $categories)); - } - - return $tags; - } - - /** - * Set tags to kolab object - */ - protected function setKolabTags($uid, $tags) - { - $config = kolab_storage_config::get_instance(); - $config->save_tags($uid, $tags); - } - - /** * Converts string of days (TU,TH) to bitmask used by ActiveSync * * @param string $days @@ -1909,7 +1463,9 @@ $result = 0; foreach ($days as $day) { - $result = $result + $this->recurDayMap$day; + if ($day) { + $result = $result + ($this->recurDayMap$day ?? 0); + } } return $result; @@ -1924,7 +1480,7 @@ */ protected function bitmask2day($days) { - $days_arr = array(); + $days_arr = ; for ($bitmask = 1; $bitmask <= self::RECUR_DOW_SATURDAY; $bitmask = $bitmask << 1) { $dayMatch = $days & $bitmask; @@ -1947,8 +1503,7 @@ if (preg_match($option, $this->device->devicetype)) { return true; } - } - else if (stripos($this->device->devicetype, $option) !== false) { + } elseif (stripos($this->device->devicetype, $option) !== false) { return true; } } @@ -1962,7 +1517,7 @@ protected function user_emails() { $user_emails = kolab_sync::get_instance()->user->list_emails(); - $user_emails = array_map(function($v) { return $v'email'; }, $user_emails); + $user_emails = array_map(function ($v) { return $v'email'; }, $user_emails); return $user_emails; } @@ -1972,10 +1527,6 @@ */ protected function serverId($uid, $folder) { - if ($this->modelName == 'mail') { - return $uid; - } - // When ActiveSync communicates with the client, it refers to objects with a ServerId // We can't use object UID for ServerId because: // - ServerId is limited to 64 chars, @@ -2006,25 +1557,11 @@ protected function objectCRC($uid, $folder) { if (!is_object($folder)) { - $folder = $this->getFolderObject($folder); + $folder = $this->backend->getFolder($folder, $this->device->deviceid, $this->modelName); } $folder_uid = $folder->get_uid(); return strtoupper(hash('crc32b', $folder_uid . $uid)); // always 8 chars } - - /** - * Apply serverId() on a set of uids - */ - protected function applyServerId($uids, $folder) - { - if (!empty($uids) && $this->modelName != 'mail') { - $self = $this; - $func = function($uid) use ($self, $folder) { return $self->serverId($uid, $folder); }; - $uids = array_map($func, $uids); - } - - return $uids; - } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_data_calendar.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -31,7 +31,7 @@ /** * Mapping from ActiveSync Calendar namespace fields */ - protected $mapping = array( + protected $mapping = 'allDayEvent' => 'allday', 'startTime' => 'start', // keep it before endTime here //'attendees' => 'attendees', @@ -54,7 +54,7 @@ 'subject' => 'title', //'timezone' => 'timezone', 'uID' => 'uid', - ); + ; /** * Kolab object type @@ -87,51 +87,59 @@ /** * 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; + public const ATTENDEE_STATUS_UNKNOWN = 0; + public const ATTENDEE_STATUS_TENTATIVE = 2; + public const ATTENDEE_STATUS_ACCEPTED = 3; + public const ATTENDEE_STATUS_DECLINED = 4; + public const ATTENDEE_STATUS_NOTRESPONDED = 5; /** * attendee types */ - const ATTENDEE_TYPE_REQUIRED = 1; - const ATTENDEE_TYPE_OPTIONAL = 2; - const ATTENDEE_TYPE_RESOURCE = 3; + public const ATTENDEE_TYPE_REQUIRED = 1; + public const ATTENDEE_TYPE_OPTIONAL = 2; + public const ATTENDEE_TYPE_RESOURCE = 3; /** * busy status constants */ - const BUSY_STATUS_FREE = 0; - const BUSY_STATUS_TENTATIVE = 1; - const BUSY_STATUS_BUSY = 2; - const BUSY_STATUS_OUTOFOFFICE = 3; + public const BUSY_STATUS_FREE = 0; + public const BUSY_STATUS_TENTATIVE = 1; + public const BUSY_STATUS_BUSY = 2; + public const BUSY_STATUS_OUTOFOFFICE = 3; /** * Sensitivity values */ - const SENSITIVITY_NORMAL = 0; - const SENSITIVITY_PERSONAL = 1; - const SENSITIVITY_PRIVATE = 2; - const SENSITIVITY_CONFIDENTIAL = 3; + public const SENSITIVITY_NORMAL = 0; + public const SENSITIVITY_PERSONAL = 1; + public const SENSITIVITY_PRIVATE = 2; + public const SENSITIVITY_CONFIDENTIAL = 3; + + /** + * Internal iTip states + */ + public const ITIP_ACCEPTED = 'ACCEPTED'; + public const ITIP_DECLINED = 'DECLINED'; + public const ITIP_TENTATIVE = 'TENTATIVE'; + public const ITIP_CANCELLED = 'CANCELLED'; - const KEY_DTSTAMP = 'x-custom.X-ACTIVESYNC-DTSTAMP'; - const KEY_REPLYTIME = 'x-custom.X-ACTIVESYNC-REPLYTIME'; + public const KEY_DTSTAMP = 'x-custom.X-ACTIVESYNC-DTSTAMP'; + public const KEY_REPLYTIME = 'x-custom.X-ACTIVESYNC-REPLYTIME'; /** * Mapping of attendee status * * @var array */ - protected $attendeeStatusMap = array( + protected $attendeeStatusMap = 'UNKNOWN' => self::ATTENDEE_STATUS_UNKNOWN, 'TENTATIVE' => self::ATTENDEE_STATUS_TENTATIVE, 'ACCEPTED' => self::ATTENDEE_STATUS_ACCEPTED, 'DECLINED' => self::ATTENDEE_STATUS_DECLINED, 'DELEGATED' => self::ATTENDEE_STATUS_UNKNOWN, 'NEEDS-ACTION' => self::ATTENDEE_STATUS_NOTRESPONDED, - ); + ; /** * Mapping of attendee type @@ -139,35 +147,35 @@ * NOTE: recurrences need extra handling! * @var array */ - protected $attendeeTypeMap = array( + protected $attendeeTypeMap = 'REQ-PARTICIPANT' => self::ATTENDEE_TYPE_REQUIRED, 'OPT-PARTICIPANT' => self::ATTENDEE_TYPE_OPTIONAL, // 'NON-PARTICIPANT' => self::ATTENDEE_TYPE_RESOURCE, // 'CHAIR' => self::ATTENDEE_TYPE_RESOURCE, - ); + ; /** * Mapping of busy status * * @var array */ - protected $busyStatusMap = array( + protected $busyStatusMap = 'free' => self::BUSY_STATUS_FREE, 'tentative' => self::BUSY_STATUS_TENTATIVE, 'busy' => self::BUSY_STATUS_BUSY, 'outofoffice' => self::BUSY_STATUS_OUTOFOFFICE, - ); + ; /** * mapping of sensitivity * * @var array */ - protected $sensitivityMap = array( + protected $sensitivityMap = 'public' => self::SENSITIVITY_PERSONAL, 'private' => self::SENSITIVITY_PRIVATE, 'confidential' => self::SENSITIVITY_CONFIDENTIAL, - ); + ; /** @@ -175,17 +183,18 @@ * * @param Syncroton_Model_SyncCollection $collection Collection data * @param string $serverId Local entry identifier - * @param boolean $as_array Return entry as array + * @param bool $as_array Return entry as array * - * @return array|Syncroton_Model_Event|array Event object + * @return array|Syncroton_Model_Event Event object */ public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId, $as_array = false) { $event = is_array($serverId) ? $serverId : $this->getObject($collection->collectionId, $serverId); - $config = $this->getFolderConfig($event'_mailbox'); - $result = array(); - $is_outlook = stripos($this->device->devicetype, 'outlook') !== false; - $is_android = stripos($this->device->devicetype, 'android') !== false; + $config = $this->getFolderConfig($event'folderId'); + $result = ; + + $is_outlook = stripos($this->device->devicetype, 'outlook') !== false; + $is_android = stripos($this->device->devicetype, 'android') !== false; // Kolab Format 3.0 and xCal does support timezone per-date, but ActiveSync allows // only one timezone per-event. We'll use timezone of the start date @@ -196,51 +205,50 @@ $value = $this->getKolabDataItem($event, $name); switch ($name) { - case 'changed': - case 'end': - case 'start': - // For all-day events Kolab uses different times - // At least Android doesn't display such event as all-day event - if ($value && is_a($value, 'DateTime')) { - $date = clone $value; - if ($event'allday') { - // need this for self::date_from_kolab() - $date->_dateonly = false; + case 'changed': + case 'end': + case 'start': + // For all-day events Kolab uses different times + // At least Android doesn't display such event as all-day event + if ($value && is_a($value, 'DateTime')) { + $date = clone $value; + if (!empty($event'allday')) { + // need this for self::date_from_kolab() + $date->_dateonly = false; // @phpstan-ignore-line + + if ($name == 'start') { + $date->setTime(0, 0, 0); + } elseif ($name == 'end') { + $date->setTime(0, 0, 0); + $date->modify('+1 day'); + } + } + // set this date for use in recurrence exceptions handling if ($name == 'start') { - $date->setTime(0, 0, 0); - } - else if ($name == 'end') { - $date->setTime(0, 0, 0); - $date->modify('+1 day'); + $event'_start' = $date; } - } - // set this date for use in recurrence exceptions handling - if ($name == 'start') { - $event'_start' = $date; + $value = self::date_from_kolab($date); } - $value = self::date_from_kolab($date); - } - - break; + break; - case 'sensitivity': - if (!empty($value)) { - $value = intval($this->sensitivityMap$value); - } - break; + case 'sensitivity': + if (!empty($value)) { + $value = intval($this->sensitivityMap$value); + } + break; - case 'free_busy': - if (!empty($value)) { - $value = $this->busyStatusMap$value; - } - break; + case 'free_busy': + if (!empty($value)) { + $value = $this->busyStatusMap$value; + } + break; - case 'description': - $value = $this->body_from_kolab($value, $collection); - break; + case 'description': + $value = $this->body_from_kolab($value, $collection); + break; } // Ignore empty values (but not integer 0) @@ -252,12 +260,12 @@ } // Event reminder time - if ($config'ALARMS') { + if (!empty($config'ALARMS')) { $result'reminder' = $this->from_kolab_alarm($event); } - $result'categories' = array(); - $result'attendees' = array(); + $result'categories' = ; + $result'attendees' = ; // Categories, Roundcube Calendar plugin supports only one category at a time if (!empty($event'categories')) { @@ -268,11 +276,11 @@ if (!empty($event'attendees')) { foreach ($event'attendees' as $idx => $attendee) { if ($attendee'role' == 'ORGANIZER') { - if ($name = $attendee'name') { - $result'organizerName' = $name; + if (!empty($attendee'name')) { + $result'organizerName' = $attendee'name'; } - if ($email = $attendee'email') { - $result'organizerEmail' = $email; + if (!empty($attendee'email')) { + $result'organizerEmail' = $attendee'email'; } unset($event'attendees'$idx); @@ -301,7 +309,7 @@ 'name' => !empty($attendee'name') ? $attendee'name' : $email, ; - $type = isset($attendee'role') ? $this->attendeeTypeMap$attendee'role' : null; + $type = isset($attendee'role') ? $this->attendeeTypeMap$attendee'role' : null; $status = isset($attendee'status') ? $this->attendeeStatusMap$attendee'status' : null; if ($this->asversion >= 12) { @@ -313,12 +321,12 @@ $att'attendeeStatus' = $status ?: self::ATTENDEE_STATUS_UNKNOWN; } - if ($email && in_array_nocase($email, $user_emails)) { + if (in_array_nocase($email, $user_emails)) { $user_rsvp = !empty($attendee'rsvp'); $resp_type = $status ?: self::ATTENDEE_STATUS_UNKNOWN; // Synchronize the attendee status to the event status to get the same behaviour as outlook. - if (($is_outlook || $is_android )&& isset($attendee'status')) { + if (($is_outlook || $is_android) && isset($attendee'status')) { if ($attendee'status' == 'ACCEPTED') { $result'busyStatus' = self::BUSY_STATUS_BUSY; } @@ -357,25 +365,23 @@ } /** - * convert contact from xml to libkolab array + * Convert an event from xml to libkolab array * - * @param Syncroton_Model_IEntry $data Contact to convert - * @param string $folderid Folder identifier - * @param array $entry Existing entry - * @param DateTimeZone $timezone Timezone of the event + * @param Syncroton_Model_Event|Syncroton_Model_EventException $data Event or event exception to convert + * @param string $folderid Folder identifier + * @param array $entry Existing entry + * @param DateTimeZone $timezone Timezone of the event * * @return array */ - public function toKolab(Syncroton_Model_IEntry $data, $folderid, $entry = null, $timezone = null) + public function toKolab($data, $folderid, $entry = null, $timezone = null) { - $foldername = isset($entry'_mailbox') ? $entry'_mailbox' : $this->getFolderName($folderid); - if (empty($entry)) { + if (empty($entry) && !empty($data->uID)) { // If we don't have an existing event (not a modification) we nevertheless check for conflicts. // This is necessary so we don't overwrite the server-side copy in case the client did not have it available // when generating an Add command. try { - $folder = $this->getFolderObject($foldername); - $entry = $folder->get_object($data->uID); + $entry = $this->getObject($folderid, $data->uID); if ($entry) { $this->logger->debug('Found and existing event for UID: ' . $data->uID); @@ -384,8 +390,9 @@ // uID is not available on exceptions, so we guard for that and silently ignore. } } - $event = !empty($entry) ? $entry : array(); - $config = $this->getFolderConfig($foldername); + + $config = $this->getFolderConfig($entry ? $entry'folderId' : $folderid); + $event = !empty($entry) ? $entry : ; $is_exception = $data instanceof Syncroton_Model_EventException; $dummy_tz = str_repeat('A', 230) . '=='; $is_outlook = stripos($this->device->devicetype, 'outlook') !== false; @@ -408,8 +415,7 @@ try { $timezone = $tzc->getTimezone($data->timezone, $expected->getName()); $timezone = new DateTimeZone($timezone); - } - catch (Exception $e) { + } catch (Exception $e) { $this->logger->warn('Failed to convert the timezone information. UID: ' . $event'uid' . 'Timezone: ' . $data->timezone); $timezone = null; } @@ -437,54 +443,54 @@ } switch ($name) { - case 'changed': - $value = null; - break; + case 'changed': + $value = null; + break; - case 'end': - case 'start': - if ($timezone && $value) { - $value->setTimezone($timezone); - } + case 'end': + case 'start': + if ($timezone && $value) { + $value->setTimezone($timezone); + } - if ($value && $data->allDayEvent) { - $value->_dateonly = true; + if ($value && $data->allDayEvent) { + $value->_dateonly = true; - // In ActiveSync all-day event ends on 00:00:00 next day - // In Kolab we just ignore the time spec. - if ($name == 'end') { - $diff = date_diff($event'start', $value); - $value = clone $event'start'; + // In ActiveSync all-day event ends on 00:00:00 next day + // In Kolab we just ignore the time spec. + if ($name == 'end') { + $diff = date_diff($event'start', $value); + $value = clone $event'start'; - if ($diff->days > 1) { - $value->add(new DateInterval('P' . ($diff->days - 1) . 'D')); + if ($diff->days > 1) { + $value->add(new DateInterval('P' . ($diff->days - 1) . 'D')); + } } } - } - break; + break; - case 'sensitivity': - $map = array_flip($this->sensitivityMap); - $value = isset($map$value) ? $map$value : null; - break; + case 'sensitivity': + $map = array_flip($this->sensitivityMap); + $value = $map$value ?? null; + break; - case 'free_busy': - // Outlook sets the busy state to the attendance state, and we don't want to change the event state based on that. - // Outlook doesn't have the concept of an event state, so we just ignore this. - if ($is_outlook || $is_android) { - continue 2; - } - $map = array_flip($this->busyStatusMap); - $value = isset($map$value) ? $map$value : null; - break; + case 'free_busy': + // Outlook sets the busy state to the attendance state, and we don't want to change the event state based on that. + // Outlook doesn't have the concept of an event state, so we just ignore this. + if ($is_outlook || $is_android) { + continue 2; + } + $map = array_flip($this->busyStatusMap); + $value = $map$value ?? null; + break; - case 'description': - $value = $this->getBody($value, Syncroton_Model_EmailBody::TYPE_PLAINTEXT); - // If description isn't specified keep old description - if ($value === null) { - continue 2; - } - break; + case 'description': + $value = $this->getBody($value, Syncroton_Model_EmailBody::TYPE_PLAINTEXT); + // If description isn't specified keep old description + if ($value === null) { + continue 2; + } + break; } $this->setKolabDataItem($event, $name, $value); @@ -494,7 +500,7 @@ // It doesn't set all-day flag but the period is a whole day if (empty($event'allday') && !empty($event'end') && !empty($event'start')) { $interval = @date_diff($event'start', $event'end'); - if ($interval && $interval->format('%y%m%d%h%i%s') === '001000') { + if ($interval->format('%y%m%d%h%i%s') === '001000') { $event'allday' = 1; $event'end' = clone $event'start'; } @@ -502,12 +508,12 @@ // Reminder // @TODO: should alarms be used when importing event from phone? - if ($config'ALARMS') { + if (!empty($config'ALARMS')) { $event'valarms' = $this->to_kolab_alarm($data->reminder, $event); } - $attendees = array(); - $categories = array(); + $attendees = ; + $categories = ; // Categories if (isset($data->categories)) { @@ -520,21 +526,21 @@ if (!$is_exception) { // Organizer specified if ($organizer_email = $data->organizerEmail) { - $attendees = array( + $attendees = 'role' => 'ORGANIZER', 'name' => $data->organizerName, 'email' => $organizer_email, - ); - } else if (!empty($event'attendees')) { + ; + } elseif (!empty($event'attendees')) { // Organizer not specified, use one from the original event if that's an update foreach ($event'attendees' as $idx => $attendee) { if (!empty($attendee'email') && !empty($attendee'role') && $attendee'role' == 'ORGANIZER') { $organizer_email = $attendee'email'; - $attendees = array( + $attendees = 'role' => 'ORGANIZER', 'name' => $attendee'name' ?? '', 'email' => $organizer_email, - ); + ; } } } @@ -547,8 +553,7 @@ if ($is_outlook && !$is_exception && $data->timezone === $dummy_tz) { $this->logger->debug('Dummy outlook update detected, ignoring attendee changes.'); $attendees = $entry'attendees'; - } - else if (isset($data->attendees)) { + } elseif (isset($data->attendees)) { foreach ($data->attendees as $attendee) { if (!empty($organizer_email) && $attendee->email && !strcasecmp($attendee->email, $organizer_email)) { // skip the organizer @@ -564,11 +569,11 @@ $role = array_search(self::ATTENDEE_TYPE_REQUIRED, $this->attendeeTypeMap); } - $_attendee = array( + $_attendee = 'role' => $role, 'name' => $attendee->name != $attendee->email ? $attendee->name : '', 'email' => $attendee->email, - ); + ; if (isset($attendee->attendeeType) && $attendee->attendeeType == self::ATTENDEE_TYPE_RESOURCE) { $_attendee'cutype' = 'RESOURCE'; @@ -580,8 +585,7 @@ $_attendee'status' = 'NEEDS-ACTION'; $_attendee'rsvp' = true; } - } - else if (!empty($event'attendees') && !empty($attendee->email)) { + } elseif (!empty($event'attendees') && !empty($attendee->email)) { // copy the old attendee status foreach ($event'attendees' as $old_attendee) { if ($old_attendee'email' == $_attendee'email' && isset($old_attendee'status')) { @@ -601,7 +605,7 @@ $status = null; if ($data->busyStatus == self::BUSY_STATUS_BUSY) { $status = "ACCEPTED"; - } else if ($data->busyStatus == self::BUSY_STATUS_TENTATIVE) { + } elseif ($data->busyStatus == self::BUSY_STATUS_TENTATIVE) { $status = "TENTATIVE"; } @@ -615,11 +619,11 @@ if (!$is_exception) { // Make sure the event has the organizer set if (!$organizer_email && ($identity = kolab_sync::get_instance()->user->get_identity())) { - $attendees = array( + $attendees = 'role' => 'ORGANIZER', 'name' => $identity'name', 'email' => $identity'email', - ); + ; } // recurrence (and exceptions) @@ -628,7 +632,7 @@ $event'attendees' = $attendees; $event'categories' = $categories; - $event'exceptions' = isset($event'recurrence''EXCEPTIONS') ? $event'recurrence''EXCEPTIONS' : array(); + $event'exceptions' = $event'recurrence''EXCEPTIONS' ?? ; // Bump SEQUENCE number on update (Outlook only). // It's been confirmed that any change of the event that has attendees specified @@ -636,12 +640,13 @@ // Unfortunately Outlook also sends an update when no SEQUENCE bump // is needed, e.g. when updating attendee status. // We try our best to bump the SEQUENCE only when expected + // @phpstan-ignore-next-line if (!empty($entry) && !$is_exception && !empty($data->attendees) && $data->timezone != $dummy_tz) { if ($last_update = $this->getKolabDataItem($event, self::KEY_DTSTAMP)) { $last_update = new DateTime($last_update); } - if ($data->dtStamp && $data->dtStamp != $last_update) { + if (!empty($data->dtStamp) && $data->dtStamp != $last_update) { if ($this->has_significant_changes($event, $entry)) { $event'sequence'++; $this->logger->debug('Found significant changes in the updated event. Bumping SEQUENCE to ' . $event'sequence'); @@ -652,7 +657,7 @@ // Because we use last event modification time above, we make sure // the event modification time is not (re)set by the server, // we use the original Outlook's timestamp. - if ($is_outlook && $data->dtStamp) { + if ($is_outlook && !empty($data->dtStamp)) { $this->setKolabDataItem($event, self::KEY_DTSTAMP, $data->dtStamp->format(DateTime::ATOM)); } @@ -673,74 +678,213 @@ */ public function setAttendeeStatus(Syncroton_Model_MeetingResponse $request) { - $status_map = array( + $status_map = 1 => 'ACCEPTED', 2 => 'TENTATIVE', 3 => 'DECLINED', - ); + ; - if ($status = $status_map$request->userResponse) { - // extract event from the invitation - list($event, $existing) = $this->get_event_from_invitation($request); -/* - switch ($status) { - case 'ACCEPTED': $event'free_busy' = 'busy'; break; - case 'TENTATIVE': $event'free_busy' = 'tentative'; break; - case 'DECLINED': $event'free_busy' = 'free'; break; + $status = $status_map$request->userResponse ?? null; + + if (empty($status)) { + throw new Syncroton_Exception_Status_MeetingResponse(Syncroton_Exception_Status_MeetingResponse::MEETING_ERROR); + } + + // extract event from the invitation + $event, $existing = $this->get_event_from_invitation($request); + /* + switch ($status) { + case 'ACCEPTED': $event'free_busy' = 'busy'; break; + case 'TENTATIVE': $event'free_busy' = 'tentative'; break; + case 'DECLINED': $event'free_busy' = 'free'; break; + } + */ + // Store response timestamp for further use + $reply_time = new DateTime('now', new DateTimeZone('UTC')); + $this->setKolabDataItem($event, self::KEY_REPLYTIME, $reply_time->format('Ymd\THis\Z')); + + // Update/Save the event + if (empty($existing)) { + $folderId = $this->save_event($event, $status); + + // Create SyncState for the new event, so it is not synced twice + if ($folderId) { + try { + $syncBackend = Syncroton_Registry::getSyncStateBackend(); + $folderBackend = Syncroton_Registry::getFolderBackend(); + $contentBackend = Syncroton_Registry::getContentStateBackend(); + $syncFolder = $folderBackend->getFolder($this->device->id, $folderId); + $syncState = $syncBackend->getSyncState($this->device->id, $syncFolder->id); + + $contentBackend->create(new Syncroton_Model_Content( + 'device_id' => $this->device->id, + 'folder_id' => $syncFolder->id, + 'contentid' => $this->serverId($event'uid', $folderId), + 'creation_time' => $syncState->lastsync, + 'creation_synckey' => $syncState->counter, + )); + } catch (Exception $e) { + // ignore + } } -*/ - // Store response timestamp for further use - $reply_time = new DateTime('now', new DateTimeZone('UTC')); - $this->setKolabDataItem($event, self::KEY_REPLYTIME, $reply_time->format('Ymd\THis\Z')); - - // Update/Save the event - if (empty($existing)) { - $folder = $this->save_event($event, $status); - - // Create SyncState for the new event, so it is not synced twice - if ($folder) { - $folderId = $this->getFolderId($folder); - - try { - $syncBackend = Syncroton_Registry::getSyncStateBackend(); - $folderBackend = Syncroton_Registry::getFolderBackend(); - $contentBackend = Syncroton_Registry::getContentStateBackend(); - $syncFolder = $folderBackend->getFolder($this->device->id, $folderId); - $syncState = $syncBackend->getSyncState($this->device->id, $syncFolder->id); - - $contentBackend->create(new Syncroton_Model_Content(array( - 'device_id' => $this->device->id, - 'folder_id' => $syncFolder->id, - 'contentid' => $this->serverId($event'uid', $folder), - 'creation_time' => $syncState->lastsync, - 'creation_synckey' => $syncState->counter, - ))); + } else { + $folderId = $this->update_event($event, $existing, $status, $request->instanceId); + } + + if (!$folderId) { + throw new Syncroton_Exception_Status_MeetingResponse(Syncroton_Exception_Status_MeetingResponse::MEETING_ERROR); + } + + // TODO: ActiveSync version >= 16, send the iTip response. + if (isset($request->sendResponse)) { + // SendResponse can contain Body to use as email body (can be empty) + // TODO: Activesync >= 16.1 proposedStartTime and proposedEndTime. + } + + // FIXME: We should not return an UID when status=DECLINED + // as it's expected by the specification. Server + // should delete an event in such a case, but we + // keep the event copy with appropriate attendee status instead. + return $this->serverId($event'uid', $folderId); + } + + /** + * Process an event from an iTip message - update the event in the recipient's calendar + * + * @param array $event Event data from the iTip + * + * @return string|null Attendee status from the iTip (self::ITIP_* constant value) + */ + public function processItipReply($event) + { + // FIXME: This does not prevent from spoofing, i.e. an iTip message + // could be sent by anyone impersonating an organizer or attendee + + // FIXME: This will not work with Kolab delegation, as we do look + // for the event instance in personal folders only (for now) + // We also do not use SENT-BY,DELEGATED-TO,DELEGATED-FROM here at all. + + // FIXME: This is potential performance problem - we update an event + // whenever we sync an email message. User can have multiple AC clients + // or many iTip messages in INBOX. Should we remember which email was + // already processed? + + // FIXME: Should we check SEQUENCE or something else to prevent + // overwriting the attendee status with outdated status (on REPLY)? + + // Here we're handling CANCEL message, find the event (or occurrence) and remove it + if ($event'_method' == 'CANCEL') { + // TODO: Performance: When we're going to delete the event we don't have to fetch it, + // we just need to find that it exists and in which folder. + + if ($existing = $this->find_event_by_uid($event'uid')) { + // Note: Normally we'd just set the event status to canceled, but + // ActiveSync clients do not understand that, we have to delete it + + if (!empty($event'recurrence_date')) { + // A single recurring event occurrence + $rec_day = $event'recurrence_date'->format('Ymd'); + // Remove the matching RDATE entry + if (!empty($existing'recurrence''RDATE')) { + foreach ($existing'recurrence''RDATE' as $j => $rdate) { + if ($rdate->format('Ymd') == $rec_day) { + unset($existing'recurrence''RDATE'$j); + break; + } + } + } + + // Check EXDATE list, maybe already cancelled + if (!empty($existing'recurrence''EXDATE')) { + foreach ($existing'recurrence''EXDATE' as $j => $exdate) { + if ($exdate->format('Ymd') == $rec_day) { + return self::ITIP_CANCELLED; // skip update + } + } + } else { + $existing'recurrence''EXDATE' = ; } - catch (Exception $e) { - // ignore + + if (!isset($existing'exceptions')) { + $existing'exceptions' = ; + } + + if (!empty($existing'exceptions')) { + foreach ($existing'exceptions' as $i => $exception) { + if (libcalendaring::is_recurrence_exception($event, $exception)) { + unset($existing'exceptions'$i); + } + } + } + + // Add an exception to the master event + $existing'recurrence''EXDATE' = $event'recurrence_date'; + + // TODO: Handle errors + $this->save_event($existing, null); + } else { + $folder = $this->backend->getFolder($existing'folderId', $this->device->deviceid, $this->modelName); + if ($folder && $folder->valid) { + // TODO: Handle errors + $folder->delete($event'uid'); } } } - else { - $folder = $this->update_event($event, $existing, $status, $request->instanceId); + + return self::ITIP_CANCELLED; + } + + // Here we're handling REPLY message + if (empty($event'attendees') || $event'_method' != 'REPLY') { + return null; + } + + $attendeeStatus = null; + $attendeeEmail = null; + + // Get the attendee/status + foreach ($event'attendees' as $attendee) { + if (empty($attendee'role') || $attendee'role' != 'ORGANIZER') { + if (!empty($attendee'email') && !empty($attendee'status')) { + // Per iTip spec. there should be only one (non-organizer) attendee here + // FIXME: Verify is it realy the case with e.g. Kolab webmail, If not, we should + // probably use the message sender from the From: header + $attendeeStatus = strtoupper($attendee'status'); + $attendeeEmail = $attendee'email'; + break; + } } + } - if (!$folder) { - throw new Syncroton_Exception_Status_MeetingResponse(Syncroton_Exception_Status_MeetingResponse::MEETING_ERROR); + // Find the event (or occurrence) and update it + if ($attendeeStatus && ($existing = $this->find_event_by_uid($event'uid'))) { + // TODO: We should probably check the SEQUENCE to not reset status to an outdated value + + if (!empty($event'recurrence_date')) { + // A single recurring event occurrence + // Find the exception entry, it should exist, if not ignore + if (!empty($existing'exceptions')) { + foreach ($existing'exceptions' as $i => $exception) { + if (!empty($exception'attendees') && libcalendaring::is_recurrence_exception($event, $exception)) { + $attendees = &$existing'exceptions'$i'attendees'; + break; + } + } + } + } elseif (!empty($existing'attendees')) { + $attendees = &$existing'attendees'; } - // TODO: ActiveSync version >= 16, send the iTip response. - if (isset($request->sendResponse)) { - // SendResponse can contain Body to use as email body (can be empty) - // TODO: Activesync >= 16.1 proposedStartTime and proposedEndTime. + if (isset($attendees)) { + $found = $this->find_and_update_attendee_status($attendees, $attendeeStatus, $attendeeEmail, $changed); + if ($found && $changed) { + // TODO: error handling + $this->save_event($existing, null); + } } } - // FIXME: We should not return an UID when status=DECLINED - // as it's expected by the specification. Server - // should delete an event in such a case, but we - // keep the event copy with appropriate attendee status instead. - return empty($status) ? null : $this->serverId($event'uid', $folder); + return $attendeeStatus; } /** @@ -758,12 +902,12 @@ // find the event in calendar $existing = $this->find_event_by_uid($event'uid'); - return array($event, $existing); + return $event, $existing; } // Event from calendar folder - if ($event = $this->getObject($request->collectionId, $request->requestId, $folder)) { - return array($event, $event); + if ($event = $this->getObject($request->collectionId, $request->requestId)) { + return $event, $event; } throw new Syncroton_Exception_Status_MeetingResponse(Syncroton_Exception_Status_MeetingResponse::INVALID_REQUEST); @@ -783,12 +927,17 @@ // TODO: should we check every existing event folder even if not subscribed for sync? - foreach ($this->listFolders() as $folder) { - $storage_folder = $this->getFolderObject($folder'imap_name'); - if ($storage_folder->get_namespace() == 'personal' - && ($result = $storage_folder->get_object($uid)) - ) { - return $result; + if ($folders = $this->listFolders()) { + foreach ($folders as $_folder) { + $folder = $this->backend->getFolder($_folder'serverId', $this->device->deviceid, $this->modelName); + + if ($folder + && $folder->get_namespace() == 'personal' + && ($result = $this->backend->getItem($_folder'serverId', $this->device->deviceid, $this->modelName, $uid)) + ) { + $result'folderId' = $_folder'serverId'; + return $result; + } } } } @@ -803,21 +952,60 @@ throw new Syncroton_Exception_Status_MeetingResponse(Syncroton_Exception_Status_MeetingResponse::INVALID_REQUEST); } - if ($event'free_busy') { - $old'free_busy' = $event'free_busy'; - } + // A single recurring event occurrence + if (!empty($event'recurrence_date')) { + $event'recurrence' = ; - // Updating an existing event is most-likely a response - // to an iTip request with bumped SEQUENCE - $old'sequence' += 1; + if ($status) { + $this->update_attendee_status($event, $status); + $status = null; + } - // Copy new custom properties - if (!empty($event'x-custom')) { - foreach ($event'x-custom' as $key => $val) { - $old'x-custom'$key = $val; + if (!isset($old'exceptions')) { + $old'exceptions' = ; + } + + $existing = false; + foreach ($old'exceptions' as $i => $exception) { + if (libcalendaring::is_recurrence_exception($event, $exception)) { + $old'exceptions'$i = $event; + $existing = true; + } + } + + // TODO: In case organizer first cancelled an occurrence and then invited + // an attendee to the same date, and attendee accepts, we should remove EXDATE entry. + // FIXME: We have to check with ActiveSync clients whether it is better + // to have an exception with DECLINED attendee status, or an EXDATE entry + + if (!$existing) { + $old'exceptions' = $event; + } + } + // A main event update + elseif (isset($event'sequence') && $event'sequence' > $old'sequence') { + // FIXME: Can we be smarter here? Should we update everything? What about e.g. new attendees? + // And do we need to check the sequence? + $props = 'start', 'end', 'title', 'description', 'location', 'free_busy'; + + foreach ($props as $prop) { + if (isset($event$prop)) { + $old$prop = $event$prop; + } + } + + // Copy new custom properties + if (!empty($event'x-custom')) { + foreach ($event'x-custom' as $key => $val) { + $old'x-custom'$key = $val; + } } } + // Updating an existing event is most-likely a response + // to an iTip request with bumped SEQUENCE + $old'sequence' = ($old'sequence' ?? 0) + 1; + // Update the event return $this->save_event($old, $status); } @@ -828,25 +1016,23 @@ */ protected function save_event(&$event, $status = null) { - // Find default folder to which we'll save the event - if (!isset($event'_mailbox')) { - $folders = $this->listFolders(); - $storage = rcube::get_instance()->get_storage(); - - // find the default - foreach ($folders as $folder) { - if ($folder'type' == 8 && $storage->folder_namespace($folder'imap_name') == 'personal') { - $event'_mailbox' = $folder'imap_name'; - break; - } - } - - // if there's no folder marked as default, use any - if (!isset($event'_mailbox') && !empty($folders)) { - foreach ($folders as $folder) { - if ($storage->folder_namespace($folder'imap_name') == 'personal') { - $event'_mailbox' = $folder'imap_name'; - break; + $first = null; + $default = null; + + if (!isset($event'folderId')) { + // Find the folder to which we'll save the event + if ($folders = $this->listFolders()) { + foreach ($folders as $_folder) { + $folder = $this->backend->getFolder($_folder'serverId', $this->device->deviceid, $this->modelName); + + if ($folder && $folder->get_namespace() == 'personal') { + if ($_folder'type' == 8) { + $default = $_folder'serverId'; + break; + } + if (!$first) { + $first = $_folder'serverId'; + } } } } @@ -861,12 +1047,12 @@ // TODO: Free/busy trigger? - if (isset($event'_mailbox')) { - $folder = $this->getFolderObject($event'_mailbox'); + $old_uid = isset($event'folderId') ? $event'uid' : null; + $folder_id = $event'folderId' ?? ($default ?? $first); + $folder = $this->backend->getFolder($folder_id, $this->device->deviceid, $this->modelName); - if ($folder && $folder->valid && $folder->save($event)) { - return $folder; - } + if (!empty($folder) && $folder->valid && $folder->save($event, $this->modelName, $old_uid)) { + return $folder_id; } return false; @@ -875,7 +1061,7 @@ /** * Update the attendee status of the user matching $emails */ - protected function find_and_update_attendee_status(&$attendees, $status, $emails) + protected function find_and_update_attendee_status(&$attendees, $status, $emails, &$changed = false) { $found = false; foreach ((array) $attendees as $i => $attendee) { @@ -883,6 +1069,7 @@ && (empty($attendee'role') || $attendee'role' != 'ORGANIZER') && in_array_nocase($attendee'email', $emails) ) { + $changed = $changed || ($status != ($attendee'status' ?? '')); $attendees$i'status' = $status; $attendees$i'rsvp' = false; $this->logger->debug('Updating existing attendee: ' . $attendee'email' . ' status: ' . $status); @@ -902,13 +1089,13 @@ if (!$this->find_and_update_attendee_status($event'attendees', $status, $emails)) { $this->logger->debug('Adding new attendee ' . $emails0 . ' status: ' . $status); // Add the user to the attendees list - $event'attendees' = array( + $event'attendees' = 'role' => 'OPT-PARTICIPANT', 'name' => '', 'email' => $emails0, 'status' => $status, 'rsvp' => false, - ); + ; } } @@ -917,31 +1104,31 @@ * * @param int $filter_type Filter type * - * @param array Filter query + * @return array Filter query */ protected function filter($filter_type = 0) { - $filter = array(array('type', '=', $this->modelName)); + $filter = 'type', '=', $this->modelName; switch ($filter_type) { - case Syncroton_Command_Sync::FILTER_2_WEEKS_BACK: - $mod = '-2 weeks'; - break; - case Syncroton_Command_Sync::FILTER_1_MONTH_BACK: - $mod = '-1 month'; - break; - case Syncroton_Command_Sync::FILTER_3_MONTHS_BACK: - $mod = '-3 months'; - break; - case Syncroton_Command_Sync::FILTER_6_MONTHS_BACK: - $mod = '-6 months'; - break; + case Syncroton_Command_Sync::FILTER_2_WEEKS_BACK: + $mod = '-2 weeks'; + break; + case Syncroton_Command_Sync::FILTER_1_MONTH_BACK: + $mod = '-1 month'; + break; + case Syncroton_Command_Sync::FILTER_3_MONTHS_BACK: + $mod = '-3 months'; + break; + case Syncroton_Command_Sync::FILTER_6_MONTHS_BACK: + $mod = '-6 months'; + break; } if (!empty($mod)) { $dt = new DateTime('now', new DateTimeZone('UTC')); $dt->modify($mod); - $filter = array('dtend', '>', $dt); + $filter = 'dtend', '>', $dt; } return $filter; @@ -971,8 +1158,7 @@ if ($event'status' == 'CANCELLED') { $status = $is_organizer ? 5 : 7; - } - else { + } else { $status = $is_organizer ? 1 : 3; } } @@ -987,7 +1173,7 @@ { if (isset($event'valarms')) { foreach ($event'valarms' as $alarm) { - if (in_array($alarm'action', array('DISPLAY', 'AUDIO'))) { + if (in_array($alarm'action', 'DISPLAY', 'AUDIO')) { $value = $alarm'trigger'; break; } @@ -997,11 +1183,10 @@ if (!empty($value) && $value instanceof DateTime) { if (!empty($event'start') && ($interval = $event'start'->diff($value))) { if ($interval->invert && !$interval->m && !$interval->y) { - return intval(round($interval->s/60) + $interval->i + $interval->h * 60 + $interval->d * 60 * 24); + return intval(round($interval->s / 60) + $interval->i + $interval->h * 60 + $interval->d * 60 * 24); } } - } - else if (!empty($value) && preg_match('/^(-+*)PT*(0-9+)(WDHMS)$/', $value, $matches)) { + } elseif (!empty($value) && preg_match('/^(-+*)PT*(0-9+)(WDHMS)$/', $value, $matches)) { $value = intval($matches2); if ($value && $matches1 != '-') { @@ -1009,10 +1194,14 @@ } switch ($matches3) { - case 'S': $value = intval(round($value/60)); break; - case 'H': $value *= 60; break; - case 'D': $value *= 24 * 60; break; - case 'W': $value *= 7 * 24 * 60; break; + case 'S': $value = intval(round($value / 60)); + break; + case 'H': $value *= 60; + break; + case 'D': $value *= 24 * 60; + break; + case 'W': $value *= 7 * 24 * 60; + break; } return $value; @@ -1025,28 +1214,27 @@ protected function to_kolab_alarm($value, $event) { if ($value === null || $value === '') { - return isset($event'valarms') ? (array) $event'valarms' : array(); + return isset($event'valarms') ? (array) $event'valarms' : ; } - $valarms = array(); - $unsupported = array(); + $valarms = ; + $unsupported = ; if (!empty($event'valarms')) { foreach ($event'valarms' as $alarm) { - if (empty($current) && in_array($alarm'action', array('DISPLAY', 'AUDIO'))) { + if (empty($current) && in_array($alarm'action', 'DISPLAY', 'AUDIO')) { $current = $alarm; - } - else { + } else { $unsupported = $alarm; } } } - $valarms = array( + $valarms = 'action' => !empty($current'action') ? $current'action' : 'DISPLAY', 'description' => !empty($current'description') ? $current'description' : '', 'trigger' => sprintf('-PT%dM', $value), - ); + ; if (!empty($unsupported)) { $valarms = array_merge($valarms, $unsupported); @@ -1079,15 +1267,13 @@ $entry->startTime = $rounded; $entry->endTime = $end; - } - else if (empty($entry->startTime)) { + } elseif (empty($entry->startTime)) { if ($entry->endTime < $now || $entry->endTime < $rounded) { throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::INVALID_ITEM); } $entry->startTime = $rounded; - } - else if (empty($entry->endTime)) { + } elseif (empty($entry->endTime)) { if ($entry->startTime < $now) { throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::INVALID_ITEM); } @@ -1103,8 +1289,8 @@ protected function has_significant_changes($event, $old) { // Calendar namespace fields - foreach (array('allday', 'start', 'end', 'location', 'recurrence') as $key) { - if ((isset($event$key) ? $event$key : null) != (isset($old$key) ? $old$key : null)) { + foreach ('allday', 'start', 'end', 'location', 'recurrence' as $key) { + if (($event$key ?? null) != ($old$key ?? null)) { // Comparing recurrence is tricky as there can be differences in default // value handling. Let's try to handle most common cases if ($key == 'recurrence' && $this->fixed_recurrence($event) == $this->fixed_recurrence($old)) { @@ -1143,7 +1329,7 @@ // Add BYDAY if not exists if ($rec'FREQ' == 'WEEKLY' && empty($rec'BYDAY')) { - $days = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); + $days = 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'; $day = $event'start'->format('w'); $rec'BYDAY' = $days$day;
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_data_contacts.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -31,7 +31,7 @@ /** * Mapping from ActiveSync Contacts namespace fields */ - protected $mapping = array( + protected $mapping = 'anniversary' => 'anniversary', 'assistantName' => 'assistant:0', //'assistantPhoneNumber' => 'assistantphonenumber', @@ -94,7 +94,7 @@ 'managerName' => 'manager:0', //'mMS' => 'mms', 'nickName' => 'nickname', - ); + ; /** * Kolab object type @@ -149,11 +149,13 @@ * * @param Syncroton_Model_SyncCollection $collection Collection data * @param string $serverId Local entry identifier + * + * @return array|Syncroton_Model_Contact Contact object */ public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId) { $data = is_array($serverId) ? $serverId : $this->getObject($collection->collectionId, $serverId); - $result = array(); + $result = ; if (empty($data)) { throw new Syncroton_Exception_NotFound("Contact $serverId not found"); @@ -164,23 +166,23 @@ $value = $this->getKolabDataItem($data, $name); switch ($name) { - case 'photo': - if ($value) { - // ActiveSync limits photo size to 48KB (of base64 encoded string) - if (strlen($value) * 1.33 > 48 * 1024) { - continue 2; + case 'photo': + if ($value) { + // ActiveSync limits photo size to 48KB (of base64 encoded string) + if (strlen($value) * 1.33 > 48 * 1024) { + continue 2; + } } - } - break; + break; - case 'birthday': - case 'anniversary': - $value = self::date_from_kolab($value); - break; + case 'birthday': + case 'anniversary': + $value = self::date_from_kolab($value); + break; - case 'notes': - $value = $this->body_from_kolab($value, $collection); - break; + case 'notes': + $value = $this->body_from_kolab($value, $collection); + break; } if (empty($value) || is_array($value)) { @@ -191,14 +193,14 @@ } // email address(es): email1Address, email2Address, email3Address - for ($x=0; $x<3; $x++) { + for ($x = 0; $x < 3; $x++) { if (!empty($data'email'$x)) { $email = $data'email'$x; if (is_array($email)) { $email = $email'address'; } if ($email) { - $result'email' . ($x+1) . 'Address' = $email; + $result'email' . ($x + 1) . 'Address' = $email; } } } @@ -209,67 +211,67 @@ /** * convert contact from xml to libkolab array * - * @param Syncroton_Model_IEntry $data Contact to convert - * @param string $folderId Folder identifier - * @param array $entry Existing entry + * @param Syncroton_Model_Contact $data Contact to convert + * @param string $folderId Folder identifier + * @param array $entry Existing entry * * @return array Kolab object array */ - public function toKolab(Syncroton_Model_IEntry $data, $folderId, $entry = null) + public function toKolab($data, $folderId, $entry = null) { - $contact = !empty($entry) ? $entry : array(); + $contact = !empty($entry) ? $entry : ; // Contacts namespace fields foreach ($this->mapping as $key => $name) { $value = $data->$key; switch ($name) { - case 'address.work.street': - if (strtolower($this->device->devicetype) == 'palm') { - // palm pre sends the whole address in the <Contacts:BusinessStreet> tag - $value = null; - } - break; + case 'address.work.street': + if (strtolower($this->device->devicetype) == 'palm') { + // palm pre sends the whole address in the <Contacts:BusinessStreet> tag + $value = null; + } + break; - case 'website.homepage.url': - // remove facebook urls - if (preg_match('/^fb:\/\//', $value)) { - $value = null; - } - break; + case 'website.homepage.url': + // remove facebook urls + if (preg_match('/^fb:\/\//', $value)) { + $value = null; + } + break; - case 'notes': - $value = $this->getBody($value, Syncroton_Model_EmailBody::TYPE_PLAINTEXT); - // If note isn't specified keep old note - if ($value === null) { - continue 2; - } - break; + case 'notes': + $value = $this->getBody($value, Syncroton_Model_EmailBody::TYPE_PLAINTEXT); + // If note isn't specified keep old note + if ($value === null) { + continue 2; + } + break; - case 'photo': - // If photo isn't specified keep old photo - if ($value === null) { - continue 2; - } - break; - - case 'birthday': - case 'anniversary': - if ($value) { - // convert date to string format, so libkolab will store - // it with no time and timezone what could be incorrectly re-calculated (#2555) - $value = $value->format('Y-m-d'); - } - break; + case 'photo': + // If photo isn't specified keep old photo + if ($value === null) { + continue 2; + } + break; + + case 'birthday': + case 'anniversary': + if ($value) { + // convert date to string format, so libkolab will store + // it with no time and timezone what could be incorrectly re-calculated (#2555) + $value = $value->format('Y-m-d'); + } + break; } $this->setKolabDataItem($contact, $name, $value); } // email address(es): email1Address, email2Address, email3Address - $emails = array(); - for ($x=0; $x<3; $x++) { - $key = 'email' . ($x+1) . 'Address'; + $emails = ; + for ($x = 0; $x < 3; $x++) { + $key = 'email' . ($x + 1) . 'Address'; if ($value = $data->$key) { // Android sends email address as: Lars Kneschke <l.kneschke@metaways.de> if (preg_match('/(.*)<(.+@^@+)>/', $value, $matches)) { @@ -287,7 +289,7 @@ $type = $email'type'; } } - $emails = array('address' => $value, 'type' => $type); + $emails = 'address' => $value, 'type' => $type; } } $contact'email' = $emails; @@ -305,12 +307,12 @@ $list = parent::getAllFolders(); if ($this->isMultiFolder() && $this->hasGAL()) { - $list$this->galFolder = new Syncroton_Model_Folder(array( + $list$this->galFolder = new Syncroton_Model_Folder( 'displayName' => $this->galFolderName, // @TODO: localization? 'serverId' => $this->galFolder, 'parentId' => 0, 'type' => 14, - )); + ); } return $list; @@ -347,7 +349,7 @@ /** * Empty folder (remove all entries and optionally subfolders) * - * @param string $folderId Folder identifier + * @param string $folderid Folder identifier * @param array $options Options */ public function emptyFolderContents($folderid, $options) @@ -406,9 +408,9 @@ /** * update existing entry * - * @param string $folderId - * @param string $serverId - * @param SimpleXMLElement $entry + * @param string $folderId + * @param string $serverId + * @param Syncroton_Model_IEntry $entry * * @return string ID of the updated entry */ @@ -422,13 +424,13 @@ } /** - * delete entry + * Delete an entry * - * @param string $folderId - * @param string $serverId - * @param array $collectionData + * @param string $folderId + * @param string $serverId + * @param ?Syncroton_Model_SyncCollection $collectionData */ - public function deleteEntry($folderId, $serverId, $collectionData) + public function deleteEntry($folderId, $serverId, $collectionData = null) { if (strpos($serverId, $this->galPrefix) === 0 && $this->hasGAL()) { throw new Syncroton_Exception_AccessDenied("Deleting GAL entries is not possible"); @@ -442,13 +444,13 @@ * * @param int $filter_type Filter type * - * @param array Filter query + * @return array Filter query */ protected function filter($filter_type = 0) { // specify object type, contact folders in Kolab might // contain also ditribution-list objects, we'll skip them - return array(array('type', '=', $this->modelName)); + return 'type', '=', $this->modelName; } /** @@ -468,7 +470,7 @@ * * @return array|int Search result as count or array of uids/objects */ - protected function searchEntries($folderid, $filter = array(), $result_type = self::RESULT_UID) + protected function searchEntries($folderid, $filter = , $result_type = self::RESULT_UID) { // GAL Folder exists, return result from LDAP only if ($folderid === $this->galFolder && $this->hasGAL()) { @@ -483,8 +485,7 @@ if ($result_type == self::RESULT_COUNT) { $result += $gal_result; - } - else { + } else { $result = array_merge($result, $gal_result); } } @@ -495,13 +496,13 @@ /** * Fetches the entry from the backend */ - protected function getObject($folderid, $entryid, &$folder = null) + protected function getObject($folderid, $entryid) { if (strpos($entryid, $this->galPrefix) === 0 && $this->hasGAL()) { return $this->getGALEntry($entryid); } - return parent::getObject($folderid, $entryid, $folder); + return parent::getObject($folderid, $entryid); } /** @@ -519,7 +520,7 @@ // will be done as delete + create foreach ($filter as $f) { if ($f0 == 'changed') { - return $result_type == self::RESULT_COUNT ? 0 : array(); + return $result_type == self::RESULT_COUNT ? 0 : ; } } @@ -528,7 +529,7 @@ return $result_type == self::RESULT_COUNT ? count($result) : $result; } - $result = array(); + $result = ; foreach ($this->getGALSources() as $source) { if ($book = kolab_sync_data_gal::get_address_book($source'id')) { @@ -537,7 +538,7 @@ $book->set_pagesize(10000); $set = $book->list_records(); - while ($contact = $set->next()) { + foreach ($set as $contact) { $result = $this->createGALEntryUID($contact, $source'id'); } } @@ -555,28 +556,30 @@ * * @param string $serverId Entry identifier * - * @return array Contact data + * @return array|null Contact data */ protected function getGALEntry($serverId) { - list($source, $timestamp, $uid) = $this->resolveGALEntryUID($serverId); + $source, $timestamp, $uid = $this->resolveGALEntryUID($serverId); if ($source && $uid && ($book = kolab_sync_data_gal::get_address_book($source))) { $book->reset(); - $set = $book->search('uid', array($uid), rcube_addressbook::SEARCH_STRICT, true, true); + $set = $book->search('uid', $uid, rcube_addressbook::SEARCH_STRICT, true, true); $result = $set->first(); if ($result'uid' == $uid && $result'changed' == $timestamp) { // As in kolab_sync_data_gal we use only one email address if (empty($result'email')) { $emails = $book->get_col_values('email', $result, true); - $result'email' = array($emails0); + $result'email' = $emails0; } return $result; } } + + return null; } /** @@ -593,12 +596,11 @@ if ($gal_sync === true) { $enabled = true; - } - else if (is_array($gal_sync)) { + } elseif (is_array($gal_sync)) { $enabled = $this->deviceTypeFilter($gal_sync); } - $this->galSources = $enabled ? kolab_sync_data_gal::get_address_sources() : array(); + $this->galSources = $enabled ? kolab_sync_data_gal::get_address_sources() : ; if ($cache_type = $rcube->config->get('activesync_gal_cache', 'db')) { $cache_ttl = $rcube->config->get('activesync_gal_cache_ttl', '1d'); @@ -633,6 +635,6 @@ return $items; // source, timestamp, uid } - return array(); + return ; } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_data_email.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -28,12 +28,12 @@ */ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_IDataSearch { - const MAX_SEARCH_RESULT = 200; + public const MAX_SEARCH_RESULT = 200; /** * Mapping from ActiveSync Email namespace fields */ - protected $mapping = array( + protected $mapping = 'cc' => 'cc', //'contentClass' => 'contentclass', 'dateReceived' => 'internaldate', @@ -48,22 +48,22 @@ 'subject' => 'subject', //'threadTopic' => 'threadtopic', 'to' => 'to', - ); + ; - static $memory_accumulated = 0; + public static $memory_accumulated = 0; /** * Special folder type/name map * * @var array */ - protected $folder_types = array( + protected $folder_types = 2 => 'Inbox', 3 => 'Drafts', 4 => 'Deleted Items', 5 => 'Sent Items', 6 => 'Outbox', - ); + ; /** * Kolab object type @@ -93,8 +93,7 @@ */ protected $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_MAIL_USER_CREATED; - private $lastsync_folder = null; - private $lastsync_time = null; + protected $storage; /** @@ -111,10 +110,6 @@ // Outlook 2013 support multi-folder $this->ext_devices = 'windowsoutlook15'; - - if ($this->asversion >= 14) { - $this->tag_categories = true; - } } /** @@ -151,7 +146,7 @@ /** * Decode a globalObjId according to https://interoperability.blob.core.windows.net/files/MS-ASEMAIL/%5bMS-ASEMAIL%5d-150526.pdf 2.2.2.3 * - * @param string the encoded globalObjId + * @param string $globalObjId The encoded globalObjId * * @return array An array with the decoded data */ @@ -191,31 +186,31 @@ $value = null; switch ($name) { - case 'internaldate': - $value = self::date_from_kolab(rcube_utils::strtotime($headers->internaldate)); - break; + case 'internaldate': + $value = self::date_from_kolab(rcube_utils::strtotime($headers->internaldate)); + break; - case 'cc': - case 'to': - case 'replyto': - case 'from': - $addresses = rcube_mime::decode_address_list($headers->$name, null, true, $headers->charset); + case 'cc': + case 'to': + case 'replyto': + case 'from': + $addresses = rcube_mime::decode_address_list($headers->$name, null, true, $headers->charset); - foreach ($addresses as $idx => $part) { - // @FIXME: set name + address or address only? - $addresses$idx = format_email_recipient($part'mailto', $part'name'); - } + foreach ($addresses as $idx => $part) { + // @FIXME: set name + address or address only? + $addresses$idx = format_email_recipient($part'mailto', $part'name'); + } - $value = implode(',', $addresses); - break; + $value = implode(',', $addresses); + break; - case 'subject': - $value = $headers->get('subject'); - break; + case 'subject': + $value = $headers->get('subject'); + break; - case 'charset': - $value = self::charset_to_cp($headers->charset); - break; + case 'charset': + $value = self::charset_to_cp($headers->charset); + break; } if (empty($value) || is_array($value)) { @@ -229,8 +224,8 @@ $result$key = $value; } -// $result'ConversationId' = 'FF68022058BD485996BE15F6F6D99320'; -// $result'ConversationIndex' = 'CA2CFA8A23'; + // $result'ConversationId' = 'FF68022058BD485996BE15F6F6D99320'; + // $result'ConversationIndex' = 'CA2CFA8A23'; // Read flag $result'read' = intval(!empty($headers->flags'SEEN')); @@ -238,10 +233,10 @@ // Flagged message if (!empty($headers->flags'FLAGGED')) { // Use FollowUp flag which is used in Android when message is marked with a star - $result'flag' = new Syncroton_Model_EmailFlag(array( + $result'flag' = new Syncroton_Model_EmailFlag( 'flagType' => 'FollowUp', 'status' => Syncroton_Model_EmailFlag::STATUS_ACTIVE, - )); + ); } else { $result'flag' = new Syncroton_Model_EmailFlag(); } @@ -250,9 +245,8 @@ if ($headers->priority) { if ($headers->priority < 3) { $result'importance' = 2; // High - } - else if ($headers->priority > 3) { - $result'importance' = 0; // Low + } elseif ($headers->priority > 3) { + $result'importance' = 0; // Low } } @@ -267,44 +261,42 @@ if (isset($prefsSyncroton_Command_Sync::BODY_TYPE_MIME'truncationSize')) { $truncateAt = $prefsSyncroton_Command_Sync::BODY_TYPE_MIME'truncationSize'; - } - else if (isset($opts'mimeTruncation') && $opts'mimeTruncation' < Syncroton_Command_Sync::TRUNCATE_NOTHING) { + } elseif (isset($opts'mimeTruncation') && $opts'mimeTruncation' < Syncroton_Command_Sync::TRUNCATE_NOTHING) { switch ($opts'mimeTruncation') { - case Syncroton_Command_Sync::TRUNCATE_ALL: - $truncateAt = 0; - break; - case Syncroton_Command_Sync::TRUNCATE_4096: - $truncateAt = 4096; - break; - case Syncroton_Command_Sync::TRUNCATE_5120: - $truncateAt = 5120; - break; - case Syncroton_Command_Sync::TRUNCATE_7168: - $truncateAt = 7168; - break; - case Syncroton_Command_Sync::TRUNCATE_10240: - $truncateAt = 10240; - break; - case Syncroton_Command_Sync::TRUNCATE_20480: - $truncateAt = 20480; - break; - case Syncroton_Command_Sync::TRUNCATE_51200: - $truncateAt = 51200; - break; - case Syncroton_Command_Sync::TRUNCATE_102400: - $truncateAt = 102400; - break; + case Syncroton_Command_Sync::TRUNCATE_ALL: + $truncateAt = 0; + break; + case Syncroton_Command_Sync::TRUNCATE_4096: + $truncateAt = 4096; + break; + case Syncroton_Command_Sync::TRUNCATE_5120: + $truncateAt = 5120; + break; + case Syncroton_Command_Sync::TRUNCATE_7168: + $truncateAt = 7168; + break; + case Syncroton_Command_Sync::TRUNCATE_10240: + $truncateAt = 10240; + break; + case Syncroton_Command_Sync::TRUNCATE_20480: + $truncateAt = 20480; + break; + case Syncroton_Command_Sync::TRUNCATE_51200: + $truncateAt = 51200; + break; + case Syncroton_Command_Sync::TRUNCATE_102400: + $truncateAt = 102400; + break; } } - } - else { + } else { // The spec is not very clear, but it looks that if MimeSupport is not set // we can't add Syncroton_Command_Sync::BODY_TYPE_MIME to the supported types // list below (Bug #1688) - $types = array( + $types = Syncroton_Command_Sync::BODY_TYPE_HTML, Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT, - ); + ; // @TODO: if client can support both HTML and TEXT use one of // them which is better according to the real message body type @@ -315,7 +307,7 @@ $truncateAt = $prefs$type'truncationSize'; } - $preview = (int) (isset($prefs$type'preview') ? $prefs$type'preview' : 0); + $preview = (int) ($prefs$type'preview' ?? 0); $airSyncBaseType = $type; break; @@ -323,7 +315,7 @@ } } - $body_params = array('type' => $airSyncBaseType); + $body_params = 'type' => $airSyncBaseType; // Message body // In Sync examples there's one in which bodyPreferences is not defined @@ -336,8 +328,7 @@ $truncateAt = 0; $body_length = 0; $isTruncated = 1; - } - else if ($airSyncBaseType == Syncroton_Command_Sync::BODY_TYPE_MIME) { + } elseif ($airSyncBaseType == Syncroton_Command_Sync::BODY_TYPE_MIME) { // Check if we have enough memory to handle the message $messageBody = $this->message_mem_check($message, $headers->size); static::$memory_accumulated += $headers->size; @@ -352,8 +343,7 @@ // strip out any non utf-8 characters $messageBody = rcube_charset::clean($messageBody); $real_length = $body_length = strlen($messageBody); - } - else { + } else { $messageBody = $this->getMessageBody($message, $airSyncBaseType == Syncroton_Command_Sync::BODY_TYPE_HTML); // strip out any non utf-8 characters $messageBody = rcube_charset::clean($messageBody); @@ -393,17 +383,15 @@ && $message->parts1->mimetype == 'application/pkcs7-signature' ) { $result'messageClass' = 'IPM.Note.SMIME.MultipartSigned'; - } - else if ($headers->ctype == 'application/pkcs7-mime' || $headers->ctype == 'application/x-pkcs7-mime') { + } elseif ($headers->ctype == 'application/pkcs7-mime' || $headers->ctype == 'application/x-pkcs7-mime') { $result'messageClass' = 'IPM.Note.SMIME'; - } - // FIXME disabled for now because it results in broken invitations on android (gmail client) and ios - // else if ($event = $this->get_invitation_event_from_message($message)) { - else if (false) { + } elseif ($event = $this->get_invitation_event_from_message($message)) { + // Note: Depending on MessageClass a client will display a proper set of buttons + // Either Accept/Maybe/Decline (REQUEST), or "Remove from Calendar" (CANCEL) or none (REPLY). $result'messageClass' = 'IPM.Schedule.Meeting.Request'; $result'contentClass' = 'urn:content-classes:calendarmessage'; - $meeting = array(); + $meeting = ; $meeting'allDayEvent' = $event'allday' ?? null ? 1 : 0; $meeting'startTime' = self::date_from_kolab($event'start'); @@ -419,7 +407,7 @@ } else { $meeting'instanceType' = Syncroton_Model_EmailMeetingRequest::TYPE_RECURRING_SINGLE; } - } else if (!empty($event'recurrence')) { + } elseif (!empty($event'recurrence')) { $meeting'instanceType' = Syncroton_Model_EmailMeetingRequest::TYPE_RECURRING_MASTER; // TODO: MeetingRequest recurrence is different that the one in Calendar // $this->recurrence_from_kolab($collection, $event, $meeting); @@ -427,7 +415,7 @@ // Organizer if (!empty($event'attendees')) { - foreach ($event'attendees' as $idx => $attendee) { + foreach ($event'attendees' as $attendee) { if (!empty($attendee'role') && $attendee'role' == 'ORGANIZER' && !empty($attendee'email')) { $meeting'organizer' = $attendee'email'; break; @@ -435,7 +423,8 @@ } } - $fileTime = ($event'start'->getTimestamp() + 11644473600) * 10000000; // 1.1.1600 - 1.1.1970 difference in seconds. Converted to microseconds + // Current time as a number of 100-nanosecond units since 1601-01-01 + $fileTime = ($event'start'->getTimestamp() + 11644473600) * 10000000; // Kolab Format 3.0 and xCal does support timezone per-date, but ActiveSync allows // only one timezone per-event. We'll use timezone of the start date @@ -448,35 +437,80 @@ 'now' => $fileTime, ); - // TODO handle other methods if ($event'_method' == 'REQUEST') { $meeting'meetingMessageType' = Syncroton_Model_EmailMeetingRequest::MESSAGE_TYPE_REQUEST; - } else { + + // Some clients (iOS) without this flag do not send the invitation reply to the organizer. + // Note: Microsoft says "the value of the ResponseRequested element comes from the PARTSTAT + // parameter value of "NEEDS-ACTION" in the request". I think it is safe to do this for all requests. + // Note: This does not have impact on the existence of Accept/Decline buttons in the client. + $meeting'responseRequested' = 1; + } else { // REPLY or CANCEL $meeting'meetingMessageType' = Syncroton_Model_EmailMeetingRequest::MESSAGE_TYPE_NORMAL; + $itip_processing = kolab_sync::get_instance()->config->get('activesync_itip_processing'); + $attendeeStatus = null; + + if ($itip_processing && empty($headers->flags'SEEN')) { + // Optionally process the message and update the event in recipient's calendar + // Warning: Only for development purposes, for now it's better to use wallace + $calendar_class = new kolab_sync_data_calendar($this->device, $this->syncTimeStamp); + $attendeeStatus = $calendar_class->processItipReply($event); + } elseif ($event'_method' == 'CANCEL') { + $attendeeStatus = kolab_sync_data_calendar::ITIP_CANCELLED; + } elseif (!empty($event'attendees')) { + // Get the attendee/status in the REPLY + foreach ($event'attendees' as $attendee) { + if (empty($attendee'role') || $attendee'role' != 'ORGANIZER') { + if (!empty($attendee'email') && !empty($attendee'status')) { + // Per iTip spec. there should be only one (non-organizer) attendee here + // FIXME: Verify is it realy the case with e.g. Kolab webmail, If not, we should + // probably use the message sender from the From: header + $attendeeStatus = strtoupper($attendee'status'); + break; + } + } + } + } + + switch ($attendeeStatus) { + case kolab_sync_data_calendar::ITIP_CANCELLED: + $result'messageClass' = 'IPM.Schedule.Meeting.Canceled'; + break; + case kolab_sync_data_calendar::ITIP_DECLINED: + $result'messageClass' = 'IPM.Schedule.Meeting.Resp.Neg'; + break; + case kolab_sync_data_calendar::ITIP_TENTATIVE: + $result'messageClass' = 'IPM.Schedule.Meeting.Resp.Tent'; + break; + case kolab_sync_data_calendar::ITIP_ACCEPTED: + $result'messageClass' = 'IPM.Schedule.Meeting.Resp.Pos'; + break; + default: + $skipRequest = true; + } } // New time proposals aren't supported by Kolab. // This disables the UI elements related to this on the client side $meeting'disallowNewTimeProposal' = 1; - $result'meetingRequest' = new Syncroton_Model_EmailMeetingRequest($meeting); + if (empty($skipRequest)) { + $result'meetingRequest' = new Syncroton_Model_EmailMeetingRequest($meeting); + } } // Categories (Tags) - if (isset($this->tag_categories) && $this->tag_categories) { - // convert kolab tags into categories - $result'categories' = $this->getKolabTags($message); - } + $result'categories' = $message->headers->others'categories' ?? ; $is_ios = preg_match('/(iphone|ipad)/i', $this->device->devicetype); // attachments $attachments = array_merge($message->attachments, $message->inline_parts); if (!empty($attachments)) { - $result'attachments' = array(); + $result'attachments' = ; foreach ($attachments as $attachment) { - $att = array(); + $att = ; if ($is_ios && !empty($event) && $attachment->mime_id == $event'_mime_id') { continue; @@ -520,25 +554,26 @@ */ public function getSearchEntry($longId, $options) { - $collection = new Syncroton_Model_SyncCollection(array( + $collection = new Syncroton_Model_SyncCollection( 'options' => $options, - )); + ); return $this->getEntry($collection, $longId); } /** - * convert contact from xml to libkolab array + * convert email from xml to libkolab array * - * @param Syncroton_Model_IEntry $data Contact to convert - * @param string $folderid Folder identifier - * @param array $entry Existing entry + * @param Syncroton_Model_Email $data Email to convert + * @param string $folderid Folder identifier + * @param array $entry Existing entry * * @return array */ - public function toKolab(Syncroton_Model_IEntry $data, $folderid, $entry = null) + public function toKolab($data, $folderid, $entry = null) { // does nothing => you can't add emails via ActiveSync + return ; } /** @@ -546,28 +581,28 @@ * * @param int $filter_type Filter type * - * @param array Filter query + * @return array Filter query */ protected function filter($filter_type = 0) { - $filter = array(); + $filter = ; switch ($filter_type) { - case Syncroton_Command_Sync::FILTER_1_DAY_BACK: - $mod = '-1 day'; - break; - case Syncroton_Command_Sync::FILTER_3_DAYS_BACK: - $mod = '-3 days'; - break; - case Syncroton_Command_Sync::FILTER_1_WEEK_BACK: - $mod = '-1 week'; - break; - case Syncroton_Command_Sync::FILTER_2_WEEKS_BACK: - $mod = '-2 weeks'; - break; - case Syncroton_Command_Sync::FILTER_1_MONTH_BACK: - $mod = '-1 month'; - break; + case Syncroton_Command_Sync::FILTER_1_DAY_BACK: + $mod = '-1 day'; + break; + case Syncroton_Command_Sync::FILTER_3_DAYS_BACK: + $mod = '-3 days'; + break; + case Syncroton_Command_Sync::FILTER_1_WEEK_BACK: + $mod = '-1 week'; + break; + case Syncroton_Command_Sync::FILTER_2_WEEKS_BACK: + $mod = '-2 weeks'; + break; + case Syncroton_Command_Sync::FILTER_1_MONTH_BACK: + $mod = '-1 month'; + break; } if (!empty($mod)) { @@ -596,18 +631,18 @@ // device doesn't support multiple folders if (!$this->isMultiFolder()) { // We'll return max. one folder of supported type - $result = array(); + $result = ; $types = $this->folder_types; foreach ($list as $idx => $folder) { $type = $folder'type' == 12 ? 2 : $folder'type'; // unknown to Inbox if ($folder_id = $types$type) { - $result$folder_id = array( + $result$folder_id = 'displayName' => $folder_id, 'serverId' => $folder_id, 'parentId' => 0, 'type' => $type, - ); + ; } } @@ -624,12 +659,14 @@ /** * Return list of folders for specified folder ID * + * @param string $folder_id Folder identifier + * * @return array Folder identifiers list */ protected function extractFolders($folder_id) { $list = $this->listFolders(); - $result = array(); + $result = ; if (!is_array($list)) { throw new Syncroton_Exception_NotFound('Folder not found'); @@ -640,8 +677,7 @@ if ($list$folder_id) { $result = $folder_id; } - } - else if ($type = array_search($folder_id, $this->folder_types)) { + } elseif ($type = array_search($folder_id, $this->folder_types)) { foreach ($list as $id => $folder) { if ($folder'type' == $type || ($folder_id == 'Inbox' && $folder'type' == 12)) { $result = $id; @@ -668,29 +704,17 @@ */ public function moveItem($srcFolderId, $serverId, $dstFolderId) { - $msg = $this->parseMessageId($serverId); - $dest = $this->extractFolders($dstFolderId); - $dest_id = array_shift($dest); - $dest_name = $this->backend->folder_id2name($dest_id, $this->device->deviceid); + $msg = $this->parseMessageId($serverId); + $dest = $this->extractFolders($dstFolderId); + $dest_id = array_shift($dest); if (empty($msg)) { throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE); } - if ($dest_name === null) { - throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_DESTINATION); - } - - if (!$this->storage->move_message($msg'uid', $dest_name, $msg'foldername')) { - throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE); - } - - // Use COPYUID feature (RFC2359) to get the new UID of the copied message - $copyuid = isset($this->storage->conn->data'COPYUID') ? $this->storage->conn->data'COPYUID' : null; + $uid = $this->backend->moveItem($msg'folderId', $this->device->deviceid, $this->modelName, $msg'uid', $dest_id); - if (is_array($copyuid) && ($uid = $copyuid1)) { - return $this->createMessageId($dest_id, $uid); - } + return $uid ? $this->serverId($uid, $dest_id) : null; } /** @@ -703,18 +727,15 @@ */ public function createEntry($folderId, Syncroton_Model_IEntry $entry) { - // Creating emails is not normally supported like this, but is implemented for testing purposes - $foldername = $this->backend->folder_id2name($folderId, $this->device->deviceid); + $params = 'flags' => !empty($entry->read) ? 'SEEN' : 'UNSEEN'; - $flag = !empty($entry->read) ? 'SEEN' : 'UNSEEN'; - $uid = $this->storage->save_message($foldername, $entry->body->data, '', false, $flag); + $uid = $this->backend->createItem($folderId, $this->device->deviceid, $this->modelName, $entry->body->data, $params); if (!$uid) { - $this->logger->error("Error while storing the message " . $this->storage->get_error_str()); throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR); } - return $this->createMessageId($folderId, $uid); + return $this->serverId($uid, $folderId); } /** @@ -732,46 +753,39 @@ throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR); } - if (isset($entry->categories)) { - // Read the message headers only when they are needed - $message = $this->getObject($serverId); + $params = 'flags' => ; - if (empty($message)) { - throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR); - } + if (isset($entry->categories)) { + $params'categories' = $entry->categories; } // Read status change if (isset($entry->read)) { - // here we update only Read flag - $flag = !empty($entry->read) ? 'SEEN' : 'UNSEEN'; - $this->storage->set_flag($msg'uid', $flag, $msg'foldername'); + $params'flags' = !empty($entry->read) ? 'SEEN' : 'UNSEEN'; } // Flag change if (isset($entry->flag)) { if (empty($entry->flag) || empty($entry->flag->flagType)) { - $this->storage->set_flag($msg'uid', 'UNFLAGGED', $msg'foldername'); - } - else if (preg_match('/follow\s*up/i', $entry->flag->flagType)) { - $this->storage->set_flag($msg'uid', 'FLAGGED', $msg'foldername'); + $params'flags' = 'UNFLAGGED'; + } elseif (preg_match('/follow\s*up/i', $entry->flag->flagType)) { + $params'flags' = 'FLAGGED'; } } - // Categories (Tags) change - if (isset($entry->categories)) { - $this->setKolabTags($message, $entry->categories); - } + $this->backend->updateItem($msg'folderId', $this->device->deviceid, $this->modelName, $msg'uid', null, $params); + + return $serverId; } /** * Delete an email (or move to Trash) * - * @param string $folderId - * @param string $serverId - * @param Syncroton_Model_SyncCollection $collection + * @param string $folderId + * @param string $serverId + * @param ?Syncroton_Model_SyncCollection $collection */ - public function deleteEntry($folderId, $serverId, $collection) + public function deleteEntry($folderId, $serverId, $collection = null) { $trash = kolab_sync::get_instance()->config->get('trash_mbox'); $msg = $this->parseMessageId($serverId); @@ -781,25 +795,12 @@ } // Note: If DeletesAsMoves is not specified in the request, its default is 1 (true). + $moveToTrash = !isset($collection->deletesAsMoves) || !empty($collection->deletesAsMoves); - // move message to trash folder - if ((!isset($collection->deletesAsMoves) || !empty($collection->deletesAsMoves)) - && strlen($trash) - && $trash != $msg'foldername' - && $this->storage->folder_exists($trash) - ) { - $this->storage->move_message($msg'uid', $trash, $msg'foldername'); - } - // delete the message - else { - // According to the ActiveSync spec. "If the DeletesAsMoves element is set to false, - // the deletion is PERMANENT.", therefore we delete the message, and not flag as deleted. - $this->storage->delete_message($msg'uid', $msg'foldername'); + $deleted = $this->backend->deleteItem($msg'folderId', $this->device->deviceid, $this->modelName, $msg'uid', $moveToTrash); - // FIXME: We could consider acting according to the 'flag_for_deletion' setting. - // Don't forget about 'read_when_deleted' setting then. - // $this->storage->set_flag($msg'uid', 'DELETED', $msg'foldername'); - // $this->storage->set_flag($msg'uid', 'SEEN', $msg'foldername'); + if (!$deleted) { + throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); } } @@ -828,7 +829,7 @@ $sent_folder = kolab_sync::get_instance()->config->get('sent_mbox'); if (strlen($sent_folder) && $this->storage->folder_exists($sent_folder)) { - return $this->storage->save_message($sent_folder, $message->source(), '', false, array('SEEN')); + return $this->storage->save_message($sent_folder, $message->source(), '', false, 'SEEN'); } } } @@ -871,19 +872,19 @@ // forward original message as attachment if (!$replaceMime) { - $this->storage->set_folder($msg'foldername'); + $this->storage->set_folder($message->folder); $attachment = $this->storage->get_raw_body($msg'uid'); if (empty($attachment)) { throw new Syncroton_Exception_Status(Syncroton_Exception_Status::ITEM_NOT_FOUND); } - $sync_msg->add_attachment($attachment, array( + $sync_msg->add_attachment($attachment, 'encoding' => '8bit', 'content_type' => 'message/rfc822', 'disposition' => 'inline', //'name' => 'message.eml', - )); + ); } // Send message @@ -891,7 +892,8 @@ // Set FORWARDED flag on the replied message if (empty($message->headers->flags'FORWARDED')) { - $this->storage->set_flag($msg'uid', 'FORWARDED', $msg'foldername'); + $params = 'flags' => 'FORWARDED'; + $this->backend->updateItem($msg'folderId', $this->device->deviceid, $this->modelName, $msg'uid', null, $params); } } @@ -941,245 +943,9 @@ // Set ANSWERED flag on the replied message if (empty($message->headers->flags'ANSWERED')) { - $this->storage->set_flag($msg'uid', 'ANSWERED', $msg'foldername'); - } - } - - /** - * Search for existing entries - * - * @param string $folderid - * @param array $filter - * @param int $result_type Type of the result (see RESULT_* constants) - * - * @return array|int Search result as count or array of uids/objects - */ - protected function searchEntries($folderid, $filter = array(), $result_type = self::RESULT_UID) - { - $folders = $this->extractFolders($folderid); - $filter_str = 'ALL UNDELETED'; - - // convert filter into one IMAP search string - foreach ($filter as $idx => $filter_item) { - if (is_array($filter_item)) { - // This is a request for changes since last time - // we'll use HIGHESTMODSEQ value from the last Sync - if ($filter_item0 == 'changed' && $filter_item1 == '>') { - $modseq_lasttime = $filter_item2; - $modseq_data = array(); - $modseq = (array) $this->backend->modseq_get($this->device->id, $folderid, $modseq_lasttime); - } - } - else { - $filter_str .= ' ' . $filter_item; - } - } - - // get members of modified relations - $changed_msgs = $this->getChangesByRelations($folderid, $filter); - - $result = $result_type == self::RESULT_COUNT ? 0 : array(); - $found = 0; - $ts = time(); - - foreach ($folders as $folder_id) { - $foldername = $this->backend->folder_id2name($folder_id, $this->device->deviceid); - - if ($foldername === null) { - continue; - } - - $found++; - - $this->storage->set_folder($foldername); - - // Synchronize folder (if it wasn't synced in this request already) - if ($this->lastsync_folder != $folderid - || $this->lastsync_time <= $ts - Syncroton_Registry::getPingTimeout() - ) { - $this->storage->folder_sync($foldername); - } - - // We're in "get changes" mode - if (isset($modseq_data)) { - $folder_data = $this->storage->folder_data($foldername); - $modified = false; - - // If previous HIGHESTMODSEQ doesn't exist we can't get changes - // We can only get folder's HIGHESTMODSEQ value and store it for the next try - // Skip search if HIGHESTMODSEQ didn't change - if (!empty($folder_data'HIGHESTMODSEQ')) { - $modseq_data$foldername = $folder_data'HIGHESTMODSEQ'; - $modseq_old = isset($modseq$foldername) ? $modseq$foldername : null; - if ($modseq_data$foldername != $modseq_old) { - $modseq_update = true; - if ($modseq && $modseq_old) { - $modified = true; - $filter_str .= " MODSEQ " . ($modseq_old + 1); - } - } - } - } - else { - $modified = true; - } - - // We could use messages cache by replacing search() with index() - // in some cases. This however is possible only if user has skip_deleted=true, - // in his Roundcube preferences, otherwise we'd make often cache re-initialization, - // because Roundcube message cache can work only with one skip_deleted - // setting at a time. We'd also need to make sure folder_sync() was called - // before (see above). - // - // if ($filter_str == 'ALL UNDELETED') - // $search = $this->storage->index($foldername, null, null, true, true); - // else - - if ($modified) { - $search = $this->storage->search_once($foldername, $filter_str); - - if (!($search instanceof rcube_result_index) || $search->is_error()) { - throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); - } - - switch ($result_type) { - case self::RESULT_COUNT: - $result += (int) $search->count(); - break; - - case self::RESULT_UID: - if ($uids = $search->get()) { - foreach ($uids as $idx => $uid) { - $uids$idx = $this->createMessageId($folder_id, $uid); - } - $result = array_merge($result, $uids); - } - break; - } - } - - // handle relation changes - if (!empty($changed_msgs)) { - $uids = $this->findRelationMembersInFolder($foldername, $changed_msgs, $filter); - - switch ($result_type) { - case self::RESULT_COUNT: - $result += (int) count($uids); - break; - - case self::RESULT_UID: - foreach ($uids as $idx => $uid) { - $uids$idx = $this->createMessageId($folder_id, $uid); - } - $result = array_unique(array_merge($result, $uids)); - break; - } - } - } - - if (!$found) { - throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); - } - - $this->lastsync_folder = $folderid; - $this->lastsync_time = $ts; - - if (!empty($modseq_update)) { - $this->backend->modseq_set($this->device->id, $folderid, - $this->syncTimeStamp, $modseq_data); - - // if previous modseq information does not exist save current set as it, - // we would at least be able to detect changes since now - if (empty($result) && empty($modseq)) { - $this->backend->modseq_set($this->device->id, $folderid, - $modseq_lasttime, $modseq_data); - } - } - - return $result; - } - - /** - * Find members (messages) in specified folder - */ - protected function findRelationMembersInFolder($foldername, $members, $filter) - { - foreach ($members as $member) { - // IMAP URI members - if ($url = kolab_storage_config::parse_member_url($member)) { - $result$url'folder'$url'uid' = $url'params'; - } - } - - // convert filter into one IMAP search string - $filter_str = 'ALL UNDELETED'; - foreach ($filter as $filter_item) { - if (is_string($filter_item)) { - $filter_str .= ' ' . $filter_item; - } - } - - $rcube = rcube::get_instance(); - $storage = $rcube->get_storage(); - $found = array(); - - // first find messages by UID - if (!empty($result$foldername)) { - $index = $storage->search_once($foldername, 'UID ' - . rcube_imap_generic::compressMessageSet(array_keys($result$foldername))); - $found = $index->get(); - - // remove found messages from the $result - if (!empty($found)) { - $result$foldername = array_diff_key($result$foldername, array_flip($found)); - - if (empty($result$foldername)) { - unset($result$foldername); - } - - // now apply the current filter to the found messages - $index = $storage->search_once($foldername, $filter_str . ' UID ' - . rcube_imap_generic::compressMessageSet($found)); - $found = $index->get(); - } + $params = 'flags' => 'ANSWERED'; + $this->backend->updateItem($msg'folderId', $this->device->deviceid, $this->modelName, $msg'uid', null, $params); } - - // search by message parameters - if (!empty($result)) { - // @TODO: do this search in chunks (for e.g. 25 messages)? - $search = ''; - $search_count = 0; - - foreach ($result as $data) { - foreach ($data as $p) { - $search_params = array(); - $search_count++; - - foreach ($p as $key => $val) { - $key = strtoupper($key); - // don't search by subject, we don't want false-positives - if ($key != 'SUBJECT') { - $search_params = 'HEADER ' . $key . ' ' . rcube_imap_generic::escape($val); - } - } - - $search .= ' (' . implode(' ', $search_params) . ')'; - } - } - - $search_str = str_repeat(' OR', $search_count-1) . $search; - - // search messages in current folder - $search = $storage->search_once($foldername, $search_str); - $uids = $search->get(); - - if (!empty($uids)) { - // add UIDs into the result - $found = array_unique(array_merge($found, $uids)); - } - } - - return $found; } /** @@ -1191,7 +957,7 @@ */ public function search(Syncroton_Model_StoreRequest $store) { - list($folders, $search_str) = $this->parse_search_query($store); + $folders, $search_str = $this->parse_search_query($store); if (empty($search_str)) { throw new Exception('Empty/invalid search request'); @@ -1201,7 +967,7 @@ throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); } - $result = array(); + $result = ; // @TODO: caching with Options->RebuildResults support @@ -1212,8 +978,8 @@ continue; } -// $this->storage->set_folder($foldername); -// $this->storage->folder_sync($foldername); + // $this->storage->set_folder($foldername); + // $this->storage->folder_sync($foldername); $search = $this->storage->search_once($foldername, $search_str); @@ -1221,13 +987,14 @@ continue; } + $search->revert(); $uids = $search->get(); foreach ($uids as $idx => $uid) { - $uids$idx = new Syncroton_Model_StoreResponseResult(array( - 'longId' => $this->createMessageId($folderid, $uid), + $uids$idx = new Syncroton_Model_StoreResponseResult( + 'longId' => $this->serverId($uid, $folderid), 'collectionId' => $folderid, 'class' => 'Email', - )); + ); } $result = array_merge($result, $uids); @@ -1252,13 +1019,13 @@ $start = $total; } if ($limit > $total) { - $limit = max($start+1, $total); + $limit = max($start + 1, $total); } if ($start > 0 || $limit < $total) { - $result = array_slice($result, $start, $limit-$start); + $result = array_slice($result, $start, $limit - $start); } - $response->range = array($start, $start + count($result) - 1); + $response->range = $start, $start + count($result) - 1; } // Build result array, convert to ActiveSync format @@ -1279,14 +1046,10 @@ $options = $store->options; $query = $store->query; $search_str = ''; - $folders = array(); + $folders = ; if (empty($query) || !is_array($query)) { - return array(); - } - - if (isset($query'and''freeText') && strlen($query'and''freeText')) { - $search = $query'and''freeText'; + return ; } if (!empty($query'and''collections')) { @@ -1309,18 +1072,19 @@ $search_str .= ' BEFORE ' . $query'and''lessThan''value'->format('d-M-Y'); } - if ($search !== null) { - // @FIXME: should we use TEXT/BODY search? - // ActiveSync protocol specification says "indexed fields" - $search_keys = array('SUBJECT', 'TO', 'FROM', 'CC'); - $search_str .= str_repeat(' OR', count($search_keys)-1); + if (isset($query'and''freeText') && strlen($query'and''freeText')) { + // @FIXME: Should we use TEXT/BODY search? ActiveSync protocol specification says "indexed fields" + $search = $query'and''freeText'; + $search_keys = 'SUBJECT', 'TO', 'FROM', 'CC'; + $search_str .= str_repeat(' OR', count($search_keys) - 1); + foreach ($search_keys as $key) { $search_str .= sprintf(" %s {%d}\r\n%s", $key, strlen($search), $search); } } - if (empty($search_str)) { - return array(); + if (!strlen($search_str)) { + return ; } $search_str = 'ALL UNDELETED ' . trim($search_str); @@ -1336,13 +1100,13 @@ $folders = array_keys($folders); } - return array($folders, $search_str); + return $folders, $search_str; } /** * Fetches the entry from the backend */ - protected function getObject($entryid, $dummy = null, &$folder = null) + protected function getObject($entryid, $dummy = null) { $message = $this->parseMessageId($entryid); @@ -1351,18 +1115,19 @@ return null; } - // get message - $message = new rcube_message($message'uid', $message'foldername'); - - return $message && !empty($message->headers) ? $message : null; + return $this->backend->getItem($message'folderId', $this->device->deviceid, $this->modelName, $message'uid'); } /** + * Get attachment data from the server. + * + * @param string $fileReference + * * @return Syncroton_Model_FileReference */ public function getFileReference($fileReference) { - list($folderid, $uid, $part_id) = explode('::', $fileReference); + $folderid, $uid, $part_id = explode('::', $fileReference); $message = $this->getObject($fileReference); @@ -1373,10 +1138,10 @@ $part = $message->mime_parts$part_id; $body = $message->get_part_body($part_id); - return new Syncroton_Model_FileReference(array( + return new Syncroton_Model_FileReference( 'contentType' => $part->mimetype, 'data' => $body, - )); + ); } /** @@ -1394,31 +1159,27 @@ } // Note: the id might be in a form of <folder>::<uid>::<part_id> - list($folderid, $uid) = explode('::', $entryid); - - $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid); - - if ($foldername === null || $foldername === false) { - return; - } + $folderid, $uid = explode('::', $entryid); - return array( - 'uid' => $uid, - 'folderid' => $folderid, - 'foldername' => $foldername, - ); + return + 'uid' => $uid, + 'folderId' => $folderid, + ; } /** * Creates entry ID of the message */ - public function createMessageId($folderid, $uid) + protected function serverId($uid, $folderid) { return $folderid . '::' . $uid; } /** * Returns body of the message in specified format + * + * @param rcube_message $message + * @param bool $html */ protected function getMessageBody($message, $html = false) { @@ -1442,10 +1203,14 @@ /** * Returns body of the message part in specified format + * + * @param rcube_message $message + * @param rcube_message_part $part + * @param bool $html */ protected function getMessagePartBody($message, $part, $html = false) { - if (empty($part->size) || !isset($part->mime_id)) { + if (empty($part->size) || empty($part->mime_id)) { // TODO: Throw an exception? return ''; } @@ -1468,34 +1233,30 @@ if ($ctype_secondary == 'html') { // charset was converted to UTF-8 in rcube_storage::get_message_part(), // change/add charset specification in HTML accordingly - $meta = '<meta http-equiv="Content-Type" content="text/html; charset='.RCUBE_CHARSET.'" />'; + $meta = '<meta http-equiv="Content-Type" content="text/html; charset=' . RCUBE_CHARSET . '" />'; // remove old meta tag and add the new one, making sure // that it is placed in the head $body = preg_replace('/<meta^>+charset=a-z0-9-_+^>*>/Ui', '', $body); - $body = preg_replace('/(<head^>*>)/Ui', '\\1'.$meta, $body, -1, $rcount); + $body = preg_replace('/(<head^>*>)/Ui', '\\1' . $meta, $body, -1, $rcount); if (!$rcount) { $body = '<head>' . $meta . '</head>' . $body; } - } - else if ($ctype_secondary == 'enriched') { + } elseif ($ctype_secondary == 'enriched') { $body = rcube_enriched::to_html($body); - } - else { + } else { // Roundcube >= 1.2 if (class_exists('rcube_text2html')) { $flowed = isset($part->ctype_parameters'format') && $part->ctype_parameters'format' == 'flowed'; $delsp = isset($part->ctype_parameters'delsp') && $part->ctype_parameters'delsp' == 'yes'; - $options = array('flowed' => $flowed, 'wrap' => false, 'delsp' => $delsp); + $options = 'flowed' => $flowed, 'wrap' => false, 'delsp' => $delsp; $text2html = new rcube_text2html($body, false, $options); $body = '<html><body>' . $text2html->get_html() . '</body></html>'; - } - else { + } else { $body = '<html><body><pre>' . $body . '</pre></body></html>'; } } - } - else { + } else { if ($ctype_secondary == 'enriched') { $body = rcube_enriched::to_html($body); $part->ctype_secondary = 'html'; @@ -1504,8 +1265,7 @@ if ($ctype_secondary == 'html') { $txt = new rcube_html2text($body, false, true); $body = $txt->get_text(); - } - else { + } else { if ($ctype_secondary == 'plain' && !empty($part->ctype_parameters'format') && $part->ctype_parameters'format' == 'flowed' @@ -1538,112 +1298,12 @@ return mb_strcut(trim($body), 0, $size); } - /** - * Returns list of tag names assigned to an email message - */ - protected function getKolabTags($message, $dummy = null) - { - // support only messages with message-id - if (!($msg_id = $message->headers->get('message-id', false))) { - return null; - } - - $config = kolab_storage_config::get_instance(); - $delta = Syncroton_Registry::getPingTimeout(); - $folder = $message->folder; - $uid = $message->uid; - - // get tag objects raleted to specified message-id - $tags = $config->get_tags($msg_id); - - foreach ($tags as $idx => $tag) { - // resolve members if it wasn't done recently - $force = empty($this->tag_rts$tag'uid') || $this->tag_rts$tag'uid' <= time() - $delta; - $members = $config->resolve_members($tag, $force); - - if (empty($members$folder) || !in_array($uid, $members$folder)) { - unset($tags$idx); - } - - if ($force) { - $this->tag_rts$tag'uid' = time(); - } - } - - $tags = array_filter(array_map(function($v) { return $v'name'; }, $tags)); - - // make sure current folder is set correctly again - $this->storage->set_folder($folder); - - return !empty($tags) ? $tags : null; - } - - /** - * Set tags to an email message - */ - protected function setKolabTags($message, $tags) - { - $config = kolab_storage_config::get_instance(); - $delta = Syncroton_Registry::getPingTimeout(); - $folder = $message->folder; - $uri = kolab_storage_config::get_message_uri($message->headers, $folder); - - // for all tag objects... - foreach ($config->get_tags() as $relation) { - // resolve members if it wasn't done recently - $uid = $relation'uid'; - $force = empty($this->tag_rts$uid) || $this->tag_rts$uid <= time() - $delta; - - if ($force) { - $config->resolve_members($relation, $force); - $this->tag_rts$relation'uid' = time(); - } - - $selected = !empty($tags) && in_array($relation'name', $tags); - $found = !empty($relation'members') && in_array($uri, $relation'members'); - $update = false; - - // remove member from the relation - if ($found && !$selected) { - $relation'members' = array_diff($relation'members', (array) $uri); - $update = true; - } - // add member to the relation - else if (!$found && $selected) { - $relation'members' = $uri; - $update = true; - } - - if ($update) { - $config->save($relation, 'relation'); - } - - $tags = array_diff($tags, (array) $relation'name'); - } - - // create new relations - if (!empty($tags)) { - foreach ($tags as $tag) { - $relation = array( - 'name' => $tag, - 'members' => (array) $uri, - 'category' => 'tag', - ); - - $config->save($relation, 'relation'); - } - } - - // make sure current folder is set correctly again - $this->storage->set_folder($folder); - } - public static function charset_to_cp($charset) { // @TODO: ????? // The body is converted to utf-8 in get_part_body(), what about headers? return 65001; // UTF-8 - + /* $aliases = array( 'asmo708' => 708, 'shiftjis' => 932, @@ -1746,6 +1406,7 @@ if (preg_match('/^(ibm|dos|cp|windows|win)0-9+/', $charset, $m)) { return substr($charset, strlen($m1) + 1); } + */ } /** @@ -1771,20 +1432,17 @@ // don't wrap already quoted lines if (isset($line0) && $line0 == '>') { $line = '>' . rtrim($line); - } - else if (mb_strlen($line) > $max) { + } elseif (mb_strlen($line) > $max) { $newline = ''; foreach (explode("\n", rcube_mime::wordwrap($line, $length - 2)) as $l) { if (strlen($l)) { $newline .= '> ' . $l . "\n"; - } - else { + } else { $newline .= ">\n"; } } $line = rtrim($newline); - } - else { + } else { $line = '> ' . $line; } @@ -1802,9 +1460,14 @@ { // Parse the message and find iTip attachments $libcal = libcalendaring::get_instance(); - $libcal->mail_message_load(array('object' => $message)); + $libcal->mail_message_load('object' => $message); $ical_objects = $libcal->get_mail_ical_objects(); + // Skip methods we do not support here + if (!in_array($ical_objects->method, 'REQUEST', 'CANCEL', 'REPLY')) { + return null; + } + // We support only one event in the iTip foreach ($ical_objects as $mime_id => $event) { if ($event'_type' == 'event') { @@ -1833,9 +1496,11 @@ private function mem_check($need) { - $mem_limit = parse_bytes(ini_get('memory_limit')); + $mem_limit = (int) parse_bytes(ini_get('memory_limit')); $memory = static::$memory_accumulated; - return $mem_limit > 0 && $memory + $need > $mem_limit ? false : true; + + // @phpstan-ignore-next-line + return ($mem_limit > 0 && $memory + $need > $mem_limit) ? false : true; } /** @@ -1856,7 +1521,7 @@ // If we already rised the memory we throw an exception, so the message // will be synchronized in the next run (then we might have enough memory) if ($memory_rised) { - throw new Syncroton_Exception_MemoryExhausted; + throw new Syncroton_Exception_MemoryExhausted(); } $memory_rised = true; @@ -1870,6 +1535,7 @@ if (ini_set('memory_limit', $memory_needed) !== false) { // Memory has been rised, check again + // @phpstan-ignore-next-line if ($this->mem_check($size * 5)) { return; }
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_data_gal.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -28,26 +28,26 @@ */ class kolab_sync_data_gal extends kolab_sync_data implements Syncroton_Data_IDataSearch { - const MAX_SEARCH_RESULT = 100; + public const MAX_SEARCH_RESULT = 100; /** * LDAP search result * * @var array */ - protected $result = array(); + protected $result = ; /** * LDAP address books list * * @var array */ - public static $address_books = array(); + public static $address_books = ; /** * Mapping from ActiveSync Contacts namespace fields */ - protected $mapping = array( + protected $mapping = 'alias' => 'nickname', 'company' => 'organization', 'displayName' => 'name', @@ -59,7 +59,7 @@ 'picture' => 'photo', 'phone' => 'phone', 'title' => 'jobtitle', - ); + ; /** * Kolab object type @@ -103,8 +103,9 @@ // Use configured fields mapping $rcube = rcube::get_instance(); $fieldmap = (array) $rcube->config->get('activesync_gal_fieldmap'); + if (!empty($fieldmap)) { - $fieldmap = array_intersec_key($fieldmap, array_keys($this->mapping)); + $fieldmap = array_intersect_key($fieldmap, array_keys($this->mapping)); $this->mapping = array_merge($this->mapping, $fieldmap); } } @@ -112,15 +113,19 @@ /** * Not used but required by parent class */ - public function toKolab(Syncroton_Model_IEntry $data, $folderId, $entry = null) + public function toKolab($data, $folderId, $entry = null) { + return ; } /** * Not used but required by parent class + * + * @return array|Syncroton_Model_Contact Contact object */ public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId) { + throw new Syncroton_Exception_NotFound("getEntry() on GAL is not implemented"); } /** @@ -133,7 +138,7 @@ */ public function getSearchEntry($data, $options) { - $result = array(); + $result = ; // Contacts namespace fields foreach ($this->mapping as $key => $name) { @@ -144,24 +149,24 @@ } switch ($name) { - case 'photo': - // @TODO: MaxPictures option - // ActiveSync limits photo size of GAL contact to 100KB - $maxsize = 102400; - if (!empty($options'picture''maxSize')) { - $maxsize = min($maxsize, $options'picture''maxSize'); - } - - if (strlen($value) > $maxsize) { - continue 2; - } - - $value = new Syncroton_Model_GALPicture(array( - 'data' => $value, // binary - 'status' => Syncroton_Model_GALPicture::STATUS_SUCCESS, - )); - - break; + case 'photo': + // @TODO: MaxPictures option + // ActiveSync limits photo size of GAL contact to 100KB + $maxsize = 102400; + if (!empty($options'picture''maxSize')) { + $maxsize = min($maxsize, $options'picture''maxSize'); + } + + if (strlen($value) > $maxsize) { + continue 2; + } + + $value = new Syncroton_Model_GALPicture( + 'data' => $value, // binary + 'status' => Syncroton_Model_GALPicture::STATUS_SUCCESS, + ); + + break; } $result$key = $value; @@ -188,7 +193,7 @@ throw new Exception('Empty/invalid search request'); } - $records = array(); + $records = ; $rcube = rcube::get_instance(); // @TODO: caching with Options->RebuildResults support @@ -220,7 +225,7 @@ // get records $result = $book->list_records(); - while ($row = $result->next()) { + foreach ($result as $row) { $row'sourceid' = $idx; // make sure 'email' item is there, convert all email:* into one @@ -255,22 +260,22 @@ $start = $total; } if ($limit > $total) { - $limit = max($start+1, $total); + $limit = max($start + 1, $total); } if ($start > 0 || $limit < $total) { - $records = array_slice($records, $start, $limit-$start); + $records = array_slice($records, $start, $limit - $start); } - $response->range = array($start, $start + count($records) - 1); + $response->range = $start, $start + count($records) - 1; } // Build result array, convert to ActiveSync format foreach ($records as $idx => $rec) { - $response->result = new Syncroton_Model_StoreResponseResult(array( + $response->result = new Syncroton_Model_StoreResponseResult( 'longId' => $rec'ID', 'properties' => $this->getSearchEntry($rec, $options), - )); + ); unset($records$idx); } @@ -282,7 +287,7 @@ * * @param string $id Address book identifier * - * @return rcube_contacts Address book object + * @return rcube_addressbook|null Address book object */ public static function get_address_book($id) { @@ -292,26 +297,31 @@ // use existing instance if (isset(self::$address_books$id) && (self::$address_books$id instanceof rcube_addressbook)) { $book = self::$address_books$id; - } - else if ($id && $ldap_config$id) { - $book = new rcube_ldap($ldap_config$id, $config->get('ldap_debug'), - $config->mail_domain($_SESSION'storage_host')); + } elseif ($id && $ldap_config$id) { + $book = new rcube_ldap( + $ldap_config$id, + $config->get('ldap_debug'), + $config->mail_domain($_SESSION'storage_host') + ); } - if (!$book) { - rcube::raise_error(array( + if (empty($book)) { + rcube::raise_error( + 'code' => 700, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Addressbook source ($id) not found!"), - true, false); + 'message' => "Addressbook source ($id) not found!", + true, + false + ); return null; } -/* - // set configured sort order - if ($sort_col = $this->config->get('addressbook_sort_col')) - $book->set_sort_order($sort_col); -*/ + /* + // set configured sort order + if ($sort_col = $this->config->get('addressbook_sort_col')) + $book->set_sort_order($sort_col); + */ // add to the 'books' array for shutdown function self::$address_books$id = $book; @@ -322,7 +332,7 @@ /** * Return LDAP address books list * - * @return array Address books array + * @return array Address books array */ public static function get_address_sources() { @@ -334,15 +344,15 @@ $async_books = (array) $config->get('autocomplete_addressbooks'); } - $list = array(); + $list = ; foreach ((array)$async_books as $id) { $prop = $ldap_config$id; if (!empty($prop) && is_array($prop)) { - $list$id = array( + $list$id = 'id' => $id, 'name' => $prop'name', - ); + ; } } @@ -360,8 +370,7 @@ if (!empty($row'email')) { if (is_array($row'email')) { $key .= ':' . implode(':', $row'email'); - } - else { + } else { $key .= ':' . $row'email'; } } @@ -374,7 +383,7 @@ */ protected function getLDAPDataItem($data, $name) { - list($name, $index) = explode(':', $name); + $name, $index = explode(':', $name); $name = str_replace('.', ':', $name); if (isset($data$name)) {
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_data_notes.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -31,13 +31,13 @@ /** * Mapping from ActiveSync Calendar namespace fields */ - protected $mapping = array( + protected $mapping = 'body' => 'description', 'categories' => 'categories', 'lastModifiedDate' => 'changed', //'messageClass' => 'messageClass', 'subject' => 'title', - ); + ; /** * Kolab object type @@ -67,11 +67,6 @@ */ protected $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_NOTE_USER_CREATED; - /** - * Enable mapping Activesync categories into Kolab tags (relations) - */ - protected $tag_categories = true; - /** * Appends note data to xml element @@ -85,21 +80,20 @@ public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId, $as_array = false) { $note = is_array($serverId) ? $serverId : $this->getObject($collection->collectionId, $serverId); -// $config = $this->getFolderConfig($note'_mailbox'); - $result = array(); + $result = ; // Calendar namespace fields foreach ($this->mapping as $key => $name) { $value = $this->getKolabDataItem($note, $name); switch ($name) { - case 'changed': - $value = self::date_from_kolab($value); - break; + case 'changed': + $value = self::date_from_kolab($value); + break; - case 'description': - $value = $this->body_from_kolab($value, $collection); - break; + case 'description': + $value = $this->body_from_kolab($value, $collection); + break; } if (empty($value) || is_array($value)) { @@ -111,44 +105,39 @@ $result'messageClass' = 'IPM.StickyNote'; - // convert kolab tags into categories - $result'categories' = $this->getKolabTags($note'uid', $result'categories'); - return $as_array ? $result : new Syncroton_Model_Note($result); } /** * convert note from xml to libkolab array * - * @param Syncroton_Model_IEntry $data Note to convert - * @param string $folderid Folder identifier - * @param array $entry Existing entry + * @param Syncroton_Model_Note $data Note to convert + * @param string $folderid Folder identifier + * @param array $entry Existing entry * * @return array */ - public function toKolab(Syncroton_Model_IEntry $data, $folderid, $entry = null) + public function toKolab($data, $folderid, $entry = null) { - $note = !empty($entry) ? $entry : array(); - $foldername = isset($note'_mailbox') ? $note'_mailbox' : $this->getFolderName($folderid); -// $config = $this->getFolderConfig($foldername); + $note = !empty($entry) ? $entry : ; // Calendar namespace fields foreach ($this->mapping as $key => $name) { $value = $data->$key; switch ($name) { - case 'description': - $supported_body_types = array( - Syncroton_Model_EmailBody::TYPE_HTML, - Syncroton_Model_EmailBody::TYPE_PLAINTEXT, - ); - $value = $this->getBody($value, $supported_body_types); - - // If description isn't specified keep old description - if ($value === null) { - continue 2; - } - break; + case 'description': + $supported_body_types = + Syncroton_Model_EmailBody::TYPE_HTML, + Syncroton_Model_EmailBody::TYPE_PLAINTEXT, + ; + $value = $this->getBody($value, $supported_body_types); + + // If description isn't specified keep old description + if ($value === null) { + continue 2; + } + break; } $this->setKolabDataItem($note, $name, $value);
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_data_tasks.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -31,7 +31,7 @@ /** * Mapping from ActiveSync Calendar namespace fields */ - protected $mapping = array( + protected $mapping = 'body' => 'description', 'categories' => 'categories', //'complete' => 'complete', // handled separately @@ -46,26 +46,26 @@ 'subject' => 'title', 'utcDueDate' => 'due', 'utcStartDate' => 'start', - ); + ; /** * Sensitivity values */ - const SENSITIVITY_NORMAL = 0; - const SENSITIVITY_PERSONAL = 1; - const SENSITIVITY_PRIVATE = 2; - const SENSITIVITY_CONFIDENTIAL = 3; + public const SENSITIVITY_NORMAL = 0; + public const SENSITIVITY_PERSONAL = 1; + public const SENSITIVITY_PRIVATE = 2; + public const SENSITIVITY_CONFIDENTIAL = 3; /** * mapping of sensitivity * * @var array */ - protected $sensitivityMap = array( + protected $sensitivityMap = 'public' => self::SENSITIVITY_PERSONAL, 'private' => self::SENSITIVITY_PRIVATE, 'confidential' => self::SENSITIVITY_CONFIDENTIAL, - ); + ; /** * Kolab object type @@ -95,11 +95,6 @@ */ protected $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_TASK_USER_CREATED; - /** - * Enable mapping Activesync categories into Kolab tags (relations) - */ - protected $tag_categories = true; - /** * Appends contact data to xml element @@ -113,41 +108,40 @@ public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId, $as_array = false) { $task = is_array($serverId) ? $serverId : $this->getObject($collection->collectionId, $serverId); -// $config = $this->getFolderConfig($task'_mailbox'); - $result = array(); + $result = ; // Completion status (required) - $result'complete' = intval($task'status' ?? null == 'COMPLETED' || $task'complete' ?? null == 100); + $result'complete' = intval(($task'status' ?? null) == 'COMPLETED' || ($task'complete' ?? null) == 100); // Calendar namespace fields foreach ($this->mapping as $key => $name) { $value = $this->getKolabDataItem($task, $name); switch ($name) { - case 'due': - case 'start': - if (preg_match('/^UTC/i', $key)) { - $value = self::date_from_kolab($value); - } - break; - - case 'changed': - $value = $result'complete' ? self::date_from_kolab($value) : null; - break; - - case 'description': - $value = $this->body_from_kolab($value, $collection); - break; - - case 'sensitivity': - if (!empty($value)) { - $value = intval($this->sensitivityMap$value); - } - break; - - case 'priority': - $value = $this->prio_to_importance($value); - break; + case 'due': + case 'start': + if (preg_match('/^UTC/i', $key)) { + $value = self::date_from_kolab($value); + } + break; + + case 'changed': + $value = $result'complete' ? self::date_from_kolab($value) : null; + break; + + case 'description': + $value = $this->body_from_kolab($value, $collection); + break; + + case 'sensitivity': + if (!empty($value)) { + $value = intval($this->sensitivityMap$value); + } + break; + + case 'priority': + $value = $this->prio_to_importance($value); + break; } if (empty($value) || is_array($value)) { @@ -157,19 +151,12 @@ $result$key = $value; } - // convert kolab tags into categories - if (!empty($result'categories')) { - $result'categories' = $this->getKolabTags($task'uid', $result'categories'); - } - // Recurrence $this->recurrence_from_kolab($collection, $task, $result, 'Task'); return $as_array ? $result : new Syncroton_Model_Task($result); } - - /** * Apply a timezone matching the utc offset. */ @@ -188,17 +175,15 @@ /** * convert contact from xml to libkolab array * - * @param Syncroton_Model_IEntry $data Contact to convert - * @param string $folderid Folder identifier - * @param array $entry Existing entry + * @param Syncroton_Model_Task $data Contact to convert + * @param string $folderid Folder identifier + * @param array $entry Existing entry * * @return array */ - public function toKolab(Syncroton_Model_IEntry $data, $folderid, $entry = null) + public function toKolab($data, $folderid, $entry = null) { - $task = !empty($entry) ? $entry : array(); - $foldername = isset($task'_mailbox') ? $task'_mailbox' : $this->getFolderName($folderid); -// $config = $this->getFolderConfig($foldername); + $task = !empty($entry) ? $entry : ; $task'allday' = 0; @@ -208,38 +193,38 @@ switch ($name) { - case 'due': - case 'start': - // We expect to always get regular and utc variants, so we only need to take one into account. - if ($key == 'utcStartDate' || $key == 'utcDueDate') { - continue 2; - } - if ($value) { - if ($name =='due' && $data->utcDueDate) { - $value = static::applyTimezone($value, $data->utcDueDate); + case 'due': + case 'start': + // We expect to always get regular and utc variants, so we only need to take one into account. + if ($key == 'utcStartDate' || $key == 'utcDueDate') { + continue 2; + } + if ($value) { + if ($name == 'due' && $data->utcDueDate) { + $value = self::applyTimezone($value, $data->utcDueDate); + } + if ($name == 'start' && $data->utcStartDate) { + $value = self::applyTimezone($value, $data->utcStartDate); + } } - if ($name =='start' && $data->utcStartDate) { - $value = static::applyTimezone($value, $data->utcStartDate); + break; + + case 'sensitivity': + $map = array_flip($this->sensitivityMap); + $value = $map$value ?? 'none' ?? self::SENSITIVITY_NORMAL; + break; + + case 'description': + $value = $this->getBody($value, Syncroton_Model_EmailBody::TYPE_PLAINTEXT); + // If description isn't specified keep old description + if ($value === null) { + continue 2; } - } - break; - - case 'sensitivity': - $map = array_flip($this->sensitivityMap); - $value = $map$value ?? 'none' ?? self::SENSITIVITY_NORMAL; - break; - - case 'description': - $value = $this->getBody($value, Syncroton_Model_EmailBody::TYPE_PLAINTEXT); - // If description isn't specified keep old description - if ($value === null) { - continue 2; - } - break; - - case 'priority': - $value = $this->importance_to_prio($value); - break; + break; + + case 'priority': + $value = $this->importance_to_prio($value); + break; } $this->setKolabDataItem($task, $name, $value); @@ -248,8 +233,7 @@ if (!empty($data->complete)) { $task'status' = 'COMPLETED'; $task'complete' = 100; - } - else if (isset($data->complete)) { + } elseif (isset($data->complete)) { if ((!empty($task'status') && $task'status' == 'COMPLETED') || (!empty($task'complete') && $task'complete' == 100) ) { @@ -269,14 +253,14 @@ * * @param int $filter_type Filter type * - * @param array Filter query + * @return array Filter query */ protected function filter($filter_type = 0) { - $filter = array(array('type', '=', $this->modelName)); + $filter = 'type', '=', $this->modelName; if ($filter_type == Syncroton_Command_Sync::FILTER_INCOMPLETE) { - $filter = array('tags', '!~', 'x-complete'); + $filter = 'tags', '!~', 'x-complete'; } return $filter; @@ -298,18 +282,18 @@ } switch ($value) { - case 1: - case 2: - case 3: - case 4: - return 2; - case 5: - return 1; - case 6: - case 7: - case 8: - case 9: - return 0; + case 1: + case 2: + case 3: + case 4: + return 2; + case 5: + return 1; + case 6: + case 7: + case 8: + case 9: + return 0; } return; @@ -327,12 +311,12 @@ } switch ($value) { - case 0: - return 9; - case 1: - return 5; - case 2: - return 1; + case 0: + return 9; + case 1: + return 5; + case 2: + return 1; } return;
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_logger.php
Changed
@@ -1,4 +1,5 @@ <?php + /* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | @@ -29,6 +30,7 @@ { public $mode; + protected $log_driver; protected $logfile; protected $format; protected $log_dir; @@ -37,12 +39,13 @@ /** * Constructor */ - function __construct($mode = null) + public function __construct($mode = null) { $rcube = rcube::get_instance(); $this->mode = intval($mode); $this->logfile = $rcube->config->get('activesync_log_file'); + $this->log_driver = $rcube->config->get('log_driver'); $this->format = $rcube->config->get('log_date_format', 'd-M-Y H:i:s O'); $this->log_dir = $rcube->config->get('log_dir'); @@ -80,8 +83,7 @@ if (is_numeric($method)) { $mode = $method; $method = array_search($method, $this->_priorities); - } - else { + } else { $mode = $this->_priorities$method; } @@ -103,14 +105,14 @@ // otherwise use separate file for info/debug and warning/error if (!$logfile) { switch ($mode) { - case self::DEBUG: - case self::INFO: - case self::NOTICE: - $file = 'console'; - break; - default: - $file = 'errors'; - break; + case self::DEBUG: + case self::INFO: + case self::NOTICE: + $file = 'console'; + break; + default: + $file = 'errors'; + break; } $logfile = $log_dir . DIRECTORY_SEPARATOR . $file; @@ -118,8 +120,7 @@ if (version_compare(version_parse(RCUBE_VERSION), '1.4.0') >= 0) { $logfile .= $rcube->config->get('log_file_ext', '.log'); } - } - else if ($logfile0 != '/') { + } elseif ($logfile0 != '/') { $logfile = $log_dir . DIRECTORY_SEPARATOR . $logfile; } @@ -129,8 +130,8 @@ // add user/request information to the log if ($mode <= self::WARN) { - $device = array(); - $params = array('cmd' => 'Cmd', 'device' => 'DeviceId', 'type' => 'DeviceType'); + $device = ; + $params = 'cmd' => 'Cmd', 'device' => 'DeviceId', 'type' => 'DeviceType'; if (!empty($this->username)) { $device'user' = $this->username; @@ -150,6 +151,13 @@ $date = rcube_utils::date_format($this->format); $logline = sprintf("%s: %s %s\n", $date, $method, $message); + // write message with file name when configured to log to STDOUT + if ($this->log_driver == 'stdout') { + $stdout = "php://stdout"; + file_put_contents($stdout, $logline, FILE_APPEND); + return; + } + if ($fp = @fopen($logfile, 'a')) { fwrite($fp, $logline); fflush($fp);
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_message.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -25,17 +25,17 @@ class kolab_sync_message { - protected $headers = array(); + protected $headers = ; protected $body; protected $ctype; - protected $ctype_params = array(); + protected $ctype_params = ; /** * Constructor * * @param string|resource $source MIME message source */ - function __construct($source) + public function __construct($source) { $this->parse_mime($source); } @@ -52,7 +52,7 @@ public function source() { - $headers = array(); + $headers = ; // Build the message back foreach ($this->headers as $header => $header_value) { @@ -89,22 +89,22 @@ * Adds attachment to the message * * @param string $body Attachment body (not encoded) - * @param string $params Attachment parameters (Mail_mimePart format) + * @param array $params Attachment parameters (Mail_mimePart format) */ - public function add_attachment($body, $params = array()) + public function add_attachment($body, $params = ) { // convert the message into multipart/mixed if ($this->ctype != 'multipart/mixed') { $boundary = '_' . md5(rand() . microtime()); $this->body = "--$boundary\r\n" - ."Content-Type: " . $this->headers'Content-Type'."\r\n" - ."Content-Transfer-Encoding: " . $this->headers'Content-Transfer-Encoding'."\r\n" - ."\r\n" . trim($this->body) . "\r\n" - ."--$boundary\r\n"; + . "Content-Type: " . $this->headers'Content-Type' . "\r\n" + . "Content-Transfer-Encoding: " . $this->headers'Content-Transfer-Encoding' . "\r\n" + . "\r\n" . trim($this->body) . "\r\n" + . "--$boundary\r\n"; $this->ctype = 'multipart/mixed'; - $this->ctype_params = array('boundary' => $boundary); + $this->ctype_params = 'boundary' => $boundary; unset($this->headers'Content-Transfer-Encoding'); $this->save_content_type($this->ctype, $this->ctype_params); } @@ -128,8 +128,8 @@ // add the attachment to the end of the message $this->body .= "\r\n" - .implode("\r\n", $body'headers') . "\r\n\r\n" - .$body'body' . "\r\n--$boundary--\r\n"; + . implode("\r\n", $body'headers') . "\r\n\r\n" + . $body'body' . "\r\n--$boundary--\r\n"; } /** @@ -153,11 +153,11 @@ * @param array $smtp_error SMTP error array (reference) * @param array $smtp_opts SMTP options (e.g. DSN request) * - * @return boolean Send status. + * @return bool Send status. */ public function send(&$smtp_error = null, $smtp_opts = null) { - $rcube = rcube::get_instance(); + $rcube = kolab_sync::get_instance(); $headers = $this->headers; $mailto = $headers'To'; @@ -170,7 +170,7 @@ $headers'From' = $this->get_identity(); } // make sure there's sender name in From: - else if ($rcube->config->get('activesync_fix_from') + elseif ($rcube->config->get('activesync_fix_from') && preg_match('/^<?((\S+|("^"+"))@\S+)>?$/', trim($headers'From'), $m) ) { $identities = kolab_sync::get_instance()->user->list_identities(); @@ -196,14 +196,17 @@ $smtp_headers = $headers; // generate list of recipients - $recipients = array(); + $recipients = ; - if (!empty($headers'To')) + if (!empty($headers'To')) { $recipients = $headers'To'; - if (!empty($headers'Cc')) + } + if (!empty($headers'Cc')) { $recipients = $headers'Cc'; - if (!empty($headers'Bcc')) + } + if (!empty($headers'Bcc')) { $recipients = $headers'Bcc'; + } if (empty($headers'To') && empty($headers'Cc')) { $headers'To' = 'undisclosed-recipients:;'; @@ -223,13 +226,13 @@ // log error if (!$sent) { - rcube::raise_error(array('code' => 800, 'type' => 'smtp', + rcube::raise_error('code' => 800, 'type' => 'smtp', 'line' => __LINE__, 'file' => __FILE__, - 'message' => "SMTP error: ".join("\n", $smtp_response)), true, false); + 'message' => "SMTP error: " . implode("\n", $smtp_response), true, false); } if ($sent) { - $rcube->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $this->body)); + $rcube->plugins->exec_hook('message_sent', 'headers' => $headers, 'body' => $this->body); // remove MDN headers after sending unset($headers'Return-Receipt-To', $headers'Disposition-Notification-To'); @@ -238,12 +241,16 @@ // get all recipient addresses $mailto = rcube_mime::decode_address_list(implode(',', $recipients), null, false, null, true); - rcube::write_log('sendmail', sprintf("User %s %s; Message %s for %s; %s", - $rcube->get_user_name(), - rcube_utils::remote_addr(), - $headers'Message-ID', - implode(', ', $mailto), - !empty($smtp_response) ? implode('; ', $smtp_response) : '') + rcube::write_log( + 'sendmail', + sprintf( + "User %s %s; Message %s for %s; %s", + $rcube->get_user_name(), + rcube_utils::remote_addr(), + $headers'Message-ID', + implode(', ', $mailto), + !empty($smtp_response) ? implode('; ', $smtp_response) : '' + ) ); } } @@ -269,7 +276,7 @@ $message = stream_get_contents($message); } - list($headers, $message) = array_pad(preg_split('/\r?\n\r?\n/', $message, 2, PREG_SPLIT_NO_EMPTY), 2, ''); + $headers, $message = array_pad(preg_split('/\r?\n\r?\n/', $message, 2, PREG_SPLIT_NO_EMPTY), 2, ''); $hdrs = self::parse_headers($headers); @@ -278,11 +285,11 @@ $boundary = '--' . $matches1; $message = explode($boundary, $message); - for ($x=1, $parts = count($message) - 1; $x<$parts; $x++) { + for ($x = 1, $parts = count($message) - 1; $x < $parts; $x++) { $message$x = "\r\n" . self::recode_message(ltrim($message$x)); } - return $headers . "\r\n\r\n" . implode($boundary , $message); + return $headers . "\r\n\r\n" . implode($boundary, $message); } // single part @@ -331,10 +338,7 @@ /** * MIME message parser * - * @param string|resource $message MIME message source - * @param bool $decode_body Enables body decoding - * - * @return array Message headers array and message body + * @param string|resource $message MIME message source */ protected function parse_mime($message) { @@ -343,7 +347,7 @@ $message = stream_get_contents($message); } - list($headers, $message) = preg_split('/\r?\n\r?\n/', $message, 2, PREG_SPLIT_NO_EMPTY); + $headers, $message = preg_split('/\r?\n\r?\n/', $message, 2, PREG_SPLIT_NO_EMPTY); $headers = self::parse_headers($headers); @@ -374,22 +378,21 @@ $headers = explode("\n", trim($headers)); $ln = 0; - $lines = array(); + $lines = ; foreach ($headers as $line) { if (ord($line0) <= 32) { $lines$ln .= (empty($lines$ln) ? '' : "\r\n") . $line; - } - else { + } else { $lines++$ln = trim($line); } } // Unify char-case of header names - $headers = array(); + $headers = ; foreach ($lines as $line) { if (strpos($line, ':') !== false) { - list($field, $string) = explode(':', $line, 2); + $field, $string = explode(':', $line, 2); if ($field = self::normalize_header_name($field)) { $headers$field = trim($string); } @@ -404,7 +407,7 @@ */ protected static function normalize_header_name($name) { - $headers_map = array( + $headers_map = 'subject' => 'Subject', 'from' => 'From', 'to' => 'To', @@ -414,11 +417,11 @@ 'references' => 'References', 'content-type' => 'Content-Type', 'content-transfer-encoding' => 'Content-Transfer-Encoding', - ); + ; $name_lc = strtolower($name); - return isset($headers_map$name_lc) ? $headers_map$name_lc : $name; + return $headers_map$name_lc ?? $name; } /** @@ -429,16 +432,16 @@ * * @return string Encoded body */ - protected function encode($body, $encoding) + protected static function encode($body, $encoding) { switch ($encoding) { - case 'base64': - $body = base64_encode($body); - $body = chunk_split($body, 76, "\r\n"); - break; - case 'quoted-printable': - $body = quoted_printable_encode($body); - break; + case 'base64': + $body = base64_encode($body); + $body = chunk_split($body, 76, "\r\n"); + break; + case 'quoted-printable': + $body = quoted_printable_encode($body); + break; } return $body; @@ -457,12 +460,12 @@ $body = str_replace("\r\n", "\n", $body); switch ($encoding) { - case 'base64': - $body = base64_decode($body); - break; - case 'quoted-printable': - $body = quoted_printable_decode($body); - break; + case 'base64': + $body = base64_decode($body); + break; + case 'quoted-printable': + $body = quoted_printable_decode($body); + break; } return $body; @@ -480,7 +483,7 @@ } } - protected function save_content_type($ctype, $params = array()) + protected function save_content_type($ctype, $params = ) { $this->ctype = $ctype; $this->ctype_params = $params;
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_plugin_api.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -35,7 +35,7 @@ * * @return rcube_plugin_api The one and only instance if this class */ - static function get_instance() + public static function get_instance() { if (!self::$instance) { self::$instance = new kolab_sync_plugin_api(); @@ -44,22 +44,20 @@ return self::$instance; } - /** * Initialize plugin engine * * This has to be done after rcmail::load_gui() or rcmail::json_init() * was called because plugins need to have access to rcmail->output * - * @param object rcube Instance of the rcube base class - * @param string Current application task (used for conditional plugin loading) + * @param rcube $app Instance of the rcube base class + * @param string $task Current application task (used for conditional plugin loading) */ public function init($app, $task = '') { $this->task = $task; } - /** * Register a handler function for template objects * @@ -72,7 +70,6 @@ // empty } - /** * Register this plugin to be responsible for a specific task * @@ -84,7 +81,6 @@ $this->tasks$task = $owner; } - /** * Include a plugin script file in the current HTML page * @@ -95,7 +91,6 @@ //empty } - /** * Include a plugin stylesheet in the current HTML page *
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_storage.php
Added
@@ -0,0 +1,2064 @@ +<?php + +/* + +--------------------------------------------------------------------------+ + | Kolab Sync (ActiveSync for Kolab) | + | | + | Copyright (C) 2011-2012, Kolab Systems AG <contact@kolabsys.com> | + | | + | This program is free software: you can redistribute it and/or modify | + | it under the terms of the GNU Affero General Public License as published | + | by the Free Software Foundation, either version 3 of the License, or | + | (at your option) any later version. | + | | + | This program is distributed in the hope that it will be useful, | + | but WITHOUT ANY WARRANTY; without even the implied warranty of | + | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | + | GNU Affero General Public License for more details. | + | | + | You should have received a copy of the GNU Affero General Public License | + | along with this program. If not, see <http://www.gnu.org/licenses/> | + +--------------------------------------------------------------------------+ + | Author: Aleksander Machniak <machniak@kolabsys.com> | + +--------------------------------------------------------------------------+ +*/ + +/** + * Storage handling class with basic Kolab support (everything stored in IMAP) + */ +class kolab_sync_storage +{ + public const INIT_SUB_PERSONAL = 1; // all subscribed folders in personal namespace + public const INIT_ALL_PERSONAL = 2; // all folders in personal namespace + public const INIT_SUB_OTHER = 4; // all subscribed folders in other users namespace + public const INIT_ALL_OTHER = 8; // all folders in other users namespace + public const INIT_SUB_SHARED = 16; // all subscribed folders in shared namespace + public const INIT_ALL_SHARED = 32; // all folders in shared namespace + + public const MODEL_CALENDAR = 'event'; + public const MODEL_CONTACTS = 'contact'; + public const MODEL_EMAIL = 'mail'; + public const MODEL_NOTES = 'note'; + public const MODEL_TASKS = 'task'; + + public const ROOT_MAILBOX = 'INBOX'; + public const ASYNC_KEY = '/private/vendor/kolab/activesync'; + public const UID_KEY = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; + + public const CTYPE_KEY = '/shared/vendor/kolab/folder-type'; + public const CTYPE_KEY_PRIVATE = '/private/vendor/kolab/folder-type'; + + public $syncTimeStamp; + + protected $storage; + protected $folder_meta; + protected $folder_uids; + protected $folders = ; + protected $modseq = ; + protected $root_meta; + protected $relations = ; + protected $relationSupport = true; + protected $tag_rts = ; + + protected static $instance; + + protected static $types = + 1 => '', + 2 => 'mail.inbox', + 3 => 'mail.drafts', + 4 => 'mail.wastebasket', + 5 => 'mail.sentitems', + 6 => 'mail.outbox', + 7 => 'task.default', + 8 => 'event.default', + 9 => 'contact.default', + 10 => 'note.default', + 11 => 'journal.default', + 12 => 'mail', + 13 => 'event', + 14 => 'contact', + 15 => 'task', + 16 => 'journal', + 17 => 'note', + ; + + + /** + * This implements the 'singleton' design pattern + * + * @return kolab_sync_storage The one and only instance + */ + public static function get_instance() + { + if (!self::$instance) { + self::$instance = new kolab_sync_storage(); + self::$instance->startup(); // init AFTER object was linked with self::$instance + } + + return self::$instance; + } + + /** + * Class initialization + */ + public function startup() + { + $this->storage = kolab_sync::get_instance()->get_storage(); + + // set additional header used by libkolab + $this->storage->set_options( + // @TODO: there can be Roundcube plugins defining additional headers, + // we maybe would need to add them here + 'fetch_headers' => 'X-KOLAB-TYPE X-KOLAB-MIME-VERSION', + 'skip_deleted' => true, + 'threading' => false, + ); + + // Disable paging + $this->storage->set_pagesize(999999); + } + + /** + * Clear internal cache state + */ + public function reset() + { + $this->folders = ; + } + + /** + * List known devices + * + * @return array Device list as hash array + */ + public function devices_list() + { + if ($this->root_meta === null) { + // @TODO: consider server annotation instead of INBOX + if ($meta = $this->storage->get_metadata(self::ROOT_MAILBOX, self::ASYNC_KEY)) { + $this->root_meta = $this->unserialize_metadata($metaself::ROOT_MAILBOXself::ASYNC_KEY); + } else { + $this->root_meta = ; + } + } + + if (!empty($this->root_meta'DEVICE') && is_array($this->root_meta'DEVICE')) { + return $this->root_meta'DEVICE'; + } + + return ; + } + + /** + * Get list of folders available for sync + * + * @param string $deviceid Device identifier + * @param string $type Folder type + * @param bool $flat_mode Enables flat-list mode + * + * @return array|bool List of mailbox folders, False on backend failure + */ + public function folders_list($deviceid, $type, $flat_mode = false) + { + // get all folders of specified type + $folders = kolab_storage::list_folders('', '*', $type, false, $typedata); + + // get folders activesync config + $folderdata = $this->folder_meta(); + + if (!is_array($folders) || !is_array($folderdata)) { + return false; + } + + $folders_list = ; + + // check if folders are "subscribed" for activesync + foreach ($folderdata as $folder => $meta) { + if (empty($meta'FOLDER') || empty($meta'FOLDER'$deviceid) + || empty($meta'FOLDER'$deviceid'S') + ) { + continue; + } + + // force numeric folder name to be a string (T1283) + $folder = (string) $folder; + + if (!empty($type) && !in_array($folder, $folders)) { + continue; + } + + // Activesync folder identifier (serverId) + $folder_type = !empty($typedata$folder) ? $typedata$folder : 'mail'; + $folder_id = $this->folder_id($folder, $folder_type); + + $folders_list$folder_id = $this->folder_data($folder, $folder_type); + } + + if ($flat_mode) { + $folders_list = $this->folders_list_flat($folders_list, $type, $typedata); + } + + return $folders_list; + } + + /** + * Converts list of folders to a "flat" list + */ + protected function folders_list_flat($folders, $type, $typedata) + { + $delim = $this->storage->get_hierarchy_delimiter(); + + foreach ($folders as $idx => $folder) { + if ($folder'parentId') { + // for non-mail folders we make the list completely flat + if ($type != self::MODEL_EMAIL) { + $display_name = kolab_storage::object_name($folder'imap_name'); + $display_name = html_entity_decode($display_name, ENT_COMPAT, RCUBE_CHARSET); + + $folders$idx'parentId' = 0; + $folders$idx'displayName' = $display_name; + } + // for mail folders we modify only folders with non-existing parents + elseif (!isset($folders$folder'parentId')) { + $items = explode($delim, $folder'imap_name'); + $parent = 0; + + // find existing parent + while (count($items) > 0) { + array_pop($items); + + $parent_name = implode($delim, $items); + $parent_type = !empty($typedata$parent_name) ? $typedata$parent_name : 'mail'; + $parent_id = $this->folder_id($parent_name, $parent_type); + + if (isset($folders$parent_id)) { + $parent = $parent_id; + break; + } + } + + if (!$parent) { + $display_name = kolab_storage::object_name($folder'imap_name'); + $display_name = html_entity_decode($display_name, ENT_COMPAT, RCUBE_CHARSET); + } else { + $parent_name = isset($parent_id) ? $folders$parent_id'imap_name' : ''; + $display_name = substr($folder'imap_name', strlen($parent_name) + 1); + $display_name = rcube_charset::convert($display_name, 'UTF7-IMAP'); + $display_name = str_replace($delim, ' » ', $display_name); + } + + $folders$idx'parentId' = $parent; + $folders$idx'displayName' = $display_name; + } + } + } + + return $folders; + } + + /** + * Getter for folder metadata + * + * @return array|bool Hash array with meta data for each folder, False on backend failure + */ + protected function folder_meta() + { + if (!isset($this->folder_meta)) { + // get folders activesync config + $folderdata = $this->storage->get_metadata("*", self::ASYNC_KEY); + + if (!is_array($folderdata)) { + return $this->folder_meta = false; + } + + $this->folder_meta = ; + + foreach ($folderdata as $folder => $meta) { + if (isset($metaself::ASYNC_KEY)) { + if ($metadata = $this->unserialize_metadata($metaself::ASYNC_KEY)) { + $this->folder_meta$folder = $metadata; + } + } + } + } + + return $this->folder_meta; + } + + /** + * Creates folder and subscribes to the device + * + * @param string $name Folder name (UTF8) + * @param int $type Folder (ActiveSync) type + * @param string $deviceid Device identifier + * @param ?string $parentid Parent folder id identifier + * + * @return string|false New folder identifier on success, False on failure + */ + public function folder_create($name, $type, $deviceid, $parentid = null) + { + $parent = null; + $name = rcube_charset::convert($name, kolab_sync::CHARSET, 'UTF7-IMAP'); + + if ($parentid) { + $parent = $this->folder_id2name($parentid, $deviceid); + + if ($parent === null) { + throw new Syncroton_Exception_Status_FolderCreate(Syncroton_Exception_Status_FolderCreate::PARENT_NOT_FOUND); + } + } + + if ($parent !== null) { + $delim = $this->storage->get_hierarchy_delimiter(); + $name = $parent . $delim . $name; + } + + if ($this->storage->folder_exists($name)) { + throw new Syncroton_Exception_Status_FolderCreate(Syncroton_Exception_Status_FolderCreate::FOLDER_EXISTS); + } + + $type = self::type_activesync2kolab($type); + $created = kolab_storage::folder_create($name, $type, true); + + if ($created) { + // Set ActiveSync subscription flag + $this->folder_set($name, $deviceid, 1); + + return $this->folder_id($name, $type); + } + + // Special case when client tries to create a subfolder of INBOX + // which is not possible on Cyrus-IMAP (T2223) + if ($parent === 'INBOX' && stripos($this->last_error(), 'invalid') !== false) { + throw new Syncroton_Exception_Status_FolderCreate(Syncroton_Exception_Status_FolderCreate::SPECIAL_FOLDER); + } + + return false; + } + + /** + * Renames a folder + * + * @param string $folderid Folder identifier + * @param string $deviceid Device identifier + * @param string $new_name New folder name (UTF8) + * @param ?string $parentid Folder parent identifier + * + * @return bool True on success, False on failure + */ + public function folder_rename($folderid, $deviceid, $new_name, $parentid) + { + $old_name = $this->folder_id2name($folderid, $deviceid); + + if ($parentid) { + $parent = $this->folder_id2name($parentid, $deviceid); + } + + $name = rcube_charset::convert($new_name, kolab_sync::CHARSET, 'UTF7-IMAP'); + + if (isset($parent)) { + $delim = $this->storage->get_hierarchy_delimiter(); + $name = $parent . $delim . $name; + } + + // Rename/move IMAP folder + if ($name === $old_name) { + return true; + } + + $this->folder_meta = null; + + // TODO: folder type change? + + $type = kolab_storage::folder_type($old_name); + + // don't use kolab_storage for moving mail folders + if (preg_match('/^mail/', $type)) { + return $this->storage->rename_folder($old_name, $name); + } else { + return kolab_storage::folder_rename($old_name, $name); + } + } + + /** + * Deletes folder + * + * @param string $folderid Folder identifier + * @param string $deviceid Device identifier + * + * @return bool True on success, False otherwise + */ + public function folder_delete($folderid, $deviceid) + { + $name = $this->folder_id2name($folderid, $deviceid); + $type = kolab_storage::folder_type($name); + + unset($this->folder_meta$name); + + // don't use kolab_storage for deleting mail folders + if (preg_match('/^mail/', $type)) { + return $this->storage->delete_folder($name); + } + + return kolab_storage::folder_delete($name); + } + + /** + * Deletes contents of a folder + * + * @param string $folderid Folder identifier + * @param string $deviceid Device identifier + * @param bool $recursive Apply to the folder and its subfolders + * + * @return bool True on success, False otherwise + */ + public function folder_empty($folderid, $deviceid, $recursive = false) + { + $foldername = $this->folder_id2name($folderid, $deviceid); + + // Remove all entries + if (!$this->storage->clear_folder($foldername)) { + return false; + } + + // Remove subfolders + if ($recursive) { + $delim = $this->storage->get_hierarchy_delimiter(); + $folderdata = $this->folder_meta(); + + if (!is_array($folderdata)) { + return false; + } + + foreach ($folderdata as $subfolder => $meta) { + if (!empty($meta'FOLDER'$deviceid'S') && strpos((string) $subfolder, $foldername . $delim)) { + if (!$this->storage->clear_folder((string) $subfolder)) { + return false; + } + } + } + } + + return true; + } + + /** + * Sets ActiveSync subscription flag on a folder + * + * @param string $name Folder name (UTF7-IMAP) + * @param string $deviceid Device identifier + * @param int $flag Flag value (0|1|2) + * + * @return bool True on success, False on failure + */ + protected function folder_set($name, $deviceid, $flag) + { + if (empty($deviceid)) { + return false; + } + + // get folders activesync config + $metadata = $this->folder_meta(); + + if (!is_array($metadata)) { + return false; + } + + $metadata = $metadata$name ?? ; + + if ($flag) { + if (empty($metadata)) { + $metadata = ; + } + + if (empty($metadata'FOLDER')) { + $metadata'FOLDER' = ; + } + + if (empty($metadata'FOLDER'$deviceid)) { + $metadata'FOLDER'$deviceid = ; + } + + // Z-Push uses: + // 1 - synchronize, no alarms + // 2 - synchronize with alarms + $metadata'FOLDER'$deviceid'S' = $flag; + } else { + unset($metadata'FOLDER'$deviceid'S'); + + if (empty($metadata'FOLDER'$deviceid)) { + unset($metadata'FOLDER'$deviceid); + } + + if (empty($metadata'FOLDER')) { + unset($metadata'FOLDER'); + } + + if (empty($metadata)) { + $metadata = null; + } + } + + // Return if nothing's been changed + if (!self::data_array_diff($this->folder_meta$name ?? null, $metadata)) { + return true; + } + + $this->folder_meta$name = $metadata; + + return $this->storage->set_metadata($name, self::ASYNC_KEY => $this->serialize_metadata($metadata)); + } + + /** + * Returns device metadata + * + * @param string $id Device ID + * + * @return array|null Device metadata + */ + public function device_get($id) + { + $devices_list = $this->devices_list(); + return $devices_list$id ?? null; + } + + /** + * Registers new device on server + * + * @param array $device Device data + * @param string $id Device ID + * + * @return bool True on success, False on failure + */ + public function device_create($device, $id) + { + // Fill local cache + $this->devices_list(); + + // Some devices create dummy devices with name "validate" (#1109) + // This device entry is used in two initial requests, but later + // the device registers a real name. We can remove this dummy entry + // on new device creation + $this->device_delete('validate'); + + // Old Kolab_ZPush device parameters + // MODE: -1 | 0 | 1 (not set | flatmode | foldermode) + // TYPE: device type string + // ALIAS: user-friendly device name + + // Syncroton (kolab_sync_backend_device) uses + // ID: internal identifier in syncroton database + // TYPE: device type string + // ALIAS: user-friendly device name + + $metadata = $this->root_meta; + $metadata'DEVICE'$id = $device; + $metadata = self::ASYNC_KEY => $this->serialize_metadata($metadata); + + $result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata); + + if ($result) { + // Update local cache + $this->root_meta'DEVICE'$id = $device; + + // subscribe default set of folders + $this->device_init_subscriptions($id); + } + + return $result; + } + + /** + * Device update. + * + * @param array $device Device data + * @param string $id Device ID + * + * @return bool True on success, False on failure + */ + public function device_update($device, $id) + { + $devices_list = $this->devices_list(); + $old_device = $devices_list$id; + + if (!$old_device) { + return false; + } + + // Do nothing if nothing is changed + if (!self::data_array_diff($old_device, $device)) { + return true; + } + + $device = array_merge($old_device, $device); + + $metadata = $this->root_meta; + $metadata'DEVICE'$id = $device; + $metadata = self::ASYNC_KEY => $this->serialize_metadata($metadata); + + $result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata); + + if ($result) { + // Update local cache + $this->root_meta'DEVICE'$id = $device; + } + + return $result; + } + + /** + * Device delete. + * + * @param string $id Device ID + * + * @return bool True on success, False on failure + */ + public function device_delete($id) + { + $device = $this->device_get($id); + + if (!$device) { + return false; + } + + unset($this->root_meta'DEVICE'$id, $this->root_meta'FOLDER'$id); + + if (empty($this->root_meta'DEVICE')) { + unset($this->root_meta'DEVICE'); + } + if (empty($this->root_meta'FOLDER')) { + unset($this->root_meta'FOLDER'); + } + + $metadata = $this->serialize_metadata($this->root_meta); + $metadata = self::ASYNC_KEY => $metadata; + + // update meta data + $result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata); + + if ($result) { + // remove device annotation for every folder + foreach ($this->folder_meta() as $folder => $meta) { + // skip root folder (already handled above) + if ($folder == self::ROOT_MAILBOX) { + continue; + } + + if (!empty($meta'FOLDER') && isset($meta'FOLDER'$id)) { + unset($meta'FOLDER'$id); + + if (empty($meta'FOLDER')) { + unset($this->folder_meta$folder'FOLDER'); + unset($meta'FOLDER'); + } + if (empty($meta)) { + unset($this->folder_meta$folder); + $meta = null; + } + + $metadata = self::ASYNC_KEY => $this->serialize_metadata($meta); + $res = $this->storage->set_metadata($folder, $metadata); + + if ($res && $meta) { + $this->folder_meta$folder = $meta; + } + } + } + } + + return $result; + } + + /** + * Creates an item in a folder. + * + * @param string $folderid Folder identifier + * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) + * @param string|array $data Object data (string for email, array for other types) + * @param array $params Additional parameters (e.g. mail flags) + * + * @return string|null Item UID on success or null on failure + */ + public function createItem($folderid, $deviceid, $type, $data, $params = ) + { + if ($type == self::MODEL_EMAIL) { + $foldername = $this->folder_id2name($folderid, $deviceid); + + $uid = $this->storage->save_message($foldername, $data, '', false, $params'flags' ?? ); + + if (!$uid) { + // $this->logger->error("Error while storing the message " . $this->storage->get_error_str()); + } + + return $uid; + } + + $useTags = $this->relationSupport && ($type == self::MODEL_TASKS || $type == self::MODEL_NOTES); + + // convert categories into tags, save them after creating an object + if ($useTags && !empty($data'categories')) { + $tags = $data'categories'; + unset($data'categories'); + } + + $folder = $this->getFolder($folderid, $deviceid, $type); + + // Set User-Agent for saved objects + $app = kolab_sync::get_instance(); + $app->config->set('useragent', $app->app_name . ' ' . kolab_sync::VERSION); + + if ($folder && $folder->valid && $folder->save($data)) { + if (!empty($tags) && ($type == self::MODEL_TASKS || $type == self::MODEL_NOTES)) { + $this->setCategories($data'uid', $tags); + } + + return $data'uid'; + } + + return null; + } + + /** + * Deletes an item from a folder by UID. + * + * @param string $folderid Folder identifier + * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) + * @param string $uid Requested object UID + * @param bool $moveToTrash Move to trash, instead of delete (for mail messages only) + * + * @return bool True on success, False on failure + */ + public function deleteItem($folderid, $deviceid, $type, $uid, $moveToTrash = false) + { + if ($type == self::MODEL_EMAIL) { + $foldername = $this->folder_id2name($folderid, $deviceid); + $trash = kolab_sync::get_instance()->config->get('trash_mbox'); + + // move message to the Trash folder + if ($moveToTrash && strlen($trash) && $trash != $foldername && $this->storage->folder_exists($trash)) { + return $this->storage->move_message($uid, $trash, $foldername); + } + + // delete the message + // According to the ActiveSync spec. "If the DeletesAsMoves element is set to false, + // the deletion is PERMANENT.", therefore we delete the message, and not flag as deleted. + + // FIXME: We could consider acting according to the 'flag_for_deletion' setting. + // Don't forget about 'read_when_deleted' setting then. + // $this->storage->set_flag($uid, 'DELETED', $foldername); + // $this->storage->set_flag($uid, 'SEEN', $foldername); + return $this->storage->delete_message($uid, $foldername); + } + + $useTags = $this->relationSupport && ($type == self::MODEL_TASKS || $type == self::MODEL_NOTES); + + $folder = $this->getFolder($folderid, $deviceid, $type); + + if (!$folder || !$folder->valid) { + throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); + } + + if ($folder->delete($uid)) { + if ($useTags) { + $this->setCategories($uid, ); + } + + return true; + } + + return false; + } + + /** + * Updates an item in a folder. + * + * @param string $folderid Folder identifier + * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) + * @param string $uid Object UID + * @param string|array $data Object data (string for email, array for other types) + * @param array $params Additional parameters (e.g. mail flags) + * + * @return string|null Item UID on success or null on failure + */ + public function updateItem($folderid, $deviceid, $type, $uid, $data, $params = ) + { + if ($type == self::MODEL_EMAIL) { + $foldername = $this->folder_id2name($folderid, $deviceid); + + // Note: We do not support a message body update, as it's not needed + + foreach (($params'flags' ?? ) as $flag) { + $this->storage->set_flag($uid, $flag, $foldername); + } + + // Categories (Tags) change + if (isset($params'categories') && $this->relationSupport) { + $message = new rcube_message($uid, $foldername); + + if (empty($message->headers)) { + throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR); + } + + $this->setCategories($message, $params'categories'); + } + + return $uid; + } + + $folder = $this->getFolder($folderid, $deviceid, $type); + + $useTags = $this->relationSupport && ($type == self::MODEL_TASKS || $type == self::MODEL_NOTES); + + // convert categories into tags, save them after updating an object + if ($useTags && array_key_exists('categories', $data)) { + $tags = (array) $data'categories'; + unset($data'categories'); + } + + // Set User-Agent for saved objects + $app = kolab_sync::get_instance(); + $app->config->set('useragent', $app->app_name . ' ' . kolab_sync::VERSION); + + if ($folder && $folder->valid && $folder->save($data, $type, $uid)) { + if (isset($tags)) { + $this->setCategories($uid, $tags); + } + + return $uid; + } + + return null; + } + + /** + * Returns list of categories assigned to an object + * + * @param object|string $object UID or rcube_message object + * @param array $categories Addition tag names to merge with + * + * @return array List of categories + */ + public function getCategories($object, $categories = ) + { + if (is_object($object)) { + // support only messages with message-id + if (!($msg_id = $object->headers->get('message-id', false))) { + return ; + } + + $config = kolab_storage_config::get_instance(); + $delta = Syncroton_Registry::getPingTimeout(); + $folder = $object->folder; + $uid = $object->uid; + + // get tag objects raleted to specified message-id + $tags = $config->get_tags($msg_id); + + foreach ($tags as $idx => $tag) { + // resolve members if it wasn't done recently + $force = empty($this->tag_rts$tag'uid') || $this->tag_rts$tag'uid' <= time() - $delta; + $members = $config->resolve_members($tag, $force); + + if (empty($members$folder) || !in_array($uid, $members$folder)) { + unset($tags$idx); + } + + if ($force) { + $this->tag_rts$tag'uid' = time(); + } + } + + // make sure current folder is set correctly again + $this->storage->set_folder($folder); + } else { + $config = kolab_storage_config::get_instance(); + $tags = $config->get_tags($object); + } + + $tags = array_filter(array_map(function ($v) { return $v'name'; }, $tags)); + + // merge result with old categories + if (!empty($categories)) { + $tags = array_unique(array_merge($tags, (array) $categories)); + } + + return $tags; + } + + /** + * Gets kolab_storage_folder object from Activesync folder ID. + * + * @param string $folderid Folder identifier + * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) + * + * @return ?kolab_storage_folder + */ + public function getFolder($folderid, $deviceid, $type) + { + $unique_key = "$folderid:$deviceid:$type"; + + if (array_key_exists($unique_key, $this->folders)) { + return $this->folders$unique_key; + } + + $foldername = $this->folder_id2name($folderid, $deviceid); + + return $this->folders$unique_key = kolab_storage::get_folder($foldername, $type); + } + + /** + * Gets Activesync preferences for a folder. + * + * @param string $folderid Folder identifier + * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) + * + * @return array Folder preferences + */ + public function getFolderConfig($folderid, $deviceid, $type) + { + $foldername = $this->folder_id2name($folderid, $deviceid); + + $metadata = $this->folder_meta(); + $config = ; + + if (!empty($metadata$foldername'FOLDER'$deviceid)) { + $config = $metadata$foldername'FOLDER'$deviceid; + } + + return + 'ALARMS' => ($config'S' ?? 0) == 2, + ; + } + + /** + * Gets an item from a folder by UID. + * + * @param string $folderid Folder identifier + * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) + * @param string $uid Requested object UID + * + * @return array|rcube_message|null Object properties + */ + public function getItem($folderid, $deviceid, $type, $uid) + { + if ($type == self::MODEL_EMAIL) { + $foldername = $this->folder_id2name($folderid, $deviceid); + $message = new rcube_message($uid, $foldername); + + if (!empty($message->headers)) { + if ($this->relationSupport) { + $message->headers->others'categories' = $this->getCategories($message); + } + + return $message; + } + + return null; + } + + $folder = $this->getFolder($folderid, $deviceid, $type); + + if (!$folder || !$folder->valid) { + throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); + } + + $result = $folder->get_object($uid); + + if ($result === false) { + throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); + } + + $useTags = $this->relationSupport && ($type == self::MODEL_TASKS || $type == self::MODEL_NOTES); + + if ($useTags) { + $result'categories' = $this->getCategories($uid, $result'categories' ?? ); + } + + return $result; + } + + /** + * Gets items matching UID by prefix. + * + * @param string $folderid Folder identifier + * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) + * @param string $uid Requested object UID prefix + * + * @return array|iterable List of objects + */ + public function getItemsByUidPrefix($folderid, $deviceid, $type, $uid) + { + $folder = $this->getFolder($folderid, $deviceid, $type); + + if (!$folder || !$folder->valid) { + throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); + } + + $result = $folder->select('uid', '~*', $uid); + + if ($result === null) { + throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); + } + + return $result; + } + + /** + * Move an item from one folder to another. + * + * @param string $srcFolderId Source folder identifier + * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) + * @param string $uid Object UID + * @param string $dstFolderId Destination folder identifier + * + * @return string New object UID + * @throws Syncroton_Exception_Status + */ + public function moveItem($srcFolderId, $deviceid, $type, $uid, $dstFolderId) + { + if ($type === self::MODEL_EMAIL) { + $src_name = $this->folder_id2name($srcFolderId, $deviceid); + $dst_name = $this->folder_id2name($dstFolderId, $deviceid); + + if ($dst_name === null) { + throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_DESTINATION); + } + + if ($src_name === null) { + throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE); + } + + if (!$this->storage->move_message($uid, $dst_name, $src_name)) { + throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_DESTINATION); + } + + // Use COPYUID feature (RFC2359) to get the new UID of the copied message + if (empty($this->storage->conn->data'COPYUID')) { + throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); + } + + return $this->storage->conn->data'COPYUID'1; + } + + $srcFolder = $this->getFolder($srcFolderId, $deviceid, $type); + $dstFolder = $this->getFolder($dstFolderId, $deviceid, $type); + + if (!$srcFolder || !$dstFolder) { + throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_DESTINATION); + } + + if (!$srcFolder->move($uid, $dstFolder)) { + throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE); + } + + return $uid; + } + + /** + * Set categories to an object + * + * @param object|string $object UID or rcube_message object + * @param array $categories List of Category names + */ + public function setCategories($object, $categories) + { + if (!is_object($object)) { + $config = kolab_storage_config::get_instance(); + $config->save_tags($object, $categories); + return; + } + + $config = kolab_storage_config::get_instance(); + $delta = Syncroton_Registry::getPingTimeout(); + $uri = kolab_storage_config::get_message_uri($object->headers, $object->folder); + + // for all tag objects... + foreach ($config->get_tags() as $relation) { + // resolve members if it wasn't done recently + $uid = $relation'uid'; + $force = empty($this->tag_rts$uid) || $this->tag_rts$uid <= time() - $delta; + + if ($force) { + $config->resolve_members($relation, $force); + $this->tag_rts$relation'uid' = time(); + } + + $selected = !empty($categories) && in_array($relation'name', $categories); + $found = !empty($relation'members') && in_array($uri, $relation'members'); + $update = false; + + // remove member from the relation + if ($found && !$selected) { + $relation'members' = array_diff($relation'members', (array) $uri); + $update = true; + } + // add member to the relation + elseif (!$found && $selected) { + $relation'members' = $uri; + $update = true; + } + + if ($update) { + $config->save($relation, 'relation'); + } + + $categories = array_diff($categories, (array) $relation'name'); + } + + // create new relations + if (!empty($categories)) { + foreach ($categories as $tag) { + $relation = + 'name' => $tag, + 'members' => (array) $uri, + 'category' => 'tag', + ; + + $config->save($relation, 'relation'); + } + } + + // make sure current folder is set correctly again + $this->storage->set_folder($object->folder); + } + + /** + * Search for existing objects in a folder + * + * @param string $folderid Folder identifier + * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) + * @param array $filter Filter + * @param int $result_type Type of the result (see kolab_sync_data::RESULT_* constants) + * @param bool $force Force IMAP folder cache synchronization + * + * @return array|int Search result as count or array of uids + */ + public function searchEntries($folderid, $deviceid, $type, $filter, $result_type, $force) + { + if ($type != self::MODEL_EMAIL) { + return $this->searchKolabEntries($folderid, $deviceid, $type, $filter, $result_type, $force); + } + + $filter_str = 'ALL UNDELETED'; + + // convert filter into one IMAP search string + foreach ($filter as $idx => $filter_item) { + if (is_array($filter_item)) { + // This is a request for changes since last time + // we'll use HIGHESTMODSEQ value from the last Sync + if ($filter_item0 == 'changed' && $filter_item1 == '>') { + $modseq_lasttime = $filter_item2; + $modseq_data = ; + $modseq = (array) $this->modseq_get($deviceid, $folderid, $modseq_lasttime); + } + } else { + $filter_str .= ' ' . $filter_item; + } + } + + // get members of modified relations + if ($this->relationSupport) { + $changed_msgs = $this->getChangesByRelations($folderid, $deviceid, $type, $filter); + } + + $result = $result_type == kolab_sync_data::RESULT_COUNT ? 0 : ; + + $foldername = $this->folder_id2name($folderid, $deviceid); + + if ($foldername === null) { + return $result; + } + + $this->storage->set_folder($foldername); + + // Synchronize folder (if it wasn't synced in this request already) + if ($force) { + $this->storage->folder_sync($foldername); + } + + // We're in "get changes" mode + if (isset($modseq_data)) { + $folder_data = $this->storage->folder_data($foldername); + $modified = false; + + // If previous HIGHESTMODSEQ doesn't exist we can't get changes + // We can only get folder's HIGHESTMODSEQ value and store it for the next try + // Skip search if HIGHESTMODSEQ didn't change + if (!empty($folder_data'HIGHESTMODSEQ')) { + $modseq_data$foldername = $folder_data'HIGHESTMODSEQ'; + $modseq_old = $modseq$foldername ?? null; + if ($modseq_data$foldername != $modseq_old) { + $modseq_update = true; + if (!empty($modseq) && $modseq_old) { + $modified = true; + $filter_str .= " MODSEQ " . ($modseq_old + 1); + } + } + } + } else { + $modified = true; + } + + // We could use messages cache by replacing search() with index() + // in some cases. This however is possible only if user has skip_deleted=true, + // in his Roundcube preferences, otherwise we'd make often cache re-initialization, + // because Roundcube message cache can work only with one skip_deleted + // setting at a time. We'd also need to make sure folder_sync() was called + // before (see above). + // + // if ($filter_str == 'ALL UNDELETED') + // $search = $this->storage->index($foldername, null, null, true, true); + // else + + if ($modified) { + $search = $this->storage->search_once($foldername, $filter_str); + + if (!($search instanceof rcube_result_index) || $search->is_error()) { + throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); + } + + switch ($result_type) { + case kolab_sync_data::RESULT_COUNT: + $result = $search->count(); + break; + + case kolab_sync_data::RESULT_UID: + $result = $search->get(); + break; + } + } + + // handle relation changes + if (!empty($changed_msgs)) { + $members = $this->findRelationMembersInFolder($foldername, $changed_msgs, $filter); + + switch ($result_type) { + case kolab_sync_data::RESULT_COUNT: + $result += count($members); + break; + + case kolab_sync_data::RESULT_UID: + $result = array_values(array_unique(array_merge($result, $members))); + break; + } + } + + if (!empty($modseq_update) && !empty($modseq_data)) { + $this->modseq_set($deviceid, $folderid, $this->syncTimeStamp, $modseq_data); + + // if previous modseq information does not exist save current set as it, + // we would at least be able to detect changes since now + if (empty($result) && empty($modseq)) { + $this->modseq_set($deviceid, $folderid, $modseq_lasttime ?? 0, $modseq_data); + } + } + + return $result; + } + + /** + * Search for existing objects in a folder + * + * @param string $folderid Folder identifier + * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) + * @param array $filter Filter + * @param int $result_type Type of the result (see kolab_sync_data::RESULT_* constants) + * @param bool $force Force IMAP folder cache synchronization + * + * @return array|int Search result as count or array of uids + */ + protected function searchKolabEntries($folderid, $deviceid, $type, $filter, $result_type, $force) + { + // there's a PHP Warning from kolab_storage if $filter isn't an array + if (empty($filter)) { + $filter = ; + } elseif ($this->relationSupport && ($type == self::MODEL_TASKS || $type == self::MODEL_NOTES)) { + $changed_objects = $this->getChangesByRelations($folderid, $deviceid, $type, $filter); + } + + $folder = $this->getFolder($folderid, $deviceid, $type); + + if (!$folder || !$folder->valid) { + throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); + } + + $error = false; + + switch ($result_type) { + case kolab_sync_data::RESULT_COUNT: + $count = $folder->count($filter); + + if ($count === null) { + throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); + } + + $result = (int) $count; + break; + + case kolab_sync_data::RESULT_UID: + default: + $uids = $folder->get_uids($filter); + + if (!is_array($uids)) { + throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); + } + + $result = $uids; + break; + } + + // handle tag modifications + if (!empty($changed_objects)) { + // build new filter + // search objects mathing current filter, + // relations may contain members of many types, we need to + // search them by UID in all requested folders to get + // only these with requested type (and that really exist + // in specified folders) + $tag_filter = 'uid', '=', $changed_objects; + foreach ($filter as $f) { + if ($f0 != 'changed') { + $tag_filter = $f; + } + } + + switch ($result_type) { + case kolab_sync_data::RESULT_COUNT: + // Note: this way we're potentally counting the same objects twice + // I'm not sure if this is a problem, we most likely do not + // need a precise result here + $count = $folder->count($tag_filter); + if ($count !== null) { + $result += (int) $count; + } + + break; + + case kolab_sync_data::RESULT_UID: + default: + $uids = $folder->get_uids($tag_filter); + if (is_array($uids) && !empty($uids)) { + $result = array_unique(array_merge($result, $uids)); + } + + break; + } + } + + return $result; + } + + /** + * Find members (messages) in specified folder + */ + protected function findRelationMembersInFolder($foldername, $members, $filter) + { + foreach ($members as $member) { + // IMAP URI members + if ($url = kolab_storage_config::parse_member_url($member)) { + $result$url'folder'$url'uid' = $url'params'; + } + } + + // convert filter into one IMAP search string + $filter_str = 'ALL UNDELETED'; + foreach ($filter as $filter_item) { + if (is_string($filter_item)) { + $filter_str .= ' ' . $filter_item; + } + } + + $found = ; + + // first find messages by UID + if (!empty($result$foldername)) { + $index = $this->storage->search_once($foldername, 'UID ' + . rcube_imap_generic::compressMessageSet(array_keys($result$foldername))); + $found = $index->get(); + + // remove found messages from the $result + if (!empty($found)) { + $result$foldername = array_diff_key($result$foldername, array_flip($found)); + + if (empty($result$foldername)) { + unset($result$foldername); + } + + // now apply the current filter to the found messages + $index = $this->storage->search_once($foldername, $filter_str . ' UID ' + . rcube_imap_generic::compressMessageSet($found)); + $found = $index->get(); + } + } + + // search by message parameters + if (!empty($result)) { + // @TODO: do this search in chunks (for e.g. 25 messages)? + $search = ''; + $search_count = 0; + + foreach ($result as $data) { + foreach ($data as $p) { + $search_params = ; + $search_count++; + + foreach ($p as $key => $val) { + $key = strtoupper($key); + // don't search by subject, we don't want false-positives + if ($key != 'SUBJECT') { + $search_params = 'HEADER ' . $key . ' ' . rcube_imap_generic::escape($val); + } + } + + $search .= ' (' . implode(' ', $search_params) . ')'; + } + } + + $search_str = str_repeat(' OR', $search_count - 1) . $search; + + // search messages in current folder + $search = $this->storage->search_once($foldername, $search_str); + $uids = $search->get(); + + if (!empty($uids)) { + // add UIDs into the result + $found = array_unique(array_merge($found, $uids)); + } + } + + return $found; + } + + /** + * Detect changes of relation (tag) objects data and assigned objects + * Returns relation member identifiers + */ + protected function getChangesByRelations($folderid, $deviceid, $type, $filter) + { + // get period filter, create new objects filter + foreach ($filter as $f) { + if ($f0 == 'changed' && $f1 == '>') { + $since = $f2; + } + } + + // this is not search for changes, do nothing + if (empty($since)) { + return; + } + + // get relations state from the last sync + $last_state = (array) $this->relations_state_get($deviceid, $folderid, $since); + + // get current relations state + $config = kolab_storage_config::get_instance(); + $default = true; + $filter = + 'type', '=', 'relation', + 'category', '=', 'tag', + ; + + $relations = $config->get_objects($filter, $default, 100); + + $result = ; + $changed = false; + + // compare states, get members of changed relations + foreach ($relations as $relation) { + $rel_id = $relation'uid'; + + if ($relation'changed') { + $relation'changed'->setTimezone(new DateTimeZone('UTC')); + } + + // last state unknown... + if (empty($last_state$rel_id)) { + // ...get all members + if (!empty($relation'members')) { + $changed = true; + $result = array_merge($result, $relation'members'); + } + } + // last state known, changed tag name... + elseif ($last_state$rel_id'name' != $relation'name') { + // ...get all (old and new) members + $members_old = explode("\n", $last_state$rel_id'members'); + $changed = true; + $members = array_unique(array_merge($relation'members', $members_old)); + $result = array_merge($result, $members); + } + // last state known, any other change change... + elseif ($last_state$rel_id'changed' < $relation'changed'->format('U')) { + // ...find new and removed members + $members_old = explode("\n", $last_state$rel_id'members'); + $new = array_diff($relation'members', $members_old); + $removed = array_diff($members_old, $relation'members'); + + if (!empty($new) || !empty($removed)) { + $changed = true; + $result = array_merge($result, $new, $removed); + } + } + + unset($last_state$rel_id); + } + + // get members of deleted relations + if (!empty($last_state)) { + $changed = true; + foreach ($last_state as $relation) { + $members = explode("\n", $relation'members'); + $result = array_merge($result, $members); + } + } + + // save current state + if ($changed) { + $data = ; + foreach ($relations as $relation) { + $data$relation'uid' = + 'name' => $relation'name', + 'changed' => $relation'changed'->format('U'), + 'members' => implode("\n", (array)$relation'members'), + ; + } + + $now = new DateTime('now', new DateTimeZone('UTC')); + + $this->relations_state_set($deviceid, $folderid, $now, $data); + } + + // in mail mode return only message URIs + if ($type == self::MODEL_EMAIL) { + // lambda function to skip email members + $filter_func = function ($value) { + return strpos($value, 'imap://') === 0; + }; + + $result = array_filter(array_unique($result), $filter_func); + } + // otherwise return only object UIDs + else { + // lambda function to skip email members + $filter_func = function ($value) { + return strpos($value, 'urn:uuid:') === 0; + }; + + // lambda function to parse member URI + $member_func = function ($value) { + if (strpos($value, 'urn:uuid:') === 0) { + $value = substr($value, 9); + } + return $value; + }; + + $result = array_map($member_func, array_filter(array_unique($result), $filter_func)); + } + + return $result; + } + + /** + * Subscribe default set of folders on device registration + */ + protected function device_init_subscriptions($deviceid) + { + // INBOX always exists + $this->folder_set('INBOX', $deviceid, 1); + + $supported_types = + 'mail.drafts', + 'mail.wastebasket', + 'mail.sentitems', + 'mail.outbox', + 'event.default', + 'contact.default', + 'note.default', + 'task.default', + 'event', + 'contact', + 'note', + 'task', + 'event.confidential', + 'event.private', + 'task.confidential', + 'task.private', + ; + + $rcube = rcube::get_instance(); + $config = $rcube->config; + $mode = (int) $config->get('activesync_init_subscriptions'); + $folders = ; + + // Subscribe to default folders + $foldertypes = kolab_storage::folders_typedata(); + + if (!empty($foldertypes)) { + $_foldertypes = array_intersect($foldertypes, $supported_types); + + // get default folders + foreach ($_foldertypes as $folder => $type) { + // only personal folders + if ($this->storage->folder_namespace($folder) == 'personal') { + $flag = preg_match('/^(event|task)/', $type) ? 2 : 1; + $this->folder_set($folder, $deviceid, $flag); + $folders = $folder; + } + } + } + + // we're in default mode, exit + if (!$mode) { + return; + } + + // below we support additionally all mail folders + $supported_types = 'mail'; + $supported_types = 'mail.junkemail'; + + // get configured special folders + $special_folders = ; + $map = + 'drafts' => 'mail.drafts', + 'junk' => 'mail.junkemail', + 'sent' => 'mail.sentitems', + 'trash' => 'mail.wastebasket', + ; + + foreach ($map as $folder => $type) { + if ($folder = $config->get($folder . '_mbox')) { + $special_folders$folder = $type; + } + } + + // get folders list(s) + if (($mode & self::INIT_ALL_PERSONAL) || ($mode & self::INIT_ALL_OTHER) || ($mode & self::INIT_ALL_SHARED)) { + $all_folders = $this->storage->list_folders(); + if (($mode & self::INIT_SUB_PERSONAL) || ($mode & self::INIT_SUB_OTHER) || ($mode & self::INIT_SUB_SHARED)) { + $subscribed_folders = $this->storage->list_folders_subscribed(); + } + } else { + $all_folders = $this->storage->list_folders_subscribed(); + } + + foreach ($all_folders as $folder) { + // folder already subscribed + if (in_array($folder, $folders)) { + continue; + } + + $type = ($foldertypes$folder ?? null) ?: 'mail'; + if ($type == 'mail' && isset($special_folders$folder)) { + $type = $special_folders$folder; + } + + if (!in_array($type, $supported_types)) { + continue; + } + + $ns = strtoupper($this->storage->folder_namespace($folder)); + + // subscribe the folder according to configured mode + // and folder namespace/subscription status + if (($mode & constant("self::INIT_ALL_{$ns}")) + || (($mode & constant("self::INIT_SUB_{$ns}")) + && (!isset($subscribed_folders) || in_array($folder, $subscribed_folders))) + ) { + $flag = preg_match('/^(event|task)/', $type) ? 2 : 1; + $this->folder_set($folder, $deviceid, $flag); + } + } + } + + /** + * Helper method to decode saved IMAP metadata + */ + protected function unserialize_metadata($str) + { + if (!empty($str)) { + $data = json_decode($str, true); + return $data; + } + + return null; + } + + /** + * Helper method to encode IMAP metadata for saving + */ + protected function serialize_metadata($data) + { + if (!empty($data) && is_array($data)) { + $data = json_encode($data); + return $data; + } + + return null; + } + + /** + * Returns Kolab folder type for specified ActiveSync type ID + */ + protected static function type_activesync2kolab($type) + { + if (!empty(self::$types$type)) { + return self::$types$type; + } + + return ''; + } + + /** + * Returns ActiveSync folder type for specified Kolab type + */ + protected static function type_kolab2activesync($type) + { + $type = preg_replace('/\.(confidential|private)$/i', '', $type); + + if ($key = array_search($type, self::$types)) { + return $key; + } + + return key(self::$types); + } + + /** + * Returns folder data in Syncroton format + */ + protected function folder_data($folder, $type) + { + // Folder name parameters + $delim = $this->storage->get_hierarchy_delimiter(); + $items = explode($delim, $folder); + $name = array_pop($items); + + // Folder UID + $folder_id = $this->folder_id($folder, $type); + + // Folder type + if (strcasecmp($folder, 'INBOX') === 0) { + // INBOX is always inbox, prevent from issues related with a change of + // folder type annotation (it can be initially unset). + $as_type = 2; + } else { + $as_type = self::type_kolab2activesync($type); + + // fix type, if there's no type annotation it's detected as UNKNOWN we'll use 'mail' (12) + if ($as_type == 1) { + $as_type = 12; + } + } + + // Syncroton folder data array + return + 'serverId' => $folder_id, + 'parentId' => count($items) ? $this->folder_id(implode($delim, $items), $type) : 0, + 'displayName' => rcube_charset::convert($name, 'UTF7-IMAP', kolab_sync::CHARSET), + 'type' => $as_type, + // for internal use + 'imap_name' => $folder, + ; + } + + /** + * Builds folder ID based on folder name + */ + protected function folder_id($name, $type = null) + { + // ActiveSync expects folder identifiers to be max.64 characters + // So we can't use just folder name + + $name = (string) $name; + + if ($name === '') { + return null; + } + + if (isset($this->folder_uids$name)) { + return $this->folder_uids$name; + } + + /* + @TODO: For now uniqueid annotation doesn't work, we will create UIDs by ourselves. + There's one inconvenience of this solution: folder name/type change + would be handled in ActiveSync as delete + create. + + // get folders unique identifier + $folderdata = $this->storage->get_metadata($name, self::UID_KEY); + + if ($folderdata && !empty($folderdata$name)) { + $uid = $folderdata$nameself::UID_KEY; + return $this->folder_uids$name = $uid; + } + */ + if (strcasecmp($name, 'INBOX') === 0) { + // INBOX is always inbox, prevent from issues related with a change of + // folder type annotation (it can be initially unset). + $type = 'mail.inbox'; + } else { + if ($type === null) { + $type = kolab_storage::folder_type($name); + } + + if ($type != null) { + $type = preg_replace('/\.(confidential|private)$/i', '', $type); + } + } + + // Add type to folder UID hash, so type change can be detected by Syncroton + $uid = $name . '!!' . $type; + $uid = md5($uid); + + return $this->folder_uids$name = $uid; + } + + /** + * Returns IMAP folder name + * + * @param string $id Folder identifier + * @param string $deviceid Device dentifier + * + * @return string|null Folder name (UTF7-IMAP) + */ + public function folder_id2name($id, $deviceid) + { + // check in cache first + if (!empty($this->folder_uids)) { + if (($name = array_search($id, $this->folder_uids)) !== false) { + return $name; + } + } + /* + @TODO: see folder_id() + + // get folders unique identifier + $folderdata = $this->storage->get_metadata('*', self::UID_KEY); + + foreach ((array)$folderdata as $folder => $data) { + if (!empty($dataself::UID_KEY)) { + $uid = $dataself::UID_KEY; + $this->folder_uids$folder = $uid; + if ($uid == $id) { + $name = $folder; + } + } + } + */ + // get all folders of specified type + $folderdata = $this->folder_meta(); + + if (!is_array($folderdata) || empty($id)) { + return null; + } + + $name = null; + + // check if folders are "subscribed" for activesync + foreach ($folderdata as $folder => $meta) { + if (empty($meta'FOLDER') || empty($meta'FOLDER'$deviceid) + || empty($meta'FOLDER'$deviceid'S') + ) { + continue; + } + + if ($uid = $this->folder_id($folder)) { + $this->folder_uids$folder = $uid; + } + + if ($uid === $id) { + $name = $folder; + } + } + + return $name; + } + + /** + * Save MODSEQ value for a folder + */ + protected function modseq_set($deviceid, $folderid, $synctime, $data) + { + $synctime = $synctime->format('Y-m-d H:i:s'); + $rcube = rcube::get_instance(); + $db = $rcube->get_dbh(); + $old_data = $this->modseq$folderid$synctime ?? null; + + if (empty($old_data)) { + $this->modseq$folderid$synctime = $data; + $data = json_encode($data); + + $db->set_option('ignore_key_errors', true); + $db->query( + "INSERT INTO `syncroton_modseq` (`device_id`, `folder_id`, `synctime`, `data`)" + . " VALUES (?, ?, ?, ?)", + $deviceid, + $folderid, + $synctime, + $data + ); + $db->set_option('ignore_key_errors', false); + } + } + + /** + * Get stored MODSEQ value for a folder + */ + protected function modseq_get($deviceid, $folderid, $synctime) + { + $synctime = $synctime->format('Y-m-d H:i:s'); + + if (empty($this->modseq$folderid$synctime)) { + $this->modseq$folderid = ; + + $rcube = rcube::get_instance(); + $db = $rcube->get_dbh(); + + $db->limitquery( + "SELECT `data`, `synctime` FROM `syncroton_modseq`" + . " WHERE `device_id` = ? AND `folder_id` = ? AND `synctime` <= ?" + . " ORDER BY `synctime` DESC", + 0, + 1, + $deviceid, + $folderid, + $synctime + ); + + if ($row = $db->fetch_assoc()) { + $synctime = $row'synctime'; + // @TODO: make sure synctime from sql is in "Y-m-d H:i:s" format + $this->modseq$folderid$synctime = json_decode($row'data', true); + } + + // Cleanup: remove all records except the current one + $db->query( + "DELETE FROM `syncroton_modseq`" + . " WHERE `device_id` = ? AND `folder_id` = ? AND `synctime` <> ?", + $deviceid, + $folderid, + $synctime + ); + } + + return $this->modseq$folderid$synctime ?? null; + } + + /** + * Set state of relation objects at specified point in time + */ + public function relations_state_set($deviceid, $folderid, $synctime, $relations) + { + $synctime = $synctime->format('Y-m-d H:i:s'); + $rcube = rcube::get_instance(); + $db = $rcube->get_dbh(); + $old_data = $this->relations$folderid$synctime ?? null; + + if (empty($old_data)) { + $this->relations$folderid$synctime = $relations; + $data = rcube_charset::clean(json_encode($relations)); + + $db->set_option('ignore_key_errors', true); + $db->query( + "INSERT INTO `syncroton_relations_state`" + . " (`device_id`, `folder_id`, `synctime`, `data`)" + . " VALUES (?, ?, ?, ?)", + $deviceid, + $folderid, + $synctime, + $data + ); + $db->set_option('ignore_key_errors', false); + } + } + + /** + * Get state of relation objects at specified point in time + */ + protected function relations_state_get($deviceid, $folderid, $synctime) + { + $synctime = $synctime->format('Y-m-d H:i:s'); + + if (empty($this->relations$folderid$synctime)) { + $this->relations$folderid = ; + + $rcube = rcube::get_instance(); + $db = $rcube->get_dbh(); + + $db->limitquery( + "SELECT `data`, `synctime` FROM `syncroton_relations_state`" + . " WHERE `device_id` = ? AND `folder_id` = ? AND `synctime` <= ?" + . " ORDER BY `synctime` DESC", + 0, + 1, + $deviceid, + $folderid, + $synctime + ); + + if ($row = $db->fetch_assoc()) { + $synctime = $row'synctime'; + // @TODO: make sure synctime from sql is in "Y-m-d H:i:s" format + $this->relations$folderid$synctime = json_decode($row'data', true); + } + + // Cleanup: remove all records except the current one + $db->query( + "DELETE FROM `syncroton_relations_state`" + . " WHERE `device_id` = ? AND `folder_id` = ? AND `synctime` <> ?", + $deviceid, + $folderid, + $synctime + ); + } + + return $this->relations$folderid$synctime ?? null; + } + + /** + * Return last storage error + */ + public static function last_error() + { + return kolab_storage::$last_error; + } + + /** + * Compares two arrays + * + * @param array $array1 + * @param array $array2 + * + * @return bool True if arrays differs, False otherwise + */ + protected static function data_array_diff($array1, $array2) + { + if (!is_array($array1) || !is_array($array2)) { + return $array1 != $array2; + } + + if (count($array1) != count($array2)) { + return true; + } + + foreach ($array1 as $key => $val) { + if (!array_key_exists($key, $array2)) { + return true; + } + if ($val !== $array2$key) { + return true; + } + } + + return false; + } +}
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_storage_kolab4.php
Added
@@ -0,0 +1,563 @@ +<?php + +/* + +--------------------------------------------------------------------------+ + | Kolab Sync (ActiveSync for Kolab) | + | | + | Copyright (C) 2011-2023, Apheleia IT AG <contact@apheleia-it.ch> | + | | + | This program is free software: you can redistribute it and/or modify | + | it under the terms of the GNU Affero General Public License as published | + | by the Free Software Foundation, either version 3 of the License, or | + | (at your option) any later version. | + | | + | This program is distributed in the hope that it will be useful, | + | but WITHOUT ANY WARRANTY; without even the implied warranty of | + | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | + | GNU Affero General Public License for more details. | + | | + | You should have received a copy of the GNU Affero General Public License | + | along with this program. If not, see <http://www.gnu.org/licenses/> | + +--------------------------------------------------------------------------+ + | Author: Aleksander Machniak <machniak@kolabsys.com> | + +--------------------------------------------------------------------------+ +*/ + +/** + * Storage handling class with Kolab 4 support (IMAP + CalDAV + CardDAV) + */ +class kolab_sync_storage_kolab4 extends kolab_sync_storage +{ + protected $davStorage = null; + protected $relationSupport = false; + + /** + * This implements the 'singleton' design pattern + * + * @return kolab_sync_storage_kolab4 The one and only instance + */ + public static function get_instance() + { + if (!self::$instance) { + self::$instance = new kolab_sync_storage_kolab4(); + self::$instance->startup(); // init AFTER object was linked with self::$instance + } + + return self::$instance; + } + + /** + * Class initialization + */ + public function startup() + { + $sync = kolab_sync::get_instance(); + + if ($sync->username === null || $sync->password === null) { + throw new Exception("Unsupported storage handler use!"); + } + + $url = $sync->config->get('activesync_dav_server', 'http://localhost'); + + if (strpos($url, '://') === false) { + $url = 'http://' . $url; + } + + // Inject user+password to the URL, there's no other way to pass it to the DAV client + $url = str_replace('://', '://' . rawurlencode($sync->username) . ':' . rawurlencode($sync->password) . '@', $url); + + $this->davStorage = new kolab_storage_dav($url); // DAV + $this->storage = $sync->get_storage(); // IMAP + + // set additional header used by libkolab + $this->storage->set_options( + 'skip_deleted' => true, + 'threading' => false, + ); + + // Disable paging + $this->storage->set_pagesize(999999); + } + + /** + * Get list of folders available for sync + * + * @param string $deviceid Device identifier + * @param string $type Folder (class) type + * @param bool $flat_mode Enables flat-list mode + * + * @return array|bool List of mailbox folders, False on backend failure + */ + public function folders_list($deviceid, $type, $flat_mode = false) + { + $list = ; + + // get mail folders subscribed for sync + if ($type === self::MODEL_EMAIL) { + $folderdata = $this->folder_meta(); + + if (!is_array($folderdata)) { + return false; + } + + $special_folders = $this->storage->get_special_folders(true); + $type_map = + 'drafts' => 3, + 'trash' => 4, + 'sent' => 5, + ; + + // Get the folders "subscribed" for activesync + foreach ($folderdata as $folder => $meta) { + if (empty($meta'FOLDER') || empty($meta'FOLDER'$deviceid) + || empty($meta'FOLDER'$deviceid'S') + ) { + continue; + } + + // Force numeric folder name to be a string (T1283) + $folder = (string) $folder; + + // Activesync folder properties + $folder_data = $this->folder_data($folder, 'mail'); + + // Set proper type for special folders + if (($type = array_search($folder, $special_folders)) && isset($type_map$type)) { + $folder_data'type' = $type_map$type; + } + + $list$folder_data'serverId' = $folder_data; + } + } elseif (in_array($type, self::MODEL_CONTACTS, self::MODEL_CALENDAR, self::MODEL_TASKS)) { + if (!empty($this->folders)) { + foreach ($this->folders as $unique_key => $folder) { + if (strpos($unique_key, "DAV:$type:") === 0) { + $folder_data = $this->folder_data($folder, $type); + $list$folder_data'serverId' = $folder_data; + } + } + } + + // TODO: For now all DAV folders are subscribed + + if (empty($list)) { + foreach ($this->davStorage->get_folders($type) as $folder) { + $folder_data = $this->folder_data($folder, $type); + $list$folder_data'serverId' = $folder_data; + + // Store all folder objects in internal cache, otherwise + // Any access to the folder (or list) will invoke excessive DAV requests + $unique_key = $folder_data'serverId' . ":$deviceid:$type"; + $this->folders$unique_key = $folder; + } + } + } + /* + // TODO + if ($flat_mode) { + $list = $this->folders_list_flat($list, $type, $typedata); + } + */ + return $list; + } + + /** + * Creates folder and subscribes to the device + * + * @param string $name Folder name (UTF8) + * @param int $type Folder (ActiveSync) type + * @param string $deviceid Device identifier + * @param ?string $parentid Parent folder identifier + * + * @return string|false New folder identifier on success, False on failure + */ + public function folder_create($name, $type, $deviceid, $parentid = null) + { + // Mail folder + if ($type <= 6 || $type == 12) { + $parent = null; + $name = rcube_charset::convert($name, kolab_sync::CHARSET, 'UTF7-IMAP'); + + if ($parentid) { + $parent = $this->folder_id2name($parentid, $deviceid); + + if ($parent === null) { + throw new Syncroton_Exception_Status_FolderCreate(Syncroton_Exception_Status_FolderCreate::PARENT_NOT_FOUND); + } + } + + if ($parent !== null) { + $delim = $this->storage->get_hierarchy_delimiter(); + $name = $parent . $delim . $name; + } + + if ($this->storage->folder_exists($name)) { + throw new Syncroton_Exception_Status_FolderCreate(Syncroton_Exception_Status_FolderCreate::FOLDER_EXISTS); + } + + // TODO: Support setting folder types? + + $created = $this->storage->create_folder($name, true); + + if ($created) { + // Set ActiveSync subscription flag + $this->folder_set($name, $deviceid, 1); + + return $this->folder_id($name, 'mail'); + } + + // Special case when client tries to create a subfolder of INBOX + // which is not possible on Cyrus-IMAP (T2223) + if ($parent == 'INBOX' && stripos($this->last_error(), 'invalid') !== false) { + throw new Syncroton_Exception('', Syncroton_Exception_Status_FolderCreate::SPECIAL_FOLDER); + } + + return false; + } elseif ($type == 8 || $type == 13 || $type == 7 || $type == 15 || $type == 9 || $type == 14) { + // DAV folder + $type = preg_replace('|\..*|', '', self::type_activesync2kolab($type)); + + // TODO: Folder hierarchy support + + // Check if folder exists + foreach ($this->davStorage->get_folders($type) as $folder) { + if ($folder->get_name() == $name) { + throw new Syncroton_Exception_Status_FolderCreate(Syncroton_Exception_Status_FolderCreate::FOLDER_EXISTS); + } + } + + $props = 'name' => $name, 'type' => $type; + + if ($id = $this->davStorage->folder_update($props)) { + return "DAV:{$type}:{$id}"; + } + + return false; + } + + throw new \Exception("Not implemented"); + } + + /** + * Renames a folder + * + * @param string $folderid Folder identifier + * @param string $deviceid Device identifier + * @param string $new_name New folder name (UTF8) + * @param ?string $parentid Folder parent identifier + * + * @return bool True on success, False on failure + */ + public function folder_rename($folderid, $deviceid, $new_name, $parentid) + { + // DAV folder + if (strpos($folderid, 'DAV:') === 0) { + , $type, $id = explode(':', $folderid); + $props = + 'id' => $id, + 'name' => $new_name, + 'type' => $type, + ; + + // TODO: Folder hierarchy support + + return $this->davStorage->folder_update($props) !== false; + } + + // Mail folder + $old_name = $this->folder_id2name($folderid, $deviceid); + + if ($parentid) { + $parent = $this->folder_id2name($parentid, $deviceid); + } + + $name = rcube_charset::convert($new_name, kolab_sync::CHARSET, 'UTF7-IMAP'); + + if (isset($parent)) { + $delim = $this->storage->get_hierarchy_delimiter(); + $name = $parent . $delim . $name; + } + + if ($name === $old_name) { + return true; + } + + $this->folder_meta = null; + + return $this->storage->rename_folder($old_name, $name); + } + + /** + * Deletes folder + * + * @param string $folderid Folder identifier + * @param string $deviceid Device identifier + * + * @return bool True on success, False otherwise + */ + public function folder_delete($folderid, $deviceid) + { + // DAV folder + if (strpos($folderid, 'DAV:') === 0) { + , $type, $id = explode(':', $folderid); + + return $this->davStorage->folder_delete($id, $type) !== false; + } + + // Mail folder + $name = $this->folder_id2name($folderid, $deviceid); + + unset($this->folder_meta$name); + + return $this->storage->delete_folder($name); + } + + /** + * Deletes contents of a folder + * + * @param string $folderid Folder identifier + * @param string $deviceid Device identifier + * @param bool $recursive Apply to the folder and its subfolders + * + * @return bool True on success, False otherwise + */ + public function folder_empty($folderid, $deviceid, $recursive = false) + { + // DAV folder + if (strpos($folderid, 'DAV:') === 0) { + , $type, $id = explode(':', $folderid); + + if ($folder = $this->davStorage->get_folder($id, $type)) { + return $folder->delete_all(); + } + + // TODO: $recursive=true + + return false; + } + + // Mail folder + return parent::folder_empty($folderid, $deviceid, $recursive); + } + + /** + * Returns folder data in Syncroton format + */ + protected function folder_data($folder, $type) + { + // Mail folders + if (strpos($type, 'mail') === 0) { + return parent::folder_data($folder, $type); + } + + // DAV folders + return + 'serverId' => "DAV:{$type}:{$folder->id}", + 'parentId' => 0, // TODO: Folder hierarchy + 'displayName' => $folder->get_name(), + 'type' => $this->type_kolab2activesync($type), + ; + } + + /** + * Builds folder ID based on folder name + * + * @param string $name Folder name (UTF7-IMAP) + * @param string $type Kolab folder type + * + * @return string|null Folder identifier (up to 64 characters) + */ + protected function folder_id($name, $type = null) + { + if (!$type) { + $type = 'mail'; + } + + // ActiveSync expects folder identifiers to be max.64 characters + // So we can't use just folder name + $name = (string) $name; + + if ($name === '') { + return null; + } + + if (strpos($type, 'mail') !== 0) { + throw new Exception("Unsupported folder_id() call on a DAV folder"); + } + + if (isset($this->folder_uids$name)) { + return $this->folder_uids$name; + } + /* + @TODO: For now uniqueid annotation doesn't work, we will create UIDs by ourselves. + There's one inconvenience of this solution: folder name/type change + would be handled in ActiveSync as delete + create. + @TODO: Consider using MAILBOXID (RFC8474) that Cyrus v3 supports + + // get folders unique identifier + $folderdata = $this->storage->get_metadata($name, self::UID_KEY); + + if ($folderdata && !empty($folderdata$name)) { + $uid = $folderdata$nameself::UID_KEY; + return $this->folder_uids$name = $uid; + } + */ + if (strcasecmp($name, 'INBOX') === 0) { + // INBOX is always inbox, prevent from issues related with a change of + // folder type annotation (it can be initially unset). + $type = 'mail.inbox'; + } + + // Add type to folder UID hash, so type change can be detected by Syncroton + $uid = $name . '!!' . $type; + $uid = md5($uid); + + return $this->folder_uids$name = $uid; + } + + /** + * Returns IMAP folder name + * + * @param string $id Folder identifier + * @param string $deviceid Device dentifier + * + * @return null|string Folder name (UTF7-IMAP) + */ + public function folder_id2name($id, $deviceid) + { + // TODO: This method should become protected and be used for mail folders only + if (strpos($id, 'DAV:') === 0) { + throw new Exception("Unsupported folder_id2name() call on a DAV folder"); + } + + // check in cache first + if (!empty($this->folder_uids)) { + if (($name = array_search($id, $this->folder_uids)) !== false) { + return $name; + } + } + + // get all folders of specified type + $folderdata = $this->folder_meta(); + + if (!is_array($folderdata) || empty($id)) { + return null; + } + + // check if folders are "subscribed" for activesync + foreach ($folderdata as $folder => $meta) { + if (empty($meta'FOLDER') || empty($meta'FOLDER'$deviceid) + || empty($meta'FOLDER'$deviceid'S') + ) { + continue; + } + + if ($uid = $this->folder_id($folder, 'mail')) { + $this->folder_uids$folder = $uid; + } + + if ($uid === $id) { + $name = $folder; + } + } + + return $name ?? null; + } + + /** + * Gets kolab_storage_folder object from Activesync folder ID. + * + * @param string $folderid Folder identifier + * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) + * + * @return ?kolab_storage_folder + */ + public function getFolder($folderid, $deviceid, $type) + { + if (strpos($folderid, 'DAV:') !== 0) { + throw new Exception("Unsupported getFolder() call on a mail folder"); + } + + $unique_key = "$folderid:$deviceid:$type"; + + if (array_key_exists($unique_key, $this->folders)) { + return $this->folders$unique_key; + } + + , $type, $id = explode(':', $folderid); + + return $this->folders$unique_key = $this->davStorage->get_folder($id, $type); + } + + /** + * Gets Activesync preferences for a folder. + * + * @param string $folderid Folder identifier + * @param string $deviceid Device identifier + * @param string $type Activesync model name (folder type) + * + * @return array Folder preferences + */ + public function getFolderConfig($folderid, $deviceid, $type) + { + // TODO: Get "alarms" from the DAV folder props, or implement + // a storage for folder properties + return + 'ALARMS' => true, + ; + } + + /** + * Return last storage error + */ + public static function last_error() + { + // TODO + return null; + } + + /** + * Subscribe default set of folders on device registration + */ + protected function device_init_subscriptions($deviceid) + { + $config = rcube::get_instance()->config; + $mode = (int) $config->get('activesync_init_subscriptions'); + + $subscribed_folders = null; + + // Special folders only + if (!$mode) { + $all_folders = $this->storage->get_special_folders(true); + // We do not subscribe to the Spam folder by default, same as the old Kolab driver does + unset($all_folders'junk'); + $all_folders = array_unique(array_merge('INBOX', array_values($all_folders))); + } + // other modes + elseif (($mode & self::INIT_ALL_PERSONAL) || ($mode & self::INIT_ALL_OTHER) || ($mode & self::INIT_ALL_SHARED)) { + $all_folders = $this->storage->list_folders(); + if (($mode & self::INIT_SUB_PERSONAL) || ($mode & self::INIT_SUB_OTHER) || ($mode & self::INIT_SUB_SHARED)) { + $subscribed_folders = $this->storage->list_folders_subscribed(); + } + } else { + $all_folders = $this->storage->list_folders_subscribed(); + } + + foreach ($all_folders as $folder) { + $ns = strtoupper($this->storage->folder_namespace($folder)); + + // subscribe the folder according to configured mode + // and folder namespace/subscription status + if (!$mode + || ($mode & constant("self::INIT_ALL_{$ns}")) + || (($mode & constant("self::INIT_SUB_{$ns}")) && ($subscribed_folders === null || in_array($folder, $subscribed_folders))) + ) { + $this->folder_set($folder, $deviceid, 1); + } + } + + // TODO: Subscribe personal DAV folders, for now we assume all are subscribed + // TODO: Subscribe shared DAV folders + } +}
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_timezone_converter.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -33,11 +33,11 @@ /** * holds the instance of the singleton * - * @var kolab_sync_timezone_onverter + * @var ?kolab_sync_timezone_converter */ - private static $_instance = NULL; + private static $_instance; - protected $_startDate = array(); + protected $_startDate = ; /** * If set then the timezone guessing results will be cached. @@ -51,19 +51,19 @@ * array of offsets known by ActiceSync clients, but unknown by php * @var array */ - protected $_knownTimezones = array( - '0AIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==' => array( - 'Pacific/Kwajalein' => 'MHT' - ) - ); + protected $_knownTimezones = + '0AIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==' => + 'Pacific/Kwajalein' => 'MHT', + , + ; - protected $_legacyTimezones = array( + protected $_legacyTimezones = // This is an outdated timezone that outlook keeps sending because of an outdate timezone database on windows - 'Lv///0kAcgBhAG4AIABTAHQAYQBuAGQAYQByAGQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkABAADABcAOwA7AOcDAAAAAEkAcgBhAG4AIABEAGEAeQBsAGkAZwBoAHQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAwAEAAAAAAAAAAAAxP///w==' => array( - 'Asia/Tehran' => '+0330' - ) - ); + 'Lv///0kAcgBhAG4AIABTAHQAYQBuAGQAYQByAGQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkABAADABcAOwA7AOcDAAAAAEkAcgBhAG4AIABEAGEAeQBsAGkAZwBoAHQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAwAEAAAAAAAAAAAAxP///w==' => + 'Asia/Tehran' => '+0330', + , + ; /** * don't use the constructor. Use the singleton. @@ -88,7 +88,7 @@ */ public static function getInstance() { - if (self::$_instance === NULL) { + if (self::$_instance === null) { self::$_instance = new kolab_sync_timezone_converter(); } @@ -147,22 +147,20 @@ { if (is_string($_offsets) && isset($this->_knownTimezones$_offsets)) { $timezones = $this->_knownTimezones$_offsets; - } - elseif (is_string($_offsets) && isset($this->_legacyTimezones$_offsets)) { + } elseif (is_string($_offsets) && isset($this->_legacyTimezones$_offsets)) { $timezones = $this->_legacyTimezones$_offsets; - } - else { + } else { if (is_string($_offsets)) { // unpack timezone info to array $_offsets = $this->_unpackTimezoneInfo($_offsets); } if (!$this->_validateOffsets($_offsets)) { - return array(); + return ; } $this->_setDefaultStartDateIfEmpty($_offsets); - $timezones = array(); + $timezones = ; foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) { $timezone = new DateTimeZone($timezoneIdentifier); if (false !== ($matchingTransition = $this->_checkTimezone($timezone, $_offsets))) { @@ -180,7 +178,7 @@ * If {@see $_expectedTimezone} is set then the method will return this timezone if it matches. * * @param string|array $_offsets Activesync timezone definition - * @param string $_expectedTomezone Expected timezone name + * @param string $_expectedTimezone Expected timezone name * * @return string Expected timezone name */ @@ -190,8 +188,7 @@ if ($_expectedTimezone && isset($timezones$_expectedTimezone)) { return $_expectedTimezone; - } - else { + } else { return key($timezones); } } @@ -223,20 +220,21 @@ * * @param DateTime $date The date with the timezone to encode * - * @return string encoded timezone + * @return string|null Timezone name */ public static function encodeTimezoneFromDate($date) { if ($date instanceof DateTime) { $timezone = $date->getTimezone(); - if ($timezone && ($tz_name = $timezone->getName()) != 'UTC') { + if (($tz_name = $timezone->getName()) != 'UTC') { $tzc = self::getInstance(); if ($tz_name = $tzc->encodeTimezone($tz_name, $date->format('Y-m-d'))) { return $tz_name; } } } + return null; } @@ -246,7 +244,7 @@ * @param string $_timezone Timezone identifier * @param string|int $_startDate Start date * - * @return array Timezone offsets + * @return array|null Timezone offsets */ public function getOffsetsForTimezone($_timezone, $_startDate = null) { @@ -256,15 +254,14 @@ try { $timezone = new DateTimeZone($_timezone); - } - catch (Exception $e) { + } catch (Exception $e) { return null; } - list($standardTransition, $daylightTransition) = $this->_getTransitionsForTimezoneAndYear($timezone, $this->_startDate'year'); + $standardTransition, $daylightTransition = $this->_getTransitionsForTimezoneAndYear($timezone, $this->_startDate'year'); if ($standardTransition) { - $offsets'bias' = $standardTransition'offset'/60*-1; + $offsets'bias' = $standardTransition'offset' / 60 * -1; if ($daylightTransition) { $offsets = $this->_generateOffsetsForTransition($offsets, $standardTransition, 'standard', $timezone); $offsets = $this->_generateOffsetsForTransition($offsets, $daylightTransition, 'daylight', $timezone); @@ -272,7 +269,7 @@ //@todo how do we get the standardBias (is usually 0)? //$offsets'standardBias' = ... - $offsets'daylightBias' = ($daylightTransition'offset' - $standardTransition'offset')/60*-1; + $offsets'daylightBias' = ($daylightTransition'offset' - $standardTransition'offset') / 60 * -1; $offsets'standardHour' -= $offsets'daylightBias' / 60; $offsets'daylightHour' += $offsets'daylightBias' / 60; } @@ -304,7 +301,7 @@ $_offsets$_type . 'Minute' = (int) $transitionDate->format('i'); $_offsets$_type . 'Hour' = (int) $transitionDate->format('G'); - for ($i=5; $i>0; $i--) { + for ($i = 5; $i > 0; $i--) { if ($this->_isNthOcurrenceOfWeekdayInMonth($transitionDate, $i)) { $_offsets$_type . 'Week' = $i; break; @@ -359,8 +356,10 @@ * Check if the given {@param $_standardTransition} and {@param $_daylightTransition} * match to the object property {@see $_offsets} * - * @param array $standardTransition - * @param array $daylightTransition + * @param array $_standardTransition + * @param array $_daylightTransition + * @param array $_offsets + * @param DateTimeZone $tz * * @return bool */ @@ -383,7 +382,7 @@ $daylightOffset = ($_offsets'bias' + $_offsets'daylightBias') * 60 * -1; // the milestone is sending a positive value for daylightBias while it should send a negative value - $daylightOffsetMilestone = ($_offsets'bias' + ($_offsets'daylightBias' * -1) ) * 60 * -1; + $daylightOffsetMilestone = ($_offsets'bias' + ($_offsets'daylightBias' * -1)) * 60 * -1; if ( !empty($_daylightTransition) @@ -436,10 +435,11 @@ } /** - * encode timezone info to activesync + * Encode timezone info to activesync * * @param array $_timezoneInfo - * @return string + * + * @return string|null */ protected function _packTimezoneInfo($_timezoneInfo) { @@ -491,11 +491,11 @@ * Used e.g. when reverse-generating ActiveSync Timezone Offset Information * based on a given Timezone, {@see getOffsetsForTimezone} * - * @return unknown_type + * @return array */ protected function _getOffsetsTemplate() { - return array( + return 'bias' => 0, 'standardName' => '', 'standardYear' => 0, @@ -516,8 +516,8 @@ 'daylightMinute' => 0, 'daylightSecond' => 0, 'daylightMilliseconds' => 0, - 'daylightBias' => 0 - ); + 'daylightBias' => 0, + ; } /** @@ -543,7 +543,7 @@ /** * Parse and set object property {@see $_startDate} * - * @param string|int $_startDate + * @param mixed $_startDate * @return void */ protected function _setStartDate($_startDate) @@ -553,17 +553,15 @@ return; } - $startDateParsed = array(); + $startDateParsed = ; if (is_string($_startDate)) { $startDateParsed'string' = $_startDate; $startDateParsed'ts' = strtotime($_startDate); - } - else if (is_int($_startDate)) { + } elseif (is_int($_startDate)) { $startDateParsed'ts' = $_startDate; $startDateParsed'string' = date('Y-m-d', $_startDate); - } - else { + } else { $this->_setDefaultStartDateIfEmpty(); return; } @@ -590,9 +588,8 @@ } if (!empty($_offsets'standardYear')) { - $this->_setStartDate($_offsets'standardYear'.'-01-01'); - } - else { + $this->_setStartDate($_offsets'standardYear' . '-01-01'); + } else { $this->_setStartDate(time()); } } @@ -608,7 +605,7 @@ */ protected function _checkTimezone(DateTimeZone $timezone, $offsets) { - list($standardTransition, $daylightTransition) = $this->_getTransitionsForTimezoneAndYear($timezone, $this->_startDate'year'); + $standardTransition, $daylightTransition = $this->_getTransitionsForTimezoneAndYear($timezone, $this->_startDate'year'); if ($this->_checkTransition($standardTransition, $daylightTransition, $offsets, $timezone)) { return $standardTransition; @@ -636,26 +633,24 @@ $transitions = $_timezone->getTransitions($start, $end); if ($transitions === false) { - return array(); + return ; } foreach ($transitions as $index => $transition) { if (date('Y', $transition'ts') == $_year) { - if (isset($transitions$index+1) && date('Y', $transitions$index'ts') == date('Y', $transitions$index+1'ts')) { - $daylightTransition = $transition'isdst' ? $transition : $transitions$index+1; - $standardTransition = $transition'isdst' ? $transitions$index+1 : $transition; - } - else { + if (isset($transitions$index + 1) && date('Y', $transitions$index'ts') == date('Y', $transitions$index + 1'ts')) { + $daylightTransition = $transition'isdst' ? $transition : $transitions$index + 1; + $standardTransition = $transition'isdst' ? $transitions$index + 1 : $transition; + } else { $daylightTransition = $transition'isdst' ? $transition : null; $standardTransition = $transition'isdst' ? null : $transition; } break; - } - else if ($index == count($transitions) -1) { + } elseif ($index == count($transitions) - 1) { $standardTransition = $transition; } } - return array($standardTransition, $daylightTransition); + return $standardTransition, $daylightTransition; } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_transaction_manager.php
Changed
@@ -1,6 +1,6 @@ <?php -/** +/* +--------------------------------------------------------------------------+ | Kolab Sync (ActiveSync for Kolab) | | | @@ -43,17 +43,17 @@ /** * @var array holds all transactionables with open transactions */ - protected $_openTransactionables = array(); + protected $_openTransactionables = ; /** * @var array list of all open (not commited) transactions */ - protected $_openTransactions = array(); + protected $_openTransactions = ; /** - * @var Syncroton_TransactionManager + * @var ?self */ - private static $_instance = NULL; + private static $_instance; /** * @var Zend_Log @@ -78,12 +78,12 @@ } /** - * @return Tinebase_TransactionManager + * @return self */ public static function getInstance() { - if (self::$_instance === NULL) { - self::$_instance = new kolab_sync_transaction_manager; + if (self::$_instance === null) { + self::$_instance = new kolab_sync_transaction_manager(); } return self::$_instance; @@ -92,9 +92,10 @@ /** * starts a transaction * - * @param mixed $_transactionable - * @return string transactionId - * @throws Tinebase_Exception_UnexpectedValue + * @param mixed $_transactionable + * + * @return string Transaction Id + * @throws Exception */ public function startTransaction($_transactionable) { @@ -105,8 +106,7 @@ if (! in_array($_transactionable, $this->_openTransactionables)) { if ($_transactionable instanceof rcube_db) { $_transactionable->startTransaction(); - } - else { + } else { $this->rollBack(); throw new Syncroton_Exception_UnexpectedValue('Unsupported transactionable!'); } @@ -114,7 +114,7 @@ array_push($this->_openTransactionables, $_transactionable); } - $transactionId = sha1(mt_rand(). microtime()); + $transactionId = sha1(mt_rand() . microtime()); array_push($this->_openTransactions, $transactionId); return $transactionId; @@ -146,10 +146,9 @@ } } - $this->_openTransactionables = array(); - $this->_openTransactions = array(); - } - else { + $this->_openTransactionables = ; + $this->_openTransactions = ; + } else { if ($this->_logger instanceof Zend_Log) { $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " commiting defered, as there are still $numOpenTransactions in the queue"); } @@ -173,7 +172,7 @@ } } - $this->_openTransactionables = array(); - $this->_openTransactions = array(); + $this->_openTransactionables = ; + $this->_openTransactions = ; } }
View file
kolab-syncroton-2.4.2.tar.gz/phpstan.bootstrap.php
Added
@@ -0,0 +1,12 @@ +<?php + +// environment initialization + +require_once 'lib/init.php'; + +set_include_path(implode(PATH_SEPARATOR, + 'lib/plugins/libkolab/lib', + 'lib/plugins/libcalendaring', + 'lib/plugins/libcalendaring/lib', + ini_get('include_path'), +));
View file
kolab-syncroton-2.4.2.tar.gz/phpstan.neon
Added
@@ -0,0 +1,24 @@ +parameters: + bootstrapFiles: + - phpstan.bootstrap.php + + excludePaths: + - vendor + - lib/plugins + - lib/ext/Roundcube + - lib/ext/Syncroton + + ignoreErrors: + - |Access to an undefined property Syncroton_Model_.*| + - |Access to offset .* on an unknown class An.| + + paths: + - lib + - tests + + level: 4 + + scanDirectories: + - lib/ext + + treatPhpDocTypesAsCertain: false
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync
Added
+(directory)
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/FoldersTest.php
Added
@@ -0,0 +1,389 @@ +<?php + +class FoldersTest extends Tests\SyncTestCase +{ + /** + * Test FolderSync command + */ + public function testFolderSync() + { + // Note: We essentially assume the test account is in an initial state, extra folders may break tests + // Anyway, we first remove folders that might have been created during tests in this file + $this->deleteTestFolder('Test Folder', 'mail'); + $this->deleteTestFolder('Test Folder New', 'mail'); + $this->deleteTestFolder('Test Contacts Folder', 'contact'); + $this->deleteTestFolder('Test Contacts New', 'contact'); + + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <FolderSync xmlns="uri:FolderHierarchy"> + <SyncKey>0</SyncKey> + </FolderSync> + EOF; + + $response = $this->request($request, 'FolderSync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + // Note: We're expecting activesync_init_subscriptions=0 here. + if ($this->isStorageDriver('kolab4')) { + $folders = + 'Calendar', Syncroton_Command_FolderSync::FOLDERTYPE_CALENDAR, + 'Contacts', Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT, + 'INBOX', Syncroton_Command_FolderSync::FOLDERTYPE_INBOX, + 'Drafts', Syncroton_Command_FolderSync::FOLDERTYPE_DRAFTS, + 'Sent', Syncroton_Command_FolderSync::FOLDERTYPE_SENTMAIL, + 'Trash', Syncroton_Command_FolderSync::FOLDERTYPE_DELETEDITEMS, + 'Tasks', Syncroton_Command_FolderSync::FOLDERTYPE_TASK, + ; + + } else { + $folders = + 'Calendar', Syncroton_Command_FolderSync::FOLDERTYPE_CALENDAR, + 'Contacts', Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT, + 'INBOX', Syncroton_Command_FolderSync::FOLDERTYPE_INBOX, + 'Drafts', Syncroton_Command_FolderSync::FOLDERTYPE_DRAFTS, + 'Sent', Syncroton_Command_FolderSync::FOLDERTYPE_SENTMAIL, + 'Trash', Syncroton_Command_FolderSync::FOLDERTYPE_DELETEDITEMS, + 'Notes', Syncroton_Command_FolderSync::FOLDERTYPE_NOTE, + 'Tasks', Syncroton_Command_FolderSync::FOLDERTYPE_TASK, + ; + } + + $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue); + $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue); + $this->assertSame(strval(count($folders)), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue); + + foreach ($folders as $idx => $folder) { + $this->assertSame($folder0, $xpath->query("//ns:FolderSync/ns:Changes/ns:Add/ns:DisplayName")->item($idx)->nodeValue); + $this->assertSame((string) $folder1, $xpath->query("//ns:FolderSync/ns:Changes/ns:Add/ns:Type")->item($idx)->nodeValue); + $this->assertSame('0', $xpath->query("//ns:FolderSync/ns:Changes/ns:Add/ns:ParentId")->item($idx)->nodeValue); + } + + // Test with multi-folder support enabled + self::$deviceType = 'iphone'; + + $response = $this->request($request, 'FolderSync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + if ($this->isStorageDriver('kolab4')) { + $folders = + 'Calendar', Syncroton_Command_FolderSync::FOLDERTYPE_CALENDAR_USER_CREATED, + // Note: Kolab 4 with Cyrus DAV uses Addressbook, but Kolab 3 with iRony would use 'Contacts' + '/^(Contacts|Addressbook)$/', Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED, + 'INBOX', Syncroton_Command_FolderSync::FOLDERTYPE_INBOX, + 'Drafts', Syncroton_Command_FolderSync::FOLDERTYPE_DRAFTS, + 'Sent', Syncroton_Command_FolderSync::FOLDERTYPE_SENTMAIL, + 'Trash', Syncroton_Command_FolderSync::FOLDERTYPE_DELETEDITEMS, + // Note: For now Kolab 4 uses the same Calendar folder for calendar and tasks + '/^(Tasks|Calendar)$/', Syncroton_Command_FolderSync::FOLDERTYPE_TASK_USER_CREATED, + ; + } + + $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue); + $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue); + $this->assertSame(strval(count($folders)), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue); + + foreach ($folders as $idx => $folder) { + $displayName = $xpath->query("//ns:FolderSync/ns:Changes/ns:Add/ns:DisplayName")->item($idx)->nodeValue; + if (str_starts_with($folder0, '/')) { + $this->assertMatchesRegularExpression($folder0, $displayName); + } else { + $this->assertSame($folder0, $displayName); + } + $this->assertSame((string) $folder1, $xpath->query("//ns:FolderSync/ns:Changes/ns:Add/ns:Type")->item($idx)->nodeValue); + $this->assertSame('0', $xpath->query("//ns:FolderSync/ns:Changes/ns:Add/ns:ParentId")->item($idx)->nodeValue); + $idx++; + } + + // After we switched to multi-folder supported mode we expect next FolderSync + // to delete the old "collective" folders + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <FolderSync xmlns="uri:FolderHierarchy"> + <SyncKey>1</SyncKey> + </FolderSync> + EOF; + + $response = $this->request($request, 'FolderSync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $deleted = $this->isStorageDriver('kolab4') ? 3 : 4; // No Notes folder in Kolab4 + $syncKey = 2; + + $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval($syncKey), $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue); + $this->assertSame(strval($deleted), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue); + $this->assertSame($deleted, $xpath->query("//ns:FolderSync/ns:Changes/ns:Delete")->length); + + return $syncKey; + } + + /** + * Test FolderCreate command + * + * @depends testFolderSync + */ + public function testFolderCreate($syncKey) + { + // Multi-folder mode + self::$deviceType = 'iphone'; + + // Create a mail folder + $folderName1 = 'Test Folder'; + $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_MAIL_USER_CREATED; + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <FolderCreate xmlns="uri:FolderHierarchy"> + <SyncKey>{$syncKey}</SyncKey> + <ParentId>0</ParentId> + <DisplayName>{$folderName1}</DisplayName> + <Type>{$folderType}</Type> + </FolderCreate> + EOF; + + $response = $this->request($request, 'FolderCreate'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('1', $xpath->query("//ns:FolderCreate/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$syncKey), $xpath->query("//ns:FolderCreate/ns:SyncKey")->item(0)->nodeValue); + $this->assertSame(1, $xpath->query("//ns:FolderCreate/ns:ServerId")->count()); + $folder1 = $xpath->query("//ns:FolderCreate/ns:ServerId")->item(0)->nodeValue; + + // Note: After FolderCreate there are no changes in the following FolderSync expected + + // Create a contacts folder + $folderName2 = 'Test Contacts Folder'; + $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED; + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <FolderCreate xmlns="uri:FolderHierarchy"> + <SyncKey>{$syncKey}</SyncKey> + <ParentId>0</ParentId> + <DisplayName>{$folderName2}</DisplayName> + <Type>{$folderType}</Type> + </FolderCreate> + EOF; + + $response = $this->request($request, 'FolderCreate'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('1', $xpath->query("//ns:FolderCreate/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$syncKey), $xpath->query("//ns:FolderCreate/ns:SyncKey")->item(0)->nodeValue); + $this->assertSame(1, $xpath->query("//ns:FolderCreate/ns:ServerId")->count()); + $folder2 = $xpath->query("//ns:FolderCreate/ns:ServerId")->item(0)->nodeValue; + + // Note: After FolderCreate there are no changes in the following FolderSync expected + + // TODO: Test folder with a parent + + return + 'SyncKey' => $syncKey, + 'folders' => + $folder1, + $folder2, + , + ; + } + + /** + * Test FolderUpdate command + * + * @depends testFolderCreate + */ + public function testFolderUpdate($params) + { + // Multi-folder mode + self::$deviceType = 'iphone'; + + // Test renaming a mail folder + $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_MAIL_USER_CREATED; + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <FolderUpdate xmlns="uri:FolderHierarchy"> + <SyncKey>{$params'SyncKey'}</SyncKey> + <ServerId>{$params'folders'0}</ServerId> + <ParentId/> + <DisplayName>Test Folder New</DisplayName> + <Type>{$folderType}</Type> + </FolderUpdate> + EOF; + + $response = $this->request($request, 'FolderUpdate'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('1', $xpath->query("//ns:FolderUpdate/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$params'SyncKey'), $xpath->query("//ns:FolderUpdate/ns:SyncKey")->item(0)->nodeValue); + + // Test FolderSync after folder update, get the new folder id (for delete test) + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <FolderSync xmlns="uri:FolderHierarchy"> + <SyncKey>{$params'SyncKey'}</SyncKey> + </FolderSync> + EOF; + + $response = $this->request($request, 'FolderSync'); + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + // Note we expect Add+Delete here, instead of Update (but this could change in the future) + $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$params'SyncKey'), $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue); + $this->assertSame(1, $xpath->query("//ns:FolderSync/ns:Changes/ns:Add")->length); + $this->assertSame(1, $xpath->query("//ns:FolderSync/ns:Changes/ns:Delete")->length); + $this->assertSame($params'folders'0, $xpath->query("//ns:FolderSync/ns:Changes/ns:Delete/ns:ServerId")->item(0)->nodeValue); + $this->assertSame('0', $xpath->query("//ns:FolderSync/ns:Changes/ns:Add/ns:ParentId")->item(0)->nodeValue); + $this->assertSame('Test Folder New', $xpath->query("//ns:FolderSync/ns:Changes/ns:Add/ns:DisplayName")->item(0)->nodeValue); + $this->assertSame(strval($folderType), $xpath->query("//ns:FolderSync/ns:Changes/ns:Add/ns:Type")->item(0)->nodeValue); + $params'folders'0 = $xpath->query("//ns:FolderSync/ns:Changes/ns:Add/ns:ServerId")->item(0)->nodeValue; + + // Test renaming a contacts folder + $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED; + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <FolderUpdate xmlns="uri:FolderHierarchy"> + <SyncKey>{$params'SyncKey'}</SyncKey> + <ServerId>{$params'folders'1}</ServerId> + <ParentId/> + <DisplayName>Test Contacts New</DisplayName> + <Type>{$folderType}</Type> + </FolderUpdate> + EOF; + + $response = $this->request($request, 'FolderUpdate'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('1', $xpath->query("//ns:FolderUpdate/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$params'SyncKey'), $xpath->query("//ns:FolderUpdate/ns:SyncKey")->item(0)->nodeValue); + + // Test FolderSync after folder update, get the new folder id (for delete test) + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <FolderSync xmlns="uri:FolderHierarchy"> + <SyncKey>{$params'SyncKey'}</SyncKey> + </FolderSync> + EOF; + + $response = $this->request($request, 'FolderSync'); + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$params'SyncKey'), $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue); + + if ($this->isStorageDriver('kolab4')) { + // Note we expect Update here, not Add+Delete, folder ID does not change + $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue); + $this->assertSame($params'folders'1, $xpath->query("//ns:FolderSync/ns:Changes/ns:Update/ns:ServerId")->item(0)->nodeValue); + $this->assertSame('Test Contacts New', $xpath->query("//ns:FolderSync/ns:Changes/ns:Update/ns:DisplayName")->item(0)->nodeValue); + $this->assertSame(strval($folderType), $xpath->query("//ns:FolderSync/ns:Changes/ns:Update/ns:Type")->item(0)->nodeValue); + } else { + // Note we expect Add+Delete here, instead of Update (but this could change in the future) + $this->assertSame('2', $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue); + $this->assertSame($params'folders'1, $xpath->query("//ns:FolderSync/ns:Changes/ns:Delete/ns:ServerId")->item(0)->nodeValue); + $this->assertSame('0', $xpath->query("//ns:FolderSync/ns:Changes/ns:Add/ns:ParentId")->item(0)->nodeValue); + $this->assertSame('Test Contacts New', $xpath->query("//ns:FolderSync/ns:Changes/ns:Add/ns:DisplayName")->item(0)->nodeValue); + $this->assertSame(strval($folderType), $xpath->query("//ns:FolderSync/ns:Changes/ns:Add/ns:Type")->item(0)->nodeValue); + $params'folders'1 = $xpath->query("//ns:FolderSync/ns:Changes/ns:Add/ns:ServerId")->item(0)->nodeValue; + } + + // TODO: Test folder with a parent change + // TODO: Assert the folder name has changed in the storage + // TODO: Test Sync after a DAV folder rename made in another client + + return $params; + } + + /** + * Test FolderDelete command + * + * @depends testFolderUpdate + */ + public function testFolderDelete($params) + { + // Multi-folder mode + self::$deviceType = 'iphone'; + + // Delete mail folder + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <FolderDelete xmlns="uri:FolderHierarchy"> + <SyncKey>{$params'SyncKey'}</SyncKey> + <ServerId>{$params'folders'0}</ServerId> + </FolderDelete> + EOF; + + $response = $this->request($request, 'FolderDelete'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('1', $xpath->query("//ns:FolderDelete/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$params'SyncKey'), $xpath->query("//ns:FolderDelete/ns:SyncKey")->item(0)->nodeValue); + + // Note: After FolderDelete there are no changes in the following FolderSync expected + + // Delete contacts folder + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <FolderDelete xmlns="uri:FolderHierarchy"> + <SyncKey>{$params'SyncKey'}</SyncKey> + <ServerId>{$params'folders'1}</ServerId> + </FolderDelete> + EOF; + + $response = $this->request($request, 'FolderDelete'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('1', $xpath->query("//ns:FolderDelete/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$params'SyncKey'), $xpath->query("//ns:FolderDelete/ns:SyncKey")->item(0)->nodeValue); + + // Note: After FolderDelete there are no changes in the following FolderSync expected + + // TODO: Assert the folders no longer exist + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/ItemOperationsTest.php
Added
@@ -0,0 +1,81 @@ +<?php + +class ItemOperationsTest extends Tests\SyncTestCase +{ + /** + * Test ItemOperations::EmptyFolderContents request + */ + public function testEmptyFolderContents() + { + $this->registerDevice(); + + // TODO: Test invalid folder ID + $collectionId = 'AAAAAAAAAAAA'; + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <ItemOperations xmlns="uri:ItemOperations" xmlns:AirSync="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"> + <EmptyFolderContents> + <AirSync:CollectionId>{$collectionId}</AirSync:CollectionId> + <Options><DeleteSubFolders/></Options> + </EmptyFolderContents> + </ItemOperations> + EOF; + + $response = $this->request($request, 'ItemOperations'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('1', $xpath->query("//ns:ItemOperations/ns:Status")->item(0)->nodeValue); + $this->assertSame( + strval(Syncroton_Exception_Status_ItemOperations::ITEM_SERVER_ERROR), + $xpath->query("//ns:ItemOperations/ns:Response/ns:EmptyFolderContents/ns:Status")->item(0)->nodeValue + ); + $this->assertSame( + $collectionId, + $xpath->query("//ns:ItemOperations/ns:Response/ns:EmptyFolderContents/AirSync:CollectionId")->item(0)->nodeValue + ); + + // Test Trash folder + $collectionId = array_search('Trash', $this->folders); + $this->assertIsString($collectionId); + + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <ItemOperations xmlns="uri:ItemOperations" xmlns:AirSync="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"> + <EmptyFolderContents> + <AirSync:CollectionId>{$collectionId}</AirSync:CollectionId> + <Options><DeleteSubFolders/></Options> + </EmptyFolderContents> + </ItemOperations> + EOF; + + $response = $this->request($request, 'ItemOperations'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('1', $xpath->query("//ns:ItemOperations/ns:Status")->item(0)->nodeValue); + $this->assertSame('1', $xpath->query("//ns:ItemOperations/ns:Response/ns:EmptyFolderContents/ns:Status")->item(0)->nodeValue); + $this->assertSame($collectionId, $xpath->query("//ns:ItemOperations/ns:Response/ns:EmptyFolderContents/AirSync:CollectionId")->item(0)->nodeValue); + + // TODO: Test DAV folder + // TODO: Test a folder with subfolders + // TODO: Test non-empty folder and assert that all objects are gone + $this->markTestIncomplete(); + } + + /** + * Test ItemOperations::Fetch request + */ + public function testFetch() + { + $this->markTestIncomplete(); + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/MeetingResponseTest.php
Added
@@ -0,0 +1,131 @@ +<?php + +namespace Tests\Sync; + +class MeetingResponseTest extends \Tests\SyncTestCase +{ + /** + * Test MeetingResponse command + */ + public function testAcceptingInvitation() + { + $this->emptyTestFolder($davFolder = 'Calendar', 'event'); + $this->emptyTestFolder('INBOX', 'mail'); + + $this->registerDevice(); + + // Do the initial INBOX sync + $folderId = '38b950ebd62cd9a66929c89615d0fc04'; // INBOX + $syncKey = 0; + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync"> + <Collections> + <Collection> + <SyncKey>{$syncKey}</SyncKey> + <CollectionId>{$folderId}</CollectionId> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('1', $xpath->query("//ns:Sync/ns:Collections/ns:Collection/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$syncKey), $xpath->query("//ns:Sync/ns:Collections/ns:Collection/ns:SyncKey")->item(0)->nodeValue); + + // Append an invitation email, and sync it + $sync = \kolab_sync::get_instance(); + $replace = + '$from' => 'test.test@domain.tld', + '$to' => $sync->config->get('activesync_test_username'), + ; + $this->appendMail('INBOX', 'mail.itip1', $replace); + + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync"> + <Collections> + <Collection> + <SyncKey>{$syncKey}</SyncKey> + <CollectionId>{$folderId}</CollectionId> + <DeletesAsMoves>1</DeletesAsMoves> + <GetChanges>1</GetChanges> + <WindowSize>1</WindowSize> + <Options> + <FilterType>0</FilterType> + <Conflict>1</Conflict> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>2</Type> + <TruncationSize>51200</TruncationSize> + <AllOrNone>0</AllOrNone> + </BodyPreference> + </Options> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $root = $xpath->query("//ns:Sync/ns:Collections/ns:Collection")->item(0); + $this->assertSame('1', $xpath->query("ns:Status", $root)->item(0)->nodeValue); + $this->assertSame(strval(++$syncKey), $xpath->query("ns:SyncKey", $root)->item(0)->nodeValue); + $this->assertSame(1, $xpath->query("ns:Commands/ns:Add", $root)->count()); + + $serverId = $xpath->query("ns:Commands/ns:Add/ns:ServerId", $root)->item(0)->nodeValue; + + $root = $xpath->query("ns:Commands/ns:Add/ns:ApplicationData", $root)->item(0); + $this->assertSame('You\'ve been invited to "Test"', $xpath->query("Email:Subject", $root)->item(0)->nodeValue); + $this->assertSame('Organizer <test.test@domain.tld>', $xpath->query("Email:From", $root)->item(0)->nodeValue); + $this->assertSame($replace'$to', $xpath->query("Email:To", $root)->item(0)->nodeValue); + $this->assertSame('0', $xpath->query("Email:Read", $root)->item(0)->nodeValue); + $this->assertSame('IPM.Schedule.Meeting.Request', $xpath->query("Email:MessageClass", $root)->item(0)->nodeValue); + $this->assertSame('urn:content-classes:calendarmessage', $xpath->query("Email:ContentClass", $root)->item(0)->nodeValue); + $root = $xpath->query("Email:MeetingRequest", $root)->item(0); + $this->assertSame('0', $xpath->query("Email:AllDayEvent", $root)->item(0)->nodeValue); + $this->assertSame('2023-12-07T13:00:00.000Z', $xpath->query("Email:StartTime", $root)->item(0)->nodeValue); + $this->assertSame('2023-12-07T13:30:00.000Z', $xpath->query("Email:EndTime", $root)->item(0)->nodeValue); + $this->assertSame('test.test@domain.tld', $xpath->query("Email:Organizer", $root)->item(0)->nodeValue); + $this->assertSame('1', $xpath->query("Email:ResponseRequested", $root)->item(0)->nodeValue); + $this->assertSame('1', $xpath->query("Email:DisallowNewTimeProposal", $root)->item(0)->nodeValue); + + // Accept the invitation + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <MeetingResponse xmlns="uri:MeetingResponse" xmlns:Search="uri:Search"> + <Request> + <UserResponse>1</UserResponse> + <CollectionId>{$folderId}</CollectionId> + <RequestId>{$serverId}</RequestId> + </Request> + </MeetingResponse> + EOF; + + $response = $this->request($request, 'MeetingResponse'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + $xpath->registerNamespace('MeetingResponse', 'uri:MeetingResponse'); + + $root = $xpath->query("//MeetingResponse:MeetingResponse/MeetingResponse:Result")->item(0); + $this->assertSame('1', $xpath->query("ns:Status", $root)->item(0)->nodeValue); + $this->assertSame($serverId, $xpath->query("ns:RequestId", $root)->item(0)->nodeValue); + $this->assertStringMatchesFormat("CRC%s", $xpath->query("ns:CalendarId", $root)->item(0)->nodeValue); + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/MoveItemsTest.php
Added
@@ -0,0 +1,334 @@ +<?php + +class MoveItemsTest extends Tests\SyncTestCase +{ + /** + * Test moving an email message + */ + public function testMoveEmail() + { + $this->emptyTestFolder('INBOX', 'mail'); + $this->emptyTestFolder('Trash', 'mail'); + $uid = $this->appendMail('INBOX', 'mail.sync1'); + $this->registerDevice(); + + $inbox = array_search('INBOX', $this->folders); + $trash = array_search('Trash', $this->folders); + + // Initial sync + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync"> + <Collections> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>{$inbox}</CollectionId> + </Collection> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>{$trash}</CollectionId> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + // Sync mail from INBOX and Trash + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"> + <Collections> + <Collection> + <SyncKey>1</SyncKey> + <CollectionId>{$inbox}</CollectionId> + <GetChanges>1</GetChanges> + </Collection> + <Collection> + <SyncKey>1</SyncKey> + <CollectionId>{$trash}</CollectionId> + <GetChanges>1</GetChanges> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame(1, $xpath->query("//ns:Sync/ns:Collections/ns:Collection")->count()); + $root = $xpath->query("//ns:Sync/ns:Collections/ns:Collection")->item(0); + $this->assertSame(1, $xpath->query("ns:Commands/ns:Add", $root)->count()); + $root = $xpath->query("ns:Commands/ns:Add", $root)->item(0); + $this->assertSame('test sync', $xpath->query("ns:ApplicationData/Email:Subject", $root)->item(0)->nodeValue); + + // Move the message to $trash + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <MoveItems xmlns="uri:Move"> + <Move> + <SrcMsgId>{$inbox}::{$uid}</SrcMsgId> + <SrcFldId>{$inbox}</SrcFldId> + <DstFldId>{$trash}</DstFldId> + </Move> + </MoveItems> + EOF; + + $response = $this->request($request, 'MoveItems'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + $xpath->registerNamespace('Move', 'uri:Move'); + + $root = $xpath->query("//Move:MoveItems/Move:Response")->item(0); + $this->assertSame('3', $xpath->query("Move:Status", $root)->item(0)->nodeValue); + $this->assertSame("{$inbox}::{$uid}", $xpath->query("Move:SrcMsgId", $root)->item(0)->nodeValue); + $serverId = $xpath->query("Move:DstMsgId", $root)->item(0)->nodeValue; + + // Sync mail from INBOX and Trash + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"> + <Collections> + <Collection> + <SyncKey>2</SyncKey> + <CollectionId>{$inbox}</CollectionId> + <GetChanges>1</GetChanges> + </Collection> + <Collection> + <SyncKey>1</SyncKey> + <CollectionId>{$trash}</CollectionId> + <GetChanges>1</GetChanges> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $root = $xpath->query("//ns:Sync/ns:Collections/ns:Collection")->item(0); // INBOX + $this->assertSame($inbox, $xpath->query("ns:CollectionId", $root)->item(0)->nodeValue); + $this->assertSame(1, $xpath->query("ns:Commands/ns:Delete", $root)->count()); + $this->assertSame("$inbox::$uid", $xpath->query("ns:Commands/ns:Delete/ns:ServerId", $root)->item(0)->nodeValue); + + $root = $xpath->query("//ns:Sync/ns:Collections/ns:Collection")->item(1); // Trash + $this->assertSame($trash, $xpath->query("ns:CollectionId", $root)->item(0)->nodeValue); + $this->assertSame(1, $xpath->query("ns:Commands/ns:Add", $root)->count()); + $this->assertSame('test sync', $xpath->query("ns:Commands/ns:Add/ns:ApplicationData/Email:Subject", $root)->item(0)->nodeValue); + } + + /** + * Test moving a contact + */ + public function testMoveContact() + { + // Test with multi-folder support enabled + self::$deviceType = 'iphone'; + + $davFolder = $this->isStorageDriver('kolab') ? 'Contacts' : 'Addressbook'; + $this->emptyTestFolder($davFolder, 'contact'); + $this->deleteTestFolder($folderName = 'Test Contacts Folder', 'contact'); + $this->appendObject($davFolder, 'contact.vcard1', 'contact'); + + $this->registerDevice(); + + $srcFolderId = array_search($davFolder, $this->folders); + + // Create a contacts folder + $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED; + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <FolderCreate xmlns="uri:FolderHierarchy"> + <SyncKey>1</SyncKey> + <ParentId>0</ParentId> + <DisplayName>{$folderName}</DisplayName> + <Type>{$folderType}</Type> + </FolderCreate> + EOF; + + $response = $this->request($request, 'FolderCreate'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $dstFolderId = $xpath->query("//ns:FolderCreate/ns:ServerId")->item(0)->nodeValue; + + // Sync both folders + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"> + <Collections> + <Collection> + <Class>Contacts</Class> + <SyncKey>0</SyncKey> + <CollectionId>{$srcFolderId}</CollectionId> + </Collection> + <Collection> + <Class>Contacts</Class> + <SyncKey>0</SyncKey> + <CollectionId>{$dstFolderId}</CollectionId> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"> + <Collections> + <Collection> + <Class>Contacts</Class> + <SyncKey>1</SyncKey> + <CollectionId>{$srcFolderId}</CollectionId> + <DeletesAsMoves/> + <GetChanges/> + <Options> + <AirSyncBase:BodyPreference> + <AirSyncBase:Type>1</AirSyncBase:Type> + <AirSyncBase:TruncationSize>5120</AirSyncBase:TruncationSize> + </AirSyncBase:BodyPreference> + <Conflict>1</Conflict> + </Options> + </Collection> + <Collection> + <Class>Contacts</Class> + <SyncKey>1</SyncKey> + <CollectionId>{$dstFolderId}</CollectionId> + <DeletesAsMoves/> + <GetChanges/> + <Options> + <AirSyncBase:BodyPreference> + <AirSyncBase:Type>1</AirSyncBase:Type> + <AirSyncBase:TruncationSize>5120</AirSyncBase:TruncationSize> + </AirSyncBase:BodyPreference> + <Conflict>1</Conflict> + </Options> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $root = $xpath->query("//ns:Sync/ns:Collections/ns:Collection")->item(0); + $this->assertSame($srcFolderId, $xpath->query("ns:CollectionId", $root)->item(0)->nodeValue); + $this->assertSame(1, $xpath->query("ns:Commands/ns:Add", $root)->count()); + $this->assertSame('Jane', $xpath->query("ns:Commands/ns:Add/ns:ApplicationData/Contacts:FirstName", $root)->item(0)->nodeValue); + $srcMsgId = $xpath->query("ns:Commands/ns:Add/ns:ServerId", $root)->item(0)->nodeValue; + + // Move the message to the other folder + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <MoveItems xmlns="uri:Move"> + <Move> + <SrcMsgId>{$srcMsgId}</SrcMsgId> + <SrcFldId>{$srcFolderId}</SrcFldId> + <DstFldId>{$dstFolderId}</DstFldId> + </Move> + </MoveItems> + EOF; + + $response = $this->request($request, 'MoveItems'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + $xpath->registerNamespace('Move', 'uri:Move'); + + $root = $xpath->query("//Move:MoveItems/Move:Response")->item(0); + $this->assertSame('3', $xpath->query("Move:Status", $root)->item(0)->nodeValue); + $this->assertSame($srcMsgId, $xpath->query("Move:SrcMsgId", $root)->item(0)->nodeValue); + $dstMsgId = $xpath->query("Move:DstMsgId", $root)->item(0)->nodeValue; + + // Sync the folders again + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"> + <Collections> + <Collection> + <Class>Contacts</Class> + <SyncKey>2</SyncKey> + <CollectionId>{$srcFolderId}</CollectionId> + <DeletesAsMoves/> + <GetChanges/> + <Options> + <AirSyncBase:BodyPreference> + <AirSyncBase:Type>1</AirSyncBase:Type> + <AirSyncBase:TruncationSize>5120</AirSyncBase:TruncationSize> + </AirSyncBase:BodyPreference> + <Conflict>1</Conflict> + </Options> + </Collection> + <Collection> + <Class>Contacts</Class> + <SyncKey>1</SyncKey> + <CollectionId>{$dstFolderId}</CollectionId> + <DeletesAsMoves/> + <GetChanges/> + <Options> + <AirSyncBase:BodyPreference> + <AirSyncBase:Type>1</AirSyncBase:Type> + <AirSyncBase:TruncationSize>5120</AirSyncBase:TruncationSize> + </AirSyncBase:BodyPreference> + <Conflict>1</Conflict> + </Options> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $root = $xpath->query("//ns:Sync/ns:Collections/ns:Collection")->item(0); // src folder + $this->assertSame($srcFolderId, $xpath->query("ns:CollectionId", $root)->item(0)->nodeValue); + $this->assertSame(1, $xpath->query("ns:Commands/ns:Delete", $root)->count()); + $this->assertSame($srcMsgId, $xpath->query("ns:Commands/ns:Delete/ns:ServerId", $root)->item(0)->nodeValue); + + $root = $xpath->query("//ns:Sync/ns:Collections/ns:Collection")->item(1); // dst folder + $this->assertSame($dstFolderId, $xpath->query("ns:CollectionId", $root)->item(0)->nodeValue); + $this->assertSame(1, $xpath->query("ns:Commands/ns:Add", $root)->count()); + $this->assertSame('Jane', $xpath->query("ns:Commands/ns:Add/ns:ApplicationData/Contacts:FirstName", $root)->item(0)->nodeValue); + $this->assertSame($dstMsgId, $xpath->query("ns:Commands/ns:Add/ns:ServerId", $root)->item(0)->nodeValue); + + $this->deleteTestFolder($folderName, 'contact'); + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/OptionsTest.php
Added
@@ -0,0 +1,16 @@ +<?php + +class OptionsTest extends Tests\SyncTestCase +{ + /** + * Test Options command/request + */ + public function testOptions() + { + $response = self::$client->request('OPTIONS', ''); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertStringContainsString('14', $response->getHeader('MS-Server-ActiveSync')0); + $this->assertStringContainsString('14.1', $response->getHeader('MS-ASProtocolVersions')0); + $this->assertStringContainsString('FolderSync', $response->getHeader('MS-ASProtocolCommands')0); + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/ProvisionTest.php
Added
@@ -0,0 +1,45 @@ +<?php + +class ProvisionTest extends Tests\SyncTestCase +{ + /** + * Test Provision command + */ + public function testProvision() + { + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Provision xmlns="uri:Provision" xmlns:Settings="uri:Settings"> + <DeviceInformation xmlns="uri:Settings"> + <Set> + <Model>moto e(6) plus</Model> + <IMEI>000000000000000</IMEI> + <FriendlyName>pokerp_reteu_64</FriendlyName> + <OS>Android 9.58-8</OS> + <OSLanguage>Polish (Poland)</OSLanguage> + <MobileOperator/> + </Set> + </DeviceInformation> + <Policies> + <Policy> + <PolicyType>MS-EAS-Provisioning-WBXML</PolicyType> + </Policy> + </Policies> + </Provision> + EOF; + + $response = $this->request($request, 'Provision'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('1', $xpath->query("//ns:Provision/ns:Status")->item(0)->nodeValue); + $this->assertSame('1', $xpath->query("//ns:Provision/Settings:DeviceInformation/Settings:Status")->item(0)->nodeValue); + $this->assertSame('2', $xpath->query("//ns:Provision/ns:Policies/ns:Policy/ns:Status")->item(0)->nodeValue); + + // TODO: Assert the properties have been set + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/SettingsTest.php
Added
@@ -0,0 +1,80 @@ +<?php + +class SettingsTest extends Tests\SyncTestCase +{ + /** + * Test Settings command + */ + public function testSettingsUserInformation() + { + // Test retrieving the settings + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Settings xmlns="uri:Settings"> + <UserInformation> + <Get/> + </UserInformation> + </Settings> + EOF; + + $response = $this->request($request, 'Settings'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = self::fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('1', $xpath->query("//ns:Settings/ns:Status")->item(0)->nodeValue); + $this->assertSame('1', $xpath->query("//ns:Settings/ns:UserInformation/ns:Status")->item(0)->nodeValue); + $this->assertSame( + self::$username, + $xpath->query("//ns:Settings/ns:UserInformation/ns:Get/ns:Accounts/ns:Account/ns:EmailAddresses/ns:PrimarySmtpAddress")->item(0)->nodeValue + ); + } + + /** + * Test Settings command + */ + public function testSettingsDeviceInfomation() + { + // Test device info update + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Settings xmlns="uri:Settings"> + <DeviceInformation> + <Set> + <Model>moto plus</Model> + <IMEI>111111111</IMEI> + <FriendlyName>fn</FriendlyName> + <OS>Android 10</OS> + <OSLanguage>English</OSLanguage> + <MobileOperator/> + </Set> + </DeviceInformation> + </Settings> + EOF; + + $response = $this->request($request, 'Settings'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('1', $xpath->query("//ns:Settings/ns:Status")->item(0)->nodeValue); + $this->assertSame('1', $xpath->query("//ns:Settings/ns:DeviceInformation/ns:Set/ns:Status")->item(0)->nodeValue); + + // TODO: Assert the properties have been set + } + + /** + * Test Settings command regarding OOF + */ + public function testSettingsOOF() + { + // TODO: Test OOF settings + $this->markTestIncomplete(); + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/Sync
Added
+(directory)
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/Sync/CalendarTest.php
Added
@@ -0,0 +1,75 @@ +<?php + +namespace Tests\Sync\Sync; + +class CalendarTest extends \Tests\SyncTestCase +{ + /** + * Test Sync command + */ + public function testSync() + { + $this->emptyTestFolder($davFolder = 'Calendar', 'event'); + $this->registerDevice(); + + // Test empty folder + $folderId = 'Calendar::Syncroton'; + $syncKey = 0; + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"> + <Collections> + <Collection> + <Class>Calendar</Class> + <SyncKey>{$syncKey}</SyncKey> + <CollectionId>{$folderId}</CollectionId> + <DeletesAsMoves/> + <GetChanges/> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $root = "//ns:Sync/ns:Collections/ns:Collection"; + $this->assertSame('1', $xpath->query("{$root}/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$syncKey), $xpath->query("{$root}/ns:SyncKey")->item(0)->nodeValue); + $this->assertSame($folderId, $xpath->query("{$root}/ns:CollectionId")->item(0)->nodeValue); + $this->assertSame(0, $xpath->query("{$root}/ns:Commands/ns:Add")->count()); + + // Append two event objects and sync them + $this->appendObject($davFolder, 'event.ics1', 'event'); + $this->appendObject($davFolder, 'event.ics2', 'event'); + + $request = str_replace("<SyncKey>0</SyncKey>", "<SyncKey>{$syncKey}</SyncKey>", $request); + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $root = "//ns:Sync/ns:Collections/ns:Collection"; + $this->assertSame('1', $xpath->query("{$root}/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$syncKey), $xpath->query("{$root}/ns:SyncKey")->item(0)->nodeValue); + $this->assertSame($folderId, $xpath->query("{$root}/ns:CollectionId")->item(0)->nodeValue); + $this->assertSame(2, $xpath->query("{$root}/ns:Commands/ns:Add")->count()); + + $root .= "/ns:Commands/ns:Add"; + $this->assertStringMatchesFormat("CRC%s", $xpath->query("{$root}/ns:ServerId")->item(0)->nodeValue); + $this->assertSame('20240715T170000Z', $xpath->query("{$root}/ns:ApplicationData/Calendar:StartTime")->item(0)->nodeValue); + $this->assertSame('Meeting', $xpath->query("{$root}/ns:ApplicationData/Calendar:Subject")->item(0)->nodeValue); + $this->assertSame('20240714T170000Z', $xpath->query("{$root}/ns:ApplicationData/Calendar:StartTime")->item(1)->nodeValue); + $this->assertSame('Party', $xpath->query("{$root}/ns:ApplicationData/Calendar:Subject")->item(1)->nodeValue); + + return $syncKey; + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/Sync/ContactsTest.php
Added
@@ -0,0 +1,250 @@ +<?php + +namespace Tests\Sync\Sync; + +class ContactsTest extends \Tests\SyncTestCase +{ + /** + * Test Sync command + */ + public function testSync() + { + $davFolder = $this->isStorageDriver('kolab') ? 'Contacts' : 'Addressbook'; + $this->emptyTestFolder($davFolder, 'contact'); + $this->deleteTestFolder('Test Contacts Folder', 'contact'); // from other test files + $this->registerDevice(); + + // Test empty contacts folder + $folderId = 'Contacts::Syncroton'; + $syncKey = 0; + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"> + <Collections> + <Collection> + <Class>Contacts</Class> + <SyncKey>{$syncKey}</SyncKey> + <CollectionId>{$folderId}</CollectionId> + <DeletesAsMoves/> + <GetChanges/> + <Options> + <AirSyncBase:BodyPreference> + <AirSyncBase:Type>1</AirSyncBase:Type> + <AirSyncBase:TruncationSize>5120</AirSyncBase:TruncationSize> + </AirSyncBase:BodyPreference> + <Conflict>1</Conflict> + </Options> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $root = "//ns:Sync/ns:Collections/ns:Collection"; + $this->assertSame('1', $xpath->query("{$root}/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$syncKey), $xpath->query("{$root}/ns:SyncKey")->item(0)->nodeValue); + $this->assertSame($folderId, $xpath->query("{$root}/ns:CollectionId")->item(0)->nodeValue); + $this->assertSame(0, $xpath->query("{$root}/ns:Commands/ns:Add")->count()); + + // Append two contact objects and sync them + // TODO: Test a folder with contact groups inside + $this->appendObject($davFolder, 'contact.vcard1', 'contact'); + $this->appendObject($davFolder, 'contact.vcard2', 'contact'); + + $request = str_replace("<SyncKey>0</SyncKey>", "<SyncKey>{$syncKey}</SyncKey>", $request); + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $root = "//ns:Sync/ns:Collections/ns:Collection"; + $this->assertSame('1', $xpath->query("{$root}/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$syncKey), $xpath->query("{$root}/ns:SyncKey")->item(0)->nodeValue); + $this->assertSame($folderId, $xpath->query("{$root}/ns:CollectionId")->item(0)->nodeValue); + $this->assertSame(2, $xpath->query("{$root}/ns:Commands/ns:Add")->count()); + + $root .= "/ns:Commands/ns:Add"; + $this->assertStringMatchesFormat("CRC%s", $xpath->query("{$root}/ns:ServerId")->item(0)->nodeValue); + $this->assertSame('Jack', $xpath->query("{$root}/ns:ApplicationData/Contacts:FirstName")->item(0)->nodeValue); + $this->assertSame('Strong', $xpath->query("{$root}/ns:ApplicationData/Contacts:LastName")->item(0)->nodeValue); + $this->assertSame('Jane', $xpath->query("{$root}/ns:ApplicationData/Contacts:FirstName")->item(1)->nodeValue); + $this->assertSame('Doe', $xpath->query("{$root}/ns:ApplicationData/Contacts:LastName")->item(1)->nodeValue); + + return $syncKey; + } + + /** + * Test adding objects from client + * + * @depends testSync + */ + public function testAddFromClient($syncKey) + { + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Contacts="uri:Contacts"> + <Collections> + <Collection> + <Class>Contacts</Class> + <SyncKey>{$syncKey}</SyncKey> + <CollectionId>Contacts::Syncroton</CollectionId> + <DeletesAsMoves/> + <GetChanges/> + <Options> + <AirSyncBase:BodyPreference> + <AirSyncBase:Type>1</AirSyncBase:Type> + <AirSyncBase:TruncationSize>5120</AirSyncBase:TruncationSize> + </AirSyncBase:BodyPreference> + <Conflict>1</Conflict> + </Options> + <Commands> + <Add> + <ClientId>42</ClientId> + <ApplicationData> + <Contacts:FirstName>Lars</Contacts:FirstName> + </ApplicationData> + </Add> + </Commands> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $root = $xpath->query("//ns:Sync/ns:Collections/ns:Collection")->item(0); + $this->assertSame('1', $xpath->query("ns:Status", $root)->item(0)->nodeValue); + $this->assertSame(strval(++$syncKey), $xpath->query("ns:SyncKey", $root)->item(0)->nodeValue); + $root = $xpath->query("ns:Responses/ns:Add", $root)->item(0); + $this->assertSame('1', $xpath->query("ns:Status", $root)->item(0)->nodeValue); + $this->assertSame('42', $xpath->query("ns:ClientId", $root)->item(0)->nodeValue); + $serverId = $xpath->query("ns:ServerId", $root)->item(0)->nodeValue; + $this->assertStringMatchesFormat("CRC%s", $serverId); + + // TODO: Test the content on the server + + return $syncKey, $serverId; + } + + /** + * Test updating objects from client + * + * @depends testAddFromClient + */ + public function testChangeFromClient($params) + { + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Contacts="uri:Contacts"> + <Collections> + <Collection> + <Class>Contacts</Class> + <SyncKey>{$params0}</SyncKey> + <CollectionId>Contacts::Syncroton</CollectionId> + <DeletesAsMoves/> + <GetChanges/> + <Options> + <AirSyncBase:BodyPreference> + <AirSyncBase:Type>1</AirSyncBase:Type> + <AirSyncBase:TruncationSize>5120</AirSyncBase:TruncationSize> + </AirSyncBase:BodyPreference> + <Conflict>1</Conflict> + </Options> + <Commands> + <Change> + <ServerId>{$params1}</ServerId> + <ApplicationData> + <Contacts:FirstName>First</Contacts:FirstName> + <Contacts:LastName>Last</Contacts:LastName> + </ApplicationData> + </Change> + </Commands> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $root = $xpath->query("//ns:Sync/ns:Collections/ns:Collection")->item(0); + $this->assertSame('1', $xpath->query("ns:Status", $root)->item(0)->nodeValue); + $this->assertSame(strval(++$params0), $xpath->query("ns:SyncKey", $root)->item(0)->nodeValue); + $this->assertSame(0, $xpath->query("ns:Responses", $root)->length); + + // TODO: Assert updated content on the server + + return $params; + } + + /** + * Test deleting objects from client + * + * @depends testChangeFromClient + */ + public function testDeleteFromClient($params) + { + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Contacts="uri:Contacts"> + <Collections> + <Collection> + <Class>Contacts</Class> + <SyncKey>{$params0}</SyncKey> + <CollectionId>Contacts::Syncroton</CollectionId> + <DeletesAsMoves/> + <GetChanges/> + <Options> + <AirSyncBase:BodyPreference> + <AirSyncBase:Type>1</AirSyncBase:Type> + <AirSyncBase:TruncationSize>5120</AirSyncBase:TruncationSize> + </AirSyncBase:BodyPreference> + <Conflict>1</Conflict> + </Options> + <Commands> + <Delete> + <ServerId>{$params1}</ServerId> + </Delete> + </Commands> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $root = $xpath->query("//ns:Sync/ns:Collections/ns:Collection")->item(0); + $this->assertSame('1', $xpath->query("ns:Status", $root)->item(0)->nodeValue); + $this->assertSame(strval(++$params0), $xpath->query("ns:SyncKey", $root)->item(0)->nodeValue); + $this->assertSame(0, $xpath->query("ns:Responses", $root)->length); + + // TODO: Assert deleted contact on the server + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/Sync/EmailTest.php
Added
@@ -0,0 +1,179 @@ +<?php + +namespace Tests\Sync\Sync; + +class EmailTest extends \Tests\SyncTestCase +{ + /** + * Test Sync command + */ + public function testSync() + { + $this->emptyTestFolder('INBOX', 'mail'); + $this->registerDevice(); + + // Test invalid collection identifier + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync"> + <Collections> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>1111111111</CollectionId> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('12', $xpath->query("//ns:Sync/ns:Status")->item(0)->nodeValue); + + // Test INBOX + $folderId = '38b950ebd62cd9a66929c89615d0fc04'; + $syncKey = 0; + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync"> + <Collections> + <Collection> + <SyncKey>{$syncKey}</SyncKey> + <CollectionId>{$folderId}</CollectionId> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $this->assertSame('1', $xpath->query("//ns:Sync/ns:Collections/ns:Collection/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$syncKey), $xpath->query("//ns:Sync/ns:Collections/ns:Collection/ns:SyncKey")->item(0)->nodeValue); + $this->assertSame('Email', $xpath->query("//ns:Sync/ns:Collections/ns:Collection/ns:Class")->item(0)->nodeValue); + $this->assertSame($folderId, $xpath->query("//ns:Sync/ns:Collections/ns:Collection/ns:CollectionId")->item(0)->nodeValue); + + // Test listing mail in INBOX, use WindowSize=1 + // Append two mail messages + $this->appendMail('INBOX', 'mail.sync1'); + $this->appendMail('INBOX', 'mail.sync2'); + + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"> + <Collections> + <Collection> + <SyncKey>{$syncKey}</SyncKey> + <CollectionId>{$folderId}</CollectionId> + <DeletesAsMoves>1</DeletesAsMoves> + <GetChanges>1</GetChanges> + <WindowSize>1</WindowSize> + <Options> + <FilterType>0</FilterType> + <Conflict>1</Conflict> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>2</Type> + <TruncationSize>51200</TruncationSize> + <AllOrNone>0</AllOrNone> + </BodyPreference> + </Options> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $root = "//ns:Sync/ns:Collections/ns:Collection"; + $this->assertSame('1', $xpath->query("{$root}/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$syncKey), $xpath->query("{$root}/ns:SyncKey")->item(0)->nodeValue); + $this->assertSame($folderId, $xpath->query("{$root}/ns:CollectionId")->item(0)->nodeValue); + $this->assertSame(1, $xpath->query("{$root}/ns:Commands/ns:Add")->count()); + + // Note: We assume messages are in IMAP default order, it may change in future + $root .= "/ns:Commands/ns:Add"; + $this->assertStringMatchesFormat("{$folderId}::%d", $xpath->query("{$root}/ns:ServerId")->item(0)->nodeValue); + $this->assertSame('test sync', $xpath->query("{$root}/ns:ApplicationData/Email:Subject")->item(0)->nodeValue); + + // List the rest of the mail + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"> + <Collections> + <Collection> + <SyncKey>{$syncKey}</SyncKey> + <CollectionId>{$folderId}</CollectionId> + <DeletesAsMoves>1</DeletesAsMoves> + <GetChanges>1</GetChanges> + <Options> + <FilterType>0</FilterType> + <Conflict>1</Conflict> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>2</Type> + <TruncationSize>51200</TruncationSize> + <AllOrNone>0</AllOrNone> + </BodyPreference> + </Options> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $root = "//ns:Sync/ns:Collections/ns:Collection"; + $this->assertSame('1', $xpath->query("{$root}/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$syncKey), $xpath->query("{$root}/ns:SyncKey")->item(0)->nodeValue); + $this->assertSame($folderId, $xpath->query("{$root}/ns:CollectionId")->item(0)->nodeValue); + $this->assertSame(1, $xpath->query("{$root}/ns:Commands/ns:Add")->count()); + + // Note: We assume messages are in IMAP default order, it may change in future + $root .= "/ns:Commands/ns:Add"; + $this->assertStringMatchesFormat("{$folderId}::%d", $xpath->query("{$root}/ns:ServerId")->item(0)->nodeValue); + $this->assertSame('sync test with attachment', $xpath->query("{$root}/ns:ApplicationData/Email:Subject")->item(0)->nodeValue); + + return $syncKey; + } + + /** + * Test updating message properties from client + * + * @depends testSync + */ + public function testChangeFromClient($syncKey) + { + $this->markTestIncomplete(); + } + + /** + * Test deleting messages from client + * + * @depends testChangeFromClient + */ + public function testDeleteFromClient($syncKey) + { + $this->markTestIncomplete(); + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/SyncTestCase.php
Added
@@ -0,0 +1,375 @@ +<?php + +namespace Tests; + +class SyncTestCase extends \PHPUnit\Framework\TestCase +{ + protected static ?\GuzzleHttp\Client $client; + protected static ?string $deviceId; + protected static ?string $deviceType; + protected static ?string $username; + protected static ?string $password; + protected static bool $authenticated = false; + + protected array $folders = ; + + /** + * {@inheritDoc} + */ + public function setUp(): void + { + if (empty(self::$username)) { + $this->markTestSkipped('Not setup'); + } + + self::$deviceType = null; + } + + /** + * {@inheritDoc} + */ + public static function setUpBeforeClass(): void + { + $sync = \kolab_sync::get_instance(); + $config = $sync->config; + $db = $sync->get_dbh(); + + self::$username = $config->get('activesync_test_username'); + self::$password = $config->get('activesync_test_password'); + + if (empty(self::$username)) { + return; + } + + self::$deviceId = 'test' . time(); + + $db->query('DELETE FROM syncroton_device'); + $db->query('DELETE FROM syncroton_synckey'); + $db->query('DELETE FROM syncroton_folder'); + $db->query('DELETE FROM syncroton_data'); + $db->query('DELETE FROM syncroton_data_folder'); + $db->query('DELETE FROM syncroton_modseq'); + $db->query('DELETE FROM syncroton_content'); + + self::$client = new \GuzzleHttp\Client( + 'http_errors' => false, + 'base_uri' => 'http://localhost:8000', + 'verify' => false, + 'auth' => self::$username, self::$password, + 'connect_timeout' => 10, + 'timeout' => 10, + 'headers' => + 'Content-Type' => 'application/xml; charset=utf-8', + 'Depth' => '1', + , + ); + + // TODO: execute: php -S localhost:8000 + } + + /** + * {@inheritDoc} + */ + public static function tearDownAfterClass(): void + { + if (self::$deviceId) { + $sync = \kolab_sync::get_instance(); + + if (self::$authenticated || $sync->authenticate(self::$username, self::$password)) { + $sync->password = self::$password; + + $storage = $sync->storage(); + $storage->device_delete(self::$deviceId); + } + + $db = $sync->get_dbh(); + $db->query('DELETE FROM syncroton_device'); + $db->query('DELETE FROM syncroton_synckey'); + $db->query('DELETE FROM syncroton_folder'); + } + } + + /** + * Append an email message to the IMAP folder + */ + protected function appendMail($folder, $filename, $replace = ) + { + $imap = $this->getImapStorage(); + + $source = __DIR__ . '/src/' . $filename; + + if (!file_exists($source)) { + exit("File does not exist: {$source}"); + } + + $is_file = true; + + if (!empty($replace)) { + $is_file = false; + $source = file_get_contents($source); + foreach ($replace as $token => $value) { + $source = str_replace($token, $value, $source); + } + } + + $uid = $imap->save_message($folder, $source, '', $is_file); + + if ($uid === false) { + exit("Failed to append mail into {$folder}"); + } + + return $uid; + } + + /** + * Append an DAV object to a DAV/IMAP folder + */ + protected function appendObject($foldername, $filename, $type) + { + $path = __DIR__ . '/src/' . $filename; + + if (!file_exists($path)) { + exit("File does not exist: {$path}"); + } + + $content = file_get_contents($path); + $uid = preg_match('/UID:(?:urn:uuid:)?(a-z0-9-+)/', $content, $m) ? $m1 : null; + + if (empty($uid)) { + exit("Filed to find UID in {$path}"); + } + + if ($this->isStorageDriver('kolab')) { + $imap = $this->getImapStorage(); + if ($imap->folder_exists($foldername)) { + // TODO + exit("Not implemented for Kolab v3 storage driver"); + } + + return; + } + + $dav = $this->getDavStorage(); + + foreach ($dav->get_folders($type) as $folder) { + if ($folder->get_name() === $foldername) { + $dav_type = $folder->get_dav_type(); + $location = $folder->object_location($uid); + + if ($folder->dav->create($location, $content, $dav_type) !== false) { + return; + } + } + } + + exit("Failed to append object into {$foldername}"); + } + + /** + * Delete a folder + */ + protected function deleteTestFolder($name, $type) + { + // Deleting IMAP folders + if ($type == 'mail' || $this->isStorageDriver('kolab')) { + $imap = $this->getImapStorage(); + if ($imap->folder_exists($name)) { + $imap->delete_folder($name); + } + + return; + } + + // Deleting DAV folders + $dav = $this->getDavStorage(); + + foreach ($dav->get_folders($type) as $folder) { + if ($folder->get_name() === $name) { + $dav->folder_delete($folder->id, $type); + } + } + } + + /** + * Remove all objects from a folder + */ + protected function emptyTestFolder($name, $type) + { + // Deleting in IMAP folders + if ($type == 'mail' || $this->isStorageDriver('kolab')) { + $imap = $this->getImapStorage(); + $imap->delete_message('*', $name); + return; + } + + // Deleting in DAV folders + $dav = $this->getDavStorage(); + + foreach ($dav->get_folders($type) as $folder) { + if ($folder->get_name() === $name) { + $folder->delete_all(); + } + } + } + + /** + * Convert WBXML binary content into XML + */ + protected function fromWbxml($binary) + { + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $binary); + rewind($stream); + $decoder = new \Syncroton_Wbxml_Decoder($stream); + + return $decoder->decode(); + } + + /** + * Initialize DAV storage + */ + protected function getDavStorage() + { + $sync = \kolab_sync::get_instance(); + $url = $sync->config->get('activesync_dav_server', 'http://localhost'); + + if (strpos($url, '://') === false) { + $url = 'http://' . $url; + } + + // Inject user+password to the URL, there's no other way to pass it to the DAV client + $url = str_replace('://', '://' . rawurlencode(self::$username) . ':' . rawurlencode(self::$password) . '@', $url); + + // Make sure user is authenticated + $this->getImapStorage(); + if (!empty($sync->user)) { + // required e.g. for DAV client cache use + \rcube::get_instance()->user = $sync->user; + } + + return new \kolab_storage_dav($url); + } + + /** + * Initialize IMAP storage + */ + protected function getImapStorage() + { + $sync = \kolab_sync::get_instance(); + + if (!self::$authenticated) { + if ($sync->authenticate(self::$username, self::$password)) { + self::$authenticated = true; + $sync->password = self::$password; + } + } + + return $sync->get_storage(); + } + + /** + * Check the configured activesync_storage driver + */ + protected function isStorageDriver($name) + { + return $name === \kolab_sync::get_instance()->config->get('activesync_storage', 'kolab'); + } + + /** + * Make a HTTP request to the ActiveSync server + */ + protected function request($body, $cmd, $type = 'POST') + { + $username = self::$username; + $deviceId = self::$deviceId; + $deviceType = self::$deviceType ?: 'WindowsOutlook15'; + + $body = $this->toWbxml($body); + + return self::$client->request( + $type, + "?Cmd={$cmd}&User={$username}&DeviceId={$deviceId}&DeviceType={$deviceType}", + + 'headers' => + 'Content-Type' => 'application/vnd.ms-sync.wbxml', + 'MS-ASProtocolVersion' => '14.0', + , + 'body' => $body, + + ); + } + + /** + * Register the device for tests, some commands do not work until device/folders are registered + */ + protected function registerDevice() + { + // Execute initial FolderSync, it is required before executing some commands + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <FolderSync xmlns="uri:FolderHierarchy"> + <SyncKey>0</SyncKey> + </FolderSync> + EOF; + + $response = $this->request($request, 'FolderSync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + foreach ($xpath->query("//ns:FolderSync/ns:Changes/ns:Add") as $idx => $folder) { + $serverId = $folder->getElementsByTagName('ServerId')->item(0)->nodeValue; + $displayName = $folder->getElementsByTagName('DisplayName')->item(0)->nodeValue; + $this->folders$serverId = $displayName; + } + } + + /** + * Convert XML into WBXML binary content + */ + protected function toWbxml($xml) + { + $outputStream = fopen('php://temp', 'r+'); + $encoder = new \Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3); + $dom = new \DOMDocument(); + $dom->loadXML($xml); + $encoder->encode($dom); + rewind($outputStream); + + return stream_get_contents($outputStream); + } + + /** + * Get XPath from a DOM + */ + protected function xpath($dom) + { + $xpath = new \DOMXpath($dom); + $xpath->registerNamespace("ns", $dom->documentElement->namespaceURI); + $xpath->registerNamespace("AirSync", "uri:AirSync"); + $xpath->registerNamespace("Calendar", "uri:Calendar"); + $xpath->registerNamespace("Contacts", "uri:Contacts"); + $xpath->registerNamespace("Email", "uri:Email"); + $xpath->registerNamespace("Email2", "uri:Email2"); + $xpath->registerNamespace("Settings", "uri:Settings"); + $xpath->registerNamespace("Tasks", "uri:Tasks"); + + return $xpath; + } + + + /** + * adapter for phpunit < 9 + */ + protected function assertMatchesRegularExpression($arg1, $arg2) + { + if (method_exists("PHPUnit\Framework\TestCase", "assertMatchesRegularExpression")) { + parent::assertMatchesRegularExpression($arg1, $arg2); + } else { + parent::assertRegExp($arg1, $arg2); + } + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Unit
Added
+(directory)
View file
kolab-syncroton-2.4.2.tar.gz/tests/Unit/BodyConverterTest.php
Added
@@ -0,0 +1,41 @@ +<?php + +class BodyConverterTest extends PHPUnit\Framework\TestCase +{ + public function data_html_to_text() + { + return + '', '', + '<div></div>', '', + '<div>a</div>', 'a', + '<html><head><title>title</title></head></html>', '', + ; + } + + /** + * @dataProvider data_html_to_text + */ + public function test_html_to_text($html, $text) + { + $converter = new kolab_sync_body_converter($html, Syncroton_Model_EmailBody::TYPE_HTML); + $output = $converter->convert(Syncroton_Model_EmailBody::TYPE_PLAINTEXT); + + $this->assertEquals(trim($text), trim($output)); + } + + /** + * Test RTF convertion to HTML and Plain text + */ + public function test_rtf_to_text() + { + $rtf = file_get_contents(__DIR__ . '/../src/sample.rtf'); + + $converter = new kolab_sync_body_converter($rtf, Syncroton_Model_EmailBody::TYPE_RTF); + + $output = $converter->convert(Syncroton_Model_EmailBody::TYPE_PLAINTEXT); + $this->assertStringContainsString('This is text', trim($output)); + + $output = $converter->convert(Syncroton_Model_EmailBody::TYPE_HTML); + $this->assertStringContainsString('<span style="font-family:Helvetica,sans-serif;color:#0000ff;">anchor</span>', trim($output)); + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Unit/DataCalendarTest.php
Added
@@ -0,0 +1,126 @@ +<?php + +class DataCalendarTest extends PHPUnit\Framework\TestCase +{ + /** + * Test for kolab_sync_data_calendar::from_kolab_alarm() + */ + public function test_from_kolab_alarm() + { + $obj = new kolab_sync_data_calendar_test(); + + $result = $obj->from_kolab_alarm(); + $this->assertSame(null, $result); + + $event = 'valarms' => + 'action' => 'DISPLAY', + 'trigger' => 'PT5M', + ; + $result = $obj->from_kolab_alarm($event); + $this->assertSame(null, $result); + + $event = 'valarms' => + 'action' => 'DISPLAY', + 'trigger' => '-PT5M', + ; + $result = $obj->from_kolab_alarm($event); + $this->assertSame(5, $result); + + $event = 'valarms' => + 'action' => 'DISPLAY', + 'trigger' => 'PT0M', + ; + $result = $obj->from_kolab_alarm($event); + $this->assertSame(0, $result); + + $event = 'valarms' => + 'action' => 'DISPLAY', + 'trigger' => '-PT0M', + ; + $result = $obj->from_kolab_alarm($event); + $this->assertSame(0, $result); + + $event = 'valarms' => + 'action' => 'DISPLAY', + 'trigger' => 'PT0S', + ; + $result = $obj->from_kolab_alarm($event); + $this->assertSame(0, $result); + + // alarms on specified DateTime (T2420) + + $event = + // no start datetime defined + 'valarms' => + + 'action' => 'DISPLAY', + 'trigger' => new DateTime('now + 1 hour'), + , + , + ; + $result = $obj->from_kolab_alarm($event); + $this->assertSame(null, $result); + + $event = + 'start' => new DateTime('now + 10 minutes'), + 'valarms' => + + 'action' => 'DISPLAY', + 'trigger' => new DateTime('now + 1 hour'), + , + , + ; + $result = $obj->from_kolab_alarm($event); + $this->assertSame(null, $result); + + $event = + 'start' => new DateTime('now + 60 minutes'), + 'valarms' => + + 'action' => 'DISPLAY', + 'trigger' => new DateTime('now + 50 minutes'), + , + , + ; + $result = $obj->from_kolab_alarm($event); + $this->assertSame(10, $result); + } + + /** + * Test for kolab_sync_data_calendar::to_kolab_alarm() + */ + public function test_to_kolab_alarm() + { + $obj = new kolab_sync_data_calendar_test(); + + $result = $obj->to_kolab_alarm(null, ); + $this->assertSame(, $result); + + $result = $obj->to_kolab_alarm(0, ); + $this->assertSame('-PT0M', $result0'trigger'); + + $result = $obj->to_kolab_alarm(15, ); + $this->assertSame('-PT15M', $result0'trigger'); + $this->assertSame('DISPLAY', $result0'action'); + } +} + +/** + * kolab_sync_data_calendar wrapper, so we can test protected methods too + */ +class kolab_sync_data_calendar_test extends kolab_sync_data_calendar +{ + public function __construct() + { + } + + public function from_kolab_alarm($value) + { + return parent::from_kolab_alarm($value); + } + + public function to_kolab_alarm($value, $event) + { + return parent::to_kolab_alarm($value, $event); + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Unit/DataEmailTest.php
Added
@@ -0,0 +1,37 @@ +<?php + +class DataEmailTest extends PHPUnit\Framework\TestCase +{ + /** + * Test GlobalObjId encoding/decoding + */ + public function test_globalobjid() + { + // https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-asemail/e7424ddc-dd10-431e-a0b7-5c794863370e + $input = 'BAAAAIIA4AB0xbcQGoLgCAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAAHZDYWwtVWlkAQAAAHs4MTQxMkQzQy0yQTI0LTRFOUQtQjIwRS0xMUY3QkJFOTI3OTl9AA=='; + $output = kolab_sync_data_email::decodeGlobalObjId($input); + + $this->assertSame(51, $output'bytecount'); + $this->assertSame('{81412D3C-2A24-4E9D-B20E-11F7BBE92799}', $output'uid'); + + $encoded = kolab_sync_data_email::encodeGlobalObjId($output); + $this->assertSame($encoded, $input); + + $input = 'BAAAAIIA4AB0xbcQGoLgCAfUCRDgQMnBJoXEAQAAAAAAAAAAEAAAAAvw7UtuTulOnjnjhns3jvM='; + $output = kolab_sync_data_email::decodeGlobalObjId($input); + + $this->assertSame(16, $output'bytecount'); + $this->assertSame(2004, $output'year'); + $this->assertSame(9, $output'month'); + $this->assertSame(16, $output'day'); + $this->assertSame(127373090979660000, $output'now'); + + // This is how the "now" value is interpreted + // $winSecs = (int)($output'now' / 10000000); // convert microseconds to seconds + // $unixTimestamp = ($winSecs - 11644473600); // subtract 1.1.1600 - 1.1.1970 difference in seconds + // print(date(DateTime::RFC822, $unixTimestamp)); + + $encoded = kolab_sync_data_email::encodeGlobalObjId($output); + $this->assertSame($encoded, $input); + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Unit/DataTasksTest.php
Added
@@ -0,0 +1,78 @@ +<?php + +class DataTasksTest extends PHPUnit\Framework\TestCase +{ + public function data_prio() + { + return + 0, null, + 1, 2, + 2, 2, + 3, 2, + 4, 2, + 5, 1, + 6, 0, + 7, 0, + 8, 0, + 9, 0, + // invalid input + 10, null, + ; + } + + public function data_importance() + { + return + 0, 9, + 1, 5, + 2, 1, + // invalid input + null, null, + 5, null, + ; + } + + /** + * Test for kolab_sync_data_tasks::prio_to_importance() + * @dataProvider data_prio() + */ + public function test_prio_to_importance($input, $output) + { + $data = new kolab_sync_data_tasks_test(); + $result = $data->prio_to_importance($input); + + $this->assertEquals($output, $result); + } + + /** + * Test for kolab_sync_data_tasks::importance_to_prio() + * @dataProvider data_importance() + */ + public function test_importance_to_prio($input, $output) + { + $data = new kolab_sync_data_tasks_test(); + $result = $data->importance_to_prio($input); + + $this->assertEquals($output, $result); + } +} + +/** + * kolab_sync_data_tasks wrapper, so we can test preotected methods too + */ +class kolab_sync_data_tasks_test extends kolab_sync_data_tasks +{ + public function __construct() + { + } + + public function prio_to_importance($value) + { + return parent::prio_to_importance($value); + } + + public function importance_to_prio($value) + { + return parent::importance_to_prio($value); + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Unit/DataTest.php
Added
@@ -0,0 +1,152 @@ +<?php + +class DataTest extends PHPUnit\Framework\TestCase +{ + /** + * Test for kolab_sync_data::recurrence_to_kolab() + */ + public function test_recurrence_to_kolab() + { + $xml = '<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Calendar="uri:Calendar"> + <ApplicationData> + <Calendar:Recurrence> + <Calendar:Type>0</Calendar:Type> + <Calendar:Interval>1</Calendar:Interval> + <Calendar:Until>20101128T225959Z</Calendar:Until> + </Calendar:Recurrence> + </ApplicationData> + </Sync>'; + + $xml = new SimpleXMLElement($xml); + $event = new Syncroton_Model_Event($xml->ApplicationData); + $data = new kolab_sync_data_test(); + + $result = $data->recurrence_to_kolab($event); + + $this->assertEquals('DAILY', $result'FREQ'); + $this->assertEquals(1, $result'INTERVAL'); + $this->assertEquals('20101128T225959Z', $result'UNTIL'->format("Ymd\THis\Z")); + + $xml = '<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Calendar="uri:Calendar"> + <ApplicationData> + <Calendar:Recurrence> + <Calendar:Type>1</Calendar:Type> + <Calendar:Interval>1</Calendar:Interval> + <Calendar:DayOfWeek>8</Calendar:DayOfWeek> + </Calendar:Recurrence> + </ApplicationData> + </Sync>'; + + $xml = new SimpleXMLElement($xml); + $event = new Syncroton_Model_Event($xml->ApplicationData); + + $result = $data->recurrence_to_kolab($event, null); + + $this->assertEquals('WEEKLY', $result'FREQ'); + $this->assertEquals(1, $result'INTERVAL'); + $this->assertEquals('WE', $result'BYDAY'); + } + + /** + * Test for kolab_sync_data::recurrence_from_kolab() + */ + public function test_recurrence_from_kolab() + { + $event = + 'uid' => '52A09F6151F020312D99779F86838CF5-93BC4FC398A3FD52', + '_type' => 'event', + 'priority' => 0, + 'attendees' => + + 'rsvp' => false, + 'email' => 'bartek.machniak@nestle.kolab.ch', + 'role' => 'ORGANIZER', + 'status' => 'ACCEPTED', + , + + 'name' => 'Machniak, Aleksander', + 'status' => 'ACCEPTED', + 'cutype' => 'INDIVIDUAL', + 'rsvp' => false, + 'email' => 'aleksander.machniak@nestle.kolab.ch', + , + , + 'created' => new DateTime('2023-10-20 09:49:26.000000'), + 'changed' => new DateTime('2023-10-20 09:49:34.000000'), + 'description' => 'description', + 'end' => new DateTime('2023-10-20 14:30:00.000000'), + 'start' => new DateTime('2023-10-20 14:00:00.000000'), + 'location' => '', + 'organizer' => + 'rsvp' => false, + 'email' => 'bartek.machniak@nestle.kolab.ch', + 'role' => 'ORGANIZER', + 'status' => 'ACCEPTED', + , + 'sequence' => 0, + 'status' => 'CONFIRMED', + 'free_busy' => 'busy', + 'allday' => false, + 'recurrence' => + 'FREQ' => 'WEEKLY', + 'INTERVAL' => '1', + , + ; + + $data = new kolab_sync_data_test(); + + $data->recurrence_from_kolab(null, $event, $result); + + $this->assertEquals($data::RECUR_TYPE_WEEKLY, $result'recurrence'->type); + $this->assertEquals($data::RECUR_DOW_FRIDAY, $result'recurrence'->dayOfWeek); + $this->assertEquals(1, $result'recurrence'->interval); + + $event'recurrence' = + 'FREQ' => 'MONTHLY', + 'BYMONTHDAY' => '2,15', + 'INTERVAL' => '5', + 'COUNT' => 3, + ; + + $data->recurrence_from_kolab(null, $event, $result); + + $this->assertEquals($data::RECUR_TYPE_MONTHLY, $result'recurrence'->type); + $this->assertEquals(2, $result'recurrence'->dayOfMonth); + $this->assertEquals(5, $result'recurrence'->interval); + $this->assertEquals(3, $result'recurrence'->occurrences); + + // TODO: More cases + } +} + +/** + * kolab_sync_data wrapper, so we can test preotected methods too + */ +class kolab_sync_data_test extends kolab_sync_data +{ + public function __construct() + { + } + + public function recurrence_to_kolab($data, $dummy1 = null, $dummy2 = null) + { + return parent::recurrence_to_kolab($data, null); + } + + public function recurrence_from_kolab($collection, $data, &$result, $type = 'Event') + { + return parent::recurrence_from_kolab($collection, $data, $result, $type); + } + + public function toKolab($data, $folderId, $entry = null, $timezone = null) + { + return ; + } + + public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId, $as_array = false) + { + throw new Syncroton_Exception_NotFound("Not implemented"); + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Unit/MessageTest.php
Added
@@ -0,0 +1,146 @@ +<?php + +class MessageTest extends PHPUnit\Framework\TestCase +{ + /** + * Test message parsing and headers setting + */ + public function test_headers() + { + $source = file_get_contents(TESTS_DIR . '/src/mail.plain'); + $message = new kolab_sync_message($source); + $headers = $message->headers(); + + $this->assertArrayHasKey('MIME-Version', $headers); + $this->assertCount(8, $headers); + $this->assertEquals('kolab@domain.tld', $headers'To'); + + // test set_header() + $message->set_header('to', 'test@domain.tld'); + $headers = $message->headers(); + + $this->assertCount(8, $headers); + $this->assertEquals('test@domain.tld', $headers'To'); + } + + /** + * Test message parsing + */ + public function test_source() + { + $source = file_get_contents(TESTS_DIR . '/src/mail.plain'); + $message = new kolab_sync_message($source); + $result = $message->source(); + + $this->assertEquals($source, str_replace("\r\n", "\n", $result)); + } + + /** + * Test adding attachments to the message + */ + public function test_attachment() + { + $source = file_get_contents(TESTS_DIR . '/src/mail.plain'); + $mixed = file_get_contents(TESTS_DIR . '/src/mail.plain.mixed'); + $mixed2 = file_get_contents(TESTS_DIR . '/src/mail.mixed'); + + // test adding attachment to text/plain message + $message = new kolab_sync_message($source); + $message->add_attachment('aaa', + 'content_type' => 'text/plain', + 'encoding' => '8bit', + ); + + $result = $message->source(); + $result = str_replace("\r\n", "\n", $result); + if (preg_match('/boundary="(^"+)"/', $result, $m)) { + $mixed = str_replace('BOUNDARY', $m1, $mixed); + } + + $this->assertEquals($mixed, $result); + + // test adding attachment to multipart/mixed message + $message = new kolab_sync_message($mixed); + $message->add_attachment('aaa', + 'content_type' => 'text/plain', + 'encoding' => 'base64', + ); + + $result = $message->source(); + $result = str_replace("\r\n", "\n", $result); + if (preg_match('/boundary="(^"+)"/', $result, $m)) { + $mixed2 = str_replace('BOUNDARY', $m1, $mixed2); + } + + $this->assertEquals($mixed2, $result); + } + + /** + * Test appending a text to the message + */ + public function test_append() + { + // test appending text to text/plain message + $source = file_get_contents(TESTS_DIR . '/src/mail.plain'); + $append = file_get_contents(TESTS_DIR . '/src/mail.plain.append'); + + $message = new kolab_sync_message($source); + $message->append('a'); + + $result = $message->source(); + $result = str_replace("\r\n", "\n", $result); + $this->assertEquals($append, $result); + } + + /** + * Test recoding the message + */ + public function test_recode_message_1() + { + $source = file_get_contents(TESTS_DIR . '/src/mail.recode1'); + $result = file_get_contents(TESTS_DIR . '/src/mail.recode1.out'); + + $message = kolab_sync_message::recode_message($source); + + $this->assertEquals($result, $message); + } + + /** + * Test recoding the message + */ + public function test_recode_message_2() + { + $source = file_get_contents(TESTS_DIR . '/src/mail.recode2'); + $result = file_get_contents(TESTS_DIR . '/src/mail.recode2.out'); + + $message = kolab_sync_message::recode_message($source); + + $this->assertEquals($result, $message); + } + + /** + * Test recoding the message + */ + public function test_recode_message_3() + { + $source = file_get_contents(TESTS_DIR . '/src/mail.recode3'); + $result = file_get_contents(TESTS_DIR . '/src/mail.recode3.out'); + + $message = kolab_sync_message::recode_message($source); + + $this->assertEquals($result, $message); + } + + /** + * Test recoding the message + */ + public function test_recode_message_4() + { + $source = file_get_contents(TESTS_DIR . '/src/mail.recode4'); + $result = file_get_contents(TESTS_DIR . '/src/mail.recode4.out'); + + $message = kolab_sync_message::recode_message($source); + + $this->assertEquals($result, $message); + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Unit/TimezoneConverterTest.php
Added
@@ -0,0 +1,174 @@ +<?php + +class TimezoneConverterTest extends PHPUnit\Framework\TestCase +{ + public function test_list_timezones() + { + // date_default_timezone_set('America/Los_Angeles'); + + $converter = kolab_sync_timezone_converter::getInstance(); + $output = $converter->getListOfTimezones('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAEAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAFAAEAAAAAAAAAxP///w=='); + + $this->assertTrue(is_array($output)); + + $converter = kolab_sync_timezone_converter::getInstance(); + $output = $converter->getListOfTimezones('xP///0MAZQBuAHQAcgBhAGwAIABFAHUAcgBvAHAAZQAgAFMAdABhAG4AZABhAHIAZAAgAFQAaQBtAGUAAAAAAAAAAAAAAAoAAAAFAAMAAAAAAAAAAAAAAEMAZQBuAHQAcgBhAGwAIABFAHUAcgBvAHAAZQAgAEQAYQB5AGwAaQBnAGgAdAAgAFQAaQBtAGUAAAAAAAAAAAAAAAMAAAAFAAIAAAAAAAAAxP///w=='); + + $this->assertTrue(is_array($output)); + $this->assertTrue(isset($output'Europe/Warsaw')); + + $converter = kolab_sync_timezone_converter::getInstance(); + $output = $converter->getListOfTimezones('4AEAAFAAYQBjAGkAZgBpAGMAIABTAHQAYQBuAGQAYQByAGQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAFAAYQBjAGkAZgBpAGMAIABEAGEAeQBsAGkAZwBoAHQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///w=='); + + $this->assertTrue(is_array($output)); + $this->assertTrue(isset($output'America/Los_Angeles')); + + $converter = kolab_sync_timezone_converter::getInstance(); + $output = $converter->getListOfTimezones('Lv///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='); + + $this->assertTrue(is_array($output)); + $this->assertTrue(isset($output'Asia/Tehran')); + + // As seen in outlook + $converter = kolab_sync_timezone_converter::getInstance(); + $output = $converter->getListOfTimezones('Lv///0kAcgBhAG4AIABTAHQAYQBuAGQAYQByAGQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkABAADABcAOwA7AOcDAAAAAEkAcgBhAG4AIABEAGEAeQBsAGkAZwBoAHQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAwAEAAAAAAAAAAAAxP///w=='); + + $this->assertTrue(is_array($output)); + $this->assertTrue(isset($output'Asia/Tehran')); + } + + public function data_get_timezone() + { + return + 'UTC', + 'Europe/Warsaw', + 'Europe/Zurich', + 'America/Los_Angeles', + 'Asia/Tehran', + ; + } + + /** + * @dataProvider data_get_timezone + */ + public function test_get_timezone($tzName) + { + date_default_timezone_set('America/Los_Angeles'); + + $converter = kolab_sync_timezone_converter::getInstance(); + $datetime = '2017-01-01T12:00:00Z'; + + $offsets = $converter->getOffsetsForTimezone($tzName, $datetime); + $output = $converter->getTimezone($offsets, $tzName); + $this->assertSame($tzName, $output); + } + + public function test_get_offsets_for_timezone() + { + date_default_timezone_set('America/Los_Angeles'); + + $converter = kolab_sync_timezone_converter::getInstance(); + $datetime = '2017-01-01T12:00:00Z'; + + $output = $converter->getOffsetsForTimezone('UTC', $datetime); + + $this->assertSame($output'bias', 0); + $this->assertSame($output'standardBias', 0); + $this->assertSame($output'daylightBias', 0); + $this->assertSame($output'standardMonth', 0); + $this->assertSame($output'daylightMonth', 0); + + $output = $converter->getOffsetsForTimezone('Europe/Warsaw', $datetime); + + $this->assertSame($output'standardBias', 0); + $this->assertSame($output'standardMonth', 10); + $this->assertSame($output'standardWeek', 5); + $this->assertSame($output'standardHour', 3); + $this->assertSame($output'daylightBias', -60); + $this->assertSame($output'daylightMonth', 3); + $this->assertSame($output'daylightWeek', 5); + $this->assertSame($output'daylightHour', 2); + + $output = $converter->getOffsetsForTimezone('America/Los_Angeles', $datetime); + + $this->assertSame($output'bias', 480); + $this->assertSame($output'standardBias', 0); + $this->assertSame($output'standardMonth', 11); + $this->assertSame($output'standardWeek', 1); + $this->assertSame($output'standardHour', 2); + $this->assertSame($output'daylightBias', -60); + $this->assertSame($output'daylightMonth', 3); + $this->assertSame($output'daylightWeek', 2); + $this->assertSame($output'daylightHour', 2); + + $output = $converter->getOffsetsForTimezone('Atlantic/Azores', $datetime); + + $this->assertSame($output'bias', 60); + $this->assertSame($output'standardBias', 0); + $this->assertSame($output'standardMonth', 10); + $this->assertSame($output'standardWeek', 5); + $this->assertSame($output'standardHour', 1); + $this->assertSame($output'daylightBias', -60); + $this->assertSame($output'daylightMonth', 3); + $this->assertSame($output'daylightWeek', 5); + $this->assertSame($output'daylightHour', 0); + + //Check before dst change + $output = $converter->getOffsetsForTimezone('Asia/Tehran', $datetime); + + $this->assertSame($output'bias', -210); + $this->assertSame($output'standardBias', 0); + $this->assertSame($output'standardMonth', 9); + $this->assertSame($output'standardWeek', 3); + $this->assertSame($output'standardDayOfWeek', 4); + $this->assertSame($output'standardHour', 24); + $this->assertSame($output'daylightBias', -60); + $this->assertSame($output'daylightMonth', 3); + $this->assertSame($output'daylightWeek', 4); + $this->assertSame($output'daylightDayOfWeek', 3); + $this->assertSame($output'daylightHour', 0); + + //Check after dst change + $output = $converter->getOffsetsForTimezone('Asia/Tehran', '2023-01-01T12:00:00Z'); + + $this->assertSame($output'bias', -210); + $this->assertSame($output'standardBias', 0); + $this->assertSame($output'standardMonth', 0); + $this->assertSame($output'standardWeek', 0); + $this->assertSame($output'standardDayOfWeek', 0); + $this->assertSame($output'standardHour', 0); + $this->assertSame($output'daylightBias', 0); + $this->assertSame($output'daylightMonth', 0); + $this->assertSame($output'daylightWeek', 0); + $this->assertSame($output'daylightDayOfWeek', 0); + $this->assertSame($output'daylightHour', 0); + } + + public function data_timezone_conversion() + { + return + //Pre dst change + 'Asia/Tehran', 'Lv///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAAgADABcAOwA7AOcDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAQAEAAAAAAAAAAAAxP///w==', '2021-07-01T12:00:00Z', + //Post dst change + 'Asia/Tehran', 'Lv///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==', '2023-04-01T12:00:00Z', + 'Pacific/Pago_Pago', 'lAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==', '2021-07-01T12:00:00Z', + 'Europe/Warsaw', 'xP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAFAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAFAAIAAAAAAAAAxP///w==', '2021-07-01T12:00:00Z', + ; + } + + /** + * @dataProvider data_timezone_conversion + */ + public function test_timezone_conversion($tz, $expected, $datetime) + { + $converter = kolab_sync_timezone_converter::getInstance(); + $output = $converter->encodeTimezone($tz, $datetime); + + $this->assertSame($expected, $output); + + $output = $converter->getListOfTimezones($output); + + $this->assertTrue(is_array($output)); + $this->assertTrue(isset($output$tz)); + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/Unit/WbxmlTest.php
Added
@@ -0,0 +1,909 @@ +<?php + +class WbxmlTest extends PHPUnit\Framework\TestCase +{ + //function testDecode() + //{ + // //TODO input some wbxml document + // // + // $dom = new DOMDocument(); + // $dom->loadXML($lastSyncCollection'lastXML'); + // // + // try { + // $decoder = new Syncroton_Wbxml_Decoder($dom); + // $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; + // } + // //TODO validate output + //} + + + + public function testEncode() + { + $outputStream = fopen("php://temp", 'r+'); + + $encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3); + + $xml = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks"> + <Collections> + <Collection> + <SyncKey>2</SyncKey> + <CollectionId>tasksId</CollectionId> + <Commands> + <Add> + <ClientId>clientId2</ClientId> + <ApplicationData> + <Subject xmlns="uri:Tasks">task2</Subject> + <Complete xmlns="uri:Tasks">0</Complete> + <DueDate xmlns="uri:Tasks">2020-11-04T00:00:00.000Z</DueDate> + <UtcDueDate xmlns="uri:Tasks">2020-11-03T23:00:00.000Z</UtcDueDate> + </ApplicationData> + </Add> + <Add> + <ClientId>clientId3</ClientId> + <ApplicationData> + <Subject xmlns="uri:Tasks">task3</Subject> + <Complete xmlns="uri:Tasks">0</Complete> + <DueDate xmlns="uri:Tasks">2020-11-04T00:00:00.000Z</DueDate> + <UtcDueDate xmlns="uri:Tasks">2020-11-03T23:00:00.000Z</UtcDueDate> + </ApplicationData> + </Add> + </Commands> + </Collection> + </Collections> + <WindowSize>16</WindowSize> + </Sync> + EOF; + + + $dom = new DOMDocument(); + $dom->loadXML($xml); + + $encoder->encode($dom); + + rewind($outputStream); + $output = stream_get_contents($outputStream); + // print("----"); + // print(var_export($output, true)); + // print("----"); + $this->assertEquals( + base64_decode('AwFqAEVcT0sDMgABUgN0YXNrc0lkAAFWR0wDY2xpZW50SWQyAAFdAAlgA3Rhc2syAAFKAzAAAUwDMjAyMC0xMS0wNFQwMDowMDowMC4wMDBaAAFNAzIwMjAtMTEtMDNUMjM6MDA6MDAuMDAwWgABAQEAAEdMA2NsaWVudElkMwABXQAJYAN0YXNrMwABSgMwAAFMAzIwMjAtMTEtMDRUMDA6MDA6MDAuMDAwWgABTQMyMDIwLTExLTAzVDIzOjAwOjAwLjAwMFoAAQEBAQEBAABVAzE2AAEB'), + $output + ); + } + + public function testEncodeFolderSync() + { + $outputStream = fopen("php://temp", 'r+'); + + $encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3); + + $xml = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <FolderSync xmlns="uri:FolderHierarchy" xmlns:Syncroton="uri:Syncroton" xmlns:Internal="uri:Internal"> + <Status>1</Status> + <SyncKey>1</SyncKey> + <Changes> + <Count>18</Count> + <Add> + <ServerId>2685b302b79f58d2753199545e3cb8be</ServerId> + <ParentId>0</ParentId> + <DisplayName>Test2</DisplayName> + <Type>13</Type> + </Add> + <Add> + <ServerId>9770b083c68e8584f396d15a116d6608</ServerId> + <ParentId>0</ParentId> + <DisplayName>DavidCalendar</DisplayName> + <Type>13</Type> + </Add> + <Add> + <ServerId>0f66388806743c514b8063bf0dc87486</ServerId> + <ParentId>0</ParentId> + <DisplayName>SergeyCalendar</DisplayName> + <Type>13</Type> + </Add> + <Add> + <ServerId>cca1b81c734abbcd669bea90d23e08ae</ServerId> + <ParentId>0</ParentId> + <DisplayName>Calendar</DisplayName> + <Type>8</Type> + </Add> + <Add> + <ServerId>ab1ddb4ef8e8f8fcc2c9f5a7f9062452</ServerId> + <ParentId>0</ParentId> + <DisplayName>PubCal</DisplayName> + <Type>13</Type> + </Add> + <Add> + <ServerId>d98bd8721371544ed095841ead941893</ServerId> + <ParentId>0</ParentId> + <DisplayName>(david) Test2</DisplayName> + <Type>13</Type> + </Add> + <Add> + <ServerId>9e7b9656ef61d4af2fb2fdcabe600079</ServerId> + <ParentId>0</ParentId> + <DisplayName>(david) DavidCalendar</DisplayName> + <Type>13</Type> + </Add> + <Add> + <ServerId>384cf2d877c39a622fdc2a16898052e2</ServerId> + <ParentId>0</ParentId> + <DisplayName>(david) Calendar</DisplayName> + <Type>13</Type> + </Add> + <Add> + <ServerId>Contacts::Syncroton</ServerId> + <ParentId>0</ParentId> + <DisplayName>Contacts</DisplayName> + <Type>9</Type> + </Add> + <Add> + <ServerId>1bb8c55fe84d52c6968db2571f7dc124</ServerId> + <ParentId>0</ParentId> + <DisplayName>Archive</DisplayName> + <Type>12</Type> + </Add> + <Add> + <ServerId>b51abe73e9e98fe200a4afe409050502</ServerId> + <ParentId>38b950ebd62cd9a66929c89615d0fc04</ParentId> + <DisplayName>Spam</DisplayName> + <Type>12</Type> + </Add> + <Add> + <ServerId>cf529c792fc87d1f207435b3921bb02e</ServerId> + <ParentId>0</ParentId> + <DisplayName>Sent</DisplayName> + <Type>5</Type> + </Add> + <Add> + <ServerId>715ed9ea29b8a5377a69c1f758037c65</ServerId> + <ParentId>0</ParentId> + <DisplayName>Spam</DisplayName> + <Type>12</Type> + </Add> + <Add> + <ServerId>db0d959a3aeb21757f8849a830947a7a</ServerId> + <ParentId>0</ParentId> + <DisplayName>Trash</DisplayName> + <Type>4</Type> + </Add> + <Add> + <ServerId>5ac9ec2e1a9d99e2e10cabe4abf26729</ServerId> + <ParentId>0</ParentId> + <DisplayName>Drafts</DisplayName> + <Type>3</Type> + </Add> + <Add> + <ServerId>38b950ebd62cd9a66929c89615d0fc04</ServerId> + <ParentId>0</ParentId> + <DisplayName>INBOX</DisplayName> + <Type>2</Type> + </Add> + <Add> + <ServerId>fc56f4c7ffe0aefa622db9f8d9186c4a</ServerId> + <ParentId>0</ParentId> + <DisplayName>Notes</DisplayName> + <Type>10</Type> + </Add> + <Add> + <ServerId>90335880f65deff6e521acea2b71a773</ServerId> + <ParentId>0</ParentId> + <DisplayName>Tasks</DisplayName> + <Type>7</Type> + </Add> + </Changes> + </FolderSync> + EOF; + + + $dom = new DOMDocument(); + $dom->loadXML($xml); + + $encoder->encode($dom); + + rewind($outputStream); + $output = stream_get_contents($outputStream); + // print("----"); + // print(var_export(base64_encode($output), true)); + // print("----"); + $this->assertEquals( + base64_decode('AwFqAAAHVkwDMQABUgMxAAFOVwMxOAABT0gDMjY4NWIzMDJiNzlmNThkMjc1MzE5OTU0NWUzY2I4YmUAAUkDMAABRwNUZXN0MgABSgMxMwABAU9IAzk3NzBiMDgzYzY4ZTg1ODRmMzk2ZDE1YTExNmQ2NjA4AAFJAzAAAUcDRGF2aWRDYWxlbmRhcgABSgMxMwABAU9IAzBmNjYzODg4MDY3NDNjNTE0YjgwNjNiZjBkYzg3NDg2AAFJAzAAAUcDU2VyZ2V5Q2FsZW5kYXIAAUoDMTMAAQFPSANjY2ExYjgxYzczNGFiYmNkNjY5YmVhOTBkMjNlMDhhZQABSQMwAAFHA0NhbGVuZGFyAAFKAzgAAQFPSANhYjFkZGI0ZWY4ZThmOGZjYzJjOWY1YTdmOTA2MjQ1MgABSQMwAAFHA1B1YkNhbAABSgMxMwABAU9IA2Q5OGJkODcyMTM3MTU0NGVkMDk1ODQxZWFkOTQxODkzAAFJAzAAAUcDKGRhdmlkKSBUZXN0MgABSgMxMwABAU9IAzllN2I5NjU2ZWY2MWQ0YWYyZmIyZmRjYWJlNjAwMDc5AAFJAzAAAUcDKGRhdmlkKSBEYXZpZENhbGVuZGFyAAFKAzEzAAEBT0gDMzg0Y2YyZDg3N2MzOWE2MjJmZGMyYTE2ODk4MDUyZTIAAUkDMAABRwMoZGF2aWQpIENhbGVuZGFyAAFKAzEzAAEBT0gDQ29udGFjdHM6OlN5bmNyb3RvbgABSQMwAAFHA0NvbnRhY3RzAAFKAzkAAQFPSAMxYmI4YzU1ZmU4NGQ1MmM2OTY4ZGIyNTcxZjdkYzEyNAABSQMwAAFHA0FyY2hpdmUAAUoDMTIAAQFPSANiNTFhYmU3M2U5ZTk4ZmUyMDBhNGFmZTQwOTA1MDUwMgABSQMzOGI5NTBlYmQ2MmNkOWE2NjkyOWM4OTYxNWQwZmMwNAABRwNTcGFtAAFKAzEyAAEBT0gDY2Y1MjljNzkyZmM4N2QxZjIwNzQzNWIzOTIxYmIwMmUAAUkDMAABRwNTZW50AAFKAzUAAQFPSAM3MTVlZDllYTI5YjhhNTM3N2E2OWMxZjc1ODAzN2M2NQABSQMwAAFHA1NwYW0AAUoDMTIAAQFPSANkYjBkOTU5YTNhZWIyMTc1N2Y4ODQ5YTgzMDk0N2E3YQABSQMwAAFHA1RyYXNoAAFKAzQAAQFPSAM1YWM5ZWMyZTFhOWQ5OWUyZTEwY2FiZTRhYmYyNjcyOQABSQMwAAFHA0RyYWZ0cwABSgMzAAEBT0gDMzhiOTUwZWJkNjJjZDlhNjY5MjljODk2MTVkMGZjMDQAAUkDMAABRwNJTkJPWAABSgMyAAEBT0gDZmM1NmY0YzdmZmUwYWVmYTYyMmRiOWY4ZDkxODZjNGEAAUkDMAABRwNOb3RlcwABSgMxMAABAU9IAzkwMzM1ODgwZjY1ZGVmZjZlNTIxYWNlYTJiNzFhNzczAAFJAzAAAUcDVGFza3MAAUoDNwABAQEB'), + $output + ); + } + + public function testEncodeCalendar() + { + $outputStream = fopen("php://temp", 'r+'); + + $encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3); + + $xml = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Calendar="uri:Calendar"> + <Collections> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>38b950ebd62cd9a66929c89615d0fc04</CollectionId> + <DeletesAsMoves>0</DeletesAsMoves> + <GetChanges>0</GetChanges> + <WindowSize>512</WindowSize> + <Options> + <FilterType>0</FilterType> + <MIMESupport>2</MIMESupport> + <MIMETruncation>8</MIMETruncation> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>4</Type> + <AllOrNone>1</AllOrNone> + </BodyPreference> + </Options> + </Collection> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>cca1b81c734abbcd669bea90d23e08ae</CollectionId> + <Supported> + <DtStamp xmlns="uri:Calendar"/> + <Categories xmlns="uri:Calendar"/> + <Sensitivity xmlns="uri:Calendar"/> + <BusyStatus xmlns="uri:Calendar"/> + <UID xmlns="uri:Calendar"/> + <Timezone xmlns="uri:Calendar"/> + <StartTime xmlns="uri:Calendar"/> + <Subject xmlns="uri:Calendar"/> + <Location xmlns="uri:Calendar"/> + <EndTime xmlns="uri:Calendar"/> + <Recurrence xmlns="uri:Calendar"/> + <AllDayEvent xmlns="uri:Calendar"/> + <Reminder xmlns="uri:Calendar"/> + <Exceptions xmlns="uri:Calendar"/> + <Attendees xmlns="uri:Calendar"/> + <MeetingStatus xmlns="uri:Calendar"/> + <ResponseRequested xmlns="uri:Calendar"/> + <DisallowNewTimeProposal xmlns="uri:Calendar"/> + </Supported> + <DeletesAsMoves>0</DeletesAsMoves> + <GetChanges>0</GetChanges> + <WindowSize>512</WindowSize> + <Options> + <FilterType>0</FilterType> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>1</Type> + <AllOrNone>1</AllOrNone> + </BodyPreference> + </Options> + </Collection> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>Contacts::Syncroton</CollectionId> + <DeletesAsMoves>0</DeletesAsMoves> + <GetChanges>0</GetChanges> + <WindowSize>512</WindowSize> + <Options> + <FilterType>0</FilterType> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>1</Type> + <AllOrNone>1</AllOrNone> + </BodyPreference> + </Options> + </Collection> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>db0d959a3aeb21757f8849a830947a7a</CollectionId> + <DeletesAsMoves>0</DeletesAsMoves> + <GetChanges>0</GetChanges> + <WindowSize>512</WindowSize> + <Options> + <FilterType>0</FilterType> + <MIMESupport>2</MIMESupport> + <MIMETruncation>8</MIMETruncation> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>4</Type> + <AllOrNone>1</AllOrNone> + </BodyPreference> + </Options> + </Collection> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>cf529c792fc87d1f207435b3921bb02e</CollectionId> + <DeletesAsMoves>0</DeletesAsMoves> + <GetChanges>0</GetChanges> + <WindowSize>512</WindowSize> + <Options> + <FilterType>0</FilterType> + <MIMESupport>2</MIMESupport> + <MIMETruncation>8</MIMETruncation> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>4</Type> + <AllOrNone>1</AllOrNone> + </BodyPreference> + </Options> + </Collection> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>90335880f65deff6e521acea2b71a773</CollectionId> + <DeletesAsMoves>0</DeletesAsMoves> + <GetChanges>0</GetChanges> + <WindowSize>512</WindowSize> + <Options> + <FilterType>0</FilterType> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>1</Type> + <AllOrNone>1</AllOrNone> + </BodyPreference> + </Options> + </Collection> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>1bb8c55fe84d52c6968db2571f7dc124</CollectionId> + <DeletesAsMoves>0</DeletesAsMoves> + <GetChanges>0</GetChanges> + <WindowSize>512</WindowSize> + <Options> + <FilterType>0</FilterType> + <MIMESupport>2</MIMESupport> + <MIMETruncation>8</MIMETruncation> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>4</Type> + <AllOrNone>1</AllOrNone> + </BodyPreference> + </Options> + </Collection> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>715ed9ea29b8a5377a69c1f758037c65</CollectionId> + <DeletesAsMoves>0</DeletesAsMoves> + <GetChanges>0</GetChanges> + <WindowSize>512</WindowSize> + <Options> + <FilterType>0</FilterType> + <MIMESupport>2</MIMESupport> + <MIMETruncation>8</MIMETruncation> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>4</Type> + <AllOrNone>1</AllOrNone> + </BodyPreference> + </Options> + </Collection> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>b51abe73e9e98fe200a4afe409050502</CollectionId> + <DeletesAsMoves>0</DeletesAsMoves> + <GetChanges>0</GetChanges> + <WindowSize>512</WindowSize> + <Options> + <FilterType>0</FilterType> + <MIMESupport>2</MIMESupport> + <MIMETruncation>8</MIMETruncation> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>4</Type> + <AllOrNone>1</AllOrNone> + </BodyPreference> + </Options> + </Collection> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>0f66388806743c514b8063bf0dc87486</CollectionId> + <Supported> + <DtStamp xmlns="uri:Calendar"/> + <Categories xmlns="uri:Calendar"/> + <Sensitivity xmlns="uri:Calendar"/> + <BusyStatus xmlns="uri:Calendar"/> + <UID xmlns="uri:Calendar"/> + <Timezone xmlns="uri:Calendar"/> + <StartTime xmlns="uri:Calendar"/> + <Subject xmlns="uri:Calendar"/> + <Location xmlns="uri:Calendar"/> + <EndTime xmlns="uri:Calendar"/> + <Recurrence xmlns="uri:Calendar"/> + <AllDayEvent xmlns="uri:Calendar"/> + <Reminder xmlns="uri:Calendar"/> + <Exceptions xmlns="uri:Calendar"/> + <Attendees xmlns="uri:Calendar"/> + <MeetingStatus xmlns="uri:Calendar"/> + <ResponseRequested xmlns="uri:Calendar"/> + <DisallowNewTimeProposal xmlns="uri:Calendar"/> + </Supported> + <DeletesAsMoves>0</DeletesAsMoves> + <GetChanges>0</GetChanges> + <WindowSize>512</WindowSize> + <Options> + <FilterType>0</FilterType> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>1</Type> + <AllOrNone>1</AllOrNone> + </BodyPreference> + </Options> + </Collection> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>2685b302b79f58d2753199545e3cb8be</CollectionId> + <Supported> + <DtStamp xmlns="uri:Calendar"/> + <Categories xmlns="uri:Calendar"/> + <Sensitivity xmlns="uri:Calendar"/> + <BusyStatus xmlns="uri:Calendar"/> + <UID xmlns="uri:Calendar"/> + <Timezone xmlns="uri:Calendar"/> + <StartTime xmlns="uri:Calendar"/> + <Subject xmlns="uri:Calendar"/> + <Location xmlns="uri:Calendar"/> + <EndTime xmlns="uri:Calendar"/> + <Recurrence xmlns="uri:Calendar"/> + <AllDayEvent xmlns="uri:Calendar"/> + <Reminder xmlns="uri:Calendar"/> + <Exceptions xmlns="uri:Calendar"/> + <Attendees xmlns="uri:Calendar"/> + <MeetingStatus xmlns="uri:Calendar"/> + <ResponseRequested xmlns="uri:Calendar"/> + <DisallowNewTimeProposal xmlns="uri:Calendar"/> + </Supported> + <DeletesAsMoves>0</DeletesAsMoves> + <GetChanges>0</GetChanges> + <WindowSize>512</WindowSize> + <Options> + <FilterType>0</FilterType> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>1</Type> + <AllOrNone>1</AllOrNone> + </BodyPreference> + </Options> + </Collection> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>384cf2d877c39a622fdc2a16898052e2</CollectionId> + <Supported> + <DtStamp xmlns="uri:Calendar"/> + <Categories xmlns="uri:Calendar"/> + <Sensitivity xmlns="uri:Calendar"/> + <BusyStatus xmlns="uri:Calendar"/> + <UID xmlns="uri:Calendar"/> + <Timezone xmlns="uri:Calendar"/> + <StartTime xmlns="uri:Calendar"/> + <Subject xmlns="uri:Calendar"/> + <Location xmlns="uri:Calendar"/> + <EndTime xmlns="uri:Calendar"/> + <Recurrence xmlns="uri:Calendar"/> + <AllDayEvent xmlns="uri:Calendar"/> + <Reminder xmlns="uri:Calendar"/> + <Exceptions xmlns="uri:Calendar"/> + <Attendees xmlns="uri:Calendar"/> + <MeetingStatus xmlns="uri:Calendar"/> + <ResponseRequested xmlns="uri:Calendar"/> + <DisallowNewTimeProposal xmlns="uri:Calendar"/> + </Supported> + <DeletesAsMoves>0</DeletesAsMoves> + <GetChanges>0</GetChanges> + <WindowSize>512</WindowSize> + <Options> + <FilterType>0</FilterType> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>1</Type> + <AllOrNone>1</AllOrNone> + </BodyPreference> + </Options> + </Collection> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>9770b083c68e8584f396d15a116d6608</CollectionId> + <Supported> + <DtStamp xmlns="uri:Calendar"/> + <Categories xmlns="uri:Calendar"/> + <Sensitivity xmlns="uri:Calendar"/> + <BusyStatus xmlns="uri:Calendar"/> + <UID xmlns="uri:Calendar"/> + <Timezone xmlns="uri:Calendar"/> + <StartTime xmlns="uri:Calendar"/> + <Subject xmlns="uri:Calendar"/> + <Location xmlns="uri:Calendar"/> + <EndTime xmlns="uri:Calendar"/> + <Recurrence xmlns="uri:Calendar"/> + <AllDayEvent xmlns="uri:Calendar"/> + <Reminder xmlns="uri:Calendar"/> + <Exceptions xmlns="uri:Calendar"/> + <Attendees xmlns="uri:Calendar"/> + <MeetingStatus xmlns="uri:Calendar"/> + <ResponseRequested xmlns="uri:Calendar"/> + <DisallowNewTimeProposal xmlns="uri:Calendar"/> + </Supported> + <DeletesAsMoves>0</DeletesAsMoves> + <GetChanges>0</GetChanges> + <WindowSize>512</WindowSize> + <Options> + <FilterType>0</FilterType> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>1</Type> + <AllOrNone>1</AllOrNone> + </BodyPreference> + </Options> + </Collection> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>9e7b9656ef61d4af2fb2fdcabe600079</CollectionId> + <Supported> + <DtStamp xmlns="uri:Calendar"/> + <Categories xmlns="uri:Calendar"/> + <Sensitivity xmlns="uri:Calendar"/> + <BusyStatus xmlns="uri:Calendar"/> + <UID xmlns="uri:Calendar"/> + <Timezone xmlns="uri:Calendar"/> + <StartTime xmlns="uri:Calendar"/> + <Subject xmlns="uri:Calendar"/> + <Location xmlns="uri:Calendar"/> + <EndTime xmlns="uri:Calendar"/> + <Recurrence xmlns="uri:Calendar"/> + <AllDayEvent xmlns="uri:Calendar"/> + <Reminder xmlns="uri:Calendar"/> + <Exceptions xmlns="uri:Calendar"/> + <Attendees xmlns="uri:Calendar"/> + <MeetingStatus xmlns="uri:Calendar"/> + <ResponseRequested xmlns="uri:Calendar"/> + <DisallowNewTimeProposal xmlns="uri:Calendar"/> + </Supported> + <DeletesAsMoves>0</DeletesAsMoves> + <GetChanges>0</GetChanges> + <WindowSize>512</WindowSize> + <Options> + <FilterType>0</FilterType> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>1</Type> + <AllOrNone>1</AllOrNone> + </BodyPreference> + </Options> + </Collection> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>ab1ddb4ef8e8f8fcc2c9f5a7f9062452</CollectionId> + <Supported> + <DtStamp xmlns="uri:Calendar"/> + <Categories xmlns="uri:Calendar"/> + <Sensitivity xmlns="uri:Calendar"/> + <BusyStatus xmlns="uri:Calendar"/> + <UID xmlns="uri:Calendar"/> + <Timezone xmlns="uri:Calendar"/> + <StartTime xmlns="uri:Calendar"/> + <Subject xmlns="uri:Calendar"/> + <Location xmlns="uri:Calendar"/> + <EndTime xmlns="uri:Calendar"/> + <Recurrence xmlns="uri:Calendar"/> + <AllDayEvent xmlns="uri:Calendar"/> + <Reminder xmlns="uri:Calendar"/> + <Exceptions xmlns="uri:Calendar"/> + <Attendees xmlns="uri:Calendar"/> + <MeetingStatus xmlns="uri:Calendar"/> + <ResponseRequested xmlns="uri:Calendar"/> + <DisallowNewTimeProposal xmlns="uri:Calendar"/> + </Supported> + <DeletesAsMoves>0</DeletesAsMoves> + <GetChanges>0</GetChanges> + <WindowSize>512</WindowSize> + <Options> + <FilterType>0</FilterType> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>1</Type> + <AllOrNone>1</AllOrNone> + </BodyPreference> + </Options> + </Collection> + <Collection> + <SyncKey>0</SyncKey> + <CollectionId>d98bd8721371544ed095841ead941893</CollectionId> + <Supported> + <DtStamp xmlns="uri:Calendar"/> + <Categories xmlns="uri:Calendar"/> + <Sensitivity xmlns="uri:Calendar"/> + <BusyStatus xmlns="uri:Calendar"/> + <UID xmlns="uri:Calendar"/> + <Timezone xmlns="uri:Calendar"/> + <StartTime xmlns="uri:Calendar"/> + <Subject xmlns="uri:Calendar"/> + <Location xmlns="uri:Calendar"/> + <EndTime xmlns="uri:Calendar"/> + <Recurrence xmlns="uri:Calendar"/> + <AllDayEvent xmlns="uri:Calendar"/> + <Reminder xmlns="uri:Calendar"/> + <Exceptions xmlns="uri:Calendar"/> + <Attendees xmlns="uri:Calendar"/> + <MeetingStatus xmlns="uri:Calendar"/> + <ResponseRequested xmlns="uri:Calendar"/> + <DisallowNewTimeProposal xmlns="uri:Calendar"/> + </Supported> + <DeletesAsMoves>0</DeletesAsMoves> + <GetChanges>0</GetChanges> + <WindowSize>512</WindowSize> + <Options> + <FilterType>0</FilterType> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>1</Type> + <AllOrNone>1</AllOrNone> + </BodyPreference> + </Options> + </Collection> + </Collections> + <WindowSize>16</WindowSize> + </Sync> + EOF; + + + $dom = new DOMDocument(); + $dom->loadXML($xml); + + $encoder->encode($dom); + + rewind($outputStream); + $output = stream_get_contents($outputStream); + // print("----"); + // print(var_export(base64_encode($output), true)); + // print("----"); + + $this->assertEquals( + base64_decode('AwFqAEVcT0sDMAABUgMzOGI5NTBlYmQ2MmNkOWE2NjkyOWM4OTYxNWQwZmMwNAABXgMwAAFTAzAAAVUDNTEyAAFXWAMwAAFiAzIAAWMDOAABABFFRgM0AAFIAzEAAQEBAQAAT0sDMAABUgNjY2ExYjgxYzczNGFiYmNkNjY5YmVhOTBkMjNlMDhhZQABYAAEEQ4lDSgFJyYXEhsGJBQHGDQzAQAAXgMwAAFTAzAAAVUDNTEyAAFXWAMwAAEAEUVGAzEAAUgDMQABAQEBAABPSwMwAAFSA0NvbnRhY3RzOjpTeW5jcm90b24AAV4DMAABUwMwAAFVAzUxMgABV1gDMAABABFFRgMxAAFIAzEAAQEBAQAAT0sDMAABUgNkYjBkOTU5YTNhZWIyMTc1N2Y4ODQ5YTgzMDk0N2E3YQABXgMwAAFTAzAAAVUDNTEyAAFXWAMwAAFiAzIAAWMDOAABABFFRgM0AAFIAzEAAQEBAQAAT0sDMAABUgNjZjUyOWM3OTJmYzg3ZDFmMjA3NDM1YjM5MjFiYjAyZQABXgMwAAFTAzAAAVUDNTEyAAFXWAMwAAFiAzIAAWMDOAABABFFRgM0AAFIAzEAAQEBAQAAT0sDMAABUgM5MDMzNTg4MGY2NWRlZmY2ZTUyMWFjZWEyYjcxYTc3MwABXgMwAAFTAzAAAVUDNTEyAAFXWAMwAAEAEUVGAzEAAUgDMQABAQEBAABPSwMwAAFSAzFiYjhjNTVmZTg0ZDUyYzY5NjhkYjI1NzFmN2RjMTI0AAFeAzAAAVMDMAABVQM1MTIAAVdYAzAAAWIDMgABYwM4AAEAEUVGAzQAAUgDMQABAQEBAABPSwMwAAFSAzcxNWVkOWVhMjliOGE1Mzc3YTY5YzFmNzU4MDM3YzY1AAFeAzAAAVMDMAABVQM1MTIAAVdYAzAAAWIDMgABYwM4AAEAEUVGAzQAAUgDMQABAQEBAABPSwMwAAFSA2I1MWFiZTczZTllOThmZTIwMGE0YWZlNDA5MDUwNTAyAAFeAzAAAVMDMAABVQM1MTIAAVdYAzAAAWIDMgABYwM4AAEAEUVGAzQAAUgDMQABAQEBAABPSwMwAAFSAzBmNjYzODg4MDY3NDNjNTE0YjgwNjNiZjBkYzg3NDg2AAFgAAQRDiUNKAUnJhcSGwYkFAcYNDMBAABeAzAAAVMDMAABVQM1MTIAAVdYAzAAAQARRUYDMQABSAMxAAEBAQEAAE9LAzAAAVIDMjY4NWIzMDJiNzlmNThkMjc1MzE5OTU0NWUzY2I4YmUAAWAABBEOJQ0oBScmFxIbBiQUBxg0MwEAAF4DMAABUwMwAAFVAzUxMgABV1gDMAABABFFRgMxAAFIAzEAAQEBAQAAT0sDMAABUgMzODRjZjJkODc3YzM5YTYyMmZkYzJhMTY4OTgwNTJlMgABYAAEEQ4lDSgFJyYXEhsGJBQHGDQzAQAAXgMwAAFTAzAAAVUDNTEyAAFXWAMwAAEAEUVGAzEAAUgDMQABAQEBAABPSwMwAAFSAzk3NzBiMDgzYzY4ZTg1ODRmMzk2ZDE1YTExNmQ2NjA4AAFgAAQRDiUNKAUnJhcSGwYkFAcYNDMBAABeAzAAAVMDMAABVQM1MTIAAVdYAzAAAQARRUYDMQABSAMxAAEBAQEAAE9LAzAAAVIDOWU3Yjk2NTZlZjYxZDRhZjJmYjJmZGNhYmU2MDAwNzkAAWAABBEOJQ0oBScmFxIbBiQUBxg0MwEAAF4DMAABUwMwAAFVAzUxMgABV1gDMAABABFFRgMxAAFIAzEAAQEBAQAAT0sDMAABUgNhYjFkZGI0ZWY4ZThmOGZjYzJjOWY1YTdmOTA2MjQ1MgABYAAEEQ4lDSgFJyYXEhsGJBQHGDQzAQAAXgMwAAFTAzAAAVUDNTEyAAFXWAMwAAEAEUVGAzEAAUgDMQABAQEBAABPSwMwAAFSA2Q5OGJkODcyMTM3MTU0NGVkMDk1ODQxZWFkOTQxODkzAAFgAAQRDiUNKAUnJhcSGwYkFAcYNDMBAABeAzAAAVMDMAABVQM1MTIAAVdYAzAAAQARRUYDMQABSAMxAAEBAQEBAABVAzE2AAEB'), + $output + ); + } + + public function testEncodeEmail() + { + $outputStream = fopen("php://temp", 'r+'); + + $encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3); + + $xml = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:Syncroton="uri:Syncroton" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Email="uri:Email" xmlns:Email2="uri:Email2" xmlns:Tasks="uri:Tasks"> + <Collections> + <Collection xmlns:default="uri:Email" xmlns:default1="uri:AirSyncBase"> + <Class>Email</Class> + <SyncKey>2</SyncKey> + <CollectionId>38b950ebd62cd9a66929c89615d0fc04</CollectionId> + <Status>1</Status> + <MoreAvailable/> + <Commands xmlns:default="uri:Email" xmlns:default1="uri:AirSyncBase"> + <Add xmlns:default="uri:Email" xmlns:default1="uri:AirSyncBase"> + <ServerId>38b950ebd62cd9a66929c89615d0fc04::1</ServerId> + <ApplicationData> + <Email:DateReceived xmlns="uri:Email">2023-05-06T14:51:40.000Z</Email:DateReceived> + <Email:From xmlns="uri:Email">"Mollekopf, Christian" <christian@example.ch></Email:From> + <Email:InternetCPID xmlns="uri:Email">65001</Email:InternetCPID> + <Email:Subject xmlns="uri:Email">Foobar 1</Email:Subject> + <Email:To xmlns="uri:Email">christian@example.ch</Email:To> + <Email:Read xmlns="uri:Email">0</Email:Read> + <Email:Flag xmlns="uri:Email"/> + <AirSyncBase:Body xmlns="uri:AirSyncBase"> + <AirSyncBase:Type>4</AirSyncBase:Type> + <AirSyncBase:Data>Return-Path: <christian@example.ch> + Received: from imapb010.mykolab.com (unix socket) + by imapb010.mykolab.com (Cyrus 2.5.10-49-g2e214b4-Kolab-2.5.10-8.1.el7.kolab_14) with LMTPA; + Wed, 09 Aug 2017 18:37:01 +0200 + X-Sieve: CMU Sieve 2.4 + Received: from int-mx002.mykolab.com (unknown 10.9.13.2) + by imapb010.mykolab.com (Postfix) with ESMTPS id 0A93910A25047 + for <christian@example.ch>; Wed, 9 Aug 2017 18:37:01 +0200 (CEST) + Received: from int-subm002.mykolab.com (unknown 10.9.37.2) + by int-mx002.mykolab.com (Postfix) with ESMTPS id EC06AF6E + for <christian@example.ch>; Wed, 9 Aug 2017 18:37:00 +0200 (CEST) + MIME-Version: 1.0 + Content-Type: multipart/mixed; + boundary="=_291b8e96564265636432c6d494e02322" + Date: Sat, 06 May 2023 14:41:40 + From: "Mollekopf, Christian" <christian@example.ch> + To: christian@example.ch + Subject: Foobar 1 + Message-ID: <foobar1@example.org> + + --=_291b8e96564265636432c6d494e02322 + Content-Type: multipart/alternative; + boundary="=_ceff0fd19756f45ed1295ee2069ff8e0" + + --=_ceff0fd19756f45ed1295ee2069ff8e0 + Content-Transfer-Encoding: 7bit + Content-Type: text/plain; charset=US-ASCII + + sdlkjsdjf + --=_ceff0fd19756f45ed1295ee2069ff8e0 + Content-Transfer-Encoding: quoted-printable + Content-Type: text/html; charset=UTF-8 + + <html><head><meta http-equiv=3D"Content-Type" content=3D"text/html; charset= + =3DUTF-8" /></head><body style=3D'font-size: 10pt; font-family: Verdana,Gen= + eva,sans-serif'> + <p>sdlkjsdjf</p> + + </body></html> + + --=_ceff0fd19756f45ed1295ee2069ff8e0-- + + --=_291b8e96564265636432c6d494e02322 + Content-Transfer-Encoding: base64 + Content-Type: text/plain; + name=xorg.conf + Content-Disposition: attachment; + filename=xorg.conf; + size=211 + + U2VjdGlvbiAiRGV2aWNlIgogICAgSWRlbnRpZmllciAgICAgIkRldmljZTAiCiAgICBEcml2ZXIg + ICAgIEJvYXJkTmFtZSAgICAgICJOVlMgNDIwME0iCiAgICBPcHRpb24gIk5vTG9nbyIgInRydWUi + CiAgICBPcHRpb24gIlVzZUVESUQiICJ0cnVlIgpFbmRTZWN0aW9uCg== + --=_291b8e96564265636432c6d494e02322--</AirSyncBase:Data> + </AirSyncBase:Body> + <AirSyncBase:NativeBodyType xmlns="uri:AirSyncBase">2</AirSyncBase:NativeBodyType> + <Email:MessageClass xmlns="uri:Email">IPM.Note</Email:MessageClass> + <Email:ContentClass xmlns="uri:Email">urn:content-classes:message</Email:ContentClass> + <AirSyncBase:Attachments xmlns="uri:AirSyncBase"> + <AirSyncBase:Attachment> + <AirSyncBase:DisplayName>xorg.conf</AirSyncBase:DisplayName> + <AirSyncBase:FileReference>38b950ebd62cd9a66929c89615d0fc04::5::2</AirSyncBase:FileReference> + <AirSyncBase:Method>1</AirSyncBase:Method> + <AirSyncBase:EstimatedDataSize>35100212</AirSyncBase:EstimatedDataSize> + </AirSyncBase:Attachment> + </AirSyncBase:Attachments> + </ApplicationData> + </Add> + </Commands> + </Collection> + </Collections> + </Sync> + EOF; + + $dom = new DOMDocument(); + $dom->loadXML($xml); + + $encoder->encode($dom); + + rewind($outputStream); + $output = stream_get_contents($outputStream); + // print("----"); + // print(var_export(base64_encode($output), true)); + // print("----"); + + $this->assertEquals( + base64_decode('AwFqAEVcT1ADRW1haWwAAUsDMgABUgMzOGI5NTBlYmQ2MmNkOWE2NjkyOWM4OTYxNWQwZmMwNAABTgMxAAEUVkdNAzM4Yjk1MGViZDYyY2Q5YTY2OTI5Yzg5NjE1ZDBmYzA0OjoxAAFdAAJPAzIwMjMtMDUtMDZUMTQ6NTE6NDAuMDAwWgABWAMiTW9sbGVrb3BmLCBDaHJpc3RpYW4iIDxjaHJpc3RpYW5AZXhhbXBsZS5jaD4AAXkDNjUwMDEAAVQDRm9vYmFyIDEAAVYDY2hyaXN0aWFuQGV4YW1wbGUuY2gAAVUDMAABOgARSkYDNAABSwNSZXR1cm4tUGF0aDogPGNocmlzdGlhbkBleGFtcGxlLmNoPg0KUmVjZWl2ZWQ6IGZyb20gaW1hcGIwMTAubXlrb2xhYi5jb20gKFt1bml4IHNvY2tldF0pDQogICAgICAgIGJ5IGltYXBiMDEwLm15a29sYWIuY29tIChDeXJ1cyAyLjUuMTAtNDktZzJlMjE0YjQtS29sYWItMi41LjEwLTguMS5lbDcua29sYWJfMTQpIHdpdGggTE1UUEE7DQogICAgICAgIFdlZCwgMDkgQXVnIDIwMTcgMTg6Mzc6MDEgKzAyMDANClgtU2lldmU6IENNVSBTaWV2ZSAyLjQNClJlY2VpdmVkOiBmcm9tIGludC1teDAwMi5teWtvbGFiLmNvbSAodW5rbm93biBbMTAuOS4xMy4yXSkNCiAgICAgICAgYnkgaW1hcGIwMTAubXlrb2xhYi5jb20gKFBvc3RmaXgpIHdpdGggRVNNVFBTIGlkIDBBOTM5MTBBMjUwNDcNCiAgICAgICAgZm9yIDxjaHJpc3RpYW5AZXhhbXBsZS5jaD47IFdlZCwgIDkgQXVnIDIwMTcgMTg6Mzc6MDEgKzAyMDAgKENFU1QpDQpSZWNlaXZlZDogZnJvbSBpbnQtc3VibTAwMi5teWtvbGFiLmNvbSAodW5rbm93biBbMTAuOS4zNy4yXSkNCiAgICAgICAgYnkgaW50LW14MDAyLm15a29sYWIuY29tIChQb3N0Zml4KSB3aXRoIEVTTVRQUyBpZCBFQzA2QUY2RQ0KICAgICAgICBmb3IgPGNocmlzdGlhbkBleGFtcGxlLmNoPjsgV2VkLCAgOSBBdWcgMjAxNyAxODozNzowMCArMDIwMCAoQ0VTVCkNCk1JTUUtVmVyc2lvbjogMS4wDQpDb250ZW50LVR5cGU6IG11bHRpcGFydC9taXhlZDsNCmJvdW5kYXJ5PSI9XzI5MWI4ZTk2NTY0MjY1NjM2NDMyYzZkNDk0ZTAyMzIyIg0KRGF0ZTogU2F0LCAwNiBNYXkgMjAyMyAxNDo0MTo0MCANCkZyb206ICJNb2xsZWtvcGYsIENocmlzdGlhbiIgPGNocmlzdGlhbkBleGFtcGxlLmNoPg0KVG86IGNocmlzdGlhbkBleGFtcGxlLmNoDQpTdWJqZWN0OiBGb29iYXIgMQ0KTWVzc2FnZS1JRDogPGZvb2JhcjFAZXhhbXBsZS5vcmc+DQoNCi0tPV8yOTFiOGU5NjU2NDI2NTYzNjQzMmM2ZDQ5NGUwMjMyMg0KQ29udGVudC1UeXBlOiBtdWx0aXBhcnQvYWx0ZXJuYXRpdmU7DQpib3VuZGFyeT0iPV9jZWZmMGZkMTk3NTZmNDVlZDEyOTVlZTIwNjlmZjhlMCINCg0KLS09X2NlZmYwZmQxOTc1NmY0NWVkMTI5NWVlMjA2OWZmOGUwDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiA3Yml0DQpDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9VVMtQVNDSUkNCg0Kc2Rsa2pzZGpmDQotLT1fY2VmZjBmZDE5NzU2ZjQ1ZWQxMjk1ZWUyMDY5ZmY4ZTANCkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IHF1b3RlZC1wcmludGFibGUNCkNvbnRlbnQtVHlwZTogdGV4dC9odG1sOyBjaGFyc2V0PVVURi04DQoNCjxodG1sPjxoZWFkPjxtZXRhIGh0dHAtZXF1aXY9M0QiQ29udGVudC1UeXBlIiBjb250ZW50PTNEInRleHQvaHRtbDsgY2hhcnNldD0NCj0zRFVURi04IiAvPjwvaGVhZD48Ym9keSBzdHlsZT0zRCdmb250LXNpemU6IDEwcHQ7IGZvbnQtZmFtaWx5OiBWZXJkYW5hLEdlbj0NCmV2YSxzYW5zLXNlcmlmJz4NCjxwPnNkbGtqc2RqZjwvcD4NCg0KPC9ib2R5PjwvaHRtbD4NCg0KLS09X2NlZmYwZmQxOTc1NmY0NWVkMTI5NWVlMjA2OWZmOGUwLS0NCg0KLS09XzI5MWI4ZTk2NTY0MjY1NjM2NDMyYzZkNDk0ZTAyMzIyDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiYXNlNjQNCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbjsNCm5hbWU9eG9yZy5jb25mDQpDb250ZW50LURpc3Bvc2l0aW9uOiBhdHRhY2htZW50Ow0KZmlsZW5hbWU9eG9yZy5jb25mOw0Kc2l6ZT0yMTENCg0KVTJWamRHbHZiaUFpUkdWMmFXTmxJZ29nSUNBZ1NXUmxiblJwWm1sbGNpQWdJQ0FnSWtSbGRtbGpaVEFpQ2lBZ0lDQkVjbWwyWlhJZw0KSUNBZ0lFSnZZWEprVG1GdFpTQWdJQ0FnSUNKT1ZsTWdOREl3TUUwaUNpQWdJQ0JQY0hScGIyNGdJazV2VEc5bmJ5SWdJblJ5ZFdVaQ0KQ2lBZ0lDQlBjSFJwYjI0Z0lsVnpaVVZFU1VRaUlDSjBjblZsSWdwRmJtUlRaV04wYVc5dUNnPT0NCi0tPV8yOTFiOGU5NjU2NDI2NTYzNjQzMmM2ZDQ5NGUwMjMyMi0tAAEBVgMyAAEAAlMDSVBNLk5vdGUAAXwDdXJuOmNvbnRlbnQtY2xhc3NlczptZXNzYWdlAAEAEU5PUAN4b3JnLmNvbmYAAVEDMzhiOTUwZWJkNjJjZDlhNjY5MjljODk2MTVkMGZjMDQ6OjU6OjIAAVIDMQABTAMzNTEwMDIxMgABAQEBAQEBAQE='), + $output + ); + } + + public function testEncodeEmailPerformanceTest() + { + $outputStream = fopen("php://temp", 'r+'); + + $encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3); + $attachment = str_repeat("ICAgIEJvYXJkTmFtZSAgICAgICJOVlMgNDIwME0iCiAgICBPcHRpb24gIk5vTG9nbyIgInRydWUi \n", 100000); + $xml = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:Syncroton="uri:Syncroton" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Email="uri:Email" xmlns:Email2="uri:Email2" xmlns:Tasks="uri:Tasks"> + <Collections> + <Collection xmlns:default="uri:Email" xmlns:default1="uri:AirSyncBase"> + <Class>Email</Class> + <SyncKey>2</SyncKey> + <CollectionId>38b950ebd62cd9a66929c89615d0fc04</CollectionId> + <Status>1</Status> + <MoreAvailable/> + <Commands xmlns:default="uri:Email" xmlns:default1="uri:AirSyncBase"> + <Add xmlns:default="uri:Email" xmlns:default1="uri:AirSyncBase"> + <ServerId>38b950ebd62cd9a66929c89615d0fc04::1</ServerId> + <ApplicationData> + <Email:DateReceived xmlns="uri:Email">2023-05-06T14:51:40.000Z</Email:DateReceived> + <Email:From xmlns="uri:Email">"Mollekopf, Christian" <christian@example.ch></Email:From> + <Email:InternetCPID xmlns="uri:Email">65001</Email:InternetCPID> + <Email:Subject xmlns="uri:Email">Foobar 1</Email:Subject> + <Email:To xmlns="uri:Email">christian@example.ch</Email:To> + <Email:Read xmlns="uri:Email">0</Email:Read> + <Email:Flag xmlns="uri:Email"/> + <AirSyncBase:Body xmlns="uri:AirSyncBase"> + <AirSyncBase:Type>4</AirSyncBase:Type> + <AirSyncBase:Data>Return-Path: <christian@example.ch> + Received: from imapb010.mykolab.com (unix socket) + by imapb010.mykolab.com (Cyrus 2.5.10-49-g2e214b4-Kolab-2.5.10-8.1.el7.kolab_14) with LMTPA; + Wed, 09 Aug 2017 18:37:01 +0200 + X-Sieve: CMU Sieve 2.4 + Received: from int-mx002.mykolab.com (unknown 10.9.13.2) + by imapb010.mykolab.com (Postfix) with ESMTPS id 0A93910A25047 + for <christian@example.ch>; Wed, 9 Aug 2017 18:37:01 +0200 (CEST) + Received: from int-subm002.mykolab.com (unknown 10.9.37.2) + by int-mx002.mykolab.com (Postfix) with ESMTPS id EC06AF6E + for <christian@example.ch>; Wed, 9 Aug 2017 18:37:00 +0200 (CEST) + MIME-Version: 1.0 + Content-Type: multipart/mixed; + boundary="=_291b8e96564265636432c6d494e02322" + Date: Sat, 06 May 2023 14:41:40 + From: "Mollekopf, Christian" <christian@example.ch> + To: christian@example.ch + Subject: Foobar 1 + Message-ID: <foobar1@example.org> + + --=_291b8e96564265636432c6d494e02322 + Content-Type: multipart/alternative; + boundary="=_ceff0fd19756f45ed1295ee2069ff8e0" + + --=_ceff0fd19756f45ed1295ee2069ff8e0 + Content-Transfer-Encoding: 7bit + Content-Type: text/plain; charset=US-ASCII + + sdlkjsdjf + --=_ceff0fd19756f45ed1295ee2069ff8e0 + Content-Transfer-Encoding: quoted-printable + Content-Type: text/html; charset=UTF-8 + + <html><head><meta http-equiv=3D"Content-Type" content=3D"text/html; charset= + =3DUTF-8" /></head><body style=3D'font-size: 10pt; font-family: Verdana,Gen= + eva,sans-serif'> + <p>sdlkjsdjf</p> + + </body></html> + + --=_ceff0fd19756f45ed1295ee2069ff8e0-- + + --=_291b8e96564265636432c6d494e02322 + Content-Transfer-Encoding: base64 + Content-Type: text/plain; + name=xorg.conf + Content-Disposition: attachment; + filename=xorg.conf; + size=211 + + U2VjdGlvbiAiRGV2aWNlIgogICAgSWRlbnRpZmllciAgICAgIkRldmljZTAiCiAgICBEcml2ZXIg + {$attachment} + CiAgICBPcHRpb24gIlVzZUVESUQiICJ0cnVlIgpFbmRTZWN0aW9uCg== + --=_291b8e96564265636432c6d494e02322--</AirSyncBase:Data> + </AirSyncBase:Body> + <AirSyncBase:NativeBodyType xmlns="uri:AirSyncBase">2</AirSyncBase:NativeBodyType> + <Email:MessageClass xmlns="uri:Email">IPM.Note</Email:MessageClass> + <Email:ContentClass xmlns="uri:Email">urn:content-classes:message</Email:ContentClass> + <AirSyncBase:Attachments xmlns="uri:AirSyncBase"> + <AirSyncBase:Attachment> + <AirSyncBase:DisplayName>xorg.conf</AirSyncBase:DisplayName> + <AirSyncBase:FileReference>38b950ebd62cd9a66929c89615d0fc04::5::2</AirSyncBase:FileReference> + <AirSyncBase:Method>1</AirSyncBase:Method> + <AirSyncBase:EstimatedDataSize>35100212</AirSyncBase:EstimatedDataSize> + </AirSyncBase:Attachment> + </AirSyncBase:Attachments> + </ApplicationData> + </Add> + </Commands> + </Collection> + </Collections> + </Sync> + EOF; + + $dom = new DOMDocument(); + $dom->loadXML($xml); + + $start = microtime(true); + $encoder->encode($dom); + $end = microtime(true); + + $this->assertTrue($end - $start < 0.05); + } + + public function testDecoder() + { + $inputStream = fopen("php://memory", 'r+'); + $input = "\x03\x01j\x00\x00\x07VR\x030\x00\x01\x01"; + fwrite($inputStream, $input); + rewind($inputStream); + + $decoder = new Syncroton_Wbxml_Decoder($inputStream); + $dom = $decoder->decode(); + $xml = $dom->saveXML(); + + $expected = '<?xml version="1.0" encoding="utf-8"?>' + . '<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">' + . '<FolderSync xmlns="uri:FolderHierarchy"><SyncKey>0</SyncKey></FolderSync>'; + + $this->assertSame($expected, str_replace("\n", '', $xml)); + } +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/body_converter.php
Deleted
@@ -1,45 +0,0 @@ -<?php - -class body_converter extends PHPUnit\Framework\TestCase -{ - function data_html_to_text() - { - return array( - array('', ''), - array('<div></div>', ''), - array('<div>a</div>', 'a'), - array('<html><head><title>title</title></head></html>', ''), - ); - } - - /** - * @dataProvider data_html_to_text - */ - function test_html_to_text($html, $text) - { - $converter = new kolab_sync_body_converter($html, Syncroton_Model_EmailBody::TYPE_HTML); - $output = $converter->convert(Syncroton_Model_EmailBody::TYPE_PLAINTEXT); - - $this->assertEquals(trim($text), trim($output)); - } - - /** - * - */ - function test_rtf_to_text() - { - $rtf = '0QAAAB0CAABMWkZ1Pzsq5D8ACQMwAQMB9wKnAgBjaBEKwHNldALRcHJx4DAgVGFoA3ECgwBQ6wNUDzcyD9MyBgAGwwKDpxIBA+MReDA0EhUgAoArApEI5jsJbzAVwzEyvjgJtBdCCjIXQRb0ORIAHxeEGOEYExjgFcMyNTX/CbQaYgoyGmEaHBaKCaUa9v8c6woUG3YdTRt/Hwwabxbt/xyPF7gePxg4JY0YVyRMKR+dJfh9CoEBMAOyMTYDMUksgSc1FGAnNhqAJ1Q3My3BNAqFfS7A'; - $rtf = base64_decode($rtf); - $text = 'Test'; - $html = '<pre>Test</pre>'; - - $converter = new kolab_sync_body_converter($rtf, Syncroton_Model_EmailBody::TYPE_RTF); - - $output = $converter->convert(Syncroton_Model_EmailBody::TYPE_PLAINTEXT); - $this->assertEquals(trim($text), trim($output)); - - $output = $converter->convert(Syncroton_Model_EmailBody::TYPE_HTML); - $this->assertEquals(trim($html), trim($output)); - } - -}
View file
kolab-syncroton-2.4.2.tar.gz/tests/bootstrap.php
Changed
@@ -1,5 +1,8 @@ <?php +error_reporting(E_ALL); +ini_set('display_errors', 1); + if (php_sapi_name() != 'cli') { die("Not in shell mode (php-cli)"); } @@ -7,5 +10,6 @@ define('TESTS_DIR', dirname(__FILE__) . '/'); require_once(TESTS_DIR . '/../lib/init.php'); +require_once(TESTS_DIR . '/SyncTestCase.php'); rcube::get_instance()->config->set('devel_mode', false);
View file
kolab-syncroton-2.4.2.tar.gz/tests/data.php
Deleted
@@ -1,74 +0,0 @@ -<?php - -class data extends PHPUnit\Framework\TestCase -{ - /** - * Test for kolab_sync_data::recurrence_to_kolab() - */ - function test_recurrence_to_kolab() - { - $xml = '<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> - <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Calendar="uri:Calendar"> - <ApplicationData> - <Calendar:Recurrence> - <Calendar:Type>0</Calendar:Type> - <Calendar:Interval>1</Calendar:Interval> - <Calendar:Until>20101128T225959Z</Calendar:Until> - </Calendar:Recurrence> - </ApplicationData> - </Sync>'; - - $xml = new SimpleXMLElement($xml); - $event = new Syncroton_Model_Event($xml->ApplicationData); - $data = new kolab_sync_data_test; - - $result = $data->recurrence_to_kolab($event); - - $this->assertEquals('DAILY', $result'FREQ'); - $this->assertEquals(1, $result'INTERVAL'); - $this->assertEquals('20101128T225959Z', $result'UNTIL'->format("Ymd\THis\Z")); - - $xml = '<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> - <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Calendar="uri:Calendar"> - <ApplicationData> - <Calendar:Recurrence> - <Calendar:Type>1</Calendar:Type> - <Calendar:Interval>1</Calendar:Interval> - <Calendar:DayOfWeek>8</Calendar:DayOfWeek> - </Calendar:Recurrence> - </ApplicationData> - </Sync>'; - - $xml = new SimpleXMLElement($xml); - $event = new Syncroton_Model_Event($xml->ApplicationData); - - $result = $data->recurrence_to_kolab($event, null); - - $this->assertEquals('WEEKLY', $result'FREQ'); - $this->assertEquals(1, $result'INTERVAL'); - $this->assertEquals('WE', $result'BYDAY'); - } -} - -/** - * kolab_sync_data wrapper, so we can test preotected methods too - */ -class kolab_sync_data_test extends kolab_sync_data -{ - function __construct() - { - } - - public function recurrence_to_kolab($data, $dummy1 = null, $dummy2 = null) - { - return parent::recurrence_to_kolab($data, null); - } - - function toKolab(Syncroton_Model_IEntry $data, $folderId, $entry = null, $timezone = null) - { - } - - function getEntry(Syncroton_Model_SyncCollection $collection, $serverId, $as_array = false) - { - } -}
View file
kolab-syncroton-2.4.2.tar.gz/tests/data_calendar.php
Deleted
@@ -1,126 +0,0 @@ -<?php - -class data_calendar extends PHPUnit\Framework\TestCase -{ - /** - * Test for kolab_sync_data_calendar::from_kolab_alarm() - */ - function test_from_kolab_alarm() - { - $obj = new kolab_sync_data_calendar_test; - - $result = $obj->from_kolab_alarm(array()); - $this->assertSame(null, $result); - - $event = array('valarms' => array(array( - 'action' => 'DISPLAY', - 'trigger' => 'PT5M', - ))); - $result = $obj->from_kolab_alarm($event); - $this->assertSame(null, $result); - - $event = array('valarms' => array(array( - 'action' => 'DISPLAY', - 'trigger' => '-PT5M', - ))); - $result = $obj->from_kolab_alarm($event); - $this->assertSame(5, $result); - - $event = array('valarms' => array(array( - 'action' => 'DISPLAY', - 'trigger' => 'PT0M', - ))); - $result = $obj->from_kolab_alarm($event); - $this->assertSame(0, $result); - - $event = array('valarms' => array(array( - 'action' => 'DISPLAY', - 'trigger' => '-PT0M', - ))); - $result = $obj->from_kolab_alarm($event); - $this->assertSame(0, $result); - - $event = array('valarms' => array(array( - 'action' => 'DISPLAY', - 'trigger' => 'PT0S', - ))); - $result = $obj->from_kolab_alarm($event); - $this->assertSame(0, $result); - - // alarms on specified DateTime (T2420) - - $event = array( - // no start datetime defined - 'valarms' => array( - array( - 'action' => 'DISPLAY', - 'trigger' => new DateTime('now + 1 hour'), - ), - ), - ); - $result = $obj->from_kolab_alarm($event); - $this->assertSame(null, $result); - - $event = array( - 'start' => new DateTime('now + 10 minutes'), - 'valarms' => array( - array( - 'action' => 'DISPLAY', - 'trigger' => new DateTime('now + 1 hour'), - ), - ), - ); - $result = $obj->from_kolab_alarm($event); - $this->assertSame(null, $result); - - $event = array( - 'start' => new DateTime('now + 60 minutes'), - 'valarms' => array( - array( - 'action' => 'DISPLAY', - 'trigger' => new DateTime('now + 50 minutes'), - ), - ), - ); - $result = $obj->from_kolab_alarm($event); - $this->assertSame(10, $result); - } - - /** - * Test for kolab_sync_data_calendar::to_kolab_alarm() - */ - function test_to_kolab_alarm() - { - $obj = new kolab_sync_data_calendar_test; - - $result = $obj->to_kolab_alarm(null, array()); - $this->assertSame(array(), $result); - - $result = $obj->to_kolab_alarm(0, array()); - $this->assertSame('-PT0M', $result0'trigger'); - - $result = $obj->to_kolab_alarm(15, array()); - $this->assertSame('-PT15M', $result0'trigger'); - $this->assertSame('DISPLAY', $result0'action'); - } -} - -/** - * kolab_sync_data_calendar wrapper, so we can test protected methods too - */ -class kolab_sync_data_calendar_test extends kolab_sync_data_calendar -{ - function __construct() - { - } - - public function from_kolab_alarm($value) - { - return parent::from_kolab_alarm($value); - } - - public function to_kolab_alarm($value, $event) - { - return parent::to_kolab_alarm($value, $event); - } -}
View file
kolab-syncroton-2.4.2.tar.gz/tests/data_tasks.php
Deleted
@@ -1,78 +0,0 @@ -<?php - -class data_tasks extends PHPUnit\Framework\TestCase -{ - function data_prio() - { - return array( - array(0, null), - array(1, 2), - array(2, 2), - array(3, 2), - array(4, 2), - array(5, 1), - array(6, 0), - array(7, 0), - array(8, 0), - array(9, 0), - // invalid input - array(10, null), - ); - } - - function data_importance() - { - return array( - array(0, 9), - array(1, 5), - array(2, 1), - // invalid input - array(null, null), - array(5, null), - ); - } - - /** - * Test for kolab_sync_data_tasks::prio_to_importance() - * @dataProvider data_prio() - */ - function test_prio_to_importance($input, $output) - { - $data = new kolab_sync_data_tasks_test; - $result = $data->prio_to_importance($input); - - $this->assertEquals($output, $result); - } - - /** - * Test for kolab_sync_data_tasks::importance_to_prio() - * @dataProvider data_importance() - */ - function test_importance_to_prio($input, $output) - { - $data = new kolab_sync_data_tasks_test; - $result = $data->importance_to_prio($input); - - $this->assertEquals($output, $result); - } -} - -/** - * kolab_sync_data_tasks wrapper, so we can test preotected methods too - */ -class kolab_sync_data_tasks_test extends kolab_sync_data_tasks -{ - function __construct() - { - } - - public function prio_to_importance($value) - { - return parent::prio_to_importance($value); - } - - public function importance_to_prio($value) - { - return parent::importance_to_prio($value); - } -}
View file
kolab-syncroton-2.4.2.tar.gz/tests/globalid_converter.php
Deleted
@@ -1,37 +0,0 @@ -<?php - -class globalid_converter extends PHPUnit\Framework\TestCase -{ - /** - * Test GlobalObjId encoding/decoding - */ - function test_globalobjid() - { - // https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-asemail/e7424ddc-dd10-431e-a0b7-5c794863370e - $input = 'BAAAAIIA4AB0xbcQGoLgCAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAAHZDYWwtVWlkAQAAAHs4MTQxMkQzQy0yQTI0LTRFOUQtQjIwRS0xMUY3QkJFOTI3OTl9AA=='; - $output = kolab_sync_data_email::decodeGlobalObjId($input); - - $this->assertSame(51, $output'bytecount'); - $this->assertSame('{81412D3C-2A24-4E9D-B20E-11F7BBE92799}', $output'uid'); - - $encoded = kolab_sync_data_email::encodeGlobalObjId($output); - $this->assertSame($encoded, $input); - - $input = 'BAAAAIIA4AB0xbcQGoLgCAfUCRDgQMnBJoXEAQAAAAAAAAAAEAAAAAvw7UtuTulOnjnjhns3jvM='; - $output = kolab_sync_data_email::decodeGlobalObjId($input); - - $this->assertSame(16, $output'bytecount'); - $this->assertSame(2004, $output'year'); - $this->assertSame(9, $output'month'); - $this->assertSame(16, $output'day'); - $this->assertSame(127373090979660000, $output'now'); - - // This is how the "now" value is interpreted - // $winSecs = (int)($output'now' / 10000000); // convert microseconds to seconds - // $unixTimestamp = ($winSecs - 11644473600); // subtract 1.1.1600 - 1.1.1970 difference in seconds - // print(date(DateTime::RFC822, $unixTimestamp)); - - $encoded = kolab_sync_data_email::encodeGlobalObjId($output); - $this->assertSame($encoded, $input); - } -}
View file
kolab-syncroton-2.4.2.tar.gz/tests/message.php
Deleted
@@ -1,146 +0,0 @@ -<?php - -class message extends PHPUnit\Framework\TestCase -{ - /** - * Test message parsing and headers setting - */ - function test_headers() - { - $source = file_get_contents(TESTS_DIR . '/src/mail.plain'); - $message = new kolab_sync_message($source); - $headers = $message->headers(); - - $this->assertArrayHasKey('MIME-Version', $headers); - $this->assertCount(8, $headers); - $this->assertEquals('kolab@domain.tld', $headers'To'); - - // test set_header() - $message->set_header('to', 'test@domain.tld'); - $headers = $message->headers(); - - $this->assertCount(8, $headers); - $this->assertEquals('test@domain.tld', $headers'To'); - } - - /** - * Test message parsing - */ - function test_source() - { - $source = file_get_contents(TESTS_DIR . '/src/mail.plain'); - $message = new kolab_sync_message($source); - $result = $message->source(); - - $this->assertEquals($source, str_replace("\r\n", "\n", $result)); - } - - /** - * Test adding attachments to the message - */ - function test_attachment() - { - $source = file_get_contents(TESTS_DIR . '/src/mail.plain'); - $mixed = file_get_contents(TESTS_DIR . '/src/mail.plain.mixed'); - $mixed2 = file_get_contents(TESTS_DIR . '/src/mail.mixed'); - - // test adding attachment to text/plain message - $message = new kolab_sync_message($source); - $message->add_attachment('aaa', array( - 'content_type' => 'text/plain', - 'encoding' => '8bit', - )); - - $result = $message->source(); - $result = str_replace("\r\n", "\n", $result); - if (preg_match('/boundary="(^"+)"/', $result, $m)) { - $mixed = str_replace('BOUNDARY', $m1, $mixed); - } - - $this->assertEquals($mixed, $result); - - // test adding attachment to multipart/mixed message - $message = new kolab_sync_message($mixed); - $message->add_attachment('aaa', array( - 'content_type' => 'text/plain', - 'encoding' => 'base64', - )); - - $result = $message->source(); - $result = str_replace("\r\n", "\n", $result); - if (preg_match('/boundary="(^"+)"/', $result, $m)) { - $mixed2 = str_replace('BOUNDARY', $m1, $mixed2); - } - - $this->assertEquals($mixed2, $result); - } - - /** - * Test appending a text to the message - */ - function test_append() - { - // test appending text to text/plain message - $source = file_get_contents(TESTS_DIR . '/src/mail.plain'); - $append = file_get_contents(TESTS_DIR . '/src/mail.plain.append'); - - $message = new kolab_sync_message($source); - $message->append('a'); - - $result = $message->source(); - $result = str_replace("\r\n", "\n", $result); - $this->assertEquals($append, $result); - } - - /** - * Test recoding the message - */ - function test_recode_message_1() - { - $source = file_get_contents(TESTS_DIR . '/src/mail.recode1'); - $result = file_get_contents(TESTS_DIR . '/src/mail.recode1.out'); - - $message = kolab_sync_message::recode_message($source); - - $this->assertEquals($result, $message); - } - - /** - * Test recoding the message - */ - function test_recode_message_2() - { - $source = file_get_contents(TESTS_DIR . '/src/mail.recode2'); - $result = file_get_contents(TESTS_DIR . '/src/mail.recode2.out'); - - $message = kolab_sync_message::recode_message($source); - - $this->assertEquals($result, $message); - } - - /** - * Test recoding the message - */ - function test_recode_message_3() - { - $source = file_get_contents(TESTS_DIR . '/src/mail.recode3'); - $result = file_get_contents(TESTS_DIR . '/src/mail.recode3.out'); - - $message = kolab_sync_message::recode_message($source); - - $this->assertEquals($result, $message); - } - - /** - * Test recoding the message - */ - function test_recode_message_4() - { - $source = file_get_contents(TESTS_DIR . '/src/mail.recode4'); - $result = file_get_contents(TESTS_DIR . '/src/mail.recode4.out'); - - $message = kolab_sync_message::recode_message($source); - - $this->assertEquals($result, $message); - } -}
View file
kolab-syncroton-2.4.2.tar.gz/tests/phpunit.xml
Changed
@@ -2,15 +2,11 @@ bootstrap="bootstrap.php" colors="true"> <testsuites> - <testsuite name="All Tests"> - <file>body_converter.php</file> - <file>data.php</file> - <file>data_calendar.php</file> - <file>data_tasks.php</file> - <file>globalid_converter.php</file> - <file>message.php</file> - <file>timezone_converter.php</file> - <file>wbxml.php</file> + <testsuite name="Unit"> + <directory suffix="Test.php">Unit</directory> + </testsuite> + <testsuite name="Sync"> + <directory suffix="Test.php">Sync</directory> </testsuite> </testsuites> </phpunit>
View file
kolab-syncroton-2.4.2.tar.gz/tests/src/contact.vcard1
Added
@@ -0,0 +1,6 @@ +BEGIN:VCARD +VERSION:3.0 +UID:urn:uuid:abcdef-0123-4567-89ab-abcdefabcdef +FN:Jane Doe +N:Doe;Jane;J.;; +END:VCARD
View file
kolab-syncroton-2.4.2.tar.gz/tests/src/contact.vcard2
Added
@@ -0,0 +1,6 @@ +BEGIN:VCARD +VERSION:3.0 +UID:urn:uuid:abcdef-0123-4567-89ab-abcdefabc123 +FN:Jack Strong +N:Strong;Jack;;; +END:VCARD
View file
kolab-syncroton-2.4.2.tar.gz/tests/src/event.ics1
Added
@@ -0,0 +1,12 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//test/test//NONSGML v1.0//EN +BEGIN:VEVENT +UID:abcdef +DTSTAMP:19970714T170000Z +ORGANIZER;CN=John Doe:MAILTO:john.doe@example.com +DTSTART:20240714T170000Z +DTEND:20240714T180000Z +SUMMARY:Party +END:VEVENT +END:VCALENDAR
View file
kolab-syncroton-2.4.2.tar.gz/tests/src/event.ics2
Added
@@ -0,0 +1,12 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//test/test//NONSGML v1.0//EN +BEGIN:VEVENT +UID:123456 +DTSTAMP:19970714T170000Z +ORGANIZER;CN=John Doe:MAILTO:john.doe@example.com +DTSTART:20240715T170000Z +DTEND:20240715T180000Z +SUMMARY:Meeting +END:VEVENT +END:VCALENDAR
View file
kolab-syncroton-2.4.2.tar.gz/tests/src/mail.itip1
Added
@@ -0,0 +1,62 @@ +MIME-Version: 1.0 +Date: Thu, 07 Dec 2023 13:29:14 +0100 +Message-ID: <14ab307198d32cee00b38ffb54c9e577@nestle.kolab.ch> +From: "Organizer" <$from> +To: <$to> +Subject: You've been invited to "Test" +Content-Type: multipart/alternative; + boundary="=_f39ac9438326f676a8d562e163aa31e0" + +--=_f39ac9438326f676a8d562e163aa31e0 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; charset=UTF-8; + format=flowed + +*Test* +--=_f39ac9438326f676a8d562e163aa31e0 +Content-Transfer-Encoding: 8bit +Content-Type: text/calendar; charset=UTF-8; method=REQUEST; name=event.ics + +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Roundcube 1.5-git//Sabre VObject 4.5.3//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VTIMEZONE +TZID:Europe/Warsaw +BEGIN:DAYLIGHT +DTSTART:20230326T010000 +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +TZNAME:CEST +END:DAYLIGHT +BEGIN:DAYLIGHT +DTSTART:20240331T010000 +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +TZNAME:CEST +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:20231029T010000 +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +TZNAME:CET +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +UID:1154B829349633D80E143AAB5641170A-93BC4FC398A3FD52 +DTSTAMP:20231207T122914Z +CREATED:20231207T122914Z +LAST-MODIFIED:20231207T122914Z +DTSTART;TZID=Europe/Warsaw:20231207T140000 +DTEND;TZID=Europe/Warsaw:20231207T143000 +SUMMARY:Test +SEQUENCE:0 +TRANSP:OPAQUE +ATTENDEE;CN="Attendee Name";PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPA + NT;CUTYPE=INDIVIDUAL;RSVP=TRUE:mailto:$to +ORGANIZER;CN="Organizer Name":mailto:$from +END:VEVENT +END:VCALENDAR + +--=_f39ac9438326f676a8d562e163aa31e0--
View file
kolab-syncroton-2.4.2.tar.gz/tests/src/mail.sync1
Added
@@ -0,0 +1,10 @@ +Date: Thu, 09 Aug 2012 13:18:31 +0000 +Subject: test sync +Message-ID: <sync1@domain.tld> +From: "Sync 1" <user@domain.tld> +To: "To 1" <kolab1@domain.tld>, "To 2" <kolab2@domain.tld> +MIME-Version: 1.0 +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: base64 + +ZWVlYQ==
View file
kolab-syncroton-2.4.2.tar.gz/tests/src/mail.sync2
Added
@@ -0,0 +1,20 @@ +Date: Thu, 10 Aug 2012 13:18:31 +0000 +Subject: sync test with attachment +Message-ID: <sync2@domain.tld> +From: user@domain.tld +To: kolab@domain.tld +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="BOUNDARY" + +--BOUNDARY +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: base64 + +ZWVl +--BOUNDARY +Content-Transfer-Encoding: base64 +Content-Type: image/jpeg; name=logo.gif +Content-Disposition: inline; filename=logo.gif; size=2574 + +/9j/4AAQSkZJRgABAgEASABIAAD/4QqARXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUA +--BOUNDARY--
View file
kolab-syncroton-2.4.2.tar.gz/tests/src/sample.rtf
Added
@@ -0,0 +1,18 @@ +{\rtf1\mac\deff2 {\fonttbl{\f0\fswiss Chicago;}{\f2\froman New York;}{\f3\fswiss Geneva;}{\f4\fmodern Monaco;}{\f11\fnil Cairo;}{\f13\fnil Zapf Dingbats;}{\f14\fnil Bookman;}{\f15\fnil N Helvetica Narrow;}{\f16\fnil Palatino;}{\f18\fnil Zapf Chancery;} +{\f20\froman Times;}{\f21\fswiss Helvetica;}{\f22\fmodern Courier;}{\f23\ftech Symbol;}{\f33\fnil Avant Garde;}{\f34\fnil New Century Schlbk;}{\f1297\fnil GoudyHundred;}{\f1602\fnil BlackChancery;}{\f2515\fnil MT Extra;}{\f4950\fnil TTYFont;} +{\f11132\fnil InsigniaLQmono;}{\f11133\fnil InsigniaLQprop;}{\f32500\fnil VT320;}{\f32525\fnil VT100;}}{\colortbl\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0; +\red255\green255\blue0;\red255\green255\blue255;}{\stylesheet{\s250\li720 \f21\fs20\ul \sbasedon0\snext0 heading 6;}{\s251\li720 \b\f21\fs20 \sbasedon0\snext0 heading 5;}{\s252\li360 \f21\ul \sbasedon0\snext0 heading 4;}{\s253\li360 \b\f21 +\sbasedon0\snext0 heading 3;}{\s254\sb120 \b\f21 \sbasedon0\snext0 heading 2;}{\s255\sb240 \b\f21\ul \sbasedon0\snext0 heading 1;}{\f21 \sbasedon222\snext0 Normal;}{\s2 \b\f21\cf1 \sbasedon0\snext2 Anchor;}}{\info{\author Chuck Shotton}} +\margl720\margr720\ftnbj\fracwidth \sectd \sbknone\linemod0\linex0\cols1\endnhere \pard\plain \s255\sb240 \b\f21\ul Heading 1\par +\pard\plain \f21 This is the first normal paragraph!\par +\pard\plain \s254\sb120 \b\f21 Heading 2\par +\pard\plain \s253\li360 \b\f21 Heading 3\par +\pard\plain \s252\li360 \f21\ul Heading 4\par +\pard\plain \s251\li720 \b\f21\fs20 Heading 5\par +\pard\plain \s250\li720 \f21\fs20\ul Heading 6\par +\pard\plain \f21 This is a chunk of normal text.\par +This is a chunk of normal text with specials, &, <, and >.\par +This is a second paragraph.\par +This is text with embedded {\b bold}, {\i italic}, and {\ul underline} styles.\par +Here is the {\cf1 anchor} style. And here is the {\cf5 Image} style.\par +} \ No newline at end of file
View file
kolab-syncroton-2.4.2.tar.gz/tests/timezone_converter.php
Deleted
@@ -1,175 +0,0 @@ -<?php - -class timezone_converter extends PHPUnit\Framework\TestCase -{ - function test_list_timezones() - { -// date_default_timezone_set('America/Los_Angeles'); - - $converter = kolab_sync_timezone_converter::getInstance(); - $output = $converter->getListOfTimezones('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAEAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAFAAEAAAAAAAAAxP///w=='); - - $this->assertTrue(is_array($output)); - $this->assertSame(array(), $output); - - $converter = kolab_sync_timezone_converter::getInstance(); - $output = $converter->getListOfTimezones('xP///0MAZQBuAHQAcgBhAGwAIABFAHUAcgBvAHAAZQAgAFMAdABhAG4AZABhAHIAZAAgAFQAaQBtAGUAAAAAAAAAAAAAAAoAAAAFAAMAAAAAAAAAAAAAAEMAZQBuAHQAcgBhAGwAIABFAHUAcgBvAHAAZQAgAEQAYQB5AGwAaQBnAGgAdAAgAFQAaQBtAGUAAAAAAAAAAAAAAAMAAAAFAAIAAAAAAAAAxP///w=='); - - $this->assertTrue(is_array($output)); - $this->assertTrue(isset($output'Europe/Warsaw')); - - $converter = kolab_sync_timezone_converter::getInstance(); - $output = $converter->getListOfTimezones('4AEAAFAAYQBjAGkAZgBpAGMAIABTAHQAYQBuAGQAYQByAGQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAFAAYQBjAGkAZgBpAGMAIABEAGEAeQBsAGkAZwBoAHQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///w=='); - - $this->assertTrue(is_array($output)); - $this->assertTrue(isset($output'America/Los_Angeles')); - - $converter = kolab_sync_timezone_converter::getInstance(); - $output = $converter->getListOfTimezones('Lv///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='); - - $this->assertTrue(is_array($output)); - $this->assertTrue(isset($output'Asia/Tehran')); - - // As seen in outlook - $converter = kolab_sync_timezone_converter::getInstance(); - $output = $converter->getListOfTimezones('Lv///0kAcgBhAG4AIABTAHQAYQBuAGQAYQByAGQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkABAADABcAOwA7AOcDAAAAAEkAcgBhAG4AIABEAGEAeQBsAGkAZwBoAHQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAwAEAAAAAAAAAAAAxP///w=='); - - $this->assertTrue(is_array($output)); - $this->assertTrue(isset($output'Asia/Tehran')); - } - - function data_get_timezone() - { - return - 'UTC', - 'Europe/Warsaw', - 'Europe/Zurich', - 'America/Los_Angeles', - 'Asia/Tehran', - ; - } - - /** - * @dataProvider data_get_timezone - */ - function test_get_timezone($tzName) - { - date_default_timezone_set('America/Los_Angeles'); - - $converter = kolab_sync_timezone_converter::getInstance(); - $datetime = '2017-01-01T12:00:00Z'; - - $offsets = $converter->getOffsetsForTimezone($tzName, $datetime); - $output = $converter->getTimezone($offsets, $tzName); - $this->assertSame($tzName, $output); - } - - function test_get_offsets_for_timezone() - { - date_default_timezone_set('America/Los_Angeles'); - - $converter = kolab_sync_timezone_converter::getInstance(); - $datetime = '2017-01-01T12:00:00Z'; - - $output = $converter->getOffsetsForTimezone('UTC', $datetime); - - $this->assertSame($output'bias', 0); - $this->assertSame($output'standardBias', 0); - $this->assertSame($output'daylightBias', 0); - $this->assertSame($output'standardMonth', 0); - $this->assertSame($output'daylightMonth', 0); - - $output = $converter->getOffsetsForTimezone('Europe/Warsaw', $datetime); - - $this->assertSame($output'standardBias', 0); - $this->assertSame($output'standardMonth', 10); - $this->assertSame($output'standardWeek', 5); - $this->assertSame($output'standardHour', 3); - $this->assertSame($output'daylightBias', -60); - $this->assertSame($output'daylightMonth', 3); - $this->assertSame($output'daylightWeek', 5); - $this->assertSame($output'daylightHour', 2); - - $output = $converter->getOffsetsForTimezone('America/Los_Angeles', $datetime); - - $this->assertSame($output'bias', 480); - $this->assertSame($output'standardBias', 0); - $this->assertSame($output'standardMonth', 11); - $this->assertSame($output'standardWeek', 1); - $this->assertSame($output'standardHour', 2); - $this->assertSame($output'daylightBias', -60); - $this->assertSame($output'daylightMonth', 3); - $this->assertSame($output'daylightWeek', 2); - $this->assertSame($output'daylightHour', 2); - - $output = $converter->getOffsetsForTimezone('Atlantic/Azores', $datetime); - - $this->assertSame($output'bias', 60); - $this->assertSame($output'standardBias', 0); - $this->assertSame($output'standardMonth', 10); - $this->assertSame($output'standardWeek', 5); - $this->assertSame($output'standardHour', 1); - $this->assertSame($output'daylightBias', -60); - $this->assertSame($output'daylightMonth', 3); - $this->assertSame($output'daylightWeek', 5); - $this->assertSame($output'daylightHour', 0); - - //Check before dst change - $output = $converter->getOffsetsForTimezone('Asia/Tehran', $datetime); - - $this->assertSame($output'bias', -210); - $this->assertSame($output'standardBias', 0); - $this->assertSame($output'standardMonth', 9); - $this->assertSame($output'standardWeek', 3); - $this->assertSame($output'standardDayOfWeek', 4); - $this->assertSame($output'standardHour', 24); - $this->assertSame($output'daylightBias', -60); - $this->assertSame($output'daylightMonth', 3); - $this->assertSame($output'daylightWeek', 4); - $this->assertSame($output'daylightDayOfWeek', 3); - $this->assertSame($output'daylightHour', 0); - - //Check after dst change - $output = $converter->getOffsetsForTimezone('Asia/Tehran', '2023-01-01T12:00:00Z'); - - $this->assertSame($output'bias', -210); - $this->assertSame($output'standardBias', 0); - $this->assertSame($output'standardMonth', 0); - $this->assertSame($output'standardWeek', 0); - $this->assertSame($output'standardDayOfWeek', 0); - $this->assertSame($output'standardHour', 0); - $this->assertSame($output'daylightBias', 0); - $this->assertSame($output'daylightMonth', 0); - $this->assertSame($output'daylightWeek', 0); - $this->assertSame($output'daylightDayOfWeek', 0); - $this->assertSame($output'daylightHour', 0); - } - - function data_timezone_conversion() - { - return array( - //Pre dst change - array('Asia/Tehran', 'Lv///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAAgADABcAOwA7AOcDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAQAEAAAAAAAAAAAAxP///w==', '2021-07-01T12:00:00Z'), - //Post dst change - array('Asia/Tehran', 'Lv///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==', '2023-04-01T12:00:00Z'), - array('Pacific/Pago_Pago', 'lAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==', '2021-07-01T12:00:00Z'), - array('Europe/Warsaw', 'xP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAFAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAFAAIAAAAAAAAAxP///w==', '2021-07-01T12:00:00Z'), - ); - } - - /** - * @dataProvider data_timezone_conversion - */ - function test_timezone_conversion($tz, $expected, $datetime) - { - $converter = kolab_sync_timezone_converter::getInstance(); - $output = $converter->encodeTimezone($tz, $datetime); - - $this->assertSame($expected, $output); - - $output = $converter->getListOfTimezones($output); - - $this->assertTrue(is_array($output)); - $this->assertTrue(isset($output$tz)); - } -}
View file
kolab-syncroton-2.4.2.tar.gz/tests/wbxml.php
Deleted
@@ -1,892 +0,0 @@ -<?php - -class wbxml extends PHPUnit\Framework\TestCase -{ - //function testDecode() - //{ - // //TODO input some wbxml document - // // - // $dom = new DOMDocument(); - // $dom->loadXML($lastSyncCollection'lastXML'); - // // - // try { - // $decoder = new Syncroton_Wbxml_Decoder($dom); - // $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; - // } - // //TODO validate output - //} - - - - public function testEncode() - { - $outputStream = fopen("php://temp", 'r+'); - - $encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3); - - $xml = <<<EOF - <?xml version="1.0" encoding="utf-8"?> - <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> - <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks"> - <Collections> - <Collection> - <SyncKey>2</SyncKey> - <CollectionId>tasksId</CollectionId> - <Commands> - <Add> - <ClientId>clientId2</ClientId> - <ApplicationData> - <Subject xmlns="uri:Tasks">task2</Subject> - <Complete xmlns="uri:Tasks">0</Complete> - <DueDate xmlns="uri:Tasks">2020-11-04T00:00:00.000Z</DueDate> - <UtcDueDate xmlns="uri:Tasks">2020-11-03T23:00:00.000Z</UtcDueDate> - </ApplicationData> - </Add> - <Add> - <ClientId>clientId3</ClientId> - <ApplicationData> - <Subject xmlns="uri:Tasks">task3</Subject> - <Complete xmlns="uri:Tasks">0</Complete> - <DueDate xmlns="uri:Tasks">2020-11-04T00:00:00.000Z</DueDate> - <UtcDueDate xmlns="uri:Tasks">2020-11-03T23:00:00.000Z</UtcDueDate> - </ApplicationData> - </Add> - </Commands> - </Collection> - </Collections> - <WindowSize>16</WindowSize> - </Sync> - EOF; - - - $dom = new DOMDocument(); - $dom->loadXML($xml); - - $encoder->encode($dom); - - rewind($outputStream); - $output = stream_get_contents($outputStream); - // print("----"); - // print(var_export($output, true)); - // print("----"); - $this->assertEquals( - base64_decode('AwFqAEVcT0sDMgABUgN0YXNrc0lkAAFWR0wDY2xpZW50SWQyAAFdAAlgA3Rhc2syAAFKAzAAAUwDMjAyMC0xMS0wNFQwMDowMDowMC4wMDBaAAFNAzIwMjAtMTEtMDNUMjM6MDA6MDAuMDAwWgABAQEAAEdMA2NsaWVudElkMwABXQAJYAN0YXNrMwABSgMwAAFMAzIwMjAtMTEtMDRUMDA6MDA6MDAuMDAwWgABTQMyMDIwLTExLTAzVDIzOjAwOjAwLjAwMFoAAQEBAQEBAABVAzE2AAEB'), - $output - ); - } - - public function testEncodeFolderSync() - { - $outputStream = fopen("php://temp", 'r+'); - - $encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3); - - $xml = <<<EOF - <?xml version="1.0" encoding="utf-8"?> - <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> - <FolderSync xmlns="uri:FolderHierarchy" xmlns:Syncroton="uri:Syncroton" xmlns:Internal="uri:Internal"> - <Status>1</Status> - <SyncKey>1</SyncKey> - <Changes> - <Count>18</Count> - <Add> - <ServerId>2685b302b79f58d2753199545e3cb8be</ServerId> - <ParentId>0</ParentId> - <DisplayName>Test2</DisplayName> - <Type>13</Type> - </Add> - <Add> - <ServerId>9770b083c68e8584f396d15a116d6608</ServerId> - <ParentId>0</ParentId> - <DisplayName>DavidCalendar</DisplayName> - <Type>13</Type> - </Add> - <Add> - <ServerId>0f66388806743c514b8063bf0dc87486</ServerId> - <ParentId>0</ParentId> - <DisplayName>SergeyCalendar</DisplayName> - <Type>13</Type> - </Add> - <Add> - <ServerId>cca1b81c734abbcd669bea90d23e08ae</ServerId> - <ParentId>0</ParentId> - <DisplayName>Calendar</DisplayName> - <Type>8</Type> - </Add> - <Add> - <ServerId>ab1ddb4ef8e8f8fcc2c9f5a7f9062452</ServerId> - <ParentId>0</ParentId> - <DisplayName>PubCal</DisplayName> - <Type>13</Type> - </Add> - <Add> - <ServerId>d98bd8721371544ed095841ead941893</ServerId> - <ParentId>0</ParentId> - <DisplayName>(david) Test2</DisplayName> - <Type>13</Type> - </Add> - <Add> - <ServerId>9e7b9656ef61d4af2fb2fdcabe600079</ServerId> - <ParentId>0</ParentId> - <DisplayName>(david) DavidCalendar</DisplayName> - <Type>13</Type> - </Add> - <Add> - <ServerId>384cf2d877c39a622fdc2a16898052e2</ServerId> - <ParentId>0</ParentId> - <DisplayName>(david) Calendar</DisplayName> - <Type>13</Type> - </Add> - <Add> - <ServerId>Contacts::Syncroton</ServerId> - <ParentId>0</ParentId> - <DisplayName>Contacts</DisplayName> - <Type>9</Type> - </Add> - <Add> - <ServerId>1bb8c55fe84d52c6968db2571f7dc124</ServerId> - <ParentId>0</ParentId> - <DisplayName>Archive</DisplayName> - <Type>12</Type> - </Add> - <Add> - <ServerId>b51abe73e9e98fe200a4afe409050502</ServerId> - <ParentId>38b950ebd62cd9a66929c89615d0fc04</ParentId> - <DisplayName>Spam</DisplayName> - <Type>12</Type> - </Add> - <Add> - <ServerId>cf529c792fc87d1f207435b3921bb02e</ServerId> - <ParentId>0</ParentId> - <DisplayName>Sent</DisplayName> - <Type>5</Type> - </Add> - <Add> - <ServerId>715ed9ea29b8a5377a69c1f758037c65</ServerId> - <ParentId>0</ParentId> - <DisplayName>Spam</DisplayName> - <Type>12</Type> - </Add> - <Add> - <ServerId>db0d959a3aeb21757f8849a830947a7a</ServerId> - <ParentId>0</ParentId> - <DisplayName>Trash</DisplayName> - <Type>4</Type> - </Add> - <Add> - <ServerId>5ac9ec2e1a9d99e2e10cabe4abf26729</ServerId> - <ParentId>0</ParentId> - <DisplayName>Drafts</DisplayName> - <Type>3</Type> - </Add> - <Add> - <ServerId>38b950ebd62cd9a66929c89615d0fc04</ServerId> - <ParentId>0</ParentId> - <DisplayName>INBOX</DisplayName> - <Type>2</Type> - </Add> - <Add> - <ServerId>fc56f4c7ffe0aefa622db9f8d9186c4a</ServerId> - <ParentId>0</ParentId> - <DisplayName>Notes</DisplayName> - <Type>10</Type> - </Add> - <Add> - <ServerId>90335880f65deff6e521acea2b71a773</ServerId> - <ParentId>0</ParentId> - <DisplayName>Tasks</DisplayName> - <Type>7</Type> - </Add> - </Changes> - </FolderSync> - EOF; - - - $dom = new DOMDocument(); - $dom->loadXML($xml); - - $encoder->encode($dom); - - rewind($outputStream); - $output = stream_get_contents($outputStream); - // print("----"); - // print(var_export(base64_encode($output), true)); - // print("----"); - $this->assertEquals( - base64_decode('AwFqAAAHVkwDMQABUgMxAAFOVwMxOAABT0gDMjY4NWIzMDJiNzlmNThkMjc1MzE5OTU0NWUzY2I4YmUAAUkDMAABRwNUZXN0MgABSgMxMwABAU9IAzk3NzBiMDgzYzY4ZTg1ODRmMzk2ZDE1YTExNmQ2NjA4AAFJAzAAAUcDRGF2aWRDYWxlbmRhcgABSgMxMwABAU9IAzBmNjYzODg4MDY3NDNjNTE0YjgwNjNiZjBkYzg3NDg2AAFJAzAAAUcDU2VyZ2V5Q2FsZW5kYXIAAUoDMTMAAQFPSANjY2ExYjgxYzczNGFiYmNkNjY5YmVhOTBkMjNlMDhhZQABSQMwAAFHA0NhbGVuZGFyAAFKAzgAAQFPSANhYjFkZGI0ZWY4ZThmOGZjYzJjOWY1YTdmOTA2MjQ1MgABSQMwAAFHA1B1YkNhbAABSgMxMwABAU9IA2Q5OGJkODcyMTM3MTU0NGVkMDk1ODQxZWFkOTQxODkzAAFJAzAAAUcDKGRhdmlkKSBUZXN0MgABSgMxMwABAU9IAzllN2I5NjU2ZWY2MWQ0YWYyZmIyZmRjYWJlNjAwMDc5AAFJAzAAAUcDKGRhdmlkKSBEYXZpZENhbGVuZGFyAAFKAzEzAAEBT0gDMzg0Y2YyZDg3N2MzOWE2MjJmZGMyYTE2ODk4MDUyZTIAAUkDMAABRwMoZGF2aWQpIENhbGVuZGFyAAFKAzEzAAEBT0gDQ29udGFjdHM6OlN5bmNyb3RvbgABSQMwAAFHA0NvbnRhY3RzAAFKAzkAAQFPSAMxYmI4YzU1ZmU4NGQ1MmM2OTY4ZGIyNTcxZjdkYzEyNAABSQMwAAFHA0FyY2hpdmUAAUoDMTIAAQFPSANiNTFhYmU3M2U5ZTk4ZmUyMDBhNGFmZTQwOTA1MDUwMgABSQMzOGI5NTBlYmQ2MmNkOWE2NjkyOWM4OTYxNWQwZmMwNAABRwNTcGFtAAFKAzEyAAEBT0gDY2Y1MjljNzkyZmM4N2QxZjIwNzQzNWIzOTIxYmIwMmUAAUkDMAABRwNTZW50AAFKAzUAAQFPSAM3MTVlZDllYTI5YjhhNTM3N2E2OWMxZjc1ODAzN2M2NQABSQMwAAFHA1NwYW0AAUoDMTIAAQFPSANkYjBkOTU5YTNhZWIyMTc1N2Y4ODQ5YTgzMDk0N2E3YQABSQMwAAFHA1RyYXNoAAFKAzQAAQFPSAM1YWM5ZWMyZTFhOWQ5OWUyZTEwY2FiZTRhYmYyNjcyOQABSQMwAAFHA0RyYWZ0cwABSgMzAAEBT0gDMzhiOTUwZWJkNjJjZDlhNjY5MjljODk2MTVkMGZjMDQAAUkDMAABRwNJTkJPWAABSgMyAAEBT0gDZmM1NmY0YzdmZmUwYWVmYTYyMmRiOWY4ZDkxODZjNGEAAUkDMAABRwNOb3RlcwABSgMxMAABAU9IAzkwMzM1ODgwZjY1ZGVmZjZlNTIxYWNlYTJiNzFhNzczAAFJAzAAAUcDVGFza3MAAUoDNwABAQEB'), - $output - ); - } - - public function testEncodeCalendar() - { - $outputStream = fopen("php://temp", 'r+'); - - $encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3); - - $xml = <<<EOF - <?xml version="1.0" encoding="utf-8"?> - <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> - <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Calendar="uri:Calendar"> - <Collections> - <Collection> - <SyncKey>0</SyncKey> - <CollectionId>38b950ebd62cd9a66929c89615d0fc04</CollectionId> - <DeletesAsMoves>0</DeletesAsMoves> - <GetChanges>0</GetChanges> - <WindowSize>512</WindowSize> - <Options> - <FilterType>0</FilterType> - <MIMESupport>2</MIMESupport> - <MIMETruncation>8</MIMETruncation> - <BodyPreference xmlns="uri:AirSyncBase"> - <Type>4</Type> - <AllOrNone>1</AllOrNone> - </BodyPreference> - </Options> - </Collection> - <Collection> - <SyncKey>0</SyncKey> - <CollectionId>cca1b81c734abbcd669bea90d23e08ae</CollectionId> - <Supported> - <DtStamp xmlns="uri:Calendar"/> - <Categories xmlns="uri:Calendar"/> - <Sensitivity xmlns="uri:Calendar"/> - <BusyStatus xmlns="uri:Calendar"/> - <UID xmlns="uri:Calendar"/> - <Timezone xmlns="uri:Calendar"/> - <StartTime xmlns="uri:Calendar"/> - <Subject xmlns="uri:Calendar"/> - <Location xmlns="uri:Calendar"/> - <EndTime xmlns="uri:Calendar"/> - <Recurrence xmlns="uri:Calendar"/> - <AllDayEvent xmlns="uri:Calendar"/> - <Reminder xmlns="uri:Calendar"/> - <Exceptions xmlns="uri:Calendar"/> - <Attendees xmlns="uri:Calendar"/> - <MeetingStatus xmlns="uri:Calendar"/> - <ResponseRequested xmlns="uri:Calendar"/> - <DisallowNewTimeProposal xmlns="uri:Calendar"/> - </Supported> - <DeletesAsMoves>0</DeletesAsMoves> - <GetChanges>0</GetChanges> - <WindowSize>512</WindowSize> - <Options> - <FilterType>0</FilterType> - <BodyPreference xmlns="uri:AirSyncBase"> - <Type>1</Type> - <AllOrNone>1</AllOrNone> - </BodyPreference> - </Options> - </Collection> - <Collection> - <SyncKey>0</SyncKey> - <CollectionId>Contacts::Syncroton</CollectionId> - <DeletesAsMoves>0</DeletesAsMoves> - <GetChanges>0</GetChanges> - <WindowSize>512</WindowSize> - <Options> - <FilterType>0</FilterType> - <BodyPreference xmlns="uri:AirSyncBase"> - <Type>1</Type> - <AllOrNone>1</AllOrNone> - </BodyPreference> - </Options> - </Collection> - <Collection> - <SyncKey>0</SyncKey> - <CollectionId>db0d959a3aeb21757f8849a830947a7a</CollectionId> - <DeletesAsMoves>0</DeletesAsMoves> - <GetChanges>0</GetChanges> - <WindowSize>512</WindowSize> - <Options> - <FilterType>0</FilterType> - <MIMESupport>2</MIMESupport> - <MIMETruncation>8</MIMETruncation> - <BodyPreference xmlns="uri:AirSyncBase"> - <Type>4</Type> - <AllOrNone>1</AllOrNone> - </BodyPreference> - </Options> - </Collection> - <Collection> - <SyncKey>0</SyncKey> - <CollectionId>cf529c792fc87d1f207435b3921bb02e</CollectionId> - <DeletesAsMoves>0</DeletesAsMoves> - <GetChanges>0</GetChanges> - <WindowSize>512</WindowSize> - <Options> - <FilterType>0</FilterType> - <MIMESupport>2</MIMESupport> - <MIMETruncation>8</MIMETruncation> - <BodyPreference xmlns="uri:AirSyncBase"> - <Type>4</Type> - <AllOrNone>1</AllOrNone> - </BodyPreference> - </Options> - </Collection> - <Collection> - <SyncKey>0</SyncKey> - <CollectionId>90335880f65deff6e521acea2b71a773</CollectionId> - <DeletesAsMoves>0</DeletesAsMoves> - <GetChanges>0</GetChanges> - <WindowSize>512</WindowSize> - <Options> - <FilterType>0</FilterType> - <BodyPreference xmlns="uri:AirSyncBase"> - <Type>1</Type> - <AllOrNone>1</AllOrNone> - </BodyPreference> - </Options> - </Collection> - <Collection> - <SyncKey>0</SyncKey> - <CollectionId>1bb8c55fe84d52c6968db2571f7dc124</CollectionId> - <DeletesAsMoves>0</DeletesAsMoves> - <GetChanges>0</GetChanges> - <WindowSize>512</WindowSize> - <Options> - <FilterType>0</FilterType> - <MIMESupport>2</MIMESupport> - <MIMETruncation>8</MIMETruncation> - <BodyPreference xmlns="uri:AirSyncBase"> - <Type>4</Type> - <AllOrNone>1</AllOrNone> - </BodyPreference> - </Options> - </Collection> - <Collection> - <SyncKey>0</SyncKey> - <CollectionId>715ed9ea29b8a5377a69c1f758037c65</CollectionId> - <DeletesAsMoves>0</DeletesAsMoves> - <GetChanges>0</GetChanges> - <WindowSize>512</WindowSize> - <Options> - <FilterType>0</FilterType> - <MIMESupport>2</MIMESupport> - <MIMETruncation>8</MIMETruncation> - <BodyPreference xmlns="uri:AirSyncBase"> - <Type>4</Type> - <AllOrNone>1</AllOrNone> - </BodyPreference> - </Options> - </Collection> - <Collection> - <SyncKey>0</SyncKey> - <CollectionId>b51abe73e9e98fe200a4afe409050502</CollectionId> - <DeletesAsMoves>0</DeletesAsMoves> - <GetChanges>0</GetChanges> - <WindowSize>512</WindowSize> - <Options> - <FilterType>0</FilterType> - <MIMESupport>2</MIMESupport> - <MIMETruncation>8</MIMETruncation> - <BodyPreference xmlns="uri:AirSyncBase"> - <Type>4</Type> - <AllOrNone>1</AllOrNone> - </BodyPreference> - </Options> - </Collection> - <Collection> - <SyncKey>0</SyncKey> - <CollectionId>0f66388806743c514b8063bf0dc87486</CollectionId> - <Supported> - <DtStamp xmlns="uri:Calendar"/> - <Categories xmlns="uri:Calendar"/> - <Sensitivity xmlns="uri:Calendar"/> - <BusyStatus xmlns="uri:Calendar"/> - <UID xmlns="uri:Calendar"/> - <Timezone xmlns="uri:Calendar"/> - <StartTime xmlns="uri:Calendar"/> - <Subject xmlns="uri:Calendar"/> - <Location xmlns="uri:Calendar"/> - <EndTime xmlns="uri:Calendar"/> - <Recurrence xmlns="uri:Calendar"/> - <AllDayEvent xmlns="uri:Calendar"/> - <Reminder xmlns="uri:Calendar"/> - <Exceptions xmlns="uri:Calendar"/> - <Attendees xmlns="uri:Calendar"/> - <MeetingStatus xmlns="uri:Calendar"/> - <ResponseRequested xmlns="uri:Calendar"/> - <DisallowNewTimeProposal xmlns="uri:Calendar"/> - </Supported> - <DeletesAsMoves>0</DeletesAsMoves> - <GetChanges>0</GetChanges> - <WindowSize>512</WindowSize> - <Options> - <FilterType>0</FilterType> - <BodyPreference xmlns="uri:AirSyncBase"> - <Type>1</Type> - <AllOrNone>1</AllOrNone> - </BodyPreference> - </Options> - </Collection> - <Collection> - <SyncKey>0</SyncKey> - <CollectionId>2685b302b79f58d2753199545e3cb8be</CollectionId> - <Supported> - <DtStamp xmlns="uri:Calendar"/> - <Categories xmlns="uri:Calendar"/> - <Sensitivity xmlns="uri:Calendar"/> - <BusyStatus xmlns="uri:Calendar"/> - <UID xmlns="uri:Calendar"/> - <Timezone xmlns="uri:Calendar"/> - <StartTime xmlns="uri:Calendar"/> - <Subject xmlns="uri:Calendar"/> - <Location xmlns="uri:Calendar"/> - <EndTime xmlns="uri:Calendar"/> - <Recurrence xmlns="uri:Calendar"/> - <AllDayEvent xmlns="uri:Calendar"/> - <Reminder xmlns="uri:Calendar"/> - <Exceptions xmlns="uri:Calendar"/> - <Attendees xmlns="uri:Calendar"/> - <MeetingStatus xmlns="uri:Calendar"/> - <ResponseRequested xmlns="uri:Calendar"/> - <DisallowNewTimeProposal xmlns="uri:Calendar"/> - </Supported> - <DeletesAsMoves>0</DeletesAsMoves> - <GetChanges>0</GetChanges> - <WindowSize>512</WindowSize> - <Options> - <FilterType>0</FilterType> - <BodyPreference xmlns="uri:AirSyncBase"> - <Type>1</Type> - <AllOrNone>1</AllOrNone> - </BodyPreference> - </Options> - </Collection> - <Collection> - <SyncKey>0</SyncKey> - <CollectionId>384cf2d877c39a622fdc2a16898052e2</CollectionId> - <Supported> - <DtStamp xmlns="uri:Calendar"/> - <Categories xmlns="uri:Calendar"/> - <Sensitivity xmlns="uri:Calendar"/> - <BusyStatus xmlns="uri:Calendar"/> - <UID xmlns="uri:Calendar"/> - <Timezone xmlns="uri:Calendar"/> - <StartTime xmlns="uri:Calendar"/> - <Subject xmlns="uri:Calendar"/> - <Location xmlns="uri:Calendar"/> - <EndTime xmlns="uri:Calendar"/> - <Recurrence xmlns="uri:Calendar"/> - <AllDayEvent xmlns="uri:Calendar"/> - <Reminder xmlns="uri:Calendar"/> - <Exceptions xmlns="uri:Calendar"/> - <Attendees xmlns="uri:Calendar"/> - <MeetingStatus xmlns="uri:Calendar"/> - <ResponseRequested xmlns="uri:Calendar"/> - <DisallowNewTimeProposal xmlns="uri:Calendar"/> - </Supported> - <DeletesAsMoves>0</DeletesAsMoves> - <GetChanges>0</GetChanges> - <WindowSize>512</WindowSize> - <Options> - <FilterType>0</FilterType> - <BodyPreference xmlns="uri:AirSyncBase"> - <Type>1</Type> - <AllOrNone>1</AllOrNone> - </BodyPreference> - </Options> - </Collection> - <Collection> - <SyncKey>0</SyncKey> - <CollectionId>9770b083c68e8584f396d15a116d6608</CollectionId> - <Supported> - <DtStamp xmlns="uri:Calendar"/> - <Categories xmlns="uri:Calendar"/> - <Sensitivity xmlns="uri:Calendar"/> - <BusyStatus xmlns="uri:Calendar"/> - <UID xmlns="uri:Calendar"/> - <Timezone xmlns="uri:Calendar"/> - <StartTime xmlns="uri:Calendar"/> - <Subject xmlns="uri:Calendar"/> - <Location xmlns="uri:Calendar"/> - <EndTime xmlns="uri:Calendar"/> - <Recurrence xmlns="uri:Calendar"/> - <AllDayEvent xmlns="uri:Calendar"/> - <Reminder xmlns="uri:Calendar"/> - <Exceptions xmlns="uri:Calendar"/> - <Attendees xmlns="uri:Calendar"/> - <MeetingStatus xmlns="uri:Calendar"/> - <ResponseRequested xmlns="uri:Calendar"/> - <DisallowNewTimeProposal xmlns="uri:Calendar"/> - </Supported> - <DeletesAsMoves>0</DeletesAsMoves> - <GetChanges>0</GetChanges> - <WindowSize>512</WindowSize> - <Options> - <FilterType>0</FilterType> - <BodyPreference xmlns="uri:AirSyncBase"> - <Type>1</Type> - <AllOrNone>1</AllOrNone> - </BodyPreference> - </Options> - </Collection> - <Collection> - <SyncKey>0</SyncKey> - <CollectionId>9e7b9656ef61d4af2fb2fdcabe600079</CollectionId> - <Supported> - <DtStamp xmlns="uri:Calendar"/> - <Categories xmlns="uri:Calendar"/> - <Sensitivity xmlns="uri:Calendar"/> - <BusyStatus xmlns="uri:Calendar"/> - <UID xmlns="uri:Calendar"/> - <Timezone xmlns="uri:Calendar"/> - <StartTime xmlns="uri:Calendar"/> - <Subject xmlns="uri:Calendar"/> - <Location xmlns="uri:Calendar"/> - <EndTime xmlns="uri:Calendar"/> - <Recurrence xmlns="uri:Calendar"/> - <AllDayEvent xmlns="uri:Calendar"/> - <Reminder xmlns="uri:Calendar"/> - <Exceptions xmlns="uri:Calendar"/> - <Attendees xmlns="uri:Calendar"/> - <MeetingStatus xmlns="uri:Calendar"/> - <ResponseRequested xmlns="uri:Calendar"/> - <DisallowNewTimeProposal xmlns="uri:Calendar"/> - </Supported> - <DeletesAsMoves>0</DeletesAsMoves> - <GetChanges>0</GetChanges> - <WindowSize>512</WindowSize> - <Options> - <FilterType>0</FilterType> - <BodyPreference xmlns="uri:AirSyncBase"> - <Type>1</Type> - <AllOrNone>1</AllOrNone> - </BodyPreference> - </Options> - </Collection> - <Collection> - <SyncKey>0</SyncKey> - <CollectionId>ab1ddb4ef8e8f8fcc2c9f5a7f9062452</CollectionId> - <Supported> - <DtStamp xmlns="uri:Calendar"/> - <Categories xmlns="uri:Calendar"/> - <Sensitivity xmlns="uri:Calendar"/> - <BusyStatus xmlns="uri:Calendar"/> - <UID xmlns="uri:Calendar"/> - <Timezone xmlns="uri:Calendar"/> - <StartTime xmlns="uri:Calendar"/> - <Subject xmlns="uri:Calendar"/> - <Location xmlns="uri:Calendar"/> - <EndTime xmlns="uri:Calendar"/> - <Recurrence xmlns="uri:Calendar"/> - <AllDayEvent xmlns="uri:Calendar"/> - <Reminder xmlns="uri:Calendar"/> - <Exceptions xmlns="uri:Calendar"/> - <Attendees xmlns="uri:Calendar"/> - <MeetingStatus xmlns="uri:Calendar"/> - <ResponseRequested xmlns="uri:Calendar"/> - <DisallowNewTimeProposal xmlns="uri:Calendar"/> - </Supported> - <DeletesAsMoves>0</DeletesAsMoves> - <GetChanges>0</GetChanges> - <WindowSize>512</WindowSize> - <Options> - <FilterType>0</FilterType> - <BodyPreference xmlns="uri:AirSyncBase"> - <Type>1</Type> - <AllOrNone>1</AllOrNone> - </BodyPreference> - </Options> - </Collection> - <Collection> - <SyncKey>0</SyncKey> - <CollectionId>d98bd8721371544ed095841ead941893</CollectionId> - <Supported> - <DtStamp xmlns="uri:Calendar"/> - <Categories xmlns="uri:Calendar"/> - <Sensitivity xmlns="uri:Calendar"/> - <BusyStatus xmlns="uri:Calendar"/> - <UID xmlns="uri:Calendar"/> - <Timezone xmlns="uri:Calendar"/> - <StartTime xmlns="uri:Calendar"/> - <Subject xmlns="uri:Calendar"/> - <Location xmlns="uri:Calendar"/> - <EndTime xmlns="uri:Calendar"/> - <Recurrence xmlns="uri:Calendar"/> - <AllDayEvent xmlns="uri:Calendar"/> - <Reminder xmlns="uri:Calendar"/> - <Exceptions xmlns="uri:Calendar"/> - <Attendees xmlns="uri:Calendar"/> - <MeetingStatus xmlns="uri:Calendar"/> - <ResponseRequested xmlns="uri:Calendar"/> - <DisallowNewTimeProposal xmlns="uri:Calendar"/> - </Supported> - <DeletesAsMoves>0</DeletesAsMoves> - <GetChanges>0</GetChanges> - <WindowSize>512</WindowSize> - <Options> - <FilterType>0</FilterType> - <BodyPreference xmlns="uri:AirSyncBase"> - <Type>1</Type> - <AllOrNone>1</AllOrNone> - </BodyPreference> - </Options> - </Collection> - </Collections> - <WindowSize>16</WindowSize> - </Sync> - EOF; - - - $dom = new DOMDocument(); - $dom->loadXML($xml); - - $encoder->encode($dom); - - rewind($outputStream); - $output = stream_get_contents($outputStream); - // print("----"); - // print(var_export(base64_encode($output), true)); - // print("----"); - - $this->assertEquals( - base64_decode('AwFqAEVcT0sDMAABUgMzOGI5NTBlYmQ2MmNkOWE2NjkyOWM4OTYxNWQwZmMwNAABXgMwAAFTAzAAAVUDNTEyAAFXWAMwAAFiAzIAAWMDOAABABFFRgM0AAFIAzEAAQEBAQAAT0sDMAABUgNjY2ExYjgxYzczNGFiYmNkNjY5YmVhOTBkMjNlMDhhZQABYAAEEQ4lDSgFJyYXEhsGJBQHGDQzAQAAXgMwAAFTAzAAAVUDNTEyAAFXWAMwAAEAEUVGAzEAAUgDMQABAQEBAABPSwMwAAFSA0NvbnRhY3RzOjpTeW5jcm90b24AAV4DMAABUwMwAAFVAzUxMgABV1gDMAABABFFRgMxAAFIAzEAAQEBAQAAT0sDMAABUgNkYjBkOTU5YTNhZWIyMTc1N2Y4ODQ5YTgzMDk0N2E3YQABXgMwAAFTAzAAAVUDNTEyAAFXWAMwAAFiAzIAAWMDOAABABFFRgM0AAFIAzEAAQEBAQAAT0sDMAABUgNjZjUyOWM3OTJmYzg3ZDFmMjA3NDM1YjM5MjFiYjAyZQABXgMwAAFTAzAAAVUDNTEyAAFXWAMwAAFiAzIAAWMDOAABABFFRgM0AAFIAzEAAQEBAQAAT0sDMAABUgM5MDMzNTg4MGY2NWRlZmY2ZTUyMWFjZWEyYjcxYTc3MwABXgMwAAFTAzAAAVUDNTEyAAFXWAMwAAEAEUVGAzEAAUgDMQABAQEBAABPSwMwAAFSAzFiYjhjNTVmZTg0ZDUyYzY5NjhkYjI1NzFmN2RjMTI0AAFeAzAAAVMDMAABVQM1MTIAAVdYAzAAAWIDMgABYwM4AAEAEUVGAzQAAUgDMQABAQEBAABPSwMwAAFSAzcxNWVkOWVhMjliOGE1Mzc3YTY5YzFmNzU4MDM3YzY1AAFeAzAAAVMDMAABVQM1MTIAAVdYAzAAAWIDMgABYwM4AAEAEUVGAzQAAUgDMQABAQEBAABPSwMwAAFSA2I1MWFiZTczZTllOThmZTIwMGE0YWZlNDA5MDUwNTAyAAFeAzAAAVMDMAABVQM1MTIAAVdYAzAAAWIDMgABYwM4AAEAEUVGAzQAAUgDMQABAQEBAABPSwMwAAFSAzBmNjYzODg4MDY3NDNjNTE0YjgwNjNiZjBkYzg3NDg2AAFgAAQRDiUNKAUnJhcSGwYkFAcYNDMBAABeAzAAAVMDMAABVQM1MTIAAVdYAzAAAQARRUYDMQABSAMxAAEBAQEAAE9LAzAAAVIDMjY4NWIzMDJiNzlmNThkMjc1MzE5OTU0NWUzY2I4YmUAAWAABBEOJQ0oBScmFxIbBiQUBxg0MwEAAF4DMAABUwMwAAFVAzUxMgABV1gDMAABABFFRgMxAAFIAzEAAQEBAQAAT0sDMAABUgMzODRjZjJkODc3YzM5YTYyMmZkYzJhMTY4OTgwNTJlMgABYAAEEQ4lDSgFJyYXEhsGJBQHGDQzAQAAXgMwAAFTAzAAAVUDNTEyAAFXWAMwAAEAEUVGAzEAAUgDMQABAQEBAABPSwMwAAFSAzk3NzBiMDgzYzY4ZTg1ODRmMzk2ZDE1YTExNmQ2NjA4AAFgAAQRDiUNKAUnJhcSGwYkFAcYNDMBAABeAzAAAVMDMAABVQM1MTIAAVdYAzAAAQARRUYDMQABSAMxAAEBAQEAAE9LAzAAAVIDOWU3Yjk2NTZlZjYxZDRhZjJmYjJmZGNhYmU2MDAwNzkAAWAABBEOJQ0oBScmFxIbBiQUBxg0MwEAAF4DMAABUwMwAAFVAzUxMgABV1gDMAABABFFRgMxAAFIAzEAAQEBAQAAT0sDMAABUgNhYjFkZGI0ZWY4ZThmOGZjYzJjOWY1YTdmOTA2MjQ1MgABYAAEEQ4lDSgFJyYXEhsGJBQHGDQzAQAAXgMwAAFTAzAAAVUDNTEyAAFXWAMwAAEAEUVGAzEAAUgDMQABAQEBAABPSwMwAAFSA2Q5OGJkODcyMTM3MTU0NGVkMDk1ODQxZWFkOTQxODkzAAFgAAQRDiUNKAUnJhcSGwYkFAcYNDMBAABeAzAAAVMDMAABVQM1MTIAAVdYAzAAAQARRUYDMQABSAMxAAEBAQEBAABVAzE2AAEB'), - $output - ); - } - - public function testEncodeEmail() - { - $outputStream = fopen("php://temp", 'r+'); - - $encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3); - - $xml = <<<EOF - <?xml version="1.0" encoding="utf-8"?> - <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> - <Sync xmlns="uri:AirSync" xmlns:Syncroton="uri:Syncroton" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Email="uri:Email" xmlns:Email2="uri:Email2" xmlns:Tasks="uri:Tasks"> - <Collections> - <Collection xmlns:default="uri:Email" xmlns:default1="uri:AirSyncBase"> - <Class>Email</Class> - <SyncKey>2</SyncKey> - <CollectionId>38b950ebd62cd9a66929c89615d0fc04</CollectionId> - <Status>1</Status> - <MoreAvailable/> - <Commands xmlns:default="uri:Email" xmlns:default1="uri:AirSyncBase"> - <Add xmlns:default="uri:Email" xmlns:default1="uri:AirSyncBase"> - <ServerId>38b950ebd62cd9a66929c89615d0fc04::1</ServerId> - <ApplicationData> - <Email:DateReceived xmlns="uri:Email">2023-05-06T14:51:40.000Z</Email:DateReceived> - <Email:From xmlns="uri:Email">"Mollekopf, Christian" <christian@example.ch></Email:From> - <Email:InternetCPID xmlns="uri:Email">65001</Email:InternetCPID> - <Email:Subject xmlns="uri:Email">Foobar 1</Email:Subject> - <Email:To xmlns="uri:Email">christian@example.ch</Email:To> - <Email:Read xmlns="uri:Email">0</Email:Read> - <Email:Flag xmlns="uri:Email"/> - <AirSyncBase:Body xmlns="uri:AirSyncBase"> - <AirSyncBase:Type>4</AirSyncBase:Type> - <AirSyncBase:Data>Return-Path: <christian@example.ch> - Received: from imapb010.mykolab.com (unix socket) - by imapb010.mykolab.com (Cyrus 2.5.10-49-g2e214b4-Kolab-2.5.10-8.1.el7.kolab_14) with LMTPA; - Wed, 09 Aug 2017 18:37:01 +0200 - X-Sieve: CMU Sieve 2.4 - Received: from int-mx002.mykolab.com (unknown 10.9.13.2) - by imapb010.mykolab.com (Postfix) with ESMTPS id 0A93910A25047 - for <christian@example.ch>; Wed, 9 Aug 2017 18:37:01 +0200 (CEST) - Received: from int-subm002.mykolab.com (unknown 10.9.37.2) - by int-mx002.mykolab.com (Postfix) with ESMTPS id EC06AF6E - for <christian@example.ch>; Wed, 9 Aug 2017 18:37:00 +0200 (CEST) - MIME-Version: 1.0 - Content-Type: multipart/mixed; - boundary="=_291b8e96564265636432c6d494e02322" - Date: Sat, 06 May 2023 14:41:40 - From: "Mollekopf, Christian" <christian@example.ch> - To: christian@example.ch - Subject: Foobar 1 - Message-ID: <foobar1@example.org> - - --=_291b8e96564265636432c6d494e02322 - Content-Type: multipart/alternative; - boundary="=_ceff0fd19756f45ed1295ee2069ff8e0" - - --=_ceff0fd19756f45ed1295ee2069ff8e0 - Content-Transfer-Encoding: 7bit - Content-Type: text/plain; charset=US-ASCII - - sdlkjsdjf - --=_ceff0fd19756f45ed1295ee2069ff8e0 - Content-Transfer-Encoding: quoted-printable - Content-Type: text/html; charset=UTF-8 - - <html><head><meta http-equiv=3D"Content-Type" content=3D"text/html; charset= - =3DUTF-8" /></head><body style=3D'font-size: 10pt; font-family: Verdana,Gen= - eva,sans-serif'> - <p>sdlkjsdjf</p> - - </body></html> - - --=_ceff0fd19756f45ed1295ee2069ff8e0-- - - --=_291b8e96564265636432c6d494e02322 - Content-Transfer-Encoding: base64 - Content-Type: text/plain; - name=xorg.conf - Content-Disposition: attachment; - filename=xorg.conf; - size=211 - - U2VjdGlvbiAiRGV2aWNlIgogICAgSWRlbnRpZmllciAgICAgIkRldmljZTAiCiAgICBEcml2ZXIg - ICAgIEJvYXJkTmFtZSAgICAgICJOVlMgNDIwME0iCiAgICBPcHRpb24gIk5vTG9nbyIgInRydWUi - CiAgICBPcHRpb24gIlVzZUVESUQiICJ0cnVlIgpFbmRTZWN0aW9uCg== - --=_291b8e96564265636432c6d494e02322--</AirSyncBase:Data> - </AirSyncBase:Body> - <AirSyncBase:NativeBodyType xmlns="uri:AirSyncBase">2</AirSyncBase:NativeBodyType> - <Email:MessageClass xmlns="uri:Email">IPM.Note</Email:MessageClass> - <Email:ContentClass xmlns="uri:Email">urn:content-classes:message</Email:ContentClass> - <AirSyncBase:Attachments xmlns="uri:AirSyncBase"> - <AirSyncBase:Attachment> - <AirSyncBase:DisplayName>xorg.conf</AirSyncBase:DisplayName> - <AirSyncBase:FileReference>38b950ebd62cd9a66929c89615d0fc04::5::2</AirSyncBase:FileReference> - <AirSyncBase:Method>1</AirSyncBase:Method> - <AirSyncBase:EstimatedDataSize>35100212</AirSyncBase:EstimatedDataSize> - </AirSyncBase:Attachment> - </AirSyncBase:Attachments> - </ApplicationData> - </Add> - </Commands> - </Collection> - </Collections> - </Sync> - EOF; - - $dom = new DOMDocument(); - $dom->loadXML($xml); - - $encoder->encode($dom); - - rewind($outputStream); - $output = stream_get_contents($outputStream); - // print("----"); - // print(var_export(base64_encode($output), true)); - // print("----"); - - $this->assertEquals( - base64_decode('AwFqAEVcT1ADRW1haWwAAUsDMgABUgMzOGI5NTBlYmQ2MmNkOWE2NjkyOWM4OTYxNWQwZmMwNAABTgMxAAEUVkdNAzM4Yjk1MGViZDYyY2Q5YTY2OTI5Yzg5NjE1ZDBmYzA0OjoxAAFdAAJPAzIwMjMtMDUtMDZUMTQ6NTE6NDAuMDAwWgABWAMiTW9sbGVrb3BmLCBDaHJpc3RpYW4iIDxjaHJpc3RpYW5AZXhhbXBsZS5jaD4AAXkDNjUwMDEAAVQDRm9vYmFyIDEAAVYDY2hyaXN0aWFuQGV4YW1wbGUuY2gAAVUDMAABOgARSkYDNAABSwNSZXR1cm4tUGF0aDogPGNocmlzdGlhbkBleGFtcGxlLmNoPg0KUmVjZWl2ZWQ6IGZyb20gaW1hcGIwMTAubXlrb2xhYi5jb20gKFt1bml4IHNvY2tldF0pDQogICAgICAgIGJ5IGltYXBiMDEwLm15a29sYWIuY29tIChDeXJ1cyAyLjUuMTAtNDktZzJlMjE0YjQtS29sYWItMi41LjEwLTguMS5lbDcua29sYWJfMTQpIHdpdGggTE1UUEE7DQogICAgICAgIFdlZCwgMDkgQXVnIDIwMTcgMTg6Mzc6MDEgKzAyMDANClgtU2lldmU6IENNVSBTaWV2ZSAyLjQNClJlY2VpdmVkOiBmcm9tIGludC1teDAwMi5teWtvbGFiLmNvbSAodW5rbm93biBbMTAuOS4xMy4yXSkNCiAgICAgICAgYnkgaW1hcGIwMTAubXlrb2xhYi5jb20gKFBvc3RmaXgpIHdpdGggRVNNVFBTIGlkIDBBOTM5MTBBMjUwNDcNCiAgICAgICAgZm9yIDxjaHJpc3RpYW5AZXhhbXBsZS5jaD47IFdlZCwgIDkgQXVnIDIwMTcgMTg6Mzc6MDEgKzAyMDAgKENFU1QpDQpSZWNlaXZlZDogZnJvbSBpbnQtc3VibTAwMi5teWtvbGFiLmNvbSAodW5rbm93biBbMTAuOS4zNy4yXSkNCiAgICAgICAgYnkgaW50LW14MDAyLm15a29sYWIuY29tIChQb3N0Zml4KSB3aXRoIEVTTVRQUyBpZCBFQzA2QUY2RQ0KICAgICAgICBmb3IgPGNocmlzdGlhbkBleGFtcGxlLmNoPjsgV2VkLCAgOSBBdWcgMjAxNyAxODozNzowMCArMDIwMCAoQ0VTVCkNCk1JTUUtVmVyc2lvbjogMS4wDQpDb250ZW50LVR5cGU6IG11bHRpcGFydC9taXhlZDsNCmJvdW5kYXJ5PSI9XzI5MWI4ZTk2NTY0MjY1NjM2NDMyYzZkNDk0ZTAyMzIyIg0KRGF0ZTogU2F0LCAwNiBNYXkgMjAyMyAxNDo0MTo0MCANCkZyb206ICJNb2xsZWtvcGYsIENocmlzdGlhbiIgPGNocmlzdGlhbkBleGFtcGxlLmNoPg0KVG86IGNocmlzdGlhbkBleGFtcGxlLmNoDQpTdWJqZWN0OiBGb29iYXIgMQ0KTWVzc2FnZS1JRDogPGZvb2JhcjFAZXhhbXBsZS5vcmc+DQoNCi0tPV8yOTFiOGU5NjU2NDI2NTYzNjQzMmM2ZDQ5NGUwMjMyMg0KQ29udGVudC1UeXBlOiBtdWx0aXBhcnQvYWx0ZXJuYXRpdmU7DQpib3VuZGFyeT0iPV9jZWZmMGZkMTk3NTZmNDVlZDEyOTVlZTIwNjlmZjhlMCINCg0KLS09X2NlZmYwZmQxOTc1NmY0NWVkMTI5NWVlMjA2OWZmOGUwDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiA3Yml0DQpDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9VVMtQVNDSUkNCg0Kc2Rsa2pzZGpmDQotLT1fY2VmZjBmZDE5NzU2ZjQ1ZWQxMjk1ZWUyMDY5ZmY4ZTANCkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IHF1b3RlZC1wcmludGFibGUNCkNvbnRlbnQtVHlwZTogdGV4dC9odG1sOyBjaGFyc2V0PVVURi04DQoNCjxodG1sPjxoZWFkPjxtZXRhIGh0dHAtZXF1aXY9M0QiQ29udGVudC1UeXBlIiBjb250ZW50PTNEInRleHQvaHRtbDsgY2hhcnNldD0NCj0zRFVURi04IiAvPjwvaGVhZD48Ym9keSBzdHlsZT0zRCdmb250LXNpemU6IDEwcHQ7IGZvbnQtZmFtaWx5OiBWZXJkYW5hLEdlbj0NCmV2YSxzYW5zLXNlcmlmJz4NCjxwPnNkbGtqc2RqZjwvcD4NCg0KPC9ib2R5PjwvaHRtbD4NCg0KLS09X2NlZmYwZmQxOTc1NmY0NWVkMTI5NWVlMjA2OWZmOGUwLS0NCg0KLS09XzI5MWI4ZTk2NTY0MjY1NjM2NDMyYzZkNDk0ZTAyMzIyDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiYXNlNjQNCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbjsNCm5hbWU9eG9yZy5jb25mDQpDb250ZW50LURpc3Bvc2l0aW9uOiBhdHRhY2htZW50Ow0KZmlsZW5hbWU9eG9yZy5jb25mOw0Kc2l6ZT0yMTENCg0KVTJWamRHbHZiaUFpUkdWMmFXTmxJZ29nSUNBZ1NXUmxiblJwWm1sbGNpQWdJQ0FnSWtSbGRtbGpaVEFpQ2lBZ0lDQkVjbWwyWlhJZw0KSUNBZ0lFSnZZWEprVG1GdFpTQWdJQ0FnSUNKT1ZsTWdOREl3TUUwaUNpQWdJQ0JQY0hScGIyNGdJazV2VEc5bmJ5SWdJblJ5ZFdVaQ0KQ2lBZ0lDQlBjSFJwYjI0Z0lsVnpaVVZFU1VRaUlDSjBjblZsSWdwRmJtUlRaV04wYVc5dUNnPT0NCi0tPV8yOTFiOGU5NjU2NDI2NTYzNjQzMmM2ZDQ5NGUwMjMyMi0tAAEBVgMyAAEAAlMDSVBNLk5vdGUAAXwDdXJuOmNvbnRlbnQtY2xhc3NlczptZXNzYWdlAAEAEU5PUAN4b3JnLmNvbmYAAVEDMzhiOTUwZWJkNjJjZDlhNjY5MjljODk2MTVkMGZjMDQ6OjU6OjIAAVIDMQABTAMzNTEwMDIxMgABAQEBAQEBAQE='), - $output - ); - } - - public function testEncodeEmailPerformanceTest() - { - $outputStream = fopen("php://temp", 'r+'); - - $encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3); - $attachment = str_repeat("ICAgIEJvYXJkTmFtZSAgICAgICJOVlMgNDIwME0iCiAgICBPcHRpb24gIk5vTG9nbyIgInRydWUi \n", 100000); - $xml = <<<EOF - <?xml version="1.0" encoding="utf-8"?> - <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> - <Sync xmlns="uri:AirSync" xmlns:Syncroton="uri:Syncroton" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Email="uri:Email" xmlns:Email2="uri:Email2" xmlns:Tasks="uri:Tasks"> - <Collections> - <Collection xmlns:default="uri:Email" xmlns:default1="uri:AirSyncBase"> - <Class>Email</Class> - <SyncKey>2</SyncKey> - <CollectionId>38b950ebd62cd9a66929c89615d0fc04</CollectionId> - <Status>1</Status> - <MoreAvailable/> - <Commands xmlns:default="uri:Email" xmlns:default1="uri:AirSyncBase"> - <Add xmlns:default="uri:Email" xmlns:default1="uri:AirSyncBase"> - <ServerId>38b950ebd62cd9a66929c89615d0fc04::1</ServerId> - <ApplicationData> - <Email:DateReceived xmlns="uri:Email">2023-05-06T14:51:40.000Z</Email:DateReceived> - <Email:From xmlns="uri:Email">"Mollekopf, Christian" <christian@example.ch></Email:From> - <Email:InternetCPID xmlns="uri:Email">65001</Email:InternetCPID> - <Email:Subject xmlns="uri:Email">Foobar 1</Email:Subject> - <Email:To xmlns="uri:Email">christian@example.ch</Email:To> - <Email:Read xmlns="uri:Email">0</Email:Read> - <Email:Flag xmlns="uri:Email"/> - <AirSyncBase:Body xmlns="uri:AirSyncBase"> - <AirSyncBase:Type>4</AirSyncBase:Type> - <AirSyncBase:Data>Return-Path: <christian@example.ch> - Received: from imapb010.mykolab.com (unix socket) - by imapb010.mykolab.com (Cyrus 2.5.10-49-g2e214b4-Kolab-2.5.10-8.1.el7.kolab_14) with LMTPA; - Wed, 09 Aug 2017 18:37:01 +0200 - X-Sieve: CMU Sieve 2.4 - Received: from int-mx002.mykolab.com (unknown 10.9.13.2) - by imapb010.mykolab.com (Postfix) with ESMTPS id 0A93910A25047 - for <christian@example.ch>; Wed, 9 Aug 2017 18:37:01 +0200 (CEST) - Received: from int-subm002.mykolab.com (unknown 10.9.37.2) - by int-mx002.mykolab.com (Postfix) with ESMTPS id EC06AF6E - for <christian@example.ch>; Wed, 9 Aug 2017 18:37:00 +0200 (CEST) - MIME-Version: 1.0 - Content-Type: multipart/mixed; - boundary="=_291b8e96564265636432c6d494e02322" - Date: Sat, 06 May 2023 14:41:40 - From: "Mollekopf, Christian" <christian@example.ch> - To: christian@example.ch - Subject: Foobar 1 - Message-ID: <foobar1@example.org> - - --=_291b8e96564265636432c6d494e02322 - Content-Type: multipart/alternative; - boundary="=_ceff0fd19756f45ed1295ee2069ff8e0" - - --=_ceff0fd19756f45ed1295ee2069ff8e0 - Content-Transfer-Encoding: 7bit - Content-Type: text/plain; charset=US-ASCII - - sdlkjsdjf - --=_ceff0fd19756f45ed1295ee2069ff8e0 - Content-Transfer-Encoding: quoted-printable - Content-Type: text/html; charset=UTF-8 - - <html><head><meta http-equiv=3D"Content-Type" content=3D"text/html; charset= - =3DUTF-8" /></head><body style=3D'font-size: 10pt; font-family: Verdana,Gen= - eva,sans-serif'> - <p>sdlkjsdjf</p> - - </body></html> - - --=_ceff0fd19756f45ed1295ee2069ff8e0-- - - --=_291b8e96564265636432c6d494e02322 - Content-Transfer-Encoding: base64 - Content-Type: text/plain; - name=xorg.conf - Content-Disposition: attachment; - filename=xorg.conf; - size=211 - - U2VjdGlvbiAiRGV2aWNlIgogICAgSWRlbnRpZmllciAgICAgIkRldmljZTAiCiAgICBEcml2ZXIg - {$attachment} - CiAgICBPcHRpb24gIlVzZUVESUQiICJ0cnVlIgpFbmRTZWN0aW9uCg== - --=_291b8e96564265636432c6d494e02322--</AirSyncBase:Data> - </AirSyncBase:Body> - <AirSyncBase:NativeBodyType xmlns="uri:AirSyncBase">2</AirSyncBase:NativeBodyType> - <Email:MessageClass xmlns="uri:Email">IPM.Note</Email:MessageClass> - <Email:ContentClass xmlns="uri:Email">urn:content-classes:message</Email:ContentClass> - <AirSyncBase:Attachments xmlns="uri:AirSyncBase"> - <AirSyncBase:Attachment> - <AirSyncBase:DisplayName>xorg.conf</AirSyncBase:DisplayName> - <AirSyncBase:FileReference>38b950ebd62cd9a66929c89615d0fc04::5::2</AirSyncBase:FileReference> - <AirSyncBase:Method>1</AirSyncBase:Method> - <AirSyncBase:EstimatedDataSize>35100212</AirSyncBase:EstimatedDataSize> - </AirSyncBase:Attachment> - </AirSyncBase:Attachments> - </ApplicationData> - </Add> - </Commands> - </Collection> - </Collections> - </Sync> - EOF; - - $dom = new DOMDocument(); - $dom->loadXML($xml); - - $start = microtime(true); - $encoder->encode($dom); - $end = microtime(true); - - $this->assertTrue($end - $start < 0.05); - } -} -
View file
kolab-syncroton.dsc
Changed
@@ -2,7 +2,7 @@ Source: kolab-syncroton Binary: kolab-syncroton Architecture: all -Version: 1:2.4.2.10-1~kolab1 +Version: 1:2.4.2.13-1~kolab1 Maintainer: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> Uploaders: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> Homepage: http://www.kolab.org/
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.