We truncated the diff of some files because they were too big.
If you want to see the full diff for every file, click here.
Changes of Revision 71
kolab-syncroton.spec
Changed
x
1
2
%global upstream_version 2.4.2
3
4
Name: kolab-syncroton
5
-Version: 2.4.2.31
6
+Version: 2.4.2.34
7
Release: 1%{?dist}
8
Summary: ActiveSync for Kolab Groupware
9
10
debian.changelog
Changed
7
1
2
-kolab-syncroton (2.4.2.31-0~kolab1) unstable; urgency=low
3
+kolab-syncroton (2.4.2.34-0~kolab1) unstable; urgency=low
4
5
* Release version 2.4.2
6
7
kolab-syncroton-2.4.2.tar.gz/bin/analyzelogs.php
Added
151
1
2
+#!/usr/bin/env php
3
+<?php
4
+/*
5
+ +--------------------------------------------------------------------------+
6
+ | Kolab Sync (ActiveSync for Kolab) |
7
+ | |
8
+ | Copyright (C) 2024, Apheleia IT AG <contact@apheleia-it.ch> |
9
+ | |
10
+ | This program is free software: you can redistribute it and/or modify |
11
+ | it under the terms of the GNU Affero General Public License as published |
12
+ | by the Free Software Foundation, either version 3 of the License, or |
13
+ | (at your option) any later version. |
14
+ | |
15
+ | This program is distributed in the hope that it will be useful, |
16
+ | but WITHOUT ANY WARRANTY; without even the implied warranty of |
17
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18
+ | GNU Affero General Public License for more details. |
19
+ | |
20
+ | You should have received a copy of the GNU Affero General Public License |
21
+ | along with this program. If not, see <http://www.gnu.org/licenses/> |
22
+ +--------------------------------------------------------------------------+
23
+ | Author: Christian Mollekopf <mollekopf@apheleia-it.ch> |
24
+ +--------------------------------------------------------------------------+
25
+*/
26
+
27
+
28
+define('RCUBE_INSTALL_PATH', realpath(dirname(__FILE__) . '/../') . '/');
29
+
30
+// Define include path
31
+$include_path = RCUBE_INSTALL_PATH . 'lib' . PATH_SEPARATOR;
32
+$include_path .= RCUBE_INSTALL_PATH . 'lib/ext' . PATH_SEPARATOR;
33
+$include_path .= ini_get('include_path');
34
+set_include_path($include_path);
35
+
36
+require_once "Syncroton/Command/ICommand.php";
37
+require_once "Syncroton/Command/Wbxml.php";
38
+require_once "Syncroton/Command/Sync.php";
39
+require_once "Syncroton/Command/Ping.php";
40
+require_once "Syncroton/Command/MoveItems.php";
41
+require_once "Syncroton/Command/FolderSync.php";
42
+
43
+$filename = $argv1;
44
+
45
+$content = file_get_contents($filename);
46
+
47
+// Split up the log files into chunks that hopefully match the commands
48
+$parts = preg_split("/\.*\: " . preg_quote("DEBUG Syncroton_Server::handle::65 REQUEST METHOD: POST", '/') . "/", $content, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
49
+
50
+function getStatusConstants($classname)
51
+{
52
+ $reflect = new ReflectionClass($classname);
53
+ $result = $reflect->getConstants();
54
+ $result = array_filter($result, function ($val) {
55
+ return str_starts_with($val, "STATUS_");
56
+ }, ARRAY_FILTER_USE_KEY);
57
+ $result = array_flip($result);
58
+ return $result;
59
+}
60
+
61
+function explainStatus($command, $status)
62
+{
63
+ if (!$status) {
64
+ return "none";
65
+ }
66
+ switch ($command) {
67
+ case "Ping":
68
+ $result = getStatusConstants("Syncroton_Command_Ping");
69
+ return $result$status ?? "Unknown";
70
+ case "Sync":
71
+ $result = getStatusConstants("Syncroton_Command_Sync");
72
+ return $result$status ?? "Unknown";
73
+ case "MoveItems":
74
+ $result = getStatusConstants("Syncroton_Command_MoveItems");
75
+ return $result$status ?? "Unknown";
76
+ case "FolderSync":
77
+ $result = getStatusConstants("Syncroton_Command_FolderSync");
78
+ return $result$status ?? "Unknown";
79
+ }
80
+ return "Unknown command";
81
+}
82
+
83
+foreach ($parts as $part) {
84
+ preg_match('/\(.*)\: /', $part, $matches);
85
+ $timestamp = $matches1;
86
+
87
+ preg_match('/\command\ => (.*)/', $part, $matches);
88
+ $command = $matches1;
89
+
90
+ preg_match('/\<Status\>(.*)\<\/Status\>/', $part, $matches);
91
+ $status = $matches1 ?? null;
92
+
93
+ $statusExplained = explainStatus($command, $status);
94
+
95
+ print(" Command: " . str_pad($command, 10) . str_pad("\tStatus: $status ($statusExplained)", 45) . "\tTimestamp: $timestamp\n");
96
+ if ($command == "Sync") {
97
+ // Find collections within this sync
98
+ // 25-Sep-2024 09:16:35.347730: INFO Syncroton_Command_Sync::handle::221 SyncKey is 7301 Class: Email CollectionId: 38b950ebd62cd9a66929c89615d0fc04
99
+ if (preg_match_all('/SyncKey is (.*) Class: (.*) CollectionId: (.*)/', $part, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
100
+ foreach ($matches as $set) {
101
+ $offset = $set01;
102
+ $collectionId = $set30;
103
+ $class = $set20;
104
+ $synckey = $set10;
105
+ print(str_pad(" Collection: $collectionId ($class)", 58) . "\tSyncKey: $synckey\n");
106
+
107
+ //Find the offset for this collections messages
108
+ if (preg_match("/Processing $collectionId\.\.\./", $part, $match, PREG_OFFSET_CAPTURE, $offset)) {
109
+ // print_r($match);
110
+ $offset = $match01 ?? null;
111
+ //Find the actual changes
112
+ if ($offset && preg_match('/found \(added\/changed\/deleted\) (.*)\/(.*)\/(.*) entries for sync from server to client/', $part, $changesMatch, PREG_OFFSET_CAPTURE, $offset)) {
113
+ // If the offset is too large we are looking at the next collection.
114
+ if ($changesMatch01 - $offset < 200) {
115
+ print(" " . $changesMatch00 . "\n");
116
+ }
117
+ }
118
+ }
119
+ //TODO We could figure out what the diff per collection was in terms of synckey to the last sync
120
+ //TODO We could figure out what we actually return in the response compared to the detected changeset
121
+ //TODO Warn if a collection is repeatedly synced with the same synckey, but changes are detected. It may be stuck in a sync loop.
122
+ }
123
+ }
124
+
125
+ // Detect entries that are being added from the client
126
+ if (preg_match_all('/found (.*) entries to be added on server/', $part, $matches)) {
127
+ foreach ($matches0 ?? as $match) {
128
+ print(" " . $match . "\n");
129
+ }
130
+ }
131
+
132
+ if (preg_match_all('/found (.*) entries to be updated on server/', $part, $matches)) {
133
+ foreach ($matches0 ?? as $match) {
134
+ print(" " . $match . "\n");
135
+ }
136
+ }
137
+
138
+ if (preg_match_all('/found (.*) entries to be deleted on server/', $part, $matches)) {
139
+ foreach ($matches0 ?? as $match) {
140
+ print(" " . $match . "\n");
141
+ }
142
+ }
143
+ }
144
+ //TODO on Sync:
145
+ //* number of Add/Change/Remove from client and from server
146
+ //* Synckey
147
+ //* list involved folders
148
+ //TODO on Sync:
149
+ //* Reason for interruption
150
+}
151
kolab-syncroton-2.4.2.tar.gz/bin/inspect.php
Changed
189
1
2
// include global functions from Roundcube Framework
3
require_once 'Roundcube/bootstrap.php';
4
5
+
6
+function filterTypeToIMAPSearch($filter_type = 0)
7
+{
8
+ switch ($filter_type) {
9
+ case 1:
10
+ $mod = '-1 day';
11
+ break;
12
+ case 2:
13
+ $mod = '-3 days';
14
+ break;
15
+ case 3:
16
+ $mod = '-1 week';
17
+ break;
18
+ case 4:
19
+ $mod = '-2 weeks';
20
+ break;
21
+ case 5:
22
+ $mod = '-1 month';
23
+ break;
24
+ }
25
+
26
+ if (!empty($mod)) {
27
+ $dt = new DateTime('now', new DateTimeZone('UTC'));
28
+ $dt->modify($mod);
29
+ // RFC3501: IMAP SEARCH
30
+ return 'SINCE ' . $dt->format('d-M-Y');
31
+ }
32
+
33
+ return "";
34
+}
35
+
36
+
37
$opts = rcube_utils::get_opt(
38
'e' => 'email',
39
'p' => 'adminpassword',
40
41
'ssl' =>
42
'verify_peer_name' => false,
43
'verify_peer' => false,
44
- 'allow_self_signed' => true
45
-
46
+ 'allow_self_signed' => true,
47
+ ,
48
;
49
50
ini_set('display_errors', 1);
51
52
$result$device_id'folders'$folder'id' =
53
"counter" => $data'counter',
54
"lastsync" => $data'lastsync',
55
- "lastfiltertype" => $data'lastfiltertype' ?? null,
56
"modseq" => $data'extra_data' ? json_decode($data'extra_data')->modseq : null,
57
;
58
}
59
60
$result$device_id'folders'$folder'id''name' = $folder'displayname';
61
+ $result$device_id'folders'$folder'id''class' = $folder'class';
62
+ $result$device_id'folders'$folder'id''lastfiltertype' = $folder'lastfiltertype' ?? null;
63
64
$imap->select($folder'displayname');
65
$result$device_id'folders'$folder'id''imapModseq' = $imap->data'HIGHESTMODSEQ' ?? null;
66
67
- $index = $imap->search($folder'displayname', 'ALL UNDELETED', false, 'COUNT');
68
+ $index = $imap->search(
69
+ $folder'displayname',
70
+ 'ALL UNDELETED ' . filterTypeToIMAPSearch($folder'lastfiltertype'),
71
+ false,
72
+ 'COUNT'
73
+ );
74
if (!$index->is_error()) {
75
$result$device_id'folders'$folder'id''imapMessagecount' = $index->count();
76
}
77
78
var_export($result);
79
}
80
81
-function println($output) {
82
+function println($output)
83
+{
84
print("{$output}\n");
85
}
86
87
-function filterType($value) {
88
- if (!$value) {
89
- return "No filter";
90
- }
91
- switch($value) {
92
- case 0: return "No filter";
93
- case 1: return "1 day";
94
- case 2: return "3 days";
95
- case 3: return "1 week";
96
- case 4: return "2 weeks";
97
- case 5: return "1 month";
98
- case 6: return "3 months";
99
- case 7: return "6 months";
100
- case 8: return "Filter by incomplete tasks";
101
- }
102
- return "Unknown value: $value";
103
+function filterType($value)
104
+{
105
+ if (!$value) {
106
+ return "No filter";
107
+ }
108
+ switch($value) {
109
+ case 0: return "No filter";
110
+ case 1: return "1 day";
111
+ case 2: return "3 days";
112
+ case 3: return "1 week";
113
+ case 4: return "2 weeks";
114
+ case 5: return "1 month";
115
+ case 6: return "3 months (WARNING: not implemented)";
116
+ case 7: return "6 months (WARNING: not implemented)";
117
+ case 8: return "Filter by incomplete tasks";
118
+ }
119
+ return "Unknown value: $value";
120
+}
121
+
122
+function getContentUids($db, $device_id, $folder_id)
123
+{
124
+ $contentSelect = $db->query(
125
+ "SELECT contentid FROM `syncroton_content`"
126
+ . " WHERE `device_id` = ? AND `folder_id` = ? AND `is_deleted` = 0",
127
+ $device_id,
128
+ $folder_id
129
+ );
130
+
131
+ $contentUids = ;
132
+ while ($content = $db->fetch_assoc($contentSelect)) {
133
+ $contentUids = explode('::', $content'contentid')1;
134
+ }
135
+ return $contentUids;
136
+}
137
+
138
+function getImapUids($imap, $folder, $lastfiltertype)
139
+{
140
+ $imap->select($folder);
141
+ $index = $imap->search($folder, 'ALL UNDELETED ' . filterTypeToIMAPSearch($lastfiltertype), true);
142
+ if (!$index->is_error()) {
143
+ return $index->get();
144
+ }
145
+ return ;
146
}
147
148
println("");
149
150
println(" Last sync: " . ($folder'lastsync' ?? "None"));
151
println(" Number of syncs: " . ($folder'counter' ?? "None"));
152
println(" Filter type: " . filterType($folder'lastfiltertype' ?? null));
153
+
154
+ if (($folder'class' == "Email") && ($folder'counter' ?? false) && $messageCount != $totalCount && ($modseq == "none" || $modseq == $imapModseq)) {
155
+ if (($folder'lastfiltertype' ?? false) && $messageCount > $totalCount) {
156
+ // This doesn't have to indicate an issue, since the timewindow of the filter wanders, so some messages that have been synchronized may no longer match the window.
157
+ } else {
158
+ println(" Issue Detected: The sync state seems to be inconsistent. The device should be fully synced, but the sync counts differ.");
159
+ println(" There are $messageCount ContentParts (should match number of messages on the device), but $totalCount messages in IMAP matching the filter.");
160
+
161
+ $contentUids = getContentUids($db, $deviceId, $folderId);
162
+ $imapUids = getImapUids($imap, $folder'name', $folder'lastfiltertype' ?? null);
163
+
164
+ $entries = array_diff($imapUids, $contentUids);
165
+ if (!empty($entries)) {
166
+ println(" The following messages are on the server, but not the device:");
167
+ foreach ($entries as $uid) {
168
+ println(" $uid");
169
+ //TODO get details from imap?
170
+ }
171
+ }
172
+
173
+ $entries = array_diff($contentUids, $imapUids);
174
+ if (!empty($entries)) {
175
+ println(" The following messages are on the device, but not the server:");
176
+ foreach ($entries as $uid) {
177
+ println(" $uid");
178
+ //TODO get details from the content part?
179
+ //TODO display creation_synckey?
180
+ }
181
+ }
182
+ println("");
183
+ }
184
+ }
185
+
186
println("");
187
}
188
}
189
kolab-syncroton-2.4.2.tar.gz/bin/resync.php
Added
118
1
2
+#!/usr/bin/php
3
+<?php
4
+/*
5
+ +--------------------------------------------------------------------------+
6
+ | Kolab Sync (ActiveSync for Kolab) |
7
+ | |
8
+ | Copyright (C) 2024, Apheleia IT AG <contact@apheleia-it.ch> |
9
+ | |
10
+ | This program is free software: you can redistribute it and/or modify |
11
+ | it under the terms of the GNU Affero General Public License as published |
12
+ | by the Free Software Foundation, either version 3 of the License, or |
13
+ | (at your option) any later version. |
14
+ | |
15
+ | This program is distributed in the hope that it will be useful, |
16
+ | but WITHOUT ANY WARRANTY; without even the implied warranty of |
17
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18
+ | GNU Affero General Public License for more details. |
19
+ | |
20
+ | You should have received a copy of the GNU Affero General Public License |
21
+ | along with this program. If not, see <http://www.gnu.org/licenses/> |
22
+ +--------------------------------------------------------------------------+
23
+ | Author: Aleksander Machniak <machniak@apheleia-it.ch> |
24
+ +--------------------------------------------------------------------------+
25
+*/
26
+
27
+define('RCUBE_INSTALL_PATH', realpath(dirname(__FILE__) . '/../') . '/');
28
+define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'lib/plugins/');
29
+
30
+// Define include path
31
+$include_path = RCUBE_INSTALL_PATH . 'lib' . PATH_SEPARATOR;
32
+$include_path .= RCUBE_INSTALL_PATH . 'lib/ext' . PATH_SEPARATOR;
33
+$include_path .= ini_get('include_path');
34
+set_include_path($include_path);
35
+
36
+// include composer autoloader (if available)
37
+if (@file_exists(RCUBE_INSTALL_PATH . 'vendor/autoload.php')) {
38
+ require RCUBE_INSTALL_PATH . 'vendor/autoload.php';
39
+}
40
+
41
+// include global functions from Roundcube Framework
42
+require_once 'Roundcube/bootstrap.php';
43
+
44
+$opts = rcube_utils::get_opt(
45
+ 'o' => 'owner',
46
+ 'f' => 'folder',
47
+ 'd' => 'deviceid',
48
+ 't' => 'devicetype', // e.g. WindowsOutlook15 or iPhone
49
+);
50
+
51
+$rcube = \rcube::get_instance();
52
+$db = $rcube->get_dbh();
53
+
54
+if (empty($opts'owner')) {
55
+ rcube::raise_error("Owner not specified (--owner).", false, true);
56
+}
57
+if (empty($opts'folder')) {
58
+ rcube::raise_error("Folder name not specified (--folder).", false, true);
59
+}
60
+
61
+$select = $db->query(
62
+ "SELECT `user_id` FROM `users` WHERE `username` = ? ORDER BY `user_id` DESC",
63
+ \strtolower($opts'owner')
64
+);
65
+
66
+if ($data = $db->fetch_assoc($select)) {
67
+ $userid = $data'user_id';
68
+} else {
69
+ rcube::raise_error("User not found in Roundcube database.", false, true);
70
+}
71
+
72
+$devices = ;
73
+if (!empty($opts'deviceid')) {
74
+ $select = $db->query(
75
+ "SELECT `id` FROM `syncroton_device` WHERE `owner_id` = ? AND `deviceid` = ?",
76
+ $userid,
77
+ $opts'deviceid'
78
+ );
79
+ while ($record = $db->fetch_assoc($select)) {
80
+ $devices = $record'id';
81
+ }
82
+} elseif (!empty($opts'devicetype')) {
83
+ $select = $db->query(
84
+ "SELECT `id` FROM `syncroton_device` WHERE `owner_id` = ? AND `devicetype` = ?",
85
+ $userid,
86
+ $opts'devicetype'
87
+ );
88
+ while ($record = $db->fetch_assoc($select)) {
89
+ $devices = $record'id';
90
+ }
91
+} else {
92
+ $select = $db->query("SELECT `id` FROM `syncroton_device` WHERE `owner_id` = ?", $userid);
93
+ while ($record = $db->fetch_assoc($select)) {
94
+ $devices = $record'id';
95
+ }
96
+}
97
+
98
+if (empty($devices)) {
99
+ rcube::raise_error("Device not found.", false, true);
100
+}
101
+
102
+// TODO: Support not only top-level folders
103
+
104
+$select = $db->query(
105
+ "SELECT `id`, `displayname`, `folderid` FROM `syncroton_folder`"
106
+ . " WHERE `device_id` IN (" . $db->array2list($devices) . ")"
107
+ . " AND `parentid` = '0' AND `displayname` = " . $db->quote($opts'folder')
108
+);
109
+
110
+while ($record = $db->fetch_assoc($select)) {
111
+ if (!empty($opts'dry-run')) {
112
+ print("DRY-RUN {$record'displayname'} ({$record'id'}:{$record'folderid'})\n");
113
+ } else {
114
+ $db->query("UPDATE `syncroton_folder` SET `resync` = 1 WHERE id = ?", $record'id');
115
+ print("{$record'displayname'} ({$record'id'}:{$record'folderid'})\n");
116
+ }
117
+}
118
kolab-syncroton-2.4.2.tar.gz/docs/SQL/mysql.initial.sql
Changed
15
1
2
`displayname` varchar(254) NOT NULL,
3
`type` int(11) NOT NULL,
4
`creation_time` datetime NOT NULL,
5
+ `creation_synckey` int(11) NOT NULL DEFAULT '0',
6
`lastfiltertype` int(11) DEFAULT NULL,
7
`supportedfields` longblob DEFAULT NULL,
8
PRIMARY KEY (`id`),
9
10
PRIMARY KEY(`name`)
11
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
12
13
-INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2024031100');
14
+INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2024102300');
15
kolab-syncroton-2.4.2.tar.gz/docs/SQL/mysql/2024101700.sql
Added
3
1
2
+ALTER TABLE `syncroton_folder` ADD `resync` tinyint(1) DEFAULT NULL;
3
kolab-syncroton-2.4.2.tar.gz/docs/SQL/mysql/2024102300.sql
Added
3
1
2
+ALTER TABLE `syncroton_folder` ADD `creation_synckey` int(11) NOT NULL DEFAULT '0';
3
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Backend/IFolder.php
Changed
13
1
2
*
3
* @param Syncroton_Model_Device|string $deviceId
4
* @param string $class
5
+ * @param int $syncKey
6
* @return array
7
*/
8
- public function getFolderState($deviceId, $class);
9
+ public function getFolderState($deviceId, $class, $syncKey);
10
11
/**
12
* delete all stored folderId's for given device
13
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/FolderCreate.php
Changed
9
1
2
$this->_folder->class = $folder->class;
3
$this->_folder->deviceId = $this->_device->id;
4
$this->_folder->creationTime = $this->_syncTimeStamp;
5
+ $this->_folder->creationSynckey = $this->_syncState->counter;
6
7
// Check if the folder already exists to avoid a duplicate insert attempt in db
8
try {
9
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/FolderSync.php
Changed
70
1
2
}
3
4
if (!($this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $syncKey)) instanceof Syncroton_Model_SyncState) {
5
+ if ($this->_logger instanceof Zend_Log) {
6
+ $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " invalidating sync state");
7
+ }
8
$this->_syncStateBackend->resetState($this->_device, 'FolderSync');
9
}
10
}
11
12
$serverFolders = $dataController->getAllFolders();
13
14
// retrieve all folders sent to client
15
- $clientFolders = $this->_folderBackend->getFolderState($this->_device, $class);
16
+ $clientFolders = $this->_folderBackend->getFolderState($this->_device, $class, $this->_syncState->counter);
17
18
if ($this->_syncState->counter > 0) {
19
// retrieve all folders changed since last sync
20
21
} else {
22
$add = $serverFolders$serverFolderId;
23
$add->creationTime = $this->_syncTimeStamp;
24
+ $add->creationSynckey = $this->_syncState->counter + 1;
25
$add->deviceId = $this->_device->id;
26
unset($add->id);
27
}
28
29
}
30
}
31
32
+ // Handle folders set for forced re-sync, we'll send a delete action to the client,
33
+ // but because the folder is still existing and subscribed on the backend it should
34
+ // "immediately" be added again (and re-synced).
35
+ $forceDeleteIds = array_keys(array_filter($clientFolders, function ($f) { return !empty($f->resync); }));
36
+ $serverFoldersIds = array_diff($serverFoldersIds, $forceDeleteIds);
37
+
38
// calculate deleted entries
39
$serverDiff = array_diff($clientFoldersIds, $serverFoldersIds);
40
foreach ($serverDiff as $serverFolderId) {
41
42
43
// store folder in backend
44
if (empty($folder->id)) {
45
- $this->_folderBackend->create($folder);
46
+ try {
47
+ $this->_folderBackend->create($folder);
48
+ } catch(Exception $zdse) {
49
+ //This can happen if we rerun a previous sync-key
50
+ $this->_folderBackend->update($folder);
51
+ }
52
}
53
}
54
55
56
$this->_folderBackend->delete($folder);
57
}
58
59
- if (empty($this->_syncState->id)) {
60
- $this->_syncStateBackend->create($this->_syncState);
61
- } else {
62
- $this->_syncStateBackend->update($this->_syncState);
63
+ // Only create this syncstate if it isn't already existing (which happens if we a sync key is re-sent)
64
+ if (!$this->_syncStateBackend->haveNext($this->_device, 'FolderSync', $this->_syncState->counter - 1)) {
65
+ // Keep previous sync states in case a sync key is re-sent
66
+ $this->_syncStateBackend->create($this->_syncState, true);
67
}
68
69
return $this->_outputDom;
70
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/MoveItems.php
Changed
51
1
2
$response = $moves->appendChild($this->_outputDom->createElementNS('uri:Move', 'Response'));
3
$response->appendChild($this->_outputDom->createElementNS('uri:Move', 'SrcMsgId', $move'srcMsgId'));
4
5
- try {
6
- if ($move'srcFldId' === $move'dstFldId') {
7
- throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::SAME_FOLDER);
8
- }
9
+ if ($this->_logger instanceof Zend_Log) {
10
+ $this->_logger->info(__METHOD__ . '::' . __LINE__ . " Moving from " . $move'srcFldId' . " to " . $move'dstFldId');
11
+ }
12
13
+ try {
14
try {
15
$sourceFolder = $this->_folderBackend->getFolder($this->_device, $move'srcFldId');
16
} catch (Syncroton_Exception_NotFound $e) {
17
18
throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_DESTINATION);
19
}
20
21
+ if ($move'srcFldId' === $move'dstFldId') {
22
+ throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::SAME_FOLDER);
23
+ }
24
+
25
$dataController = Syncroton_Data_Factory::factory($sourceFolder->class, $this->_device, $this->_syncTimeStamp);
26
$newId = $dataController->moveItem($move'srcFldId', $move'srcMsgId', $move'dstFldId');
27
-
28
if (!$newId) {
29
+ // We don't actually know what the reason was that this failed, but from the resolution description this error seems to make the most sense,
30
+ // and we rule out most other reasons before.
31
throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE);
32
}
33
34
$response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', Syncroton_Command_MoveItems::STATUS_SUCCESS));
35
$response->appendChild($this->_outputDom->createElementNS('uri:Move', 'DstMsgId', $newId));
36
- } catch (Syncroton_Exception_Status $e) {
37
+ } catch (Syncroton_Exception_Status_MoveItems $e) {
38
+ if ($this->_logger instanceof Zend_Log) {
39
+ $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " Move failed: " . $e->getMessage());
40
+ }
41
$response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', $e->getCode()));
42
} catch (Exception $e) {
43
- $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', Syncroton_Exception_Status::SERVER_ERROR));
44
+ if ($this->_logger instanceof Zend_Log) {
45
+ $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " Move failed: " . $e->getMessage());
46
+ }
47
+ $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', Syncroton_Exception_Status_MoveItems::FOLDER_LOCKED));
48
}
49
}
50
51
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/Ping.php
Changed
10
1
2
do {
3
// take a break to save battery lifetime
4
call_user_func($sleepCallback);
5
- sleep(Syncroton_Registry::getPingTimeout());
6
+ sleep(min(Syncroton_Registry::getPingTimeout(), $lifeTime));
7
8
// make sure the connection is still alive, abort otherwise
9
if (connection_aborted()) {
10
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/Sync.php
Changed
201
1
2
}
3
}
4
5
+ private function getServerModifications($dataController, $collectionData, $clientModifications)
6
+ {
7
+ $serverModifications =
8
+ 'added' => ,
9
+ 'changed' => ,
10
+ 'deleted' => ,
11
+ ;
12
+
13
+ // We first use hasChanges because it has a fast path for when there are no changes by fetching the count of messages only.
14
+ // However, in all other cases we will end up fetching the same entries as below, which is less than ideal.
15
+ // TODO: We should create a new method, which checks if there are no changes, and otherwise just let the code below figure out
16
+ // if there are any changes to process.
17
+ if (!$dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState)) {
18
+ return $serverModifications;
19
+ }
20
+
21
+ // update _syncTimeStamp as $dataController->hasChanges might have spent some time
22
+ $this->_syncTimeStamp = new DateTime('now', new DateTimeZone('UTC'));
23
+
24
+ // fetch entries added since last sync
25
+ $allClientEntries = $this->_contentStateBackend->getFolderState(
26
+ $this->_device,
27
+ $collectionData->folder,
28
+ $collectionData->syncState->counter
29
+ );
30
+
31
+ // fetch entries changed since last sync
32
+ $allChangedEntries = $dataController->getChangedEntries(
33
+ $collectionData->collectionId,
34
+ $collectionData->syncState,
35
+ $collectionData->options'filterType'
36
+ );
37
+
38
+ // fetch all entries
39
+ $allServerEntries = $dataController->getServerEntries(
40
+ $collectionData->collectionId,
41
+ $collectionData->options'filterType'
42
+ );
43
+
44
+ // add entries
45
+ $serverDiff = array_diff($allServerEntries, $allClientEntries);
46
+ // add entries which produced problems during delete from client
47
+ $serverModifications'added' = $clientModifications'forceAdd';
48
+ // add entries not yet sent to client
49
+ $serverModifications'added' = array_unique(array_merge($serverModifications'added', $serverDiff));
50
+
51
+ // @todo still needed?
52
+ foreach($serverModifications'added' as $id => $serverId) {
53
+ // skip entries added by client during this sync session
54
+ if(isset($clientModifications'added'$serverId) && !isset($clientModifications'forceAdd'$serverId)) {
55
+ if ($this->_logger instanceof Zend_Log) {
56
+ $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped added entry: " . $serverId);
57
+ }
58
+ unset($serverModifications'added'$id);
59
+ }
60
+ }
61
+
62
+ // entries to be deleted
63
+ $serverModifications'deleted' = array_diff($allClientEntries, $allServerEntries);
64
+
65
+ // entries changed since last sync
66
+ $serverModifications'changed' = array_merge($allChangedEntries, $clientModifications'forceChange');
67
+
68
+ foreach($serverModifications'changed' as $id => $serverId) {
69
+ // skip entry, if it got changed by client during current sync
70
+ if(isset($clientModifications'changed'$serverId) && !isset($clientModifications'forceChange'$serverId)) {
71
+ if ($this->_logger instanceof Zend_Log) {
72
+ $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped changed entry: " . $serverId);
73
+ }
74
+ unset($serverModifications'changed'$id);
75
+ }
76
+ // skip entry, make sure we don't sent entries already added by client in this request
77
+ elseif (isset($clientModifications'added'$serverId) && !isset($clientModifications'forceAdd'$serverId)) {
78
+ if ($this->_logger instanceof Zend_Log) {
79
+ $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped change for added entry: " . $serverId);
80
+ }
81
+ unset($serverModifications'changed'$id);
82
+ }
83
+ }
84
+
85
+ // entries comeing in scope are already in $serverModifications'added' and do not need to
86
+ // be send with $serverCanges
87
+ $serverModifications'changed' = array_diff($serverModifications'changed', $serverModifications'added');
88
+
89
+ return $serverModifications;
90
+ }
91
+
92
/**
93
* (non-PHPdoc)
94
* @see Syncroton_Command_Wbxml::getResponse()
95
96
;
97
98
$status = self::STATUS_SUCCESS;
99
- $hasChanges = 0;
100
101
if ($collectionData->getChanges === true) {
102
// continue sync session?
103
if(is_array($collectionData->syncState->pendingdata)) {
104
$serverModifications = $collectionData->syncState->pendingdata;
105
if ($this->_logger instanceof Zend_Log) {
106
- $this->_logger->info(__METHOD__ . '::' . __LINE__ . " restored from sync state: (added/changed/deleted) " . count($serverModifications'added') . '/' . count($serverModifications'changed') . '/' . count($serverModifications'deleted') . ' entries for sync from server to client');
107
+ $this->_logger->info(__METHOD__ . '::' . __LINE__ . " restored from sync state.");
108
}
109
} else {
110
try {
111
- $hasChanges = $dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState);
112
+ $serverModifications = $this->getServerModifications($dataController, $collectionData, $clientModifications);
113
} catch (Syncroton_Exception_NotFound $e) {
114
if ($this->_logger instanceof Zend_Log) {
115
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folder changes checking failed (not found): " . $e->getTraceAsString());
116
117
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " Folder changes checking failed: " . $e->getMessage());
118
}
119
120
- // Prevent from removing client entries when getServerEntries() fails
121
- // @todo: should we break the loop here?
122
- $status = self::STATUS_SERVER_ERROR;
123
- }
124
- }
125
-
126
- if ($hasChanges) {
127
- // update _syncTimeStamp as $dataController->hasChanges might have spent some time
128
- $this->_syncTimeStamp = new DateTime('now', new DateTimeZone('UTC'));
129
-
130
- try {
131
- // fetch entries added since last sync
132
- $allClientEntries = $this->_contentStateBackend->getFolderState(
133
- $this->_device,
134
- $collectionData->folder,
135
- $collectionData->syncState->counter
136
- );
137
-
138
- // fetch entries changed since last sync
139
- $allChangedEntries = $dataController->getChangedEntries(
140
- $collectionData->collectionId,
141
- $collectionData->syncState,
142
- $collectionData->options'filterType'
143
- );
144
-
145
- // fetch all entries
146
- $allServerEntries = $dataController->getServerEntries(
147
- $collectionData->collectionId,
148
- $collectionData->options'filterType'
149
- );
150
-
151
- // add entries
152
- $serverDiff = array_diff($allServerEntries, $allClientEntries);
153
- // add entries which produced problems during delete from client
154
- $serverModifications'added' = $clientModifications'forceAdd';
155
- // add entries not yet sent to client
156
- $serverModifications'added' = array_unique(array_merge($serverModifications'added', $serverDiff));
157
-
158
- // @todo still needed?
159
- foreach($serverModifications'added' as $id => $serverId) {
160
- // skip entries added by client during this sync session
161
- if(isset($clientModifications'added'$serverId) && !isset($clientModifications'forceAdd'$serverId)) {
162
- if ($this->_logger instanceof Zend_Log) {
163
- $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped added entry: " . $serverId);
164
- }
165
- unset($serverModifications'added'$id);
166
- }
167
- }
168
-
169
- // entries to be deleted
170
- $serverModifications'deleted' = array_diff($allClientEntries, $allServerEntries);
171
-
172
- // entries changed since last sync
173
- $serverModifications'changed' = array_merge($allChangedEntries, $clientModifications'forceChange');
174
-
175
- foreach($serverModifications'changed' as $id => $serverId) {
176
- // skip entry, if it got changed by client during current sync
177
- if(isset($clientModifications'changed'$serverId) && !isset($clientModifications'forceChange'$serverId)) {
178
- if ($this->_logger instanceof Zend_Log) {
179
- $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped changed entry: " . $serverId);
180
- }
181
- unset($serverModifications'changed'$id);
182
- }
183
- // skip entry, make sure we don't sent entries already added by client in this request
184
- elseif (isset($clientModifications'added'$serverId) && !isset($clientModifications'forceAdd'$serverId)) {
185
- if ($this->_logger instanceof Zend_Log) {
186
- $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped change for added entry: " . $serverId);
187
- }
188
- unset($serverModifications'changed'$id);
189
- }
190
- }
191
-
192
- // entries comeing in scope are already in $serverModifications'added' and do not need to
193
- // be send with $serverCanges
194
- $serverModifications'changed' = array_diff($serverModifications'changed', $serverModifications'added');
195
- } catch (Exception $e) {
196
- if ($this->_logger instanceof Zend_Log) {
197
- $this->_logger->crit(__METHOD__ . '::' . __LINE__ . " Folder state checking failed: " . $e->getMessage());
198
- }
199
if ($this->_logger instanceof Zend_Log) {
200
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folder state checking failed: " . $e->getTraceAsString());
201
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Model/Folder.php
Changed
11
1
2
'ownerId' => 'type' => 'string',
3
'class' => 'type' => 'string',
4
'creationTime' => 'type' => 'datetime',
5
+ 'creationSynckey' => 'type' => 'number',
6
'lastfiltertype' => 'type' => 'number',
7
+ 'resync' => 'type' => 'number',
8
,
9
;
10
}
11
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Model/IFolder.php
Changed
9
1
2
* @property string $parentId
3
* @property string $displayName
4
* @property DateTime $creationTime
5
+ * @property int $creationSynckey
6
* @property int $lastfiltertype
7
* @property int $type
8
*/
9
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync.php
Changed
31
1
2
$userid = $this->authenticate($_SERVER'PHP_AUTH_USER', $_SERVER'PHP_AUTH_PW');
3
}
4
5
- $this->plugins->exec_hook('ready', 'task' => 'syncroton');
6
+ if (!empty($userid)) {
7
+ $this->plugins->exec_hook('ready', 'task' => 'syncroton');
8
9
- // Set log directory per-user (again, in case the username changed above)
10
- $this->set_log_dir();
11
+ // Set log directory per-user (again, in case the username changed above)
12
+ $this->set_log_dir();
13
14
- // Save user password for Roundcube Framework
15
- $this->password = $_SERVER'PHP_AUTH_PW';
16
+ // Save user password for Roundcube Framework
17
+ $this->password = $_SERVER'PHP_AUTH_PW';
18
+ }
19
20
// Register Syncroton backends/callbacks
21
Syncroton_Registry::set(Syncroton_Registry::LOGGERBACKEND, $this->logger);
22
23
Syncroton_Registry::set(Syncroton_Registry::MAX_COLLECTIONS, (int) $this->config->get('activesync_max_folders', 100));
24
25
// Run Syncroton
26
- $syncroton = new Syncroton_Server($userid);
27
+ $syncroton = new Syncroton_Server($userid ?? null);
28
$syncroton->handle();
29
}
30
31
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_backend_folder.php
Changed
68
1
2
*
3
* @param Syncroton_Model_Device|string $deviceid Device object or identifier
4
* @param string $class Class name
5
+ * @param int $syncKey Sync key
6
*
7
* @return array List of object identifiers
8
*/
9
- public function getFolderState($deviceid, $class)
10
+ public function getFolderState($deviceid, $class, $syncKey = null)
11
{
12
$device_id = $deviceid instanceof Syncroton_Model_IDevice ? $deviceid->id : $deviceid;
13
14
$where = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id);
15
$where = $this->db->quote_identifier('class') . ' = ' . $this->db->quote($class);
16
+ if ($syncKey) {
17
+ $where = $this->db->quote_identifier('creation_synckey') . ' < ' . $this->db->quote($syncKey + 1);
18
+ }
19
20
$select = $this->db->query('SELECT * FROM `' . $this->table_name . '` WHERE ' . implode(' AND ', $where));
21
$result = ;
22
23
$select = $this->db->query('SELECT * FROM `' . $this->table_name . '` WHERE ' . implode(' AND ', $where));
24
$folder = $this->db->fetch_assoc($select);
25
26
+ if (!empty($folder'resync')) {
27
+ throw new Syncroton_Exception_NotFound("Folder $folderid not found because of resync");
28
+ }
29
if (empty($folder)) {
30
- throw new Syncroton_Exception_NotFound('Folder not found');
31
+ throw new Syncroton_Exception_NotFound("Folder $folderid not found");
32
}
33
34
return $this->get_object($folder);
35
36
// Reset imap cache so we work with up-to-date folders list
37
rcube::get_instance()->get_storage()->clear_cache('mailboxes', true);
38
39
+ // Retrieve all folders already sent to the client
40
+ $select = $this->db->query("SELECT * FROM `{$this->table_name}` WHERE `device_id` = ?", $device->id);
41
+
42
+ while ($folder = $this->db->fetch_assoc($select)) {
43
+ if (!empty($folder'resync')) {
44
+ // Folder re-sync requested
45
+ return true;
46
+ }
47
+
48
+ $client_folders$folder'folderid' = $this->get_object($folder);
49
+ }
50
+
51
foreach ($folder_classes as $class) {
52
try {
53
// retrieve all folders available in data backend
54
55
}
56
}
57
58
- // retrieve all folders sent to the client
59
- $select = $this->db->query("SELECT * FROM `{$this->table_name}` WHERE `device_id` = ?", $device->id);
60
-
61
- while ($folder = $this->db->fetch_assoc($select)) {
62
- $client_folders$folder'folderid' = $this->get_object($folder);
63
- }
64
-
65
ksort($client_folders);
66
ksort($server_folders);
67
68
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_data_email.php
Changed
28
1
2
$result = ;
3
4
if (!is_array($list)) {
5
- throw new Syncroton_Exception_NotFound('Folder not found');
6
+ throw new Syncroton_Exception_NotFound("Folder $folder_id not found: no folders available");
7
}
8
9
// device supports multiple folders?
10
11
}
12
13
if (empty($result)) {
14
- throw new Syncroton_Exception_NotFound('Folder not found');
15
+ throw new Syncroton_Exception_NotFound("Folder $folder_id not found.");
16
}
17
18
return $result;
19
20
$message = $this->getObject($fileReference);
21
22
if (!$message) {
23
- throw new Syncroton_Exception_NotFound('Message not found');
24
+ throw new Syncroton_Exception_NotFound("Message $fileReference not found");
25
}
26
27
$part = $message->mime_parts$part_id;
28
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_logger.php
Changed
12
1
2
}
3
4
foreach ($params as $key => $val) {
5
- if ($val = $_GET$val) {
6
- $device$key = $val;
7
+ if (isset($_GET$val)) {
8
+ $device$key = $_GET$val;
9
}
10
}
11
12
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_storage.php
Changed
23
1
2
3
// Use COPYUID feature (RFC2359) to get the new UID of the copied message
4
if (empty($this->storage->conn->data'COPYUID')) {
5
+ // Check if the source item actually exists. Cyrus IMAP reports
6
+ // OK on a MOVE with an invalid UID, But COPYUID will be empty.
7
+ // This way we only incur the cost of the extra check once the move fails.
8
+ if (!$this->storage->get_message_headers($uid, $src_name)) {
9
+ throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE);
10
+ }
11
throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR);
12
}
13
14
15
16
//If we had a collision before
17
if (isset($this->relations$folderid$synctime . "-1")) {
18
- return $this->relations$folderid$synctime. "-1";
19
+ return $this->relations$folderid$synctime . "-1";
20
}
21
if (!isset($this->relations$folderid$synctime)) {
22
$rcube = rcube::get_instance();
23
kolab-syncroton-2.4.2.tar.gz/tests/Sync/FoldersTest.php
Changed
201
1
2
class FoldersTest extends Tests\SyncTestCase
3
{
4
/**
5
- * Test FolderSync command
6
+ * Cleanup folders
7
*/
8
- public function testFolderSync()
9
+ public function setUp(): void
10
{
11
// Note: We essentially assume the test account is in an initial state, extra folders may break tests
12
// Anyway, we first remove folders that might have been created during tests in this file
13
$this->deleteTestFolder('Test Folder', 'mail');
14
+ $this->deleteTestFolder('NewFolder', 'mail');
15
+ $this->deleteTestFolder('NewFolder2', 'mail');
16
$this->deleteTestFolder('Test Folder New', 'mail');
17
$this->deleteTestFolder('Test Contacts Folder', 'contact');
18
$this->deleteTestFolder('Test Contacts New', 'contact');
19
+ parent::setUp();
20
+ }
21
+
22
+ /**
23
+ * Test FolderSync command
24
+ */
25
+ public function testFolderSyncBasic()
26
+ {
27
+ $request = <<<EOF
28
+ <?xml version="1.0" encoding="utf-8"?>
29
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
30
+ <FolderSync xmlns="uri:FolderHierarchy">
31
+ <SyncKey>0</SyncKey>
32
+ </FolderSync>
33
+ EOF;
34
+
35
+ $response = $this->request($request, 'FolderSync');
36
+
37
+ $this->assertEquals(200, $response->getStatusCode());
38
+
39
+ $dom = $this->fromWbxml($response->getBody());
40
+ $xpath = $this->xpath($dom);
41
+
42
+ $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue);
43
+ $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue);
44
+ // We expect some folders to exist (dont' know how many)
45
+ $this->assertTrue(intval($xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue) > 2);
46
+
47
+ $request = <<<EOF
48
+ <?xml version="1.0" encoding="utf-8"?>
49
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
50
+ <FolderSync xmlns="uri:FolderHierarchy">
51
+ <SyncKey>1</SyncKey>
52
+ </FolderSync>
53
+ EOF;
54
+
55
+ $response = $this->request($request, 'FolderSync');
56
+
57
+ $this->assertEquals(200, $response->getStatusCode());
58
+
59
+ $dom = $this->fromWbxml($response->getBody());
60
+ $xpath = $this->xpath($dom);
61
+
62
+ $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue);
63
+ $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue);
64
+ // No changes on second sync
65
+ $this->assertSame(strval(0), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue);
66
+
67
+
68
+ //Clear the creation_synckey (that's the migration scenario)
69
+ //Shouldn't trigger a change
70
+ $rcube = \rcube::get_instance();
71
+ $db = $rcube->get_dbh();
72
+ $result = $db->query(
73
+ "UPDATE `syncroton_folder` SET `creation_synckey` = null",
74
+ );
75
+
76
+ $request = <<<EOF
77
+ <?xml version="1.0" encoding="utf-8"?>
78
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
79
+ <FolderSync xmlns="uri:FolderHierarchy">
80
+ <SyncKey>1</SyncKey>
81
+ </FolderSync>
82
+ EOF;
83
+
84
+ $response = $this->request($request, 'FolderSync');
85
+ $this->assertEquals(200, $response->getStatusCode());
86
+ $dom = $this->fromWbxml($response->getBody());
87
+ $xpath = $this->xpath($dom);
88
+ $this->printDom($dom);
89
+ $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue);
90
+ $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue);
91
+ // No changes on second sync
92
+ $this->assertSame(strval(0), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue);
93
+ }
94
+
95
+ /**
96
+ * Test invalid sync key
97
+ */
98
+ public function testFolderInvalidSyncKey()
99
+ {
100
+ $request = <<<EOF
101
+ <?xml version="1.0" encoding="utf-8"?>
102
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
103
+ <FolderSync xmlns="uri:FolderHierarchy">
104
+ <SyncKey>999</SyncKey>
105
+ </FolderSync>
106
+ EOF;
107
+
108
+ $response = $this->request($request, 'FolderSync');
109
+
110
+ $this->assertEquals(200, $response->getStatusCode());
111
+
112
+ $dom = $this->fromWbxml($response->getBody());
113
+ $xpath = $this->xpath($dom);
114
+
115
+ $this->assertSame('9', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue);
116
+ }
117
+
118
+
119
+ /**
120
+ * Test synckey reuse
121
+ */
122
+ public function testSyncKeyResend()
123
+ {
124
+ $this->deleteTestFolder('NewFolder', 'mail');
125
+ $this->deleteTestFolder('NewFolder2', 'mail');
126
+ $request = <<<EOF
127
+ <?xml version="1.0" encoding="utf-8"?>
128
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
129
+ <FolderSync xmlns="uri:FolderHierarchy">
130
+ <SyncKey>0</SyncKey>
131
+ </FolderSync>
132
+ EOF;
133
+ $response = $this->request($request, 'FolderSync');
134
+ $this->assertEquals(200, $response->getStatusCode());
135
+ $dom = $this->fromWbxml($response->getBody());
136
+ $xpath = $this->xpath($dom);
137
+ $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue);
138
+
139
+ //Now change something
140
+ $this->createTestFolder("NewFolder", "mail");
141
+ $request = <<<EOF
142
+ <?xml version="1.0" encoding="utf-8"?>
143
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
144
+ <FolderSync xmlns="uri:FolderHierarchy">
145
+ <SyncKey>1</SyncKey>
146
+ </FolderSync>
147
+ EOF;
148
+
149
+ $response = $this->request($request, 'FolderSync');
150
+ $this->assertEquals(200, $response->getStatusCode());
151
+ $dom = $this->fromWbxml($response->getBody());
152
+ $xpath = $this->xpath($dom);
153
+ $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue);
154
+ $this->assertSame('2', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue);
155
+ $this->assertSame(strval(1), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue);
156
+
157
+ //Resend the same synckey
158
+ $request = <<<EOF
159
+ <?xml version="1.0" encoding="utf-8"?>
160
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
161
+ <FolderSync xmlns="uri:FolderHierarchy">
162
+ <SyncKey>1</SyncKey>
163
+ </FolderSync>
164
+ EOF;
165
+
166
+ $response = $this->request($request, 'FolderSync');
167
+ $this->assertEquals(200, $response->getStatusCode());
168
+ $dom = $this->fromWbxml($response->getBody());
169
+ $xpath = $this->xpath($dom);
170
+ $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue);
171
+ $this->assertSame('2', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue);
172
+ $this->assertSame(strval(1), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue);
173
+
174
+ //And now make sure we can still move on
175
+ $request = <<<EOF
176
+ <?xml version="1.0" encoding="utf-8"?>
177
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
178
+ <FolderSync xmlns="uri:FolderHierarchy">
179
+ <SyncKey>2</SyncKey>
180
+ </FolderSync>
181
+ EOF;
182
+ $response = $this->request($request, 'FolderSync');
183
+ $this->assertEquals(200, $response->getStatusCode());
184
+ $dom = $this->fromWbxml($response->getBody());
185
+ $xpath = $this->xpath($dom);
186
+ $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue);
187
+ $this->assertSame('2', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue);
188
+
189
+ //Now add another folder
190
+ $this->createTestFolder("NewFolder2", "mail");
191
+ $request = <<<EOF
192
+ <?xml version="1.0" encoding="utf-8"?>
193
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
194
+ <FolderSync xmlns="uri:FolderHierarchy">
195
+ <SyncKey>2</SyncKey>
196
+ </FolderSync>
197
+ EOF;
198
+ $response = $this->request($request, 'FolderSync');
199
+ $this->assertEquals(200, $response->getStatusCode());
200
+ $dom = $this->fromWbxml($response->getBody());
201
kolab-syncroton-2.4.2.tar.gz/tests/Sync/MoveItemsTest.php
Changed
105
1
2
$this->assertSame('test sync', $xpath->query("ns:Commands/ns:Add/ns:ApplicationData/Email:Subject", $root)->item(0)->nodeValue);
3
}
4
5
+ public function testInvalidMove()
6
+ {
7
+ $this->emptyTestFolder('INBOX', 'mail');
8
+ $this->emptyTestFolder('Trash', 'mail');
9
+ $uid = $this->appendMail('INBOX', 'mail.sync1');
10
+ $this->registerDevice();
11
+ $inbox = array_search('INBOX', $this->folders);
12
+ $trash = array_search('Trash', $this->folders);
13
+
14
+ // Move item that doesn't exist
15
+ $request = <<<EOF
16
+ <?xml version="1.0" encoding="utf-8"?>
17
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
18
+ <MoveItems xmlns="uri:Move">
19
+ <Move>
20
+ <SrcMsgId>foobar::99999</SrcMsgId>
21
+ <SrcFldId>foobar</SrcFldId>
22
+ <DstFldId>foobar</DstFldId>
23
+ </Move>
24
+ </MoveItems>
25
+ EOF;
26
+
27
+ $response = $this->request($request, 'MoveItems');
28
+
29
+ $this->assertEquals(200, $response->getStatusCode());
30
+
31
+ $dom = $this->fromWbxml($response->getBody());
32
+ $xpath = $this->xpath($dom);
33
+ $xpath->registerNamespace('Move', 'uri:Move');
34
+
35
+ $root = $xpath->query("//Move:MoveItems/Move:Response")->item(0);
36
+ $this->assertSame('1', $xpath->query("Move:Status", $root)->item(0)->nodeValue);
37
+
38
+ // Move item that doesn't exist
39
+ $request = <<<EOF
40
+ <?xml version="1.0" encoding="utf-8"?>
41
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
42
+ <MoveItems xmlns="uri:Move">
43
+ <Move>
44
+ <SrcMsgId>{$inbox}::99999</SrcMsgId>
45
+ <SrcFldId>{$inbox}</SrcFldId>
46
+ <DstFldId>{$trash}</DstFldId>
47
+ </Move>
48
+ </MoveItems>
49
+ EOF;
50
+
51
+ $response = $this->request($request, 'MoveItems');
52
+
53
+ $this->assertEquals(200, $response->getStatusCode());
54
+
55
+ $dom = $this->fromWbxml($response->getBody());
56
+ $xpath = $this->xpath($dom);
57
+ $xpath->registerNamespace('Move', 'uri:Move');
58
+
59
+ $root = $xpath->query("//Move:MoveItems/Move:Response")->item(0);
60
+ $this->assertSame('1', $xpath->query("Move:Status", $root)->item(0)->nodeValue);
61
+ }
62
+
63
/**
64
* Test moving a contact
65
*/
66
public function testMoveContact()
67
{
68
+ if ($this->isStorageDriver('kolab')) {
69
+ // The Contacts folder is not available, and consequently appendObject fails
70
+ $this->markTestSkipped('This test only works with the DAV backend.');
71
+ }
72
+
73
// Test with multi-folder support enabled
74
self::$deviceType = 'iphone';
75
76
+ // @phpstan-ignore-next-line
77
$davFolder = $this->isStorageDriver('kolab') ? 'Contacts' : 'Addressbook';
78
$this->emptyTestFolder($davFolder, 'contact');
79
$this->deleteTestFolder($folderName = 'Test Contacts Folder', 'contact');
80
81
$this->registerDevice();
82
83
$srcFolderId = array_search($davFolder, $this->folders);
84
+ $this->assertTrue(!empty($srcFolderId));
85
86
// Create a contacts folder
87
$folderType = Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED;
88
89
$response = $this->request($request, 'Sync');
90
91
$this->assertEquals(200, $response->getStatusCode());
92
+ $root = $xpath->query("//ns:Sync/ns:Collections/ns:Collection")->item(0);
93
+ $this->assertSame('1', $xpath->query("ns:Status", $root)->item(0)->nodeValue);
94
95
$request = <<<EOF
96
<?xml version="1.0" encoding="utf-8"?>
97
98
$xpath = $this->xpath($dom);
99
100
$root = $xpath->query("//ns:Sync/ns:Collections/ns:Collection")->item(0);
101
+ $this->assertSame('1', $xpath->query("ns:Status", $root)->item(0)->nodeValue);
102
$this->assertSame($srcFolderId, $xpath->query("ns:CollectionId", $root)->item(0)->nodeValue);
103
$this->assertSame(1, $xpath->query("ns:Commands/ns:Add", $root)->count());
104
$this->assertSame('Jane', $xpath->query("ns:Commands/ns:Add/ns:ApplicationData/Contacts:FirstName", $root)->item(0)->nodeValue);
105
kolab-syncroton-2.4.2.tar.gz/tests/Sync/PingTest.php
Added
104
1
2
+<?php
3
+
4
+class PingTest extends Tests\SyncTestCase
5
+{
6
+
7
+ /**
8
+ * Test Ping command
9
+ */
10
+ public function testFolderSyncBasic()
11
+ {
12
+ $request = <<<EOF
13
+ <?xml version="1.0" encoding="utf-8"?>
14
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
15
+ <Ping xmlns="uri:Ping">
16
+ <HeartbeatInterval>900</HeartbeatInterval>
17
+ <Folders>
18
+ <Folder>
19
+ <Id>38b950ebd62cd9a66929c89615d0fc04</Id>
20
+ <Class>Email</Class>
21
+ </Folder>
22
+ </Folders>
23
+ </Ping>
24
+ EOF;
25
+
26
+ $response = $this->request($request, 'Ping');
27
+
28
+ $this->assertEquals(200, $response->getStatusCode());
29
+
30
+ $dom = $this->fromWbxml($response->getBody());
31
+ $xpath = $this->xpath($dom);
32
+ $this->printDom($dom);
33
+
34
+ //Initially we know no folders
35
+ $this->assertSame('7', $xpath->query("//ns:Ping/ns:Status")->item(0)->nodeValue);
36
+
37
+
38
+ //We discover folders with a foldersync
39
+ $request = <<<EOF
40
+ <?xml version="1.0" encoding="utf-8"?>
41
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
42
+ <FolderSync xmlns="uri:FolderHierarchy">
43
+ <SyncKey>0</SyncKey>
44
+ </FolderSync>
45
+ EOF;
46
+
47
+ $response = $this->request($request, 'FolderSync');
48
+ $this->assertEquals(200, $response->getStatusCode());
49
+
50
+ //Now we get to the actual ping
51
+ $request = <<<EOF
52
+ <?xml version="1.0" encoding="utf-8"?>
53
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
54
+ <Ping xmlns="uri:Ping">
55
+ <HeartbeatInterval>0</HeartbeatInterval>
56
+ <Folders>
57
+ <Folder>
58
+ <Id>38b950ebd62cd9a66929c89615d0fc04</Id>
59
+ <Class>Email</Class>
60
+ </Folder>
61
+ </Folders>
62
+ </Ping>
63
+ EOF;
64
+
65
+ $response = $this->request($request, 'Ping');
66
+ $this->assertEquals(200, $response->getStatusCode());
67
+ $dom = $this->fromWbxml($response->getBody());
68
+ $xpath = $this->xpath($dom);
69
+ // $this->printDom($dom);
70
+ //Initially we know no folders
71
+ $this->assertSame('2', $xpath->query("//ns:Ping/ns:Status")->item(0)->nodeValue);
72
+ }
73
+
74
+ /**
75
+ * Test Ping command
76
+ */
77
+ public function testUnknownFolder()
78
+ {
79
+ $request = <<<EOF
80
+ <?xml version="1.0" encoding="utf-8"?>
81
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
82
+ <Ping xmlns="uri:Ping">
83
+ <HeartbeatInterval>900</HeartbeatInterval>
84
+ <Folders>
85
+ <Folder>
86
+ <Id>foobar</Id>
87
+ <Class>Email</Class>
88
+ </Folder>
89
+ </Folders>
90
+ </Ping>
91
+ EOF;
92
+
93
+ $response = $this->request($request, 'Ping');
94
+
95
+ $this->assertEquals(200, $response->getStatusCode());
96
+
97
+ $dom = $this->fromWbxml($response->getBody());
98
+ $xpath = $this->xpath($dom);
99
+ // $this->printDom($dom);
100
+
101
+ $this->assertSame('7', $xpath->query("//ns:Ping/ns:Status")->item(0)->nodeValue);
102
+ }
103
+}
104
kolab-syncroton-2.4.2.tar.gz/tests/Sync/Sync/InconsistencyTest.php
Added
148
1
2
+<?php
3
+
4
+namespace Tests\Sync\Sync;
5
+
6
+class InconsistencyTest extends \Tests\SyncTestCase
7
+{
8
+ /**
9
+ * Test Sync command
10
+ */
11
+ public function testSync()
12
+ {
13
+ $this->emptyTestFolder('INBOX', 'mail');
14
+ $this->registerDevice();
15
+
16
+ // Append two mail messages
17
+ $uid1 = $this->appendMail('INBOX', 'mail.sync1');
18
+ $this->appendMail('INBOX', 'mail.sync2');
19
+
20
+ // Initial sync
21
+ $folderId = '38b950ebd62cd9a66929c89615d0fc04';
22
+ $syncKey = 0;
23
+ $request = <<<EOF
24
+ <?xml version="1.0" encoding="utf-8"?>
25
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
26
+ <Sync xmlns="uri:AirSync">
27
+ <Collections>
28
+ <Collection>
29
+ <SyncKey>{$syncKey}</SyncKey>
30
+ <CollectionId>{$folderId}</CollectionId>
31
+ </Collection>
32
+ </Collections>
33
+ </Sync>
34
+ EOF;
35
+
36
+ $response = $this->request($request, 'Sync');
37
+ $this->assertEquals(200, $response->getStatusCode());
38
+ $syncKey++;
39
+
40
+ $request = <<<EOF
41
+ <?xml version="1.0" encoding="utf-8"?>
42
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
43
+ <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
44
+ <Collections>
45
+ <Collection>
46
+ <SyncKey>{$syncKey}</SyncKey>
47
+ <CollectionId>{$folderId}</CollectionId>
48
+ <DeletesAsMoves>1</DeletesAsMoves>
49
+ <GetChanges>1</GetChanges>
50
+ <WindowSize>2</WindowSize>
51
+ <Options>
52
+ <FilterType>0</FilterType>
53
+ <Conflict>1</Conflict>
54
+ <BodyPreference xmlns="uri:AirSyncBase">
55
+ <Type>2</Type>
56
+ <TruncationSize>51200</TruncationSize>
57
+ <AllOrNone>0</AllOrNone>
58
+ </BodyPreference>
59
+ </Options>
60
+ </Collection>
61
+ </Collections>
62
+ </Sync>
63
+ EOF;
64
+
65
+ $response = $this->request($request, 'Sync');
66
+
67
+ $this->assertEquals(200, $response->getStatusCode());
68
+
69
+ $dom = $this->fromWbxml($response->getBody());
70
+ $xpath = $this->xpath($dom);
71
+
72
+ $root = "//ns:Sync/ns:Collections/ns:Collection";
73
+ $this->assertSame('1', $xpath->query("{$root}/ns:Status")->item(0)->nodeValue);
74
+ $this->assertSame(strval(++$syncKey), $xpath->query("{$root}/ns:SyncKey")->item(0)->nodeValue);
75
+ $this->assertSame($folderId, $xpath->query("{$root}/ns:CollectionId")->item(0)->nodeValue);
76
+ $this->assertSame(2, $xpath->query("{$root}/ns:Commands/ns:Add")->count());
77
+
78
+ // Initial sync is complete
79
+ // We now artifically create a sync inconsistency be deleting the content part of the first mail.
80
+ // This replicates a situation that we've seen, but don't know yet how it was created in the first place.
81
+ $rcube = \rcube::get_instance();
82
+ $db = $rcube->get_dbh();
83
+ $result = $db->query(
84
+ "DELETE FROM `syncroton_content`"
85
+ . " WHERE `contentid` = ?",
86
+ "$folderId::$uid1"
87
+ );
88
+ $this->assertNull($db->is_error($result));
89
+
90
+ // Now sync again
91
+ $request = <<<EOF
92
+ <?xml version="1.0" encoding="utf-8"?>
93
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
94
+ <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
95
+ <Collections>
96
+ <Collection>
97
+ <SyncKey>{$syncKey}</SyncKey>
98
+ <CollectionId>{$folderId}</CollectionId>
99
+ <DeletesAsMoves>1</DeletesAsMoves>
100
+ <GetChanges>1</GetChanges>
101
+ <Options>
102
+ <FilterType>0</FilterType>
103
+ <Conflict>1</Conflict>
104
+ <BodyPreference xmlns="uri:AirSyncBase">
105
+ <Type>2</Type>
106
+ <TruncationSize>51200</TruncationSize>
107
+ <AllOrNone>0</AllOrNone>
108
+ </BodyPreference>
109
+ </Options>
110
+ </Collection>
111
+ </Collections>
112
+ </Sync>
113
+ EOF;
114
+
115
+ $response = $this->request($request, 'Sync');
116
+
117
+ $this->assertEquals(200, $response->getStatusCode());
118
+
119
+ $dom = $this->fromWbxml($response->getBody());
120
+ $xpath = $this->xpath($dom);
121
+
122
+ $root = "//ns:Sync/ns:Collections/ns:Collection";
123
+ $this->assertSame('1', $xpath->query("{$root}/ns:Status")->item(0)->nodeValue);
124
+ $this->assertSame(strval(++$syncKey), $xpath->query("{$root}/ns:SyncKey")->item(0)->nodeValue);
125
+ $this->assertSame($folderId, $xpath->query("{$root}/ns:CollectionId")->item(0)->nodeValue);
126
+ $this->assertSame(1, $xpath->query("{$root}/ns:Commands/ns:Add")->count());
127
+
128
+
129
+ //Assert that we have all content parts back
130
+ $sync = \kolab_sync::get_instance();
131
+ $device = $sync->storage()->device_get(self::$deviceId);
132
+
133
+ $result = $db->query(
134
+ "SELECT `contentid` FROM `syncroton_content`"
135
+ . " WHERE `device_id` = ?",
136
+ $device'ID'
137
+ );
138
+ $data = ;
139
+ while ($state = $db->fetch_assoc($result)) {
140
+ $data = $state;
141
+ }
142
+ $this->assertSame(2, count($data));
143
+
144
+ return $syncKey;
145
+ }
146
+
147
+}
148
kolab-syncroton-2.4.2.tar.gz/tests/Sync/Sync/RelationsTest.php
Changed
45
1
2
3
class RelationsTest extends \Tests\SyncTestCase
4
{
5
-
6
- protected function initialSyncRequest($folderId) {
7
+ protected function initialSyncRequest($folderId)
8
+ {
9
$request = <<<EOF
10
<?xml version="1.0" encoding="utf-8"?>
11
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
12
13
return $this->request($request, 'Sync');
14
}
15
16
- protected function syncRequest($syncKey, $folderId, $windowSize = null) {
17
+ protected function syncRequest($syncKey, $folderId, $windowSize = null)
18
+ {
19
$request = <<<EOF
20
<?xml version="1.0" encoding="utf-8"?>
21
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
22
23
$this->assertSame('Email', $xpath->query("//ns:Sync/ns:Collections/ns:Collection/ns:Class")->item(0)->nodeValue);
24
$this->assertSame($folderId, $xpath->query("//ns:Sync/ns:Collections/ns:Collection/ns:CollectionId")->item(0)->nodeValue);
25
26
- // First we append
27
+ // First we append
28
$uid1 = $this->appendMail('INBOX', 'mail.sync1');
29
$uid2 = $this->appendMail('INBOX', 'mail.sync2');
30
$this->appendMail('INBOX', 'mail.sync1', 'sync1' => 'sync3');
31
32
$this->assertSame(0, $xpath->query("{$root}/ns:ApplicationData/Email:Categories")->count());
33
//FIXME this currently fails because we omit the empty categories element
34
// $this->assertSame("", $xpath->query("{$root}/ns:ApplicationData/Email:Categories")->item(0)->nodeValue);
35
-
36
+
37
38
// Assert the db state
39
$result = $db->query(
40
41
return $syncKey;
42
}
43
}
44
-
45
kolab-syncroton-2.4.2.tar.gz/tests/SyncTestCase.php
Changed
56
1
2
if ($imap->folder_exists($foldername)) {
3
// TODO
4
exit("Not implemented for Kolab v3 storage driver");
5
+ } else {
6
+ exit("Folder is missing");
7
}
8
-
9
- return;
10
}
11
12
$dav = $this->getDavStorage();
13
14
}
15
16
/**
17
+ * Create a folder
18
+ */
19
+ protected function createTestFolder($name, $type)
20
+ {
21
+ // Create IMAP folders
22
+ if ($type == 'mail' || $this->isStorageDriver('kolab')) {
23
+ $imap = $this->getImapStorage();
24
+ //TODO set type if not mail
25
+ $imap->create_folder($name, true);
26
+
27
+ $metadata = ;
28
+ $metadata'FOLDER' = ;
29
+ $metadata'FOLDER'self::$deviceId = ;
30
+ $metadata'FOLDER'self::$deviceId'S' = '1';
31
+ $imap->set_metadata($name, '/private/vendor/kolab/activesync' => json_encode($metadata));
32
+
33
+ return;
34
+ }
35
+ }
36
+
37
+ /**
38
* Remove all objects from a folder
39
*/
40
protected function emptyTestFolder($name, $type)
41
42
return $xpath;
43
}
44
45
+ /**
46
+ * Pretty print the DOM
47
+ */
48
+ protected function printDom($dom)
49
+ {
50
+ $dom->formatOutput = true;
51
+ print($dom->saveXML());
52
+ }
53
54
/**
55
* adapter for phpunit < 9
56
kolab-syncroton.dsc
Changed
10
1
2
Source: kolab-syncroton
3
Binary: kolab-syncroton
4
Architecture: all
5
-Version: 1:2.4.2.31-1~kolab1
6
+Version: 1:2.4.2.34-1~kolab1
7
Maintainer: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com>
8
Uploaders: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com>
9
Homepage: http://www.kolab.org/
10