@@ -8,69 +8,69 @@ |
||
8 | 8 | */ |
9 | 9 | |
10 | 10 | class UserStoreInfo { |
11 | - private $foldercount; |
|
12 | - private $storesize; |
|
13 | - private $fullname; |
|
14 | - private $emailaddress; |
|
11 | + private $foldercount; |
|
12 | + private $storesize; |
|
13 | + private $fullname; |
|
14 | + private $emailaddress; |
|
15 | 15 | |
16 | - /** |
|
17 | - * Constructor. |
|
18 | - */ |
|
19 | - public function __construct() { |
|
20 | - $this->foldercount = 0; |
|
21 | - $this->storesize = 0; |
|
22 | - $this->fullname = null; |
|
23 | - $this->emailaddress = null; |
|
24 | - } |
|
16 | + /** |
|
17 | + * Constructor. |
|
18 | + */ |
|
19 | + public function __construct() { |
|
20 | + $this->foldercount = 0; |
|
21 | + $this->storesize = 0; |
|
22 | + $this->fullname = null; |
|
23 | + $this->emailaddress = null; |
|
24 | + } |
|
25 | 25 | |
26 | - /** |
|
27 | - * Sets data for the user's store. |
|
28 | - * |
|
29 | - * @param int $foldercount |
|
30 | - * @param int $storesize |
|
31 | - * @param string $fullname |
|
32 | - * @param string $emailaddress |
|
33 | - */ |
|
34 | - public function SetData($foldercount, $storesize, $fullname, $emailaddress) { |
|
35 | - $this->foldercount = $foldercount; |
|
36 | - $this->storesize = $storesize; |
|
37 | - $this->fullname = $fullname; |
|
38 | - $this->emailaddress = $emailaddress; |
|
39 | - } |
|
26 | + /** |
|
27 | + * Sets data for the user's store. |
|
28 | + * |
|
29 | + * @param int $foldercount |
|
30 | + * @param int $storesize |
|
31 | + * @param string $fullname |
|
32 | + * @param string $emailaddress |
|
33 | + */ |
|
34 | + public function SetData($foldercount, $storesize, $fullname, $emailaddress) { |
|
35 | + $this->foldercount = $foldercount; |
|
36 | + $this->storesize = $storesize; |
|
37 | + $this->fullname = $fullname; |
|
38 | + $this->emailaddress = $emailaddress; |
|
39 | + } |
|
40 | 40 | |
41 | - /** |
|
42 | - * Returns the number of folders in user's store. |
|
43 | - * |
|
44 | - * @return int |
|
45 | - */ |
|
46 | - public function GetFolderCount() { |
|
47 | - return $this->foldercount; |
|
48 | - } |
|
41 | + /** |
|
42 | + * Returns the number of folders in user's store. |
|
43 | + * |
|
44 | + * @return int |
|
45 | + */ |
|
46 | + public function GetFolderCount() { |
|
47 | + return $this->foldercount; |
|
48 | + } |
|
49 | 49 | |
50 | - /** |
|
51 | - * Returns the user's store size in bytes. |
|
52 | - * |
|
53 | - * @return int |
|
54 | - */ |
|
55 | - public function GetStoreSize() { |
|
56 | - return $this->storesize; |
|
57 | - } |
|
50 | + /** |
|
51 | + * Returns the user's store size in bytes. |
|
52 | + * |
|
53 | + * @return int |
|
54 | + */ |
|
55 | + public function GetStoreSize() { |
|
56 | + return $this->storesize; |
|
57 | + } |
|
58 | 58 | |
59 | - /** |
|
60 | - * Returns the fullname of the user. |
|
61 | - * |
|
62 | - * @return string |
|
63 | - */ |
|
64 | - public function GetFullName() { |
|
65 | - return $this->fullname; |
|
66 | - } |
|
59 | + /** |
|
60 | + * Returns the fullname of the user. |
|
61 | + * |
|
62 | + * @return string |
|
63 | + */ |
|
64 | + public function GetFullName() { |
|
65 | + return $this->fullname; |
|
66 | + } |
|
67 | 67 | |
68 | - /** |
|
69 | - * Returns the email address of the user. |
|
70 | - * |
|
71 | - * @return string |
|
72 | - */ |
|
73 | - public function GetEmailAddress() { |
|
74 | - return $this->emailaddress; |
|
75 | - } |
|
68 | + /** |
|
69 | + * Returns the email address of the user. |
|
70 | + * |
|
71 | + * @return string |
|
72 | + */ |
|
73 | + public function GetEmailAddress() { |
|
74 | + return $this->emailaddress; |
|
75 | + } |
|
76 | 76 | } |
@@ -12,389 +12,389 @@ discard block |
||
12 | 12 | */ |
13 | 13 | |
14 | 14 | class LoopDetection extends InterProcessData { |
15 | - public const INTERPROCESSLD = "processstack"; |
|
16 | - public const BROKENMSGS = "brokenmsgs"; |
|
17 | - private static $processident; |
|
18 | - private static $processentry; |
|
19 | - private $ignore_messageid; |
|
20 | - private $broken_message_uuid; |
|
21 | - private $broken_message_counter; |
|
22 | - |
|
23 | - /** |
|
24 | - * Constructor. |
|
25 | - */ |
|
26 | - public function __construct() { |
|
27 | - // initialize super parameters |
|
28 | - $this->allocate = 1024000; // 1 MB |
|
29 | - $this->type = "grommunio-sync:loopdetection"; |
|
30 | - parent::__construct(); |
|
31 | - |
|
32 | - $this->ignore_messageid = false; |
|
33 | - } |
|
34 | - |
|
35 | - /** |
|
36 | - * PROCESS LOOP DETECTION. |
|
37 | - */ |
|
38 | - |
|
39 | - /** |
|
40 | - * Adds the process entry to the process stack. |
|
41 | - * |
|
42 | - * @return bool |
|
43 | - */ |
|
44 | - public function ProcessLoopDetectionInit() { |
|
45 | - return $this->updateProcessStack(); |
|
46 | - } |
|
47 | - |
|
48 | - /** |
|
49 | - * Marks the process entry as termineted successfully on the process stack. |
|
50 | - * |
|
51 | - * @return bool |
|
52 | - */ |
|
53 | - public function ProcessLoopDetectionTerminate() { |
|
54 | - // just to be sure that the entry is there |
|
55 | - self::GetProcessEntry(); |
|
56 | - |
|
57 | - self::$processentry['end'] = time(); |
|
58 | - SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->ProcessLoopDetectionTerminate()"); |
|
59 | - |
|
60 | - return $this->updateProcessStack(); |
|
61 | - } |
|
62 | - |
|
63 | - /** |
|
64 | - * Returns a unique identifier for the internal process tracking. |
|
65 | - * |
|
66 | - * @return string |
|
67 | - */ |
|
68 | - public static function GetProcessIdentifier() { |
|
69 | - if (!isset(self::$processident)) { |
|
70 | - self::$processident = sprintf('%04x%04x', mt_rand(0, 0xFFFF), mt_rand(0, 0xFFFF)); |
|
71 | - } |
|
72 | - |
|
73 | - return self::$processident; |
|
74 | - } |
|
75 | - |
|
76 | - /** |
|
77 | - * Returns a unique entry with information about the current process. |
|
78 | - * |
|
79 | - * @return array |
|
80 | - */ |
|
81 | - public static function GetProcessEntry() { |
|
82 | - if (!isset(self::$processentry)) { |
|
83 | - self::$processentry = []; |
|
84 | - self::$processentry['id'] = self::GetProcessIdentifier(); |
|
85 | - self::$processentry['pid'] = self::$pid; |
|
86 | - self::$processentry['time'] = self::$start; |
|
87 | - self::$processentry['cc'] = Request::GetCommandCode(); |
|
88 | - } |
|
89 | - |
|
90 | - return self::$processentry; |
|
91 | - } |
|
92 | - |
|
93 | - /** |
|
94 | - * Adds an Exceptions to the process tracking. |
|
95 | - * |
|
96 | - * @param Exception $exception |
|
97 | - * |
|
98 | - * @return bool |
|
99 | - */ |
|
100 | - public function ProcessLoopDetectionAddException($exception) { |
|
101 | - // generate entry if not already there |
|
102 | - self::GetProcessEntry(); |
|
103 | - |
|
104 | - if (!isset(self::$processentry['stat'])) { |
|
105 | - self::$processentry['stat'] = []; |
|
106 | - } |
|
107 | - |
|
108 | - self::$processentry['stat'][get_class($exception)] = $exception->getCode(); |
|
109 | - |
|
110 | - $this->updateProcessStack(); |
|
111 | - |
|
112 | - return true; |
|
113 | - } |
|
114 | - |
|
115 | - /** |
|
116 | - * Adds a folderid and connected status code to the process tracking. |
|
117 | - * |
|
118 | - * @param string $folderid |
|
119 | - * @param int $status |
|
120 | - * |
|
121 | - * @return bool |
|
122 | - */ |
|
123 | - public function ProcessLoopDetectionAddStatus($folderid, $status) { |
|
124 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->ProcessLoopDetectionAddStatus: '%s' with status %d", $folderid ? $folderid : 'hierarchy', $status)); |
|
125 | - // generate entry if not already there |
|
126 | - self::GetProcessEntry(); |
|
127 | - |
|
128 | - if ($folderid === false) { |
|
129 | - $folderid = "hierarchy"; |
|
130 | - } |
|
131 | - |
|
132 | - if (!isset(self::$processentry['stat'])) { |
|
133 | - self::$processentry['stat'] = []; |
|
134 | - } |
|
135 | - |
|
136 | - self::$processentry['stat'][$folderid] = $status; |
|
137 | - |
|
138 | - $this->updateProcessStack(); |
|
139 | - |
|
140 | - return true; |
|
141 | - } |
|
142 | - |
|
143 | - /** |
|
144 | - * Marks the current process as a PUSH connection. |
|
145 | - * |
|
146 | - * @return bool |
|
147 | - */ |
|
148 | - public function ProcessLoopDetectionSetAsPush() { |
|
149 | - // generate entry if not already there |
|
150 | - self::GetProcessEntry(); |
|
151 | - self::$processentry['push'] = true; |
|
152 | - |
|
153 | - return $this->updateProcessStack(); |
|
154 | - } |
|
155 | - |
|
156 | - /** |
|
157 | - * Indicates if a simple Hierarchy sync should be done after Ping. |
|
158 | - * |
|
159 | - * When trying to sync a non existing folder, Sync will return Status 12. |
|
160 | - * This should trigger a hierarchy sync by the client, but this is not always done. |
|
161 | - * Clients continue trying to Ping, which fails as well and triggers a Sync again. |
|
162 | - * This goes on forever, like here: https://jira.z-hub.io/browse/ZP-1077 |
|
163 | - * |
|
164 | - * Ping could indicate to perform a FolderSync as well after a few Sync/Ping cycles. |
|
165 | - * |
|
166 | - * @return bool |
|
167 | - */ |
|
168 | - public function ProcessLoopDetectionIsHierarchySyncAdvised() { |
|
169 | - $me = self::GetProcessEntry(); |
|
170 | - if ($me['cc'] !== GSync::COMMAND_PING) { |
|
171 | - return false; |
|
172 | - } |
|
173 | - |
|
174 | - $loopingFolders = []; |
|
175 | - |
|
176 | - $lookback = self::$start - 600; // look at the last 5 min |
|
177 | - foreach ($this->getProcessStack() as $se) { |
|
178 | - if ($se['time'] > $lookback && $se['time'] < (self::$start - 1)) { |
|
179 | - // look for sync command |
|
180 | - if (isset($se['stat']) && ($se['cc'] == GSync::COMMAND_SYNC || $se['cc'] == GSync::COMMAND_PING)) { |
|
181 | - foreach ($se['stat'] as $key => $value) { |
|
182 | - // we only care about hierarchy errors of this folder |
|
183 | - if ($se['cc'] == GSync::COMMAND_SYNC) { |
|
184 | - if ($value == SYNC_STATUS_FOLDERHIERARCHYCHANGED) { |
|
185 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->ProcessLoopDetectionIsHierarchySyncAdvised(): seen Sync command with Exception or folderid '%s' and code '%s'", $key, $value)); |
|
186 | - } |
|
187 | - } |
|
188 | - if (!isset($loopingFolders[$key])) { |
|
189 | - $loopingFolders[$key] = [GSync::COMMAND_SYNC => [], GSync::COMMAND_PING => []]; |
|
190 | - } |
|
191 | - if (!isset($loopingFolders[$key][$se['cc']][$value])) { |
|
192 | - $loopingFolders[$key][$se['cc']][$value] = 0; |
|
193 | - } |
|
194 | - ++$loopingFolders[$key][$se['cc']][$value]; |
|
195 | - } |
|
196 | - } |
|
197 | - } |
|
198 | - } |
|
199 | - |
|
200 | - $filtered = []; |
|
201 | - foreach ($loopingFolders as $fid => $data) { |
|
202 | - // Ping is constantly generating changes for this folder |
|
203 | - if (isset($data[GSync::COMMAND_PING][SYNC_PINGSTATUS_CHANGES]) && $data[GSync::COMMAND_PING][SYNC_PINGSTATUS_CHANGES] >= 3) { |
|
204 | - // but the Sync request is not treating it (not being requested) |
|
205 | - if (count($data[GSync::COMMAND_SYNC]) == 0) { |
|
206 | - SLog::Write(LOGLEVEL_INFO, sprintf("LoopDetection->ProcessLoopDetectionIsHierarchySyncAdvised(): Ping loop of folderid '%s' detected that is not being synchronized.", $fid)); |
|
207 | - |
|
208 | - return true; |
|
209 | - } |
|
210 | - // Sync is executed, but a foldersync should be executed (hierarchy errors) |
|
211 | - if (isset($data[GSync::COMMAND_SYNC][SYNC_STATUS_FOLDERHIERARCHYCHANGED]) && |
|
212 | - $data[GSync::COMMAND_SYNC][SYNC_STATUS_FOLDERHIERARCHYCHANGED] > 3) { |
|
213 | - SLog::Write(LOGLEVEL_INFO, sprintf("LoopDetection->ProcessLoopDetectionIsHierarchySyncAdvised(): Sync(with error)/Ping loop of folderid '%s' detected.", $fid)); |
|
214 | - |
|
215 | - return true; |
|
216 | - } |
|
217 | - } |
|
218 | - } |
|
219 | - |
|
220 | - return false; |
|
221 | - } |
|
222 | - |
|
223 | - /** |
|
224 | - * Indicates if a full Hierarchy Resync is necessary. |
|
225 | - * |
|
226 | - * In some occasions the mobile tries to sync a folder with an invalid/not-existing ID. |
|
227 | - * In these cases a status exception like SYNC_STATUS_FOLDERHIERARCHYCHANGED is returned |
|
228 | - * so the mobile executes a FolderSync expecting that some action is taken on that folder (e.g. remove). |
|
229 | - * |
|
230 | - * If the FolderSync is not doing anything relevant, then the Sync is attempted again |
|
231 | - * resulting in the same error and looping between these two processes. |
|
232 | - * |
|
233 | - * This method checks if in the last process stack a Sync and FolderSync were triggered to |
|
234 | - * catch the loop at the 2nd interaction (Sync->FolderSync->Sync->FolderSync => ReSync) |
|
235 | - * Ticket: https://jira.zarafa.com/browse/ZP-5 |
|
236 | - * |
|
237 | - * @return bool |
|
238 | - */ |
|
239 | - public function ProcessLoopDetectionIsHierarchyResyncRequired() { |
|
240 | - $seenFailed = []; |
|
241 | - $seenFolderSync = false; |
|
242 | - |
|
243 | - $lookback = self::$start - 600; // look at the last 5 min |
|
244 | - foreach ($this->getProcessStack() as $se) { |
|
245 | - if ($se['time'] > $lookback && $se['time'] < (self::$start - 1)) { |
|
246 | - // look for sync command |
|
247 | - if (isset($se['stat']) && ($se['cc'] == GSync::COMMAND_SYNC || $se['cc'] == GSync::COMMAND_PING)) { |
|
248 | - foreach ($se['stat'] as $key => $value) { |
|
249 | - // don't count PING with changes on a folder or sync with success |
|
250 | - if (($se['cc'] == GSync::COMMAND_PING && $value == SYNC_PINGSTATUS_CHANGES) || |
|
251 | - ($se['cc'] == GSync::COMMAND_SYNC && $value == SYNC_STATUS_SUCCESS)) { |
|
252 | - continue; |
|
253 | - } |
|
254 | - if (!isset($seenFailed[$key])) { |
|
255 | - $seenFailed[$key] = 0; |
|
256 | - } |
|
257 | - ++$seenFailed[$key]; |
|
258 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): seen command with Exception or folderid '%s' and code '%s'", $key, $value)); |
|
259 | - } |
|
260 | - } |
|
261 | - // look for FolderSync command with previous failed commands |
|
262 | - if ($se['cc'] == GSync::COMMAND_FOLDERSYNC && !empty($seenFailed) && $se['id'] != self::GetProcessIdentifier()) { |
|
263 | - // a full folderresync was already triggered |
|
264 | - if (isset($se['stat'], $se['stat']['hierarchy']) && $se['stat']['hierarchy'] == SYNC_FSSTATUS_SYNCKEYERROR) { |
|
265 | - SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): a full FolderReSync was already requested. Resetting fail counter."); |
|
266 | - $seenFailed = []; |
|
267 | - } |
|
268 | - else { |
|
269 | - $seenFolderSync = true; |
|
270 | - if (!empty($seenFailed)) { |
|
271 | - SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): seen FolderSync after other failing command"); |
|
272 | - } |
|
273 | - } |
|
274 | - } |
|
275 | - } |
|
276 | - } |
|
277 | - |
|
278 | - $filtered = []; |
|
279 | - foreach ($seenFailed as $k => $count) { |
|
280 | - if ($count > 1) { |
|
281 | - $filtered[] = $k; |
|
282 | - } |
|
283 | - } |
|
284 | - |
|
285 | - if ($seenFolderSync && !empty($filtered)) { |
|
286 | - SLog::Write(LOGLEVEL_INFO, "LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): Potential loop detected. Full hierarchysync indicated."); |
|
287 | - |
|
288 | - return true; |
|
289 | - } |
|
290 | - |
|
291 | - return false; |
|
292 | - } |
|
293 | - |
|
294 | - /** |
|
295 | - * Indicates if a previous process could not be terminated. |
|
296 | - * |
|
297 | - * Checks if there is an end time for the last entry on the stack |
|
298 | - * |
|
299 | - * @return bool |
|
300 | - */ |
|
301 | - public function ProcessLoopDetectionPreviousConnectionFailed() { |
|
302 | - $stack = $this->getProcessStack(); |
|
303 | - $errors = false; |
|
304 | - if (count($stack) > 1) { |
|
305 | - $se = $stack[0]; |
|
306 | - if (!isset($se['end']) && $se['cc'] != GSync::COMMAND_PING && !isset($se['push'])) { |
|
307 | - // there is no end time |
|
308 | - SLog::Write(LOGLEVEL_ERROR, sprintf("LoopDetection->ProcessLoopDetectionPreviousConnectionFailed(): Command '%s' at %s with pid '%d' terminated unexpectedly or is still running.", Utils::GetCommandFromCode($se['cc']), Utils::GetFormattedTime($se['time']), $se['pid'])); |
|
309 | - SLog::Write(LOGLEVEL_ERROR, "Please check your logs for this PID and errors like PHP-Fatals or Apache segmentation faults and report your results to the grommunio dev team."); |
|
310 | - $errors = true; |
|
311 | - } |
|
312 | - } |
|
313 | - |
|
314 | - return $errors; |
|
315 | - } |
|
316 | - |
|
317 | - /** |
|
318 | - * Gets the PID of an outdated search process. |
|
319 | - * |
|
320 | - * Returns false if there isn't any process |
|
321 | - * |
|
322 | - * @return bool |
|
323 | - */ |
|
324 | - public function ProcessLoopDetectionGetOutdatedSearchPID() { |
|
325 | - $stack = $this->getProcessStack(); |
|
326 | - if (count($stack) > 1) { |
|
327 | - $se = $stack[0]; |
|
328 | - if ($se['cc'] == GSync::COMMAND_SEARCH) { |
|
329 | - return $se['pid']; |
|
330 | - } |
|
331 | - } |
|
332 | - |
|
333 | - return false; |
|
334 | - } |
|
335 | - |
|
336 | - /** |
|
337 | - * Inserts or updates the current process entry on the stack. |
|
338 | - * |
|
339 | - * @return bool |
|
340 | - */ |
|
341 | - private function updateProcessStack() { |
|
342 | - // initialize params |
|
343 | - $this->initializeParams(); |
|
344 | - |
|
345 | - $ok = false; |
|
346 | - $tryCount = 1; |
|
347 | - while (!$ok && $tryCount < 5) { |
|
348 | - list($stack, $stackRaw) = $this->getDeviceUserData($this->type, parent::$devid, parent::$user, self::INTERPROCESSLD, true); |
|
349 | - |
|
350 | - // insert/update current process entry |
|
351 | - $nstack = []; |
|
352 | - $updateentry = self::GetProcessEntry(); |
|
353 | - $found = false; |
|
354 | - |
|
355 | - foreach ($stack as $entry) { |
|
356 | - if ($entry['id'] != $updateentry['id']) { |
|
357 | - $nstack[] = $entry; |
|
358 | - } |
|
359 | - else { |
|
360 | - $nstack[] = $updateentry; |
|
361 | - $found = true; |
|
362 | - } |
|
363 | - } |
|
364 | - |
|
365 | - if (!$found) { |
|
366 | - $nstack[] = $updateentry; |
|
367 | - } |
|
368 | - |
|
369 | - if (count($nstack) > 10) { |
|
370 | - $nstack = array_slice($nstack, -10, 10); |
|
371 | - } |
|
372 | - |
|
373 | - // update loop data |
|
374 | - $ok = $this->setDeviceUserData($this->type, $nstack, parent::$devid, parent::$user, self::INTERPROCESSLD, $doCas = "replace", $stackRaw); |
|
375 | - if (!$ok) { |
|
376 | - SLog::Write(LOGLEVEL_WARN, sprintf("LoopDetection->updateProcessStack(): CAS failed on try %d!", $tryCount)); |
|
377 | - usleep(100000); |
|
378 | - ++$tryCount; |
|
379 | - } |
|
380 | - } |
|
381 | - |
|
382 | - return $ok; |
|
383 | - } |
|
384 | - |
|
385 | - /** |
|
386 | - * Returns the current process stack. |
|
387 | - * |
|
388 | - * @return array |
|
389 | - */ |
|
390 | - private function getProcessStack() { |
|
391 | - // initialize params |
|
392 | - $this->initializeParams(); |
|
393 | - |
|
394 | - return $this->getDeviceUserData($this->type, parent::$devid, parent::$user, self::INTERPROCESSLD); |
|
395 | - } |
|
396 | - |
|
397 | - /* |
|
15 | + public const INTERPROCESSLD = "processstack"; |
|
16 | + public const BROKENMSGS = "brokenmsgs"; |
|
17 | + private static $processident; |
|
18 | + private static $processentry; |
|
19 | + private $ignore_messageid; |
|
20 | + private $broken_message_uuid; |
|
21 | + private $broken_message_counter; |
|
22 | + |
|
23 | + /** |
|
24 | + * Constructor. |
|
25 | + */ |
|
26 | + public function __construct() { |
|
27 | + // initialize super parameters |
|
28 | + $this->allocate = 1024000; // 1 MB |
|
29 | + $this->type = "grommunio-sync:loopdetection"; |
|
30 | + parent::__construct(); |
|
31 | + |
|
32 | + $this->ignore_messageid = false; |
|
33 | + } |
|
34 | + |
|
35 | + /** |
|
36 | + * PROCESS LOOP DETECTION. |
|
37 | + */ |
|
38 | + |
|
39 | + /** |
|
40 | + * Adds the process entry to the process stack. |
|
41 | + * |
|
42 | + * @return bool |
|
43 | + */ |
|
44 | + public function ProcessLoopDetectionInit() { |
|
45 | + return $this->updateProcessStack(); |
|
46 | + } |
|
47 | + |
|
48 | + /** |
|
49 | + * Marks the process entry as termineted successfully on the process stack. |
|
50 | + * |
|
51 | + * @return bool |
|
52 | + */ |
|
53 | + public function ProcessLoopDetectionTerminate() { |
|
54 | + // just to be sure that the entry is there |
|
55 | + self::GetProcessEntry(); |
|
56 | + |
|
57 | + self::$processentry['end'] = time(); |
|
58 | + SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->ProcessLoopDetectionTerminate()"); |
|
59 | + |
|
60 | + return $this->updateProcessStack(); |
|
61 | + } |
|
62 | + |
|
63 | + /** |
|
64 | + * Returns a unique identifier for the internal process tracking. |
|
65 | + * |
|
66 | + * @return string |
|
67 | + */ |
|
68 | + public static function GetProcessIdentifier() { |
|
69 | + if (!isset(self::$processident)) { |
|
70 | + self::$processident = sprintf('%04x%04x', mt_rand(0, 0xFFFF), mt_rand(0, 0xFFFF)); |
|
71 | + } |
|
72 | + |
|
73 | + return self::$processident; |
|
74 | + } |
|
75 | + |
|
76 | + /** |
|
77 | + * Returns a unique entry with information about the current process. |
|
78 | + * |
|
79 | + * @return array |
|
80 | + */ |
|
81 | + public static function GetProcessEntry() { |
|
82 | + if (!isset(self::$processentry)) { |
|
83 | + self::$processentry = []; |
|
84 | + self::$processentry['id'] = self::GetProcessIdentifier(); |
|
85 | + self::$processentry['pid'] = self::$pid; |
|
86 | + self::$processentry['time'] = self::$start; |
|
87 | + self::$processentry['cc'] = Request::GetCommandCode(); |
|
88 | + } |
|
89 | + |
|
90 | + return self::$processentry; |
|
91 | + } |
|
92 | + |
|
93 | + /** |
|
94 | + * Adds an Exceptions to the process tracking. |
|
95 | + * |
|
96 | + * @param Exception $exception |
|
97 | + * |
|
98 | + * @return bool |
|
99 | + */ |
|
100 | + public function ProcessLoopDetectionAddException($exception) { |
|
101 | + // generate entry if not already there |
|
102 | + self::GetProcessEntry(); |
|
103 | + |
|
104 | + if (!isset(self::$processentry['stat'])) { |
|
105 | + self::$processentry['stat'] = []; |
|
106 | + } |
|
107 | + |
|
108 | + self::$processentry['stat'][get_class($exception)] = $exception->getCode(); |
|
109 | + |
|
110 | + $this->updateProcessStack(); |
|
111 | + |
|
112 | + return true; |
|
113 | + } |
|
114 | + |
|
115 | + /** |
|
116 | + * Adds a folderid and connected status code to the process tracking. |
|
117 | + * |
|
118 | + * @param string $folderid |
|
119 | + * @param int $status |
|
120 | + * |
|
121 | + * @return bool |
|
122 | + */ |
|
123 | + public function ProcessLoopDetectionAddStatus($folderid, $status) { |
|
124 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->ProcessLoopDetectionAddStatus: '%s' with status %d", $folderid ? $folderid : 'hierarchy', $status)); |
|
125 | + // generate entry if not already there |
|
126 | + self::GetProcessEntry(); |
|
127 | + |
|
128 | + if ($folderid === false) { |
|
129 | + $folderid = "hierarchy"; |
|
130 | + } |
|
131 | + |
|
132 | + if (!isset(self::$processentry['stat'])) { |
|
133 | + self::$processentry['stat'] = []; |
|
134 | + } |
|
135 | + |
|
136 | + self::$processentry['stat'][$folderid] = $status; |
|
137 | + |
|
138 | + $this->updateProcessStack(); |
|
139 | + |
|
140 | + return true; |
|
141 | + } |
|
142 | + |
|
143 | + /** |
|
144 | + * Marks the current process as a PUSH connection. |
|
145 | + * |
|
146 | + * @return bool |
|
147 | + */ |
|
148 | + public function ProcessLoopDetectionSetAsPush() { |
|
149 | + // generate entry if not already there |
|
150 | + self::GetProcessEntry(); |
|
151 | + self::$processentry['push'] = true; |
|
152 | + |
|
153 | + return $this->updateProcessStack(); |
|
154 | + } |
|
155 | + |
|
156 | + /** |
|
157 | + * Indicates if a simple Hierarchy sync should be done after Ping. |
|
158 | + * |
|
159 | + * When trying to sync a non existing folder, Sync will return Status 12. |
|
160 | + * This should trigger a hierarchy sync by the client, but this is not always done. |
|
161 | + * Clients continue trying to Ping, which fails as well and triggers a Sync again. |
|
162 | + * This goes on forever, like here: https://jira.z-hub.io/browse/ZP-1077 |
|
163 | + * |
|
164 | + * Ping could indicate to perform a FolderSync as well after a few Sync/Ping cycles. |
|
165 | + * |
|
166 | + * @return bool |
|
167 | + */ |
|
168 | + public function ProcessLoopDetectionIsHierarchySyncAdvised() { |
|
169 | + $me = self::GetProcessEntry(); |
|
170 | + if ($me['cc'] !== GSync::COMMAND_PING) { |
|
171 | + return false; |
|
172 | + } |
|
173 | + |
|
174 | + $loopingFolders = []; |
|
175 | + |
|
176 | + $lookback = self::$start - 600; // look at the last 5 min |
|
177 | + foreach ($this->getProcessStack() as $se) { |
|
178 | + if ($se['time'] > $lookback && $se['time'] < (self::$start - 1)) { |
|
179 | + // look for sync command |
|
180 | + if (isset($se['stat']) && ($se['cc'] == GSync::COMMAND_SYNC || $se['cc'] == GSync::COMMAND_PING)) { |
|
181 | + foreach ($se['stat'] as $key => $value) { |
|
182 | + // we only care about hierarchy errors of this folder |
|
183 | + if ($se['cc'] == GSync::COMMAND_SYNC) { |
|
184 | + if ($value == SYNC_STATUS_FOLDERHIERARCHYCHANGED) { |
|
185 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->ProcessLoopDetectionIsHierarchySyncAdvised(): seen Sync command with Exception or folderid '%s' and code '%s'", $key, $value)); |
|
186 | + } |
|
187 | + } |
|
188 | + if (!isset($loopingFolders[$key])) { |
|
189 | + $loopingFolders[$key] = [GSync::COMMAND_SYNC => [], GSync::COMMAND_PING => []]; |
|
190 | + } |
|
191 | + if (!isset($loopingFolders[$key][$se['cc']][$value])) { |
|
192 | + $loopingFolders[$key][$se['cc']][$value] = 0; |
|
193 | + } |
|
194 | + ++$loopingFolders[$key][$se['cc']][$value]; |
|
195 | + } |
|
196 | + } |
|
197 | + } |
|
198 | + } |
|
199 | + |
|
200 | + $filtered = []; |
|
201 | + foreach ($loopingFolders as $fid => $data) { |
|
202 | + // Ping is constantly generating changes for this folder |
|
203 | + if (isset($data[GSync::COMMAND_PING][SYNC_PINGSTATUS_CHANGES]) && $data[GSync::COMMAND_PING][SYNC_PINGSTATUS_CHANGES] >= 3) { |
|
204 | + // but the Sync request is not treating it (not being requested) |
|
205 | + if (count($data[GSync::COMMAND_SYNC]) == 0) { |
|
206 | + SLog::Write(LOGLEVEL_INFO, sprintf("LoopDetection->ProcessLoopDetectionIsHierarchySyncAdvised(): Ping loop of folderid '%s' detected that is not being synchronized.", $fid)); |
|
207 | + |
|
208 | + return true; |
|
209 | + } |
|
210 | + // Sync is executed, but a foldersync should be executed (hierarchy errors) |
|
211 | + if (isset($data[GSync::COMMAND_SYNC][SYNC_STATUS_FOLDERHIERARCHYCHANGED]) && |
|
212 | + $data[GSync::COMMAND_SYNC][SYNC_STATUS_FOLDERHIERARCHYCHANGED] > 3) { |
|
213 | + SLog::Write(LOGLEVEL_INFO, sprintf("LoopDetection->ProcessLoopDetectionIsHierarchySyncAdvised(): Sync(with error)/Ping loop of folderid '%s' detected.", $fid)); |
|
214 | + |
|
215 | + return true; |
|
216 | + } |
|
217 | + } |
|
218 | + } |
|
219 | + |
|
220 | + return false; |
|
221 | + } |
|
222 | + |
|
223 | + /** |
|
224 | + * Indicates if a full Hierarchy Resync is necessary. |
|
225 | + * |
|
226 | + * In some occasions the mobile tries to sync a folder with an invalid/not-existing ID. |
|
227 | + * In these cases a status exception like SYNC_STATUS_FOLDERHIERARCHYCHANGED is returned |
|
228 | + * so the mobile executes a FolderSync expecting that some action is taken on that folder (e.g. remove). |
|
229 | + * |
|
230 | + * If the FolderSync is not doing anything relevant, then the Sync is attempted again |
|
231 | + * resulting in the same error and looping between these two processes. |
|
232 | + * |
|
233 | + * This method checks if in the last process stack a Sync and FolderSync were triggered to |
|
234 | + * catch the loop at the 2nd interaction (Sync->FolderSync->Sync->FolderSync => ReSync) |
|
235 | + * Ticket: https://jira.zarafa.com/browse/ZP-5 |
|
236 | + * |
|
237 | + * @return bool |
|
238 | + */ |
|
239 | + public function ProcessLoopDetectionIsHierarchyResyncRequired() { |
|
240 | + $seenFailed = []; |
|
241 | + $seenFolderSync = false; |
|
242 | + |
|
243 | + $lookback = self::$start - 600; // look at the last 5 min |
|
244 | + foreach ($this->getProcessStack() as $se) { |
|
245 | + if ($se['time'] > $lookback && $se['time'] < (self::$start - 1)) { |
|
246 | + // look for sync command |
|
247 | + if (isset($se['stat']) && ($se['cc'] == GSync::COMMAND_SYNC || $se['cc'] == GSync::COMMAND_PING)) { |
|
248 | + foreach ($se['stat'] as $key => $value) { |
|
249 | + // don't count PING with changes on a folder or sync with success |
|
250 | + if (($se['cc'] == GSync::COMMAND_PING && $value == SYNC_PINGSTATUS_CHANGES) || |
|
251 | + ($se['cc'] == GSync::COMMAND_SYNC && $value == SYNC_STATUS_SUCCESS)) { |
|
252 | + continue; |
|
253 | + } |
|
254 | + if (!isset($seenFailed[$key])) { |
|
255 | + $seenFailed[$key] = 0; |
|
256 | + } |
|
257 | + ++$seenFailed[$key]; |
|
258 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): seen command with Exception or folderid '%s' and code '%s'", $key, $value)); |
|
259 | + } |
|
260 | + } |
|
261 | + // look for FolderSync command with previous failed commands |
|
262 | + if ($se['cc'] == GSync::COMMAND_FOLDERSYNC && !empty($seenFailed) && $se['id'] != self::GetProcessIdentifier()) { |
|
263 | + // a full folderresync was already triggered |
|
264 | + if (isset($se['stat'], $se['stat']['hierarchy']) && $se['stat']['hierarchy'] == SYNC_FSSTATUS_SYNCKEYERROR) { |
|
265 | + SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): a full FolderReSync was already requested. Resetting fail counter."); |
|
266 | + $seenFailed = []; |
|
267 | + } |
|
268 | + else { |
|
269 | + $seenFolderSync = true; |
|
270 | + if (!empty($seenFailed)) { |
|
271 | + SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): seen FolderSync after other failing command"); |
|
272 | + } |
|
273 | + } |
|
274 | + } |
|
275 | + } |
|
276 | + } |
|
277 | + |
|
278 | + $filtered = []; |
|
279 | + foreach ($seenFailed as $k => $count) { |
|
280 | + if ($count > 1) { |
|
281 | + $filtered[] = $k; |
|
282 | + } |
|
283 | + } |
|
284 | + |
|
285 | + if ($seenFolderSync && !empty($filtered)) { |
|
286 | + SLog::Write(LOGLEVEL_INFO, "LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): Potential loop detected. Full hierarchysync indicated."); |
|
287 | + |
|
288 | + return true; |
|
289 | + } |
|
290 | + |
|
291 | + return false; |
|
292 | + } |
|
293 | + |
|
294 | + /** |
|
295 | + * Indicates if a previous process could not be terminated. |
|
296 | + * |
|
297 | + * Checks if there is an end time for the last entry on the stack |
|
298 | + * |
|
299 | + * @return bool |
|
300 | + */ |
|
301 | + public function ProcessLoopDetectionPreviousConnectionFailed() { |
|
302 | + $stack = $this->getProcessStack(); |
|
303 | + $errors = false; |
|
304 | + if (count($stack) > 1) { |
|
305 | + $se = $stack[0]; |
|
306 | + if (!isset($se['end']) && $se['cc'] != GSync::COMMAND_PING && !isset($se['push'])) { |
|
307 | + // there is no end time |
|
308 | + SLog::Write(LOGLEVEL_ERROR, sprintf("LoopDetection->ProcessLoopDetectionPreviousConnectionFailed(): Command '%s' at %s with pid '%d' terminated unexpectedly or is still running.", Utils::GetCommandFromCode($se['cc']), Utils::GetFormattedTime($se['time']), $se['pid'])); |
|
309 | + SLog::Write(LOGLEVEL_ERROR, "Please check your logs for this PID and errors like PHP-Fatals or Apache segmentation faults and report your results to the grommunio dev team."); |
|
310 | + $errors = true; |
|
311 | + } |
|
312 | + } |
|
313 | + |
|
314 | + return $errors; |
|
315 | + } |
|
316 | + |
|
317 | + /** |
|
318 | + * Gets the PID of an outdated search process. |
|
319 | + * |
|
320 | + * Returns false if there isn't any process |
|
321 | + * |
|
322 | + * @return bool |
|
323 | + */ |
|
324 | + public function ProcessLoopDetectionGetOutdatedSearchPID() { |
|
325 | + $stack = $this->getProcessStack(); |
|
326 | + if (count($stack) > 1) { |
|
327 | + $se = $stack[0]; |
|
328 | + if ($se['cc'] == GSync::COMMAND_SEARCH) { |
|
329 | + return $se['pid']; |
|
330 | + } |
|
331 | + } |
|
332 | + |
|
333 | + return false; |
|
334 | + } |
|
335 | + |
|
336 | + /** |
|
337 | + * Inserts or updates the current process entry on the stack. |
|
338 | + * |
|
339 | + * @return bool |
|
340 | + */ |
|
341 | + private function updateProcessStack() { |
|
342 | + // initialize params |
|
343 | + $this->initializeParams(); |
|
344 | + |
|
345 | + $ok = false; |
|
346 | + $tryCount = 1; |
|
347 | + while (!$ok && $tryCount < 5) { |
|
348 | + list($stack, $stackRaw) = $this->getDeviceUserData($this->type, parent::$devid, parent::$user, self::INTERPROCESSLD, true); |
|
349 | + |
|
350 | + // insert/update current process entry |
|
351 | + $nstack = []; |
|
352 | + $updateentry = self::GetProcessEntry(); |
|
353 | + $found = false; |
|
354 | + |
|
355 | + foreach ($stack as $entry) { |
|
356 | + if ($entry['id'] != $updateentry['id']) { |
|
357 | + $nstack[] = $entry; |
|
358 | + } |
|
359 | + else { |
|
360 | + $nstack[] = $updateentry; |
|
361 | + $found = true; |
|
362 | + } |
|
363 | + } |
|
364 | + |
|
365 | + if (!$found) { |
|
366 | + $nstack[] = $updateentry; |
|
367 | + } |
|
368 | + |
|
369 | + if (count($nstack) > 10) { |
|
370 | + $nstack = array_slice($nstack, -10, 10); |
|
371 | + } |
|
372 | + |
|
373 | + // update loop data |
|
374 | + $ok = $this->setDeviceUserData($this->type, $nstack, parent::$devid, parent::$user, self::INTERPROCESSLD, $doCas = "replace", $stackRaw); |
|
375 | + if (!$ok) { |
|
376 | + SLog::Write(LOGLEVEL_WARN, sprintf("LoopDetection->updateProcessStack(): CAS failed on try %d!", $tryCount)); |
|
377 | + usleep(100000); |
|
378 | + ++$tryCount; |
|
379 | + } |
|
380 | + } |
|
381 | + |
|
382 | + return $ok; |
|
383 | + } |
|
384 | + |
|
385 | + /** |
|
386 | + * Returns the current process stack. |
|
387 | + * |
|
388 | + * @return array |
|
389 | + */ |
|
390 | + private function getProcessStack() { |
|
391 | + // initialize params |
|
392 | + $this->initializeParams(); |
|
393 | + |
|
394 | + return $this->getDeviceUserData($this->type, parent::$devid, parent::$user, self::INTERPROCESSLD); |
|
395 | + } |
|
396 | + |
|
397 | + /* |
|
398 | 398 | * TRACKING OF BROKEN MESSAGES |
399 | 399 | * if a previousily ignored message is streamed again to the device it's tracked here. |
400 | 400 | * |
@@ -403,563 +403,563 @@ discard block |
||
403 | 403 | * - next uuid counter is the same or uuid changed -> message is still broken |
404 | 404 | */ |
405 | 405 | |
406 | - /** |
|
407 | - * Adds a message to the tracking of broken messages |
|
408 | - * Being tracked means that a broken message was streamed to the device. |
|
409 | - * We save the latest uuid and counter so if on the next sync the counter is higher |
|
410 | - * the message was accepted by the device. |
|
411 | - * |
|
412 | - * @param string $folderid the parent folder of the message |
|
413 | - * @param string $id the id of the message |
|
414 | - * |
|
415 | - * @return bool |
|
416 | - */ |
|
417 | - public function SetBrokenMessage($folderid, $id) { |
|
418 | - if ($folderid == false || |
|
419 | - !isset($this->broken_message_uuid) || |
|
420 | - !isset($this->broken_message_counter) || |
|
421 | - $this->broken_message_uuid == false || |
|
422 | - $this->broken_message_counter == false) { |
|
423 | - return false; |
|
424 | - } |
|
425 | - |
|
426 | - $ok = false; |
|
427 | - $brokenkey = self::BROKENMSGS . "-" . $folderid; |
|
428 | - |
|
429 | - // initialize params |
|
430 | - $this->initializeParams(); |
|
431 | - $tryCount = 1; |
|
432 | - while (!$ok && $tryCount < 5) { |
|
433 | - list($brokenmsgs, $brokenmsgsRaw) = $this->getDeviceUserData($this->type, parent::$devid, parent::$user, $brokenkey, true); |
|
434 | - |
|
435 | - $brokenmsgs[$id] = ['uuid' => $this->broken_message_uuid, 'counter' => $this->broken_message_counter]; |
|
436 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->SetBrokenMessage('%s', '%s'): tracking broken message", $folderid, $id)); |
|
437 | - |
|
438 | - // update data |
|
439 | - $ok = $this->setDeviceUserData($this->type, $brokenmsgs, parent::$devid, parent::$user, $brokenkey, $doCas = "replace", $brokenmsgsRaw); |
|
440 | - if (!$ok) { |
|
441 | - SLog::Write(LOGLEVEL_WARN, sprintf("LoopDetection->SetBrokenMessage(): CAS failed on try %d!", $tryCount)); |
|
442 | - usleep(100000); |
|
443 | - ++$tryCount; |
|
444 | - } |
|
445 | - } |
|
446 | - |
|
447 | - return $ok; |
|
448 | - } |
|
449 | - |
|
450 | - /** |
|
451 | - * Gets a list of all ids of a folder which were tracked and which were |
|
452 | - * accepted by the device from the last sync. |
|
453 | - * |
|
454 | - * @param string $folderid the parent folder of the message |
|
455 | - * @param string $id the id of the message |
|
456 | - * |
|
457 | - * @return array |
|
458 | - */ |
|
459 | - public function GetSyncedButBeforeIgnoredMessages($folderid) { |
|
460 | - if ($folderid == false || |
|
461 | - !isset($this->broken_message_uuid) || |
|
462 | - !isset($this->broken_message_counter) || |
|
463 | - $this->broken_message_uuid == false || |
|
464 | - $this->broken_message_counter == false) { |
|
465 | - return []; |
|
466 | - } |
|
467 | - |
|
468 | - $brokenkey = self::BROKENMSGS . "-" . $folderid; |
|
469 | - $removeIds = []; |
|
470 | - $okIds = []; |
|
471 | - |
|
472 | - // initialize params |
|
473 | - $this->initializeParams(); |
|
474 | - |
|
475 | - $ok = false; |
|
476 | - $tryCount = 1; |
|
477 | - while (!$ok && $tryCount < 5) { |
|
478 | - list($brokenmsgs, $brokenmsgsRaw) = $this->getDeviceUserData($this->type, parent::$devid, parent::$user, $brokenkey, true); |
|
479 | - |
|
480 | - if (empty($brokenmsgs)) { |
|
481 | - break; |
|
482 | - } |
|
483 | - |
|
484 | - foreach ($brokenmsgs as $id => $data) { |
|
485 | - // previously broken message was successfully synced! |
|
486 | - if ($data['uuid'] == $this->broken_message_uuid && $data['counter'] < $this->broken_message_counter) { |
|
487 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->GetSyncedButBeforeIgnoredMessages('%s'): message '%s' was successfully synchronized", $folderid, $id)); |
|
488 | - $okIds[] = $id; |
|
489 | - } |
|
490 | - |
|
491 | - // if the uuid has changed this is old data which should also be removed |
|
492 | - if ($data['uuid'] != $this->broken_message_uuid) { |
|
493 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->GetSyncedButBeforeIgnoredMessages('%s'): stored message id '%s' for uuid '%s' is obsolete", $folderid, $id, $data['uuid'])); |
|
494 | - $removeIds[] = $id; |
|
495 | - } |
|
496 | - } |
|
497 | - |
|
498 | - // remove data |
|
499 | - foreach (array_merge($okIds, $removeIds) as $id) { |
|
500 | - unset($brokenmsgs[$id]); |
|
501 | - } |
|
502 | - |
|
503 | - if (empty($brokenmsgs)) { |
|
504 | - $this->delDeviceUserData($this->type, parent::$devid, parent::$user, $brokenkey); |
|
505 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->GetSyncedButBeforeIgnoredMessages('%s'): removed folder from tracking of ignored messages", $folderid)); |
|
506 | - |
|
507 | - break; |
|
508 | - } |
|
509 | - |
|
510 | - // update data |
|
511 | - $ok = $this->setDeviceUserData($this->type, $brokenmsgs, parent::$devid, parent::$user, $brokenkey, $doCas = "replace", $brokenmsgsRaw); |
|
512 | - if (!$ok) { |
|
513 | - SLog::Write(LOGLEVEL_WARN, sprintf("LoopDetection->GetSyncedButBeforeIgnoredMessages(): CAS failed on try %d!", $tryCount)); |
|
514 | - usleep(100000); |
|
515 | - ++$tryCount; |
|
516 | - } |
|
517 | - } |
|
518 | - |
|
519 | - return $okIds; |
|
520 | - } |
|
521 | - |
|
522 | - /** |
|
523 | - * Marks a SyncState as "already used", e.g. when an import process started. |
|
524 | - * This is most critical for DiffBackends, as an imported message would be exported again |
|
525 | - * in the heartbeat if the notification is triggered before the import is complete. |
|
526 | - * |
|
527 | - * @param string $folderid folder id |
|
528 | - * @param string $uuid synkkey |
|
529 | - * @param string $counter synckey counter |
|
530 | - * |
|
531 | - * @return bool |
|
532 | - */ |
|
533 | - public function SetSyncStateUsage($folderid, $uuid, $counter) { |
|
534 | - // initialize params |
|
535 | - $this->initializeParams(); |
|
536 | - |
|
537 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->SetSyncStateUsage(): uuid: %s counter: %d", $uuid, $counter)); |
|
538 | - |
|
539 | - $ok = false; |
|
540 | - $tryCount = 1; |
|
541 | - while (!$ok && $tryCount < 5) { |
|
542 | - list($current, $currentRaw) = $this->getDeviceUserData($this->type, parent::$devid, parent::$user, $folderid, true); |
|
543 | - |
|
544 | - if (!isset($current["uuid"])) { |
|
545 | - $current["uuid"] = $uuid; |
|
546 | - } |
|
547 | - if (!isset($current["count"])) { |
|
548 | - $current["count"] = $counter; |
|
549 | - } |
|
550 | - if (!isset($current["queued"])) { |
|
551 | - $current["queued"] = 0; |
|
552 | - } |
|
553 | - |
|
554 | - // update the usage flag |
|
555 | - $current["usage"] = $counter; |
|
556 | - |
|
557 | - // update loop data |
|
558 | - $ok = $this->setDeviceUserData($this->type, $current, parent::$devid, parent::$user, $folderid, $doCas = "replace", $currentRaw); |
|
559 | - if (!$ok) { |
|
560 | - SLog::Write(LOGLEVEL_WARN, sprintf("LoopDetection->SetSyncStateUsage(): CAS failed on try %d!", $tryCount)); |
|
561 | - usleep(100000); |
|
562 | - ++$tryCount; |
|
563 | - } |
|
564 | - } |
|
565 | - |
|
566 | - return $ok; |
|
567 | - } |
|
568 | - |
|
569 | - /** |
|
570 | - * Checks if the given counter for a certain uuid+folderid was exported before. |
|
571 | - * Returns also true if the counter are the same but previously there were |
|
572 | - * changes to be exported. |
|
573 | - * |
|
574 | - * @param string $folderid folder id |
|
575 | - * @param string $uuid synkkey |
|
576 | - * @param string $counter synckey counter |
|
577 | - * |
|
578 | - * @return bool indicating if an uuid+counter were exported (with changes) before |
|
579 | - */ |
|
580 | - public function IsSyncStateObsolete($folderid, $uuid, $counter) { |
|
581 | - // initialize params |
|
582 | - $this->initializeParams(); |
|
583 | - |
|
584 | - $obsolete = false; |
|
585 | - |
|
586 | - $current = $this->getDeviceUserData($this->type, parent::$devid, parent::$user, $folderid); |
|
587 | - |
|
588 | - if (!empty($current)) { |
|
589 | - if (!isset($current["uuid"]) || $current["uuid"] != $uuid) { |
|
590 | - SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IsSyncStateObsolete(): yes, uuid changed or not set"); |
|
591 | - $obsolete = true; |
|
592 | - } |
|
593 | - else { |
|
594 | - SLog::Write(LOGLEVEL_DEBUG, sprintf( |
|
595 | - "LoopDetection->IsSyncStateObsolete(): check folderid: '%s' uuid '%s' counter: %d - last counter: %d with %d queued", |
|
596 | - $folderid, |
|
597 | - $uuid, |
|
598 | - $counter, |
|
599 | - $current["count"], |
|
600 | - $current["queued"] |
|
601 | - )); |
|
602 | - |
|
603 | - if ($current["uuid"] == $uuid && ( |
|
604 | - $current["count"] > $counter || |
|
605 | - ($current["count"] == $counter && $current["queued"] > 0) || |
|
606 | - (isset($current["usage"]) && $current["usage"] >= $counter) |
|
607 | - )) { |
|
608 | - $usage = isset($current["usage"]) ? sprintf(" - counter %d already expired", $current["usage"]) : ""; |
|
609 | - SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IsSyncStateObsolete(): yes, counter already processed" . $usage); |
|
610 | - $obsolete = true; |
|
611 | - } |
|
612 | - } |
|
613 | - } |
|
614 | - else { |
|
615 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IsSyncStateObsolete(): check folderid: '%s' uuid '%s' counter: %d - no data found: not obsolete", $folderid, $uuid, $counter)); |
|
616 | - } |
|
617 | - |
|
618 | - return $obsolete; |
|
619 | - } |
|
620 | - |
|
621 | - /* |
|
406 | + /** |
|
407 | + * Adds a message to the tracking of broken messages |
|
408 | + * Being tracked means that a broken message was streamed to the device. |
|
409 | + * We save the latest uuid and counter so if on the next sync the counter is higher |
|
410 | + * the message was accepted by the device. |
|
411 | + * |
|
412 | + * @param string $folderid the parent folder of the message |
|
413 | + * @param string $id the id of the message |
|
414 | + * |
|
415 | + * @return bool |
|
416 | + */ |
|
417 | + public function SetBrokenMessage($folderid, $id) { |
|
418 | + if ($folderid == false || |
|
419 | + !isset($this->broken_message_uuid) || |
|
420 | + !isset($this->broken_message_counter) || |
|
421 | + $this->broken_message_uuid == false || |
|
422 | + $this->broken_message_counter == false) { |
|
423 | + return false; |
|
424 | + } |
|
425 | + |
|
426 | + $ok = false; |
|
427 | + $brokenkey = self::BROKENMSGS . "-" . $folderid; |
|
428 | + |
|
429 | + // initialize params |
|
430 | + $this->initializeParams(); |
|
431 | + $tryCount = 1; |
|
432 | + while (!$ok && $tryCount < 5) { |
|
433 | + list($brokenmsgs, $brokenmsgsRaw) = $this->getDeviceUserData($this->type, parent::$devid, parent::$user, $brokenkey, true); |
|
434 | + |
|
435 | + $brokenmsgs[$id] = ['uuid' => $this->broken_message_uuid, 'counter' => $this->broken_message_counter]; |
|
436 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->SetBrokenMessage('%s', '%s'): tracking broken message", $folderid, $id)); |
|
437 | + |
|
438 | + // update data |
|
439 | + $ok = $this->setDeviceUserData($this->type, $brokenmsgs, parent::$devid, parent::$user, $brokenkey, $doCas = "replace", $brokenmsgsRaw); |
|
440 | + if (!$ok) { |
|
441 | + SLog::Write(LOGLEVEL_WARN, sprintf("LoopDetection->SetBrokenMessage(): CAS failed on try %d!", $tryCount)); |
|
442 | + usleep(100000); |
|
443 | + ++$tryCount; |
|
444 | + } |
|
445 | + } |
|
446 | + |
|
447 | + return $ok; |
|
448 | + } |
|
449 | + |
|
450 | + /** |
|
451 | + * Gets a list of all ids of a folder which were tracked and which were |
|
452 | + * accepted by the device from the last sync. |
|
453 | + * |
|
454 | + * @param string $folderid the parent folder of the message |
|
455 | + * @param string $id the id of the message |
|
456 | + * |
|
457 | + * @return array |
|
458 | + */ |
|
459 | + public function GetSyncedButBeforeIgnoredMessages($folderid) { |
|
460 | + if ($folderid == false || |
|
461 | + !isset($this->broken_message_uuid) || |
|
462 | + !isset($this->broken_message_counter) || |
|
463 | + $this->broken_message_uuid == false || |
|
464 | + $this->broken_message_counter == false) { |
|
465 | + return []; |
|
466 | + } |
|
467 | + |
|
468 | + $brokenkey = self::BROKENMSGS . "-" . $folderid; |
|
469 | + $removeIds = []; |
|
470 | + $okIds = []; |
|
471 | + |
|
472 | + // initialize params |
|
473 | + $this->initializeParams(); |
|
474 | + |
|
475 | + $ok = false; |
|
476 | + $tryCount = 1; |
|
477 | + while (!$ok && $tryCount < 5) { |
|
478 | + list($brokenmsgs, $brokenmsgsRaw) = $this->getDeviceUserData($this->type, parent::$devid, parent::$user, $brokenkey, true); |
|
479 | + |
|
480 | + if (empty($brokenmsgs)) { |
|
481 | + break; |
|
482 | + } |
|
483 | + |
|
484 | + foreach ($brokenmsgs as $id => $data) { |
|
485 | + // previously broken message was successfully synced! |
|
486 | + if ($data['uuid'] == $this->broken_message_uuid && $data['counter'] < $this->broken_message_counter) { |
|
487 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->GetSyncedButBeforeIgnoredMessages('%s'): message '%s' was successfully synchronized", $folderid, $id)); |
|
488 | + $okIds[] = $id; |
|
489 | + } |
|
490 | + |
|
491 | + // if the uuid has changed this is old data which should also be removed |
|
492 | + if ($data['uuid'] != $this->broken_message_uuid) { |
|
493 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->GetSyncedButBeforeIgnoredMessages('%s'): stored message id '%s' for uuid '%s' is obsolete", $folderid, $id, $data['uuid'])); |
|
494 | + $removeIds[] = $id; |
|
495 | + } |
|
496 | + } |
|
497 | + |
|
498 | + // remove data |
|
499 | + foreach (array_merge($okIds, $removeIds) as $id) { |
|
500 | + unset($brokenmsgs[$id]); |
|
501 | + } |
|
502 | + |
|
503 | + if (empty($brokenmsgs)) { |
|
504 | + $this->delDeviceUserData($this->type, parent::$devid, parent::$user, $brokenkey); |
|
505 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->GetSyncedButBeforeIgnoredMessages('%s'): removed folder from tracking of ignored messages", $folderid)); |
|
506 | + |
|
507 | + break; |
|
508 | + } |
|
509 | + |
|
510 | + // update data |
|
511 | + $ok = $this->setDeviceUserData($this->type, $brokenmsgs, parent::$devid, parent::$user, $brokenkey, $doCas = "replace", $brokenmsgsRaw); |
|
512 | + if (!$ok) { |
|
513 | + SLog::Write(LOGLEVEL_WARN, sprintf("LoopDetection->GetSyncedButBeforeIgnoredMessages(): CAS failed on try %d!", $tryCount)); |
|
514 | + usleep(100000); |
|
515 | + ++$tryCount; |
|
516 | + } |
|
517 | + } |
|
518 | + |
|
519 | + return $okIds; |
|
520 | + } |
|
521 | + |
|
522 | + /** |
|
523 | + * Marks a SyncState as "already used", e.g. when an import process started. |
|
524 | + * This is most critical for DiffBackends, as an imported message would be exported again |
|
525 | + * in the heartbeat if the notification is triggered before the import is complete. |
|
526 | + * |
|
527 | + * @param string $folderid folder id |
|
528 | + * @param string $uuid synkkey |
|
529 | + * @param string $counter synckey counter |
|
530 | + * |
|
531 | + * @return bool |
|
532 | + */ |
|
533 | + public function SetSyncStateUsage($folderid, $uuid, $counter) { |
|
534 | + // initialize params |
|
535 | + $this->initializeParams(); |
|
536 | + |
|
537 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->SetSyncStateUsage(): uuid: %s counter: %d", $uuid, $counter)); |
|
538 | + |
|
539 | + $ok = false; |
|
540 | + $tryCount = 1; |
|
541 | + while (!$ok && $tryCount < 5) { |
|
542 | + list($current, $currentRaw) = $this->getDeviceUserData($this->type, parent::$devid, parent::$user, $folderid, true); |
|
543 | + |
|
544 | + if (!isset($current["uuid"])) { |
|
545 | + $current["uuid"] = $uuid; |
|
546 | + } |
|
547 | + if (!isset($current["count"])) { |
|
548 | + $current["count"] = $counter; |
|
549 | + } |
|
550 | + if (!isset($current["queued"])) { |
|
551 | + $current["queued"] = 0; |
|
552 | + } |
|
553 | + |
|
554 | + // update the usage flag |
|
555 | + $current["usage"] = $counter; |
|
556 | + |
|
557 | + // update loop data |
|
558 | + $ok = $this->setDeviceUserData($this->type, $current, parent::$devid, parent::$user, $folderid, $doCas = "replace", $currentRaw); |
|
559 | + if (!$ok) { |
|
560 | + SLog::Write(LOGLEVEL_WARN, sprintf("LoopDetection->SetSyncStateUsage(): CAS failed on try %d!", $tryCount)); |
|
561 | + usleep(100000); |
|
562 | + ++$tryCount; |
|
563 | + } |
|
564 | + } |
|
565 | + |
|
566 | + return $ok; |
|
567 | + } |
|
568 | + |
|
569 | + /** |
|
570 | + * Checks if the given counter for a certain uuid+folderid was exported before. |
|
571 | + * Returns also true if the counter are the same but previously there were |
|
572 | + * changes to be exported. |
|
573 | + * |
|
574 | + * @param string $folderid folder id |
|
575 | + * @param string $uuid synkkey |
|
576 | + * @param string $counter synckey counter |
|
577 | + * |
|
578 | + * @return bool indicating if an uuid+counter were exported (with changes) before |
|
579 | + */ |
|
580 | + public function IsSyncStateObsolete($folderid, $uuid, $counter) { |
|
581 | + // initialize params |
|
582 | + $this->initializeParams(); |
|
583 | + |
|
584 | + $obsolete = false; |
|
585 | + |
|
586 | + $current = $this->getDeviceUserData($this->type, parent::$devid, parent::$user, $folderid); |
|
587 | + |
|
588 | + if (!empty($current)) { |
|
589 | + if (!isset($current["uuid"]) || $current["uuid"] != $uuid) { |
|
590 | + SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IsSyncStateObsolete(): yes, uuid changed or not set"); |
|
591 | + $obsolete = true; |
|
592 | + } |
|
593 | + else { |
|
594 | + SLog::Write(LOGLEVEL_DEBUG, sprintf( |
|
595 | + "LoopDetection->IsSyncStateObsolete(): check folderid: '%s' uuid '%s' counter: %d - last counter: %d with %d queued", |
|
596 | + $folderid, |
|
597 | + $uuid, |
|
598 | + $counter, |
|
599 | + $current["count"], |
|
600 | + $current["queued"] |
|
601 | + )); |
|
602 | + |
|
603 | + if ($current["uuid"] == $uuid && ( |
|
604 | + $current["count"] > $counter || |
|
605 | + ($current["count"] == $counter && $current["queued"] > 0) || |
|
606 | + (isset($current["usage"]) && $current["usage"] >= $counter) |
|
607 | + )) { |
|
608 | + $usage = isset($current["usage"]) ? sprintf(" - counter %d already expired", $current["usage"]) : ""; |
|
609 | + SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IsSyncStateObsolete(): yes, counter already processed" . $usage); |
|
610 | + $obsolete = true; |
|
611 | + } |
|
612 | + } |
|
613 | + } |
|
614 | + else { |
|
615 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IsSyncStateObsolete(): check folderid: '%s' uuid '%s' counter: %d - no data found: not obsolete", $folderid, $uuid, $counter)); |
|
616 | + } |
|
617 | + |
|
618 | + return $obsolete; |
|
619 | + } |
|
620 | + |
|
621 | + /* |
|
622 | 622 | * MESSAGE LOOP DETECTION. |
623 | 623 | */ |
624 | 624 | |
625 | - /** |
|
626 | - * Loop detection mechanism. |
|
627 | - * |
|
628 | - * 1. request counter is higher than the previous counter (somehow default) |
|
629 | - * 1.1) standard situation -> do nothing |
|
630 | - * 1.2) loop information exists |
|
631 | - * 1.2.1) request counter < maxCounter AND no ignored data -> continue in loop mode |
|
632 | - * 1.2.2) request counter < maxCounter AND ignored data -> we have already encountered issue, return to normal |
|
633 | - * |
|
634 | - * 2. request counter is the same as the previous, but no data was sent on the last request (standard situation) |
|
635 | - * |
|
636 | - * 3. request counter is the same as the previous and last time objects were sent (loop!) |
|
637 | - * 3.0) no loop was detected before, but with big window size -> lower window size first - NO LOOP mode yet |
|
638 | - * 3.1) no loop was detected before, entering loop mode -> save loop data, loopcount = 1 |
|
639 | - * 3.2) loop was detected before, but are gone -> loop resolved |
|
640 | - * 3.3) loop was detected before, continuing in loop mode -> this is probably the broken element,loopcount++, |
|
641 | - * 3.3.1) item identified, loopcount >= 3 -> ignore item, set ignoredata flag |
|
642 | - * |
|
643 | - * @param string $folderid the current folder id to be worked on |
|
644 | - * @param string $uuid the synkkey |
|
645 | - * @param string $counter the synckey counter |
|
646 | - * @param string $maxItems the current amount of items to be sent to the mobile |
|
647 | - * @param string $queuedMessages the amount of messages which were found by the exporter |
|
648 | - * |
|
649 | - * @return boolean/int when returning true if a loop has been identified - returns new suggested window size if window might have been too big |
|
650 | - */ |
|
651 | - public function Detect($folderid, $uuid, $counter, $maxItems, $queuedMessages) { |
|
652 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->Detect(): folderid:'%s' uuid:'%s' counter:'%s' max:'%s' queued:'%s'", $folderid, $uuid, $counter, $maxItems, $queuedMessages)); |
|
653 | - $this->broken_message_uuid = $uuid; |
|
654 | - $this->broken_message_counter = $counter; |
|
655 | - |
|
656 | - // if an incoming loop is already detected, do nothing |
|
657 | - if ($maxItems === 0 && $queuedMessages > 0) { |
|
658 | - GSync::GetTopCollector()->AnnounceInformation("Incoming loop!", true); |
|
659 | - |
|
660 | - return true; |
|
661 | - } |
|
662 | - |
|
663 | - // initialize params |
|
664 | - $this->initializeParams(); |
|
665 | - |
|
666 | - $loop = false; |
|
667 | - |
|
668 | - $ok = false; |
|
669 | - $tryCount = 1; |
|
670 | - while (!$ok && $tryCount < 5) { |
|
671 | - list($current, $currentRaw) = $this->getDeviceUserData($this->type, parent::$devid, parent::$user, $folderid, true); |
|
672 | - |
|
673 | - // completely new/unknown UUID |
|
674 | - if (empty($current)) { |
|
675 | - $current = ["uuid" => $uuid, "count" => $counter - 1, "queued" => $queuedMessages]; |
|
676 | - } |
|
677 | - |
|
678 | - // old UUID in cache - the device requested a new state!! |
|
679 | - if (isset($current['uuid']) && $current['uuid'] != $uuid) { |
|
680 | - SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): UUID changed for folder"); |
|
681 | - |
|
682 | - // some devices (iPhones) may request new UUIDs after broken items were sent several times |
|
683 | - if (isset($current['queued']) && $current['queued'] > 0 && |
|
684 | - (isset($current['maxCount']) && $current['maxCount'] > $current['count'] + 1 || $counter == 1)) { |
|
685 | - SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): UUID changed and while items where sent to device - forcing loop mode"); |
|
686 | - $loop = true; // force loop mode |
|
687 | - $current['queued'] = $queuedMessages; |
|
688 | - } |
|
689 | - else { |
|
690 | - $current['queued'] = 0; |
|
691 | - } |
|
692 | - |
|
693 | - // set new data, unset old loop information |
|
694 | - $current["uuid"] = $uuid; |
|
695 | - $current['count'] = $counter; |
|
696 | - unset($current['loopcount'], $current['ignored'], $current['maxCount'], $current['potential'], $current['windowLimit']); |
|
697 | - } |
|
698 | - |
|
699 | - // see if there are values |
|
700 | - if (isset($current['uuid']) && $current['uuid'] == $uuid && |
|
701 | - isset($current['count'])) { |
|
702 | - // case 1 - standard, during loop-resolving & resolving |
|
703 | - if ($current['count'] < $counter) { |
|
704 | - // case 1.1 |
|
705 | - $current['count'] = $counter; |
|
706 | - $current['queued'] = $queuedMessages; |
|
707 | - if (isset($current["usage"]) && $current["usage"] < $current['count']) { |
|
708 | - unset($current["usage"]); |
|
709 | - } |
|
710 | - |
|
711 | - // case 1.2 |
|
712 | - if (isset($current['maxCount'])) { |
|
713 | - SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 1.2 detected"); |
|
714 | - |
|
715 | - // case 1.2.1 |
|
716 | - // broken item not identified yet |
|
717 | - if (!isset($current['ignored']) && $counter < $current['maxCount']) { |
|
718 | - $current['loopcount'] = 1; |
|
719 | - $loop = true; // continue in loop-resolving |
|
720 | - SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 1.2.1 detected"); |
|
721 | - } |
|
722 | - // case 1.2.2 - if there were any broken items they should be gone, return to normal |
|
723 | - else { |
|
724 | - SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 1.2.2 detected"); |
|
725 | - unset($current['loopcount'], $current['ignored'], $current['maxCount'], $current['potential'], $current['windowLimit']); |
|
726 | - } |
|
727 | - } |
|
728 | - } |
|
729 | - |
|
730 | - // case 2 - same counter, but there were no changes before and are there now |
|
731 | - elseif ($current['count'] == $counter && $current['queued'] == 0 && $queuedMessages > 0) { |
|
732 | - $current['queued'] = $queuedMessages; |
|
733 | - if (isset($current["usage"]) && $current["usage"] < $current['count']) { |
|
734 | - unset($current["usage"]); |
|
735 | - } |
|
736 | - } |
|
737 | - |
|
738 | - // case 3 - same counter, changes sent before, hanging loop and ignoring |
|
739 | - elseif ($current['count'] == $counter && $current['queued'] > 0) { |
|
740 | - if (!isset($current['loopcount'])) { |
|
741 | - // ZP-1213 we are potentially syncing a lot of data, e.g. OL with 512 WindowSize |
|
742 | - // In case there are more then 40 items in the last request, we limit to 25 items |
|
743 | - // before entering 1-by-1 loop detection if counter is re-requested |
|
744 | - if ($maxItems > 40 && !isset($current['windowLimit'])) { |
|
745 | - // case 3.0) we have just encountered a loop, but with a big window size, lower window first |
|
746 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->Detect(): case 3.0 detected - big windowsize of %d, lowering before entering loop mode", $maxItems)); |
|
747 | - // return suggested new window size |
|
748 | - $current['windowLimit'] = 25; |
|
749 | - $loop = $current['windowLimit']; |
|
750 | - } |
|
751 | - else { |
|
752 | - // case 3.1) we have just encountered a loop! |
|
753 | - SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.1 detected - loop detected, init loop mode"); |
|
754 | - if (isset($current['windowLimit'])) { |
|
755 | - $maxItems = $current['windowLimit']; |
|
756 | - unset($current['windowLimit']); |
|
757 | - } |
|
758 | - $current['loopcount'] = 1; |
|
759 | - // the MaxCount is the max number of messages exported before |
|
760 | - $current['maxCount'] = $counter + (($maxItems < $queuedMessages) ? $maxItems : $queuedMessages); |
|
761 | - $loop = true; // loop mode!! |
|
762 | - } |
|
763 | - } |
|
764 | - elseif ($queuedMessages == 0) { |
|
765 | - // case 3.2) there was a loop before but now the changes are GONE |
|
766 | - SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.2 detected - changes gone - clearing loop data"); |
|
767 | - $current['queued'] = 0; |
|
768 | - unset($current['loopcount'], $current['ignored'], $current['maxCount'], $current['potential'], $current['windowLimit']); |
|
769 | - } |
|
770 | - else { |
|
771 | - // case 3.3) still looping the same message! Increase counter |
|
772 | - SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.3 detected - in loop mode, increase loop counter"); |
|
773 | - ++$current['loopcount']; |
|
774 | - |
|
775 | - // case 3.3.1 - we got our broken item! |
|
776 | - if ($current['loopcount'] >= 3 && isset($current['potential'])) { |
|
777 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->Detect(): case 3.3.1 detected - broken item should be next, attempt to ignore it - id '%s'", $current['potential'])); |
|
778 | - $this->ignore_messageid = $current['potential']; |
|
779 | - } |
|
780 | - $current['maxCount'] = $counter + (($maxItems < $queuedMessages) ? $maxItems : $queuedMessages); |
|
781 | - $loop = true; // loop mode!! |
|
782 | - } |
|
783 | - } |
|
784 | - } |
|
785 | - if (isset($current['loopcount'])) { |
|
786 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->Detect(): loop data: loopcount(%d), maxCount(%d), queued(%d), ignored(%s)", $current['loopcount'], $current['maxCount'], $current['queued'], (isset($current['ignored']) ? $current['ignored'] : 'false'))); |
|
787 | - } |
|
788 | - |
|
789 | - // update loop data |
|
790 | - $ok = $this->setDeviceUserData($this->type, $current, parent::$devid, parent::$user, $folderid, $doCas = "replace", $currentRaw); |
|
791 | - if (!$ok) { |
|
792 | - SLog::Write(LOGLEVEL_WARN, sprintf("LoopDetection->Detect(): CAS failed on try %d!", $tryCount)); |
|
793 | - usleep(100000); |
|
794 | - ++$tryCount; |
|
795 | - } |
|
796 | - } |
|
797 | - // end exclusive block |
|
798 | - |
|
799 | - if ($loop === true && $this->ignore_messageid == false) { |
|
800 | - GSync::GetTopCollector()->AnnounceInformation("Loop detection", true); |
|
801 | - } |
|
802 | - |
|
803 | - return $loop; |
|
804 | - } |
|
805 | - |
|
806 | - /** |
|
807 | - * Indicates if the next messages should be ignored (not be sent to the mobile!). |
|
808 | - * |
|
809 | - * @param string $messageid (opt) id of the message which is to be exported next |
|
810 | - * @param string $folderid (opt) parent id of the message |
|
811 | - * @param bool $markAsIgnored (opt) to peek without setting the next message to be |
|
812 | - * ignored, set this value to false |
|
813 | - * |
|
814 | - * @return bool |
|
815 | - */ |
|
816 | - public function IgnoreNextMessage($markAsIgnored = true, $messageid = false, $folderid = false) { |
|
817 | - // as the next message id is not available at all point this method is called, we use different indicators. |
|
818 | - // potentialbroken indicates that we know that the broken message should be exported next, |
|
819 | - // alltho we do not know for sure as it's export message orders can change |
|
820 | - // if the $messageid is available and matches then we are sure and only then really ignore it |
|
821 | - |
|
822 | - $potentialBroken = false; |
|
823 | - $realBroken = false; |
|
824 | - if (Request::GetCommandCode() == GSync::COMMAND_SYNC && $this->ignore_messageid !== false) { |
|
825 | - $potentialBroken = true; |
|
826 | - } |
|
827 | - |
|
828 | - if ($messageid !== false && $this->ignore_messageid == $messageid) { |
|
829 | - $realBroken = true; |
|
830 | - } |
|
831 | - |
|
832 | - // this call is just to know what should be happening |
|
833 | - // no further actions necessary |
|
834 | - if ($markAsIgnored === false) { |
|
835 | - return $potentialBroken; |
|
836 | - } |
|
837 | - |
|
838 | - // we should really do something here |
|
839 | - |
|
840 | - // first we check if we are in the loop mode, if so, |
|
841 | - // we update the potential broken id message so we loop count the same message |
|
842 | - |
|
843 | - $changedData = false; |
|
844 | - $ok = false; |
|
845 | - $tryCount = 1; |
|
846 | - while (!$ok && $tryCount < 5) { |
|
847 | - list($current, $currentRaw) = $this->getDeviceUserData($this->type, parent::$devid, parent::$user, $folderid, true); |
|
848 | - |
|
849 | - // we found our broken message! |
|
850 | - if ($realBroken) { |
|
851 | - $this->ignore_messageid = false; |
|
852 | - $current['ignored'] = $messageid; |
|
853 | - $changedData = true; |
|
854 | - |
|
855 | - // check if this message was broken before - here we know that it still is and remove it from the tracking |
|
856 | - $brokenkey = self::BROKENMSGS . "-" . $folderid; |
|
857 | - // TODO: this is currently not supported here! It's in a different structure now! |
|
625 | + /** |
|
626 | + * Loop detection mechanism. |
|
627 | + * |
|
628 | + * 1. request counter is higher than the previous counter (somehow default) |
|
629 | + * 1.1) standard situation -> do nothing |
|
630 | + * 1.2) loop information exists |
|
631 | + * 1.2.1) request counter < maxCounter AND no ignored data -> continue in loop mode |
|
632 | + * 1.2.2) request counter < maxCounter AND ignored data -> we have already encountered issue, return to normal |
|
633 | + * |
|
634 | + * 2. request counter is the same as the previous, but no data was sent on the last request (standard situation) |
|
635 | + * |
|
636 | + * 3. request counter is the same as the previous and last time objects were sent (loop!) |
|
637 | + * 3.0) no loop was detected before, but with big window size -> lower window size first - NO LOOP mode yet |
|
638 | + * 3.1) no loop was detected before, entering loop mode -> save loop data, loopcount = 1 |
|
639 | + * 3.2) loop was detected before, but are gone -> loop resolved |
|
640 | + * 3.3) loop was detected before, continuing in loop mode -> this is probably the broken element,loopcount++, |
|
641 | + * 3.3.1) item identified, loopcount >= 3 -> ignore item, set ignoredata flag |
|
642 | + * |
|
643 | + * @param string $folderid the current folder id to be worked on |
|
644 | + * @param string $uuid the synkkey |
|
645 | + * @param string $counter the synckey counter |
|
646 | + * @param string $maxItems the current amount of items to be sent to the mobile |
|
647 | + * @param string $queuedMessages the amount of messages which were found by the exporter |
|
648 | + * |
|
649 | + * @return boolean/int when returning true if a loop has been identified - returns new suggested window size if window might have been too big |
|
650 | + */ |
|
651 | + public function Detect($folderid, $uuid, $counter, $maxItems, $queuedMessages) { |
|
652 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->Detect(): folderid:'%s' uuid:'%s' counter:'%s' max:'%s' queued:'%s'", $folderid, $uuid, $counter, $maxItems, $queuedMessages)); |
|
653 | + $this->broken_message_uuid = $uuid; |
|
654 | + $this->broken_message_counter = $counter; |
|
655 | + |
|
656 | + // if an incoming loop is already detected, do nothing |
|
657 | + if ($maxItems === 0 && $queuedMessages > 0) { |
|
658 | + GSync::GetTopCollector()->AnnounceInformation("Incoming loop!", true); |
|
659 | + |
|
660 | + return true; |
|
661 | + } |
|
662 | + |
|
663 | + // initialize params |
|
664 | + $this->initializeParams(); |
|
665 | + |
|
666 | + $loop = false; |
|
667 | + |
|
668 | + $ok = false; |
|
669 | + $tryCount = 1; |
|
670 | + while (!$ok && $tryCount < 5) { |
|
671 | + list($current, $currentRaw) = $this->getDeviceUserData($this->type, parent::$devid, parent::$user, $folderid, true); |
|
672 | + |
|
673 | + // completely new/unknown UUID |
|
674 | + if (empty($current)) { |
|
675 | + $current = ["uuid" => $uuid, "count" => $counter - 1, "queued" => $queuedMessages]; |
|
676 | + } |
|
677 | + |
|
678 | + // old UUID in cache - the device requested a new state!! |
|
679 | + if (isset($current['uuid']) && $current['uuid'] != $uuid) { |
|
680 | + SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): UUID changed for folder"); |
|
681 | + |
|
682 | + // some devices (iPhones) may request new UUIDs after broken items were sent several times |
|
683 | + if (isset($current['queued']) && $current['queued'] > 0 && |
|
684 | + (isset($current['maxCount']) && $current['maxCount'] > $current['count'] + 1 || $counter == 1)) { |
|
685 | + SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): UUID changed and while items where sent to device - forcing loop mode"); |
|
686 | + $loop = true; // force loop mode |
|
687 | + $current['queued'] = $queuedMessages; |
|
688 | + } |
|
689 | + else { |
|
690 | + $current['queued'] = 0; |
|
691 | + } |
|
692 | + |
|
693 | + // set new data, unset old loop information |
|
694 | + $current["uuid"] = $uuid; |
|
695 | + $current['count'] = $counter; |
|
696 | + unset($current['loopcount'], $current['ignored'], $current['maxCount'], $current['potential'], $current['windowLimit']); |
|
697 | + } |
|
698 | + |
|
699 | + // see if there are values |
|
700 | + if (isset($current['uuid']) && $current['uuid'] == $uuid && |
|
701 | + isset($current['count'])) { |
|
702 | + // case 1 - standard, during loop-resolving & resolving |
|
703 | + if ($current['count'] < $counter) { |
|
704 | + // case 1.1 |
|
705 | + $current['count'] = $counter; |
|
706 | + $current['queued'] = $queuedMessages; |
|
707 | + if (isset($current["usage"]) && $current["usage"] < $current['count']) { |
|
708 | + unset($current["usage"]); |
|
709 | + } |
|
710 | + |
|
711 | + // case 1.2 |
|
712 | + if (isset($current['maxCount'])) { |
|
713 | + SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 1.2 detected"); |
|
714 | + |
|
715 | + // case 1.2.1 |
|
716 | + // broken item not identified yet |
|
717 | + if (!isset($current['ignored']) && $counter < $current['maxCount']) { |
|
718 | + $current['loopcount'] = 1; |
|
719 | + $loop = true; // continue in loop-resolving |
|
720 | + SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 1.2.1 detected"); |
|
721 | + } |
|
722 | + // case 1.2.2 - if there were any broken items they should be gone, return to normal |
|
723 | + else { |
|
724 | + SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 1.2.2 detected"); |
|
725 | + unset($current['loopcount'], $current['ignored'], $current['maxCount'], $current['potential'], $current['windowLimit']); |
|
726 | + } |
|
727 | + } |
|
728 | + } |
|
729 | + |
|
730 | + // case 2 - same counter, but there were no changes before and are there now |
|
731 | + elseif ($current['count'] == $counter && $current['queued'] == 0 && $queuedMessages > 0) { |
|
732 | + $current['queued'] = $queuedMessages; |
|
733 | + if (isset($current["usage"]) && $current["usage"] < $current['count']) { |
|
734 | + unset($current["usage"]); |
|
735 | + } |
|
736 | + } |
|
737 | + |
|
738 | + // case 3 - same counter, changes sent before, hanging loop and ignoring |
|
739 | + elseif ($current['count'] == $counter && $current['queued'] > 0) { |
|
740 | + if (!isset($current['loopcount'])) { |
|
741 | + // ZP-1213 we are potentially syncing a lot of data, e.g. OL with 512 WindowSize |
|
742 | + // In case there are more then 40 items in the last request, we limit to 25 items |
|
743 | + // before entering 1-by-1 loop detection if counter is re-requested |
|
744 | + if ($maxItems > 40 && !isset($current['windowLimit'])) { |
|
745 | + // case 3.0) we have just encountered a loop, but with a big window size, lower window first |
|
746 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->Detect(): case 3.0 detected - big windowsize of %d, lowering before entering loop mode", $maxItems)); |
|
747 | + // return suggested new window size |
|
748 | + $current['windowLimit'] = 25; |
|
749 | + $loop = $current['windowLimit']; |
|
750 | + } |
|
751 | + else { |
|
752 | + // case 3.1) we have just encountered a loop! |
|
753 | + SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.1 detected - loop detected, init loop mode"); |
|
754 | + if (isset($current['windowLimit'])) { |
|
755 | + $maxItems = $current['windowLimit']; |
|
756 | + unset($current['windowLimit']); |
|
757 | + } |
|
758 | + $current['loopcount'] = 1; |
|
759 | + // the MaxCount is the max number of messages exported before |
|
760 | + $current['maxCount'] = $counter + (($maxItems < $queuedMessages) ? $maxItems : $queuedMessages); |
|
761 | + $loop = true; // loop mode!! |
|
762 | + } |
|
763 | + } |
|
764 | + elseif ($queuedMessages == 0) { |
|
765 | + // case 3.2) there was a loop before but now the changes are GONE |
|
766 | + SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.2 detected - changes gone - clearing loop data"); |
|
767 | + $current['queued'] = 0; |
|
768 | + unset($current['loopcount'], $current['ignored'], $current['maxCount'], $current['potential'], $current['windowLimit']); |
|
769 | + } |
|
770 | + else { |
|
771 | + // case 3.3) still looping the same message! Increase counter |
|
772 | + SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.3 detected - in loop mode, increase loop counter"); |
|
773 | + ++$current['loopcount']; |
|
774 | + |
|
775 | + // case 3.3.1 - we got our broken item! |
|
776 | + if ($current['loopcount'] >= 3 && isset($current['potential'])) { |
|
777 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->Detect(): case 3.3.1 detected - broken item should be next, attempt to ignore it - id '%s'", $current['potential'])); |
|
778 | + $this->ignore_messageid = $current['potential']; |
|
779 | + } |
|
780 | + $current['maxCount'] = $counter + (($maxItems < $queuedMessages) ? $maxItems : $queuedMessages); |
|
781 | + $loop = true; // loop mode!! |
|
782 | + } |
|
783 | + } |
|
784 | + } |
|
785 | + if (isset($current['loopcount'])) { |
|
786 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->Detect(): loop data: loopcount(%d), maxCount(%d), queued(%d), ignored(%s)", $current['loopcount'], $current['maxCount'], $current['queued'], (isset($current['ignored']) ? $current['ignored'] : 'false'))); |
|
787 | + } |
|
788 | + |
|
789 | + // update loop data |
|
790 | + $ok = $this->setDeviceUserData($this->type, $current, parent::$devid, parent::$user, $folderid, $doCas = "replace", $currentRaw); |
|
791 | + if (!$ok) { |
|
792 | + SLog::Write(LOGLEVEL_WARN, sprintf("LoopDetection->Detect(): CAS failed on try %d!", $tryCount)); |
|
793 | + usleep(100000); |
|
794 | + ++$tryCount; |
|
795 | + } |
|
796 | + } |
|
797 | + // end exclusive block |
|
798 | + |
|
799 | + if ($loop === true && $this->ignore_messageid == false) { |
|
800 | + GSync::GetTopCollector()->AnnounceInformation("Loop detection", true); |
|
801 | + } |
|
802 | + |
|
803 | + return $loop; |
|
804 | + } |
|
805 | + |
|
806 | + /** |
|
807 | + * Indicates if the next messages should be ignored (not be sent to the mobile!). |
|
808 | + * |
|
809 | + * @param string $messageid (opt) id of the message which is to be exported next |
|
810 | + * @param string $folderid (opt) parent id of the message |
|
811 | + * @param bool $markAsIgnored (opt) to peek without setting the next message to be |
|
812 | + * ignored, set this value to false |
|
813 | + * |
|
814 | + * @return bool |
|
815 | + */ |
|
816 | + public function IgnoreNextMessage($markAsIgnored = true, $messageid = false, $folderid = false) { |
|
817 | + // as the next message id is not available at all point this method is called, we use different indicators. |
|
818 | + // potentialbroken indicates that we know that the broken message should be exported next, |
|
819 | + // alltho we do not know for sure as it's export message orders can change |
|
820 | + // if the $messageid is available and matches then we are sure and only then really ignore it |
|
821 | + |
|
822 | + $potentialBroken = false; |
|
823 | + $realBroken = false; |
|
824 | + if (Request::GetCommandCode() == GSync::COMMAND_SYNC && $this->ignore_messageid !== false) { |
|
825 | + $potentialBroken = true; |
|
826 | + } |
|
827 | + |
|
828 | + if ($messageid !== false && $this->ignore_messageid == $messageid) { |
|
829 | + $realBroken = true; |
|
830 | + } |
|
831 | + |
|
832 | + // this call is just to know what should be happening |
|
833 | + // no further actions necessary |
|
834 | + if ($markAsIgnored === false) { |
|
835 | + return $potentialBroken; |
|
836 | + } |
|
837 | + |
|
838 | + // we should really do something here |
|
839 | + |
|
840 | + // first we check if we are in the loop mode, if so, |
|
841 | + // we update the potential broken id message so we loop count the same message |
|
842 | + |
|
843 | + $changedData = false; |
|
844 | + $ok = false; |
|
845 | + $tryCount = 1; |
|
846 | + while (!$ok && $tryCount < 5) { |
|
847 | + list($current, $currentRaw) = $this->getDeviceUserData($this->type, parent::$devid, parent::$user, $folderid, true); |
|
848 | + |
|
849 | + // we found our broken message! |
|
850 | + if ($realBroken) { |
|
851 | + $this->ignore_messageid = false; |
|
852 | + $current['ignored'] = $messageid; |
|
853 | + $changedData = true; |
|
854 | + |
|
855 | + // check if this message was broken before - here we know that it still is and remove it from the tracking |
|
856 | + $brokenkey = self::BROKENMSGS . "-" . $folderid; |
|
857 | + // TODO: this is currently not supported here! It's in a different structure now! |
|
858 | 858 | // if (isset($loopdata[self::$devid][self::$user][$brokenkey]) && isset($loopdata[self::$devid][self::$user][$brokenkey][$messageid])) { |
859 | 859 | // SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IgnoreNextMessage(): previously broken message '%s' is still broken and will not be tracked anymore", $messageid)); |
860 | 860 | // unset($loopdata[self::$devid][self::$user][$brokenkey][$messageid]); |
861 | 861 | // } |
862 | - } |
|
863 | - // not the broken message yet |
|
864 | - else { |
|
865 | - // update potential id if looping on an item |
|
866 | - if (isset($current['loopcount'])) { |
|
867 | - $current['potential'] = $messageid; |
|
868 | - |
|
869 | - // this message should be the broken one, but is not!! |
|
870 | - // we should reset the loop count because this is certainly not the broken one |
|
871 | - if ($potentialBroken) { |
|
872 | - $current['loopcount'] = 1; |
|
873 | - SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IgnoreNextMessage(): this should be the broken one, but is not! Resetting loop count."); |
|
874 | - } |
|
875 | - |
|
876 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IgnoreNextMessage(): Loop mode, potential broken message id '%s'", $current['potential'])); |
|
877 | - |
|
878 | - $changedData = true; |
|
879 | - } |
|
880 | - } |
|
881 | - |
|
882 | - if ($changedData !== true) { |
|
883 | - break; |
|
884 | - } |
|
885 | - // update loop data |
|
886 | - $ok = $this->setDeviceUserData($this->type, $current, parent::$devid, parent::$user, $folderid, $doCas = "replace", $currentRaw); |
|
887 | - if (!$ok) { |
|
888 | - SLog::Write(LOGLEVEL_WARN, sprintf("LoopDetection->Detect(): CAS failed on try %d!", $tryCount)); |
|
889 | - usleep(100000); |
|
890 | - ++$tryCount; |
|
891 | - } |
|
892 | - } |
|
893 | - |
|
894 | - if ($realBroken) { |
|
895 | - GSync::GetTopCollector()->AnnounceInformation("Broken message ignored", true); |
|
896 | - } |
|
897 | - |
|
898 | - return $realBroken; |
|
899 | - } |
|
900 | - |
|
901 | - /** |
|
902 | - * Clears loop detection data. |
|
903 | - * |
|
904 | - * @param string $user (opt) user which data should be removed - user can not be specified without |
|
905 | - * @param string $devid (opt) device id which data to be removed |
|
906 | - * |
|
907 | - * @return bool |
|
908 | - */ |
|
909 | - public function ClearData($user = false, $devid = false) { |
|
910 | - $stat = true; |
|
911 | - $ok = false; |
|
912 | - // TODO: implement this |
|
913 | - // exclusive block |
|
914 | - if ($this->blockMutex()) { |
|
915 | - $loopdata = ($this->hasData()) ? $this->getData() : []; |
|
916 | - |
|
917 | - if ($user == false && $devid == false) { |
|
918 | - $loopdata = []; |
|
919 | - } |
|
920 | - elseif ($user == false && $devid != false) { |
|
921 | - $loopdata[$devid] = []; |
|
922 | - } |
|
923 | - elseif ($user != false && $devid != false) { |
|
924 | - $loopdata[$devid][$user] = []; |
|
925 | - } |
|
926 | - elseif ($user != false && $devid == false) { |
|
927 | - SLog::Write(LOGLEVEL_WARN, sprintf("Not possible to reset loop detection data for user '%s' without a specifying a device id", $user)); |
|
928 | - $stat = false; |
|
929 | - } |
|
930 | - |
|
931 | - if ($stat) { |
|
932 | - $ok = $this->setData($loopdata); |
|
933 | - } |
|
934 | - |
|
935 | - $this->releaseMutex(); |
|
936 | - } |
|
937 | - // end exclusive block |
|
938 | - |
|
939 | - return $stat && $ok; |
|
940 | - } |
|
941 | - |
|
942 | - /** |
|
943 | - * Returns loop detection data for a user and device. |
|
944 | - * |
|
945 | - * @param string $user |
|
946 | - * @param string $devid |
|
947 | - * |
|
948 | - * @return array/boolean returns false if data not available |
|
949 | - */ |
|
950 | - public function GetCachedData($user, $devid) { |
|
951 | - // not implemented (also nowhere used apparently) |
|
952 | - return false; |
|
953 | - // exclusive block |
|
954 | - if ($this->blockMutex()) { |
|
955 | - $loopdata = ($this->hasData()) ? $this->getData() : []; |
|
956 | - $this->releaseMutex(); |
|
957 | - } |
|
958 | - // end exclusive block |
|
959 | - if (isset($loopdata, $loopdata[$devid], $loopdata[$devid][$user])) { |
|
960 | - return $loopdata[$devid][$user]; |
|
961 | - } |
|
962 | - |
|
963 | - return false; |
|
964 | - } |
|
862 | + } |
|
863 | + // not the broken message yet |
|
864 | + else { |
|
865 | + // update potential id if looping on an item |
|
866 | + if (isset($current['loopcount'])) { |
|
867 | + $current['potential'] = $messageid; |
|
868 | + |
|
869 | + // this message should be the broken one, but is not!! |
|
870 | + // we should reset the loop count because this is certainly not the broken one |
|
871 | + if ($potentialBroken) { |
|
872 | + $current['loopcount'] = 1; |
|
873 | + SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IgnoreNextMessage(): this should be the broken one, but is not! Resetting loop count."); |
|
874 | + } |
|
875 | + |
|
876 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IgnoreNextMessage(): Loop mode, potential broken message id '%s'", $current['potential'])); |
|
877 | + |
|
878 | + $changedData = true; |
|
879 | + } |
|
880 | + } |
|
881 | + |
|
882 | + if ($changedData !== true) { |
|
883 | + break; |
|
884 | + } |
|
885 | + // update loop data |
|
886 | + $ok = $this->setDeviceUserData($this->type, $current, parent::$devid, parent::$user, $folderid, $doCas = "replace", $currentRaw); |
|
887 | + if (!$ok) { |
|
888 | + SLog::Write(LOGLEVEL_WARN, sprintf("LoopDetection->Detect(): CAS failed on try %d!", $tryCount)); |
|
889 | + usleep(100000); |
|
890 | + ++$tryCount; |
|
891 | + } |
|
892 | + } |
|
893 | + |
|
894 | + if ($realBroken) { |
|
895 | + GSync::GetTopCollector()->AnnounceInformation("Broken message ignored", true); |
|
896 | + } |
|
897 | + |
|
898 | + return $realBroken; |
|
899 | + } |
|
900 | + |
|
901 | + /** |
|
902 | + * Clears loop detection data. |
|
903 | + * |
|
904 | + * @param string $user (opt) user which data should be removed - user can not be specified without |
|
905 | + * @param string $devid (opt) device id which data to be removed |
|
906 | + * |
|
907 | + * @return bool |
|
908 | + */ |
|
909 | + public function ClearData($user = false, $devid = false) { |
|
910 | + $stat = true; |
|
911 | + $ok = false; |
|
912 | + // TODO: implement this |
|
913 | + // exclusive block |
|
914 | + if ($this->blockMutex()) { |
|
915 | + $loopdata = ($this->hasData()) ? $this->getData() : []; |
|
916 | + |
|
917 | + if ($user == false && $devid == false) { |
|
918 | + $loopdata = []; |
|
919 | + } |
|
920 | + elseif ($user == false && $devid != false) { |
|
921 | + $loopdata[$devid] = []; |
|
922 | + } |
|
923 | + elseif ($user != false && $devid != false) { |
|
924 | + $loopdata[$devid][$user] = []; |
|
925 | + } |
|
926 | + elseif ($user != false && $devid == false) { |
|
927 | + SLog::Write(LOGLEVEL_WARN, sprintf("Not possible to reset loop detection data for user '%s' without a specifying a device id", $user)); |
|
928 | + $stat = false; |
|
929 | + } |
|
930 | + |
|
931 | + if ($stat) { |
|
932 | + $ok = $this->setData($loopdata); |
|
933 | + } |
|
934 | + |
|
935 | + $this->releaseMutex(); |
|
936 | + } |
|
937 | + // end exclusive block |
|
938 | + |
|
939 | + return $stat && $ok; |
|
940 | + } |
|
941 | + |
|
942 | + /** |
|
943 | + * Returns loop detection data for a user and device. |
|
944 | + * |
|
945 | + * @param string $user |
|
946 | + * @param string $devid |
|
947 | + * |
|
948 | + * @return array/boolean returns false if data not available |
|
949 | + */ |
|
950 | + public function GetCachedData($user, $devid) { |
|
951 | + // not implemented (also nowhere used apparently) |
|
952 | + return false; |
|
953 | + // exclusive block |
|
954 | + if ($this->blockMutex()) { |
|
955 | + $loopdata = ($this->hasData()) ? $this->getData() : []; |
|
956 | + $this->releaseMutex(); |
|
957 | + } |
|
958 | + // end exclusive block |
|
959 | + if (isset($loopdata, $loopdata[$devid], $loopdata[$devid][$user])) { |
|
960 | + return $loopdata[$devid][$user]; |
|
961 | + } |
|
962 | + |
|
963 | + return false; |
|
964 | + } |
|
965 | 965 | } |
@@ -424,7 +424,7 @@ discard block |
||
424 | 424 | } |
425 | 425 | |
426 | 426 | $ok = false; |
427 | - $brokenkey = self::BROKENMSGS . "-" . $folderid; |
|
427 | + $brokenkey = self::BROKENMSGS."-".$folderid; |
|
428 | 428 | |
429 | 429 | // initialize params |
430 | 430 | $this->initializeParams(); |
@@ -465,7 +465,7 @@ discard block |
||
465 | 465 | return []; |
466 | 466 | } |
467 | 467 | |
468 | - $brokenkey = self::BROKENMSGS . "-" . $folderid; |
|
468 | + $brokenkey = self::BROKENMSGS."-".$folderid; |
|
469 | 469 | $removeIds = []; |
470 | 470 | $okIds = []; |
471 | 471 | |
@@ -606,7 +606,7 @@ discard block |
||
606 | 606 | (isset($current["usage"]) && $current["usage"] >= $counter) |
607 | 607 | )) { |
608 | 608 | $usage = isset($current["usage"]) ? sprintf(" - counter %d already expired", $current["usage"]) : ""; |
609 | - SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IsSyncStateObsolete(): yes, counter already processed" . $usage); |
|
609 | + SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IsSyncStateObsolete(): yes, counter already processed".$usage); |
|
610 | 610 | $obsolete = true; |
611 | 611 | } |
612 | 612 | } |
@@ -758,7 +758,7 @@ discard block |
||
758 | 758 | $current['loopcount'] = 1; |
759 | 759 | // the MaxCount is the max number of messages exported before |
760 | 760 | $current['maxCount'] = $counter + (($maxItems < $queuedMessages) ? $maxItems : $queuedMessages); |
761 | - $loop = true; // loop mode!! |
|
761 | + $loop = true; // loop mode!! |
|
762 | 762 | } |
763 | 763 | } |
764 | 764 | elseif ($queuedMessages == 0) { |
@@ -778,7 +778,7 @@ discard block |
||
778 | 778 | $this->ignore_messageid = $current['potential']; |
779 | 779 | } |
780 | 780 | $current['maxCount'] = $counter + (($maxItems < $queuedMessages) ? $maxItems : $queuedMessages); |
781 | - $loop = true; // loop mode!! |
|
781 | + $loop = true; // loop mode!! |
|
782 | 782 | } |
783 | 783 | } |
784 | 784 | } |
@@ -853,7 +853,7 @@ discard block |
||
853 | 853 | $changedData = true; |
854 | 854 | |
855 | 855 | // check if this message was broken before - here we know that it still is and remove it from the tracking |
856 | - $brokenkey = self::BROKENMSGS . "-" . $folderid; |
|
856 | + $brokenkey = self::BROKENMSGS."-".$folderid; |
|
857 | 857 | // TODO: this is currently not supported here! It's in a different structure now! |
858 | 858 | // if (isset($loopdata[self::$devid][self::$user][$brokenkey]) && isset($loopdata[self::$devid][self::$user][$brokenkey][$messageid])) { |
859 | 859 | // SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IgnoreNextMessage(): previously broken message '%s' is still broken and will not be tracked anymore", $messageid)); |
@@ -264,8 +264,7 @@ discard block |
||
264 | 264 | if (isset($se['stat'], $se['stat']['hierarchy']) && $se['stat']['hierarchy'] == SYNC_FSSTATUS_SYNCKEYERROR) { |
265 | 265 | SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): a full FolderReSync was already requested. Resetting fail counter."); |
266 | 266 | $seenFailed = []; |
267 | - } |
|
268 | - else { |
|
267 | + } else { |
|
269 | 268 | $seenFolderSync = true; |
270 | 269 | if (!empty($seenFailed)) { |
271 | 270 | SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): seen FolderSync after other failing command"); |
@@ -355,8 +354,7 @@ discard block |
||
355 | 354 | foreach ($stack as $entry) { |
356 | 355 | if ($entry['id'] != $updateentry['id']) { |
357 | 356 | $nstack[] = $entry; |
358 | - } |
|
359 | - else { |
|
357 | + } else { |
|
360 | 358 | $nstack[] = $updateentry; |
361 | 359 | $found = true; |
362 | 360 | } |
@@ -589,8 +587,7 @@ discard block |
||
589 | 587 | if (!isset($current["uuid"]) || $current["uuid"] != $uuid) { |
590 | 588 | SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IsSyncStateObsolete(): yes, uuid changed or not set"); |
591 | 589 | $obsolete = true; |
592 | - } |
|
593 | - else { |
|
590 | + } else { |
|
594 | 591 | SLog::Write(LOGLEVEL_DEBUG, sprintf( |
595 | 592 | "LoopDetection->IsSyncStateObsolete(): check folderid: '%s' uuid '%s' counter: %d - last counter: %d with %d queued", |
596 | 593 | $folderid, |
@@ -610,8 +607,7 @@ discard block |
||
610 | 607 | $obsolete = true; |
611 | 608 | } |
612 | 609 | } |
613 | - } |
|
614 | - else { |
|
610 | + } else { |
|
615 | 611 | SLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IsSyncStateObsolete(): check folderid: '%s' uuid '%s' counter: %d - no data found: not obsolete", $folderid, $uuid, $counter)); |
616 | 612 | } |
617 | 613 | |
@@ -685,8 +681,7 @@ discard block |
||
685 | 681 | SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): UUID changed and while items where sent to device - forcing loop mode"); |
686 | 682 | $loop = true; // force loop mode |
687 | 683 | $current['queued'] = $queuedMessages; |
688 | - } |
|
689 | - else { |
|
684 | + } else { |
|
690 | 685 | $current['queued'] = 0; |
691 | 686 | } |
692 | 687 | |
@@ -747,8 +742,7 @@ discard block |
||
747 | 742 | // return suggested new window size |
748 | 743 | $current['windowLimit'] = 25; |
749 | 744 | $loop = $current['windowLimit']; |
750 | - } |
|
751 | - else { |
|
745 | + } else { |
|
752 | 746 | // case 3.1) we have just encountered a loop! |
753 | 747 | SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.1 detected - loop detected, init loop mode"); |
754 | 748 | if (isset($current['windowLimit'])) { |
@@ -760,14 +754,12 @@ discard block |
||
760 | 754 | $current['maxCount'] = $counter + (($maxItems < $queuedMessages) ? $maxItems : $queuedMessages); |
761 | 755 | $loop = true; // loop mode!! |
762 | 756 | } |
763 | - } |
|
764 | - elseif ($queuedMessages == 0) { |
|
757 | + } elseif ($queuedMessages == 0) { |
|
765 | 758 | // case 3.2) there was a loop before but now the changes are GONE |
766 | 759 | SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.2 detected - changes gone - clearing loop data"); |
767 | 760 | $current['queued'] = 0; |
768 | 761 | unset($current['loopcount'], $current['ignored'], $current['maxCount'], $current['potential'], $current['windowLimit']); |
769 | - } |
|
770 | - else { |
|
762 | + } else { |
|
771 | 763 | // case 3.3) still looping the same message! Increase counter |
772 | 764 | SLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.3 detected - in loop mode, increase loop counter"); |
773 | 765 | ++$current['loopcount']; |
@@ -916,14 +908,11 @@ discard block |
||
916 | 908 | |
917 | 909 | if ($user == false && $devid == false) { |
918 | 910 | $loopdata = []; |
919 | - } |
|
920 | - elseif ($user == false && $devid != false) { |
|
911 | + } elseif ($user == false && $devid != false) { |
|
921 | 912 | $loopdata[$devid] = []; |
922 | - } |
|
923 | - elseif ($user != false && $devid != false) { |
|
913 | + } elseif ($user != false && $devid != false) { |
|
924 | 914 | $loopdata[$devid][$user] = []; |
925 | - } |
|
926 | - elseif ($user != false && $devid == false) { |
|
915 | + } elseif ($user != false && $devid == false) { |
|
927 | 916 | SLog::Write(LOGLEVEL_WARN, sprintf("Not possible to reset loop detection data for user '%s' without a specifying a device id", $user)); |
928 | 917 | $stat = false; |
929 | 918 | } |
@@ -8,682 +8,682 @@ discard block |
||
8 | 8 | */ |
9 | 9 | |
10 | 10 | class GSync { |
11 | - public const UNAUTHENTICATED = 1; |
|
12 | - public const UNPROVISIONED = 2; |
|
13 | - public const NOACTIVESYNCCOMMAND = 3; |
|
14 | - public const WEBSERVICECOMMAND = 4; // DEPRECATED |
|
15 | - public const HIERARCHYCOMMAND = 5; |
|
16 | - public const PLAININPUT = 6; |
|
17 | - public const REQUESTHANDLER = 7; |
|
18 | - public const CLASS_NAME = 1; |
|
19 | - public const CLASS_REQUIRESPROTOCOLVERSION = 2; |
|
20 | - public const CLASS_DEFAULTTYPE = 3; |
|
21 | - public const CLASS_OTHERTYPES = 4; |
|
22 | - |
|
23 | - // AS versions |
|
24 | - public const ASV_1 = "1.0"; |
|
25 | - public const ASV_2 = "2.0"; |
|
26 | - public const ASV_21 = "2.1"; |
|
27 | - public const ASV_25 = "2.5"; |
|
28 | - public const ASV_12 = "12.0"; |
|
29 | - public const ASV_121 = "12.1"; |
|
30 | - public const ASV_14 = "14.0"; |
|
31 | - public const ASV_141 = "14.1"; |
|
32 | - |
|
33 | - /** |
|
34 | - * Command codes for base64 encoded requests (AS >= 12.1). |
|
35 | - */ |
|
36 | - public const COMMAND_SYNC = 0; |
|
37 | - public const COMMAND_SENDMAIL = 1; |
|
38 | - public const COMMAND_SMARTFORWARD = 2; |
|
39 | - public const COMMAND_SMARTREPLY = 3; |
|
40 | - public const COMMAND_GETATTACHMENT = 4; |
|
41 | - public const COMMAND_FOLDERSYNC = 9; |
|
42 | - public const COMMAND_FOLDERCREATE = 10; |
|
43 | - public const COMMAND_FOLDERDELETE = 11; |
|
44 | - public const COMMAND_FOLDERUPDATE = 12; |
|
45 | - public const COMMAND_MOVEITEMS = 13; |
|
46 | - public const COMMAND_GETITEMESTIMATE = 14; |
|
47 | - public const COMMAND_MEETINGRESPONSE = 15; |
|
48 | - public const COMMAND_SEARCH = 16; |
|
49 | - public const COMMAND_SETTINGS = 17; |
|
50 | - public const COMMAND_PING = 18; |
|
51 | - public const COMMAND_ITEMOPERATIONS = 19; |
|
52 | - public const COMMAND_PROVISION = 20; |
|
53 | - public const COMMAND_RESOLVERECIPIENTS = 21; |
|
54 | - public const COMMAND_VALIDATECERT = 22; |
|
55 | - |
|
56 | - // Deprecated commands |
|
57 | - public const COMMAND_GETHIERARCHY = -1; |
|
58 | - public const COMMAND_CREATECOLLECTION = -2; |
|
59 | - public const COMMAND_DELETECOLLECTION = -3; |
|
60 | - public const COMMAND_MOVECOLLECTION = -4; |
|
61 | - public const COMMAND_NOTIFY = -5; |
|
62 | - |
|
63 | - // Latest supported State version |
|
64 | - public const STATE_VERSION = IStateMachine::STATEVERSION_02; |
|
65 | - |
|
66 | - // Versions 1.0, 2.0, 2.1 and 2.5 are deprecated (ZP-604) |
|
67 | - private static $supportedASVersions = [ |
|
68 | - self::ASV_12, |
|
69 | - self::ASV_121, |
|
70 | - self::ASV_14, |
|
71 | - self::ASV_141, |
|
72 | - ]; |
|
73 | - |
|
74 | - private static $supportedCommands = [ |
|
75 | - // COMMAND AS VERSION REQUESTHANDLER OTHER SETTINGS |
|
76 | - self::COMMAND_SYNC => [self::ASV_1, self::REQUESTHANDLER => "Sync"], |
|
77 | - self::COMMAND_SENDMAIL => [self::ASV_1, self::REQUESTHANDLER => "SendMail"], |
|
78 | - self::COMMAND_SMARTFORWARD => [self::ASV_1, self::REQUESTHANDLER => "SendMail"], |
|
79 | - self::COMMAND_SMARTREPLY => [self::ASV_1, self::REQUESTHANDLER => "SendMail"], |
|
80 | - self::COMMAND_GETATTACHMENT => [self::ASV_1, self::REQUESTHANDLER => "GetAttachment"], |
|
81 | - self::COMMAND_GETHIERARCHY => [self::ASV_1, self::REQUESTHANDLER => "GetHierarchy", self::HIERARCHYCOMMAND], // deprecated but implemented |
|
82 | - self::COMMAND_CREATECOLLECTION => [self::ASV_1], // deprecated & not implemented |
|
83 | - self::COMMAND_DELETECOLLECTION => [self::ASV_1], // deprecated & not implemented |
|
84 | - self::COMMAND_MOVECOLLECTION => [self::ASV_1], // deprecated & not implemented |
|
85 | - self::COMMAND_FOLDERSYNC => [self::ASV_2, self::REQUESTHANDLER => "FolderSync", self::HIERARCHYCOMMAND], |
|
86 | - self::COMMAND_FOLDERCREATE => [self::ASV_2, self::REQUESTHANDLER => "FolderChange", self::HIERARCHYCOMMAND], |
|
87 | - self::COMMAND_FOLDERDELETE => [self::ASV_2, self::REQUESTHANDLER => "FolderChange", self::HIERARCHYCOMMAND], |
|
88 | - self::COMMAND_FOLDERUPDATE => [self::ASV_2, self::REQUESTHANDLER => "FolderChange", self::HIERARCHYCOMMAND], |
|
89 | - self::COMMAND_MOVEITEMS => [self::ASV_1, self::REQUESTHANDLER => "MoveItems"], |
|
90 | - self::COMMAND_GETITEMESTIMATE => [self::ASV_1, self::REQUESTHANDLER => "GetItemEstimate"], |
|
91 | - self::COMMAND_MEETINGRESPONSE => [self::ASV_1, self::REQUESTHANDLER => "MeetingResponse"], |
|
92 | - self::COMMAND_RESOLVERECIPIENTS => [self::ASV_1, self::REQUESTHANDLER => "ResolveRecipients"], |
|
93 | - self::COMMAND_VALIDATECERT => [self::ASV_1, self::REQUESTHANDLER => "ValidateCert"], |
|
94 | - self::COMMAND_PROVISION => [self::ASV_25, self::REQUESTHANDLER => "Provisioning", self::UNAUTHENTICATED, self::UNPROVISIONED], |
|
95 | - self::COMMAND_SEARCH => [self::ASV_1, self::REQUESTHANDLER => "Search"], |
|
96 | - self::COMMAND_PING => [self::ASV_2, self::REQUESTHANDLER => "Ping", self::UNPROVISIONED], |
|
97 | - self::COMMAND_NOTIFY => [self::ASV_1, self::REQUESTHANDLER => "Notify"], // deprecated & not implemented |
|
98 | - self::COMMAND_ITEMOPERATIONS => [self::ASV_12, self::REQUESTHANDLER => "ItemOperations"], |
|
99 | - self::COMMAND_SETTINGS => [self::ASV_12, self::REQUESTHANDLER => "Settings"], |
|
100 | - ]; |
|
101 | - |
|
102 | - private static $classes = [ |
|
103 | - "Email" => [ |
|
104 | - self::CLASS_NAME => "SyncMail", |
|
105 | - self::CLASS_REQUIRESPROTOCOLVERSION => false, |
|
106 | - self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_INBOX, |
|
107 | - self::CLASS_OTHERTYPES => [ |
|
108 | - SYNC_FOLDER_TYPE_OTHER, |
|
109 | - SYNC_FOLDER_TYPE_DRAFTS, |
|
110 | - SYNC_FOLDER_TYPE_WASTEBASKET, |
|
111 | - SYNC_FOLDER_TYPE_SENTMAIL, |
|
112 | - SYNC_FOLDER_TYPE_OUTBOX, |
|
113 | - SYNC_FOLDER_TYPE_USER_MAIL, |
|
114 | - SYNC_FOLDER_TYPE_JOURNAL, |
|
115 | - SYNC_FOLDER_TYPE_USER_JOURNAL, |
|
116 | - ], |
|
117 | - ], |
|
118 | - "Contacts" => [ |
|
119 | - self::CLASS_NAME => "SyncContact", |
|
120 | - self::CLASS_REQUIRESPROTOCOLVERSION => true, |
|
121 | - self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_CONTACT, |
|
122 | - self::CLASS_OTHERTYPES => [SYNC_FOLDER_TYPE_USER_CONTACT, SYNC_FOLDER_TYPE_UNKNOWN], |
|
123 | - ], |
|
124 | - "Calendar" => [ |
|
125 | - self::CLASS_NAME => "SyncAppointment", |
|
126 | - self::CLASS_REQUIRESPROTOCOLVERSION => false, |
|
127 | - self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_APPOINTMENT, |
|
128 | - self::CLASS_OTHERTYPES => [SYNC_FOLDER_TYPE_USER_APPOINTMENT], |
|
129 | - ], |
|
130 | - "Tasks" => [ |
|
131 | - self::CLASS_NAME => "SyncTask", |
|
132 | - self::CLASS_REQUIRESPROTOCOLVERSION => false, |
|
133 | - self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_TASK, |
|
134 | - self::CLASS_OTHERTYPES => [SYNC_FOLDER_TYPE_USER_TASK], |
|
135 | - ], |
|
136 | - "Notes" => [ |
|
137 | - self::CLASS_NAME => "SyncNote", |
|
138 | - self::CLASS_REQUIRESPROTOCOLVERSION => false, |
|
139 | - self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_NOTE, |
|
140 | - self::CLASS_OTHERTYPES => [SYNC_FOLDER_TYPE_USER_NOTE], |
|
141 | - ], |
|
142 | - ]; |
|
143 | - |
|
144 | - private static $stateMachine; |
|
145 | - private static $deviceManager; |
|
146 | - private static $provisioningManager; |
|
147 | - private static $topCollector; |
|
148 | - private static $backend; |
|
149 | - private static $addSyncFolders; |
|
150 | - private static $policies; |
|
151 | - private static $redis; |
|
152 | - |
|
153 | - /** |
|
154 | - * Verifies configuration. |
|
155 | - * |
|
156 | - * @throws FatalMisconfigurationException |
|
157 | - * |
|
158 | - * @return bool |
|
159 | - */ |
|
160 | - public static function CheckConfig() { |
|
161 | - // check the php version |
|
162 | - if (version_compare(phpversion(), '5.4.0') < 0) { |
|
163 | - throw new FatalException("The configured PHP version is too old. Please make sure at least PHP 5.4 is used."); |
|
164 | - } |
|
165 | - |
|
166 | - // some basic checks |
|
167 | - if (!defined('BASE_PATH')) { |
|
168 | - throw new FatalMisconfigurationException("The BASE_PATH is not configured. Check if the config.php file is in place."); |
|
169 | - } |
|
170 | - |
|
171 | - if (substr(BASE_PATH, -1, 1) != "/") { |
|
172 | - throw new FatalMisconfigurationException("The BASE_PATH should terminate with a '/'"); |
|
173 | - } |
|
174 | - |
|
175 | - if (!file_exists(BASE_PATH)) { |
|
176 | - throw new FatalMisconfigurationException("The configured BASE_PATH does not exist or can not be accessed."); |
|
177 | - } |
|
178 | - |
|
179 | - if (defined('BASE_PATH_CLI') && file_exists(BASE_PATH_CLI)) { |
|
180 | - define('REAL_BASE_PATH', BASE_PATH_CLI); |
|
181 | - } |
|
182 | - else { |
|
183 | - define('REAL_BASE_PATH', BASE_PATH); |
|
184 | - } |
|
185 | - |
|
186 | - if (!defined('LOGBACKEND')) { |
|
187 | - define('LOGBACKEND', 'filelog'); |
|
188 | - } |
|
189 | - |
|
190 | - if (strtolower(LOGBACKEND) == 'syslog') { |
|
191 | - define('LOGBACKEND_CLASS', 'Syslog'); |
|
192 | - if (!defined('LOG_SYSLOG_FACILITY')) { |
|
193 | - define('LOG_SYSLOG_FACILITY', LOG_LOCAL0); |
|
194 | - } |
|
195 | - |
|
196 | - if (!defined('LOG_SYSLOG_HOST')) { |
|
197 | - define('LOG_SYSLOG_HOST', false); |
|
198 | - } |
|
199 | - |
|
200 | - if (!defined('LOG_SYSLOG_PORT')) { |
|
201 | - define('LOG_SYSLOG_PORT', 514); |
|
202 | - } |
|
203 | - |
|
204 | - if (!defined('LOG_SYSLOG_PROGRAM')) { |
|
205 | - define('LOG_SYSLOG_PROGRAM', 'grommunio-sync'); |
|
206 | - } |
|
207 | - |
|
208 | - if (!is_numeric(LOG_SYSLOG_PORT)) { |
|
209 | - throw new FatalMisconfigurationException("The LOG_SYSLOG_PORT must a be a number."); |
|
210 | - } |
|
211 | - |
|
212 | - if (LOG_SYSLOG_HOST && LOG_SYSLOG_PORT <= 0) { |
|
213 | - throw new FatalMisconfigurationException("LOG_SYSLOG_HOST is defined but the LOG_SYSLOG_PORT does not seem to be valid."); |
|
214 | - } |
|
215 | - } |
|
216 | - elseif (strtolower(LOGBACKEND) == 'filelog') { |
|
217 | - define('LOGBACKEND_CLASS', 'FileLog'); |
|
218 | - if (!defined('LOGFILEDIR')) { |
|
219 | - throw new FatalMisconfigurationException("The LOGFILEDIR is not configured. Check if the config.php file is in place."); |
|
220 | - } |
|
221 | - |
|
222 | - if (substr(LOGFILEDIR, -1, 1) != "/") { |
|
223 | - throw new FatalMisconfigurationException("The LOGFILEDIR should terminate with a '/'"); |
|
224 | - } |
|
225 | - |
|
226 | - if (!file_exists(LOGFILEDIR)) { |
|
227 | - throw new FatalMisconfigurationException("The configured LOGFILEDIR does not exist or can not be accessed."); |
|
228 | - } |
|
229 | - |
|
230 | - if ((!file_exists(LOGFILE) && !touch(LOGFILE)) || !is_writable(LOGFILE)) { |
|
231 | - throw new FatalMisconfigurationException("The configured LOGFILE can not be modified."); |
|
232 | - } |
|
233 | - |
|
234 | - if ((!file_exists(LOGERRORFILE) && !touch(LOGERRORFILE)) || !is_writable(LOGERRORFILE)) { |
|
235 | - throw new FatalMisconfigurationException("The configured LOGERRORFILE can not be modified."); |
|
236 | - } |
|
237 | - |
|
238 | - // check ownership on the (eventually) just created files |
|
239 | - Utils::FixFileOwner(LOGFILE); |
|
240 | - Utils::FixFileOwner(LOGERRORFILE); |
|
241 | - } |
|
242 | - else { |
|
243 | - define('LOGBACKEND_CLASS', LOGBACKEND); |
|
244 | - } |
|
245 | - |
|
246 | - // set time zone |
|
247 | - // code contributed by Robert Scheck (rsc) |
|
248 | - if (defined('TIMEZONE') ? constant('TIMEZONE') : false) { |
|
249 | - if (!@date_default_timezone_set(TIMEZONE)) { |
|
250 | - throw new FatalMisconfigurationException(sprintf("The configured TIMEZONE '%s' is not valid. Please check supported timezones at http://www.php.net/manual/en/timezones.php", constant('TIMEZONE'))); |
|
251 | - } |
|
252 | - } |
|
253 | - elseif (!ini_get('date.timezone')) { |
|
254 | - date_default_timezone_set('Europe/Vienna'); |
|
255 | - } |
|
256 | - |
|
257 | - if (defined('USE_X_FORWARDED_FOR_HEADER')) { |
|
258 | - SLog::Write(LOGLEVEL_INFO, "The configuration parameter 'USE_X_FORWARDED_FOR_HEADER' was deprecated in favor of 'USE_CUSTOM_REMOTE_IP_HEADER'. Please update your configuration."); |
|
259 | - } |
|
260 | - |
|
261 | - // check redis configuration - set defaults |
|
262 | - if (!defined('REDIS_HOST')) { |
|
263 | - define('REDIS_HOST', 'localhost'); |
|
264 | - } |
|
265 | - if (!defined('REDIS_PORT')) { |
|
266 | - define('REDIS_PORT', 6379); |
|
267 | - } |
|
268 | - if (!defined('REDIS_AUTH')) { |
|
269 | - define('REDIS_AUTH', ''); |
|
270 | - } |
|
271 | - |
|
272 | - return true; |
|
273 | - } |
|
274 | - |
|
275 | - /** |
|
276 | - * Verifies Timezone, StateMachine and Backend configuration. |
|
277 | - * |
|
278 | - * @return bool |
|
279 | - * @trows FatalMisconfigurationException |
|
280 | - */ |
|
281 | - public static function CheckAdvancedConfig() { |
|
282 | - global $specialLogUsers, $additionalFolders; |
|
283 | - |
|
284 | - if (!is_array($specialLogUsers)) { |
|
285 | - throw new FatalMisconfigurationException("The WBXML log users is not an array."); |
|
286 | - } |
|
287 | - |
|
288 | - if (!defined('SYNC_CONTACTS_MAXPICTURESIZE')) { |
|
289 | - define('SYNC_CONTACTS_MAXPICTURESIZE', 49152); |
|
290 | - } |
|
291 | - elseif ((!is_int(SYNC_CONTACTS_MAXPICTURESIZE) || SYNC_CONTACTS_MAXPICTURESIZE < 1)) { |
|
292 | - throw new FatalMisconfigurationException("The SYNC_CONTACTS_MAXPICTURESIZE value must be a number higher than 0."); |
|
293 | - } |
|
294 | - |
|
295 | - if (!defined('USE_PARTIAL_FOLDERSYNC')) { |
|
296 | - define('USE_PARTIAL_FOLDERSYNC', false); |
|
297 | - } |
|
298 | - |
|
299 | - if (!defined('PING_LOWER_BOUND_LIFETIME')) { |
|
300 | - define('PING_LOWER_BOUND_LIFETIME', false); |
|
301 | - } |
|
302 | - elseif (PING_LOWER_BOUND_LIFETIME !== false && (!is_int(PING_LOWER_BOUND_LIFETIME) || PING_LOWER_BOUND_LIFETIME < 1 || PING_LOWER_BOUND_LIFETIME > 3540)) { |
|
303 | - throw new FatalMisconfigurationException("The PING_LOWER_BOUND_LIFETIME value must be 'false' or a number between 1 and 3540 inclusively."); |
|
304 | - } |
|
305 | - if (!defined('PING_HIGHER_BOUND_LIFETIME')) { |
|
306 | - define('PING_HIGHER_BOUND_LIFETIME', false); |
|
307 | - } |
|
308 | - elseif (PING_HIGHER_BOUND_LIFETIME !== false && (!is_int(PING_HIGHER_BOUND_LIFETIME) || PING_HIGHER_BOUND_LIFETIME < 1 || PING_HIGHER_BOUND_LIFETIME > 3540)) { |
|
309 | - throw new FatalMisconfigurationException("The PING_HIGHER_BOUND_LIFETIME value must be 'false' or a number between 1 and 3540 inclusively."); |
|
310 | - } |
|
311 | - if (PING_HIGHER_BOUND_LIFETIME !== false && PING_LOWER_BOUND_LIFETIME !== false && PING_HIGHER_BOUND_LIFETIME < PING_LOWER_BOUND_LIFETIME) { |
|
312 | - throw new FatalMisconfigurationException("The PING_HIGHER_BOUND_LIFETIME value must be greater or equal to PING_LOWER_BOUND_LIFETIME."); |
|
313 | - } |
|
314 | - |
|
315 | - if (!defined('RETRY_AFTER_DELAY')) { |
|
316 | - define('RETRY_AFTER_DELAY', 300); |
|
317 | - } |
|
318 | - elseif (RETRY_AFTER_DELAY !== false && (!is_int(RETRY_AFTER_DELAY) || RETRY_AFTER_DELAY < 1)) { |
|
319 | - throw new FatalMisconfigurationException("The RETRY_AFTER_DELAY value must be 'false' or a number greater than 0."); |
|
320 | - } |
|
321 | - |
|
322 | - // set Grommunio backend defaults if not set |
|
323 | - if (!defined('MAPI_SERVER')) { |
|
324 | - define('MAPI_SERVER', 'default:'); |
|
325 | - } |
|
326 | - if (!defined('STORE_STATE_FOLDER')) { |
|
327 | - define('STORE_STATE_FOLDER', 'GS-SyncState'); |
|
328 | - } |
|
329 | - |
|
330 | - // the check on additional folders will not throw hard errors, as this is probably changed on live systems |
|
331 | - if (isset($additionalFolders) && !is_array($additionalFolders)) { |
|
332 | - SLog::Write(LOGLEVEL_ERROR, "GSync::CheckConfig(): The additional folders synchronization not available as array."); |
|
333 | - } |
|
334 | - else { |
|
335 | - // check configured data |
|
336 | - foreach ($additionalFolders as $af) { |
|
337 | - if (!is_array($af) || !isset($af['store']) || !isset($af['folderid']) || !isset($af['name']) || !isset($af['type'])) { |
|
338 | - SLog::Write(LOGLEVEL_ERROR, "GSync::CheckConfig(): the additional folder synchronization is not configured correctly. Missing parameters. Entry will be ignored."); |
|
339 | - |
|
340 | - continue; |
|
341 | - } |
|
342 | - |
|
343 | - if ($af['store'] == "" || $af['folderid'] == "" || $af['name'] == "" || $af['type'] == "") { |
|
344 | - SLog::Write(LOGLEVEL_WARN, "GSync::CheckConfig(): the additional folder synchronization is not configured correctly. Empty parameters. Entry will be ignored."); |
|
345 | - |
|
346 | - continue; |
|
347 | - } |
|
348 | - |
|
349 | - if (!in_array($af['type'], [SYNC_FOLDER_TYPE_USER_NOTE, SYNC_FOLDER_TYPE_USER_CONTACT, SYNC_FOLDER_TYPE_USER_APPOINTMENT, SYNC_FOLDER_TYPE_USER_TASK, SYNC_FOLDER_TYPE_USER_MAIL])) { |
|
350 | - SLog::Write(LOGLEVEL_ERROR, sprintf("GSync::CheckConfig(): the type of the additional synchronization folder '%s is not permitted.", $af['name'])); |
|
351 | - |
|
352 | - continue; |
|
353 | - } |
|
354 | - // the data will be initialized when used via self::getAddFolders() |
|
355 | - } |
|
356 | - } |
|
357 | - |
|
358 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("Used timezone '%s'", date_default_timezone_get())); |
|
359 | - |
|
360 | - // get the statemachine, which will also try to load the backend.. This could throw errors |
|
361 | - self::GetStateMachine(); |
|
362 | - |
|
363 | - return true; |
|
364 | - } |
|
365 | - |
|
366 | - /** |
|
367 | - * Returns the StateMachine object |
|
368 | - * which has to be an IStateMachine implementation. |
|
369 | - * |
|
370 | - * @throws FatalNotImplementedException |
|
371 | - * @throws HTTPReturnCodeException |
|
372 | - * |
|
373 | - * @return object implementation of IStateMachine |
|
374 | - */ |
|
375 | - public static function GetStateMachine() { |
|
376 | - if (!isset(GSync::$stateMachine)) { |
|
377 | - // the backend could also return an own IStateMachine implementation |
|
378 | - GSync::$stateMachine = self::GetBackend()->GetStateMachine(); |
|
379 | - |
|
380 | - if (GSync::$stateMachine->GetStateVersion() !== GSync::GetLatestStateVersion()) { |
|
381 | - if (class_exists("TopCollector")) { |
|
382 | - self::GetTopCollector()->AnnounceInformation("Run migration script!", true); |
|
383 | - } |
|
384 | - |
|
385 | - throw new ServiceUnavailableException(sprintf("The state version available to the %s is not the latest version - please run the state upgrade script. See release notes for more information.", get_class(GSync::$stateMachine))); |
|
386 | - } |
|
387 | - } |
|
388 | - |
|
389 | - return GSync::$stateMachine; |
|
390 | - } |
|
391 | - |
|
392 | - /** |
|
393 | - * Returns the Redis object. |
|
394 | - * |
|
395 | - * @return object Redis |
|
396 | - */ |
|
397 | - public static function GetRedis() { |
|
398 | - if (!isset(GSync::$redis)) { |
|
399 | - GSync::$redis = new RedisConnection(); |
|
400 | - } |
|
401 | - |
|
402 | - return GSync::$redis; |
|
403 | - } |
|
404 | - |
|
405 | - /** |
|
406 | - * Returns the latest version of supported states. |
|
407 | - * |
|
408 | - * @return int |
|
409 | - */ |
|
410 | - public static function GetLatestStateVersion() { |
|
411 | - return self::STATE_VERSION; |
|
412 | - } |
|
413 | - |
|
414 | - /** |
|
415 | - * Returns the ProvisioningManager object. |
|
416 | - * |
|
417 | - * @return object ProvisioningManager |
|
418 | - */ |
|
419 | - public static function GetProvisioningManager() { |
|
420 | - if (!isset(self::$provisioningManager)) { |
|
421 | - self::$provisioningManager = new ProvisioningManager(); |
|
422 | - } |
|
423 | - |
|
424 | - return self::$provisioningManager; |
|
425 | - } |
|
426 | - |
|
427 | - /** |
|
428 | - * Returns the DeviceManager object. |
|
429 | - * |
|
430 | - * @param bool $initialize (opt) default true: initializes the DeviceManager if not already done |
|
431 | - * |
|
432 | - * @return object DeviceManager |
|
433 | - */ |
|
434 | - public static function GetDeviceManager($initialize = true) { |
|
435 | - if (!isset(GSync::$deviceManager) && $initialize) { |
|
436 | - GSync::$deviceManager = new DeviceManager(); |
|
437 | - } |
|
438 | - |
|
439 | - return GSync::$deviceManager; |
|
440 | - } |
|
441 | - |
|
442 | - /** |
|
443 | - * Returns the Top data collector object. |
|
444 | - * |
|
445 | - * @return object TopCollector |
|
446 | - */ |
|
447 | - public static function GetTopCollector() { |
|
448 | - if (!isset(GSync::$topCollector)) { |
|
449 | - GSync::$topCollector = new TopCollector(); |
|
450 | - } |
|
451 | - |
|
452 | - return GSync::$topCollector; |
|
453 | - } |
|
454 | - |
|
455 | - /** |
|
456 | - * Loads a backend file. |
|
457 | - * |
|
458 | - * @param string $backendname |
|
459 | - * |
|
460 | - * @throws FatalNotImplementedException |
|
461 | - * |
|
462 | - * @return bool |
|
463 | - */ |
|
464 | - public static function IncludeBackend($backendname) { |
|
465 | - if ($backendname == false) { |
|
466 | - return false; |
|
467 | - } |
|
468 | - |
|
469 | - $backendname = strtolower($backendname); |
|
470 | - if (substr($backendname, 0, 7) !== 'backend') { |
|
471 | - throw new FatalNotImplementedException(sprintf("Backend '%s' is not allowed", $backendname)); |
|
472 | - } |
|
473 | - |
|
474 | - $rbn = substr($backendname, 7); |
|
475 | - |
|
476 | - $subdirbackend = REAL_BASE_PATH . "backend/" . $rbn . "/" . $rbn . ".php"; |
|
477 | - $stdbackend = REAL_BASE_PATH . "backend/" . $rbn . ".php"; |
|
478 | - |
|
479 | - if (is_file($subdirbackend)) { |
|
480 | - $toLoad = $subdirbackend; |
|
481 | - } |
|
482 | - elseif (is_file($stdbackend)) { |
|
483 | - $toLoad = $stdbackend; |
|
484 | - } |
|
485 | - else { |
|
486 | - return false; |
|
487 | - } |
|
488 | - |
|
489 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("Including backend file: '%s'", $toLoad)); |
|
490 | - |
|
491 | - return include_once $toLoad; |
|
492 | - } |
|
493 | - |
|
494 | - /** |
|
495 | - * Returns the Backend for this request |
|
496 | - * the backend has to be an IBackend implementation. |
|
497 | - * |
|
498 | - * @return object IBackend implementation |
|
499 | - */ |
|
500 | - public static function GetBackend() { |
|
501 | - // if the backend is not yet loaded, load backend drivers and instantiate it |
|
502 | - if (!isset(GSync::$backend)) { |
|
503 | - // Initialize Grommunio |
|
504 | - GSync::$backend = new Grommunio(); |
|
505 | - } |
|
506 | - |
|
507 | - return GSync::$backend; |
|
508 | - } |
|
509 | - |
|
510 | - /** |
|
511 | - * Returns additional folder objects which should be synchronized to the device. |
|
512 | - * |
|
513 | - * @param bool $backendIdsAsKeys if true the keys are backendids else folderids, default: true |
|
514 | - * |
|
515 | - * @return array |
|
516 | - */ |
|
517 | - public static function GetAdditionalSyncFolders($backendIdsAsKeys = true) { |
|
518 | - // get user based folders which should be synchronized |
|
519 | - $userFolder = self::GetDeviceManager()->GetAdditionalUserSyncFolders(); |
|
520 | - $addfolders = self::getAddSyncFolders() + $userFolder; |
|
521 | - // if requested, we rewrite the backendids to folderids here |
|
522 | - if ($backendIdsAsKeys === false && !empty($addfolders)) { |
|
523 | - SLog::Write(LOGLEVEL_DEBUG, "GSync::GetAdditionalSyncFolders(): Requested AS folderids as keys for additional folders array, converting"); |
|
524 | - $faddfolders = []; |
|
525 | - foreach ($addfolders as $backendId => $addFolder) { |
|
526 | - $fid = self::GetDeviceManager()->GetFolderIdForBackendId($backendId); |
|
527 | - $faddfolders[$fid] = $addFolder; |
|
528 | - } |
|
529 | - $addfolders = $faddfolders; |
|
530 | - } |
|
531 | - |
|
532 | - return $addfolders; |
|
533 | - } |
|
534 | - |
|
535 | - /** |
|
536 | - * Returns additional folder objects which should be synchronized to the device. |
|
537 | - * |
|
538 | - * @param string $backendid |
|
539 | - * @param bool $noDebug (opt) by default, debug message is shown |
|
540 | - * |
|
541 | - * @return string |
|
542 | - */ |
|
543 | - public static function GetAdditionalSyncFolderStore($backendid, $noDebug = false) { |
|
544 | - if (isset(self::getAddSyncFolders()[$backendid]->Store)) { |
|
545 | - $val = self::getAddSyncFolders()[$backendid]->Store; |
|
546 | - } |
|
547 | - else { |
|
548 | - $val = self::GetDeviceManager()->GetAdditionalUserSyncFolder($backendid); |
|
549 | - if (isset($val['store'])) { |
|
550 | - $val = $val['store']; |
|
551 | - } |
|
552 | - } |
|
553 | - |
|
554 | - if (!$noDebug) { |
|
555 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::GetAdditionalSyncFolderStore('%s'): '%s'", $backendid, Utils::PrintAsString($val))); |
|
556 | - } |
|
557 | - |
|
558 | - return $val; |
|
559 | - } |
|
560 | - |
|
561 | - /** |
|
562 | - * Returns a SyncObject class name for a folder class. |
|
563 | - * |
|
564 | - * @param string $folderclass |
|
565 | - * |
|
566 | - * @throws FatalNotImplementedException |
|
567 | - * |
|
568 | - * @return string |
|
569 | - */ |
|
570 | - public static function getSyncObjectFromFolderClass($folderclass) { |
|
571 | - if (!isset(self::$classes[$folderclass])) { |
|
572 | - throw new FatalNotImplementedException("Class '{$folderclass}' is not supported"); |
|
573 | - } |
|
574 | - |
|
575 | - $class = self::$classes[$folderclass][self::CLASS_NAME]; |
|
576 | - if (self::$classes[$folderclass][self::CLASS_REQUIRESPROTOCOLVERSION]) { |
|
577 | - return new $class(Request::GetProtocolVersion()); |
|
578 | - } |
|
579 | - |
|
580 | - return new $class(); |
|
581 | - } |
|
582 | - |
|
583 | - /** |
|
584 | - * Initializes the SyncObjects for additional folders on demand. |
|
585 | - * Uses DeviceManager->BuildSyncFolderObject() to do patching required for ZP-907. |
|
586 | - * |
|
587 | - * @return array |
|
588 | - */ |
|
589 | - private static function getAddSyncFolders() { |
|
590 | - global $additionalFolders; |
|
591 | - if (!isset(self::$addSyncFolders)) { |
|
592 | - self::$addSyncFolders = []; |
|
593 | - |
|
594 | - if (isset($additionalFolders) && !is_array($additionalFolders)) { |
|
595 | - SLog::Write(LOGLEVEL_ERROR, "GSync::getAddSyncFolders() : The additional folders synchronization not available as array."); |
|
596 | - } |
|
597 | - else { |
|
598 | - foreach ($additionalFolders as $af) { |
|
599 | - if (!is_array($af) || !isset($af['store']) || !isset($af['folderid']) || !isset($af['name']) || !isset($af['type'])) { |
|
600 | - SLog::Write(LOGLEVEL_ERROR, "GSync::getAddSyncFolders() : the additional folder synchronization is not configured correctly. Missing parameters. Entry will be ignored."); |
|
601 | - |
|
602 | - continue; |
|
603 | - } |
|
604 | - |
|
605 | - if ($af['store'] == "" || $af['folderid'] == "" || $af['name'] == "" || $af['type'] == "") { |
|
606 | - SLog::Write(LOGLEVEL_WARN, "GSync::getAddSyncFolders() : the additional folder synchronization is not configured correctly. Empty parameters. Entry will be ignored."); |
|
607 | - |
|
608 | - continue; |
|
609 | - } |
|
610 | - |
|
611 | - if (!in_array($af['type'], [SYNC_FOLDER_TYPE_USER_NOTE, SYNC_FOLDER_TYPE_USER_CONTACT, SYNC_FOLDER_TYPE_USER_APPOINTMENT, SYNC_FOLDER_TYPE_USER_TASK, SYNC_FOLDER_TYPE_USER_MAIL])) { |
|
612 | - SLog::Write(LOGLEVEL_ERROR, sprintf("GSync::getAddSyncFolders() : the type of the additional synchronization folder '%s is not permitted.", $af['name'])); |
|
613 | - |
|
614 | - continue; |
|
615 | - } |
|
616 | - |
|
617 | - // don't fail hard if no flags are set, but we at least warn about it |
|
618 | - if (!isset($af['flags'])) { |
|
619 | - SLog::Write(LOGLEVEL_WARN, sprintf("GSync::getAddSyncFolders() : the additional folder '%s' is not configured completely. Missing 'flags' parameter, defaulting to DeviceManager::FLD_FLAGS_NONE.", $af['name'])); |
|
620 | - $af['flags'] = DeviceManager::FLD_FLAGS_NONE; |
|
621 | - } |
|
622 | - |
|
623 | - $folder = self::GetDeviceManager()->BuildSyncFolderObject($af['store'], $af['folderid'], '0', $af['name'], $af['type'], $af['flags'], DeviceManager::FLD_ORIGIN_CONFIG); |
|
624 | - self::$addSyncFolders[$folder->BackendId] = $folder; |
|
625 | - } |
|
626 | - } |
|
627 | - } |
|
628 | - |
|
629 | - return self::$addSyncFolders; |
|
630 | - } |
|
631 | - |
|
632 | - /** |
|
633 | - * Returns the default foldertype for a folder class. |
|
634 | - * |
|
635 | - * @param string $folderclass folderclass sent by the mobile |
|
636 | - * |
|
637 | - * @return string |
|
638 | - */ |
|
639 | - public static function getDefaultFolderTypeFromFolderClass($folderclass) { |
|
640 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::getDefaultFolderTypeFromFolderClass('%s'): '%d'", $folderclass, self::$classes[$folderclass][self::CLASS_DEFAULTTYPE])); |
|
641 | - |
|
642 | - return self::$classes[$folderclass][self::CLASS_DEFAULTTYPE]; |
|
643 | - } |
|
644 | - |
|
645 | - /** |
|
646 | - * Returns the folder class for a foldertype. |
|
647 | - * |
|
648 | - * @param string $foldertype |
|
649 | - * |
|
650 | - * @return string/false false if no class for this type is available |
|
651 | - */ |
|
652 | - public static function GetFolderClassFromFolderType($foldertype) { |
|
653 | - $class = false; |
|
654 | - foreach (self::$classes as $aClass => $cprops) { |
|
655 | - if ($cprops[self::CLASS_DEFAULTTYPE] == $foldertype || in_array($foldertype, $cprops[self::CLASS_OTHERTYPES])) { |
|
656 | - $class = $aClass; |
|
657 | - |
|
658 | - break; |
|
659 | - } |
|
660 | - } |
|
661 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::GetFolderClassFromFolderType('%s'): %s", $foldertype, Utils::PrintAsString($class))); |
|
662 | - |
|
663 | - return $class; |
|
664 | - } |
|
665 | - |
|
666 | - /** |
|
667 | - * Prints the grommunio-sync legal header to STDOUT |
|
668 | - * Using this breaks ActiveSync synchronization if wbxml is expected. |
|
669 | - * |
|
670 | - * @param string $message (opt) message to be displayed |
|
671 | - * @param string $additionalMessage (opt) additional message to be displayed |
|
672 | - * |
|
673 | - * @return |
|
674 | - */ |
|
675 | - public static function PrintGrommunioSyncLegal($message = "", $additionalMessage = "") { |
|
676 | - SLog::Write(LOGLEVEL_DEBUG, "GSync::PrintGrommunioSyncLegal()"); |
|
677 | - |
|
678 | - if ($message) { |
|
679 | - $message = "<h3>" . $message . "</h3>"; |
|
680 | - } |
|
681 | - if ($additionalMessage) { |
|
682 | - $additionalMessage .= "<br>"; |
|
683 | - } |
|
684 | - |
|
685 | - header("Content-type: text/html"); |
|
686 | - echo <<<END |
|
11 | + public const UNAUTHENTICATED = 1; |
|
12 | + public const UNPROVISIONED = 2; |
|
13 | + public const NOACTIVESYNCCOMMAND = 3; |
|
14 | + public const WEBSERVICECOMMAND = 4; // DEPRECATED |
|
15 | + public const HIERARCHYCOMMAND = 5; |
|
16 | + public const PLAININPUT = 6; |
|
17 | + public const REQUESTHANDLER = 7; |
|
18 | + public const CLASS_NAME = 1; |
|
19 | + public const CLASS_REQUIRESPROTOCOLVERSION = 2; |
|
20 | + public const CLASS_DEFAULTTYPE = 3; |
|
21 | + public const CLASS_OTHERTYPES = 4; |
|
22 | + |
|
23 | + // AS versions |
|
24 | + public const ASV_1 = "1.0"; |
|
25 | + public const ASV_2 = "2.0"; |
|
26 | + public const ASV_21 = "2.1"; |
|
27 | + public const ASV_25 = "2.5"; |
|
28 | + public const ASV_12 = "12.0"; |
|
29 | + public const ASV_121 = "12.1"; |
|
30 | + public const ASV_14 = "14.0"; |
|
31 | + public const ASV_141 = "14.1"; |
|
32 | + |
|
33 | + /** |
|
34 | + * Command codes for base64 encoded requests (AS >= 12.1). |
|
35 | + */ |
|
36 | + public const COMMAND_SYNC = 0; |
|
37 | + public const COMMAND_SENDMAIL = 1; |
|
38 | + public const COMMAND_SMARTFORWARD = 2; |
|
39 | + public const COMMAND_SMARTREPLY = 3; |
|
40 | + public const COMMAND_GETATTACHMENT = 4; |
|
41 | + public const COMMAND_FOLDERSYNC = 9; |
|
42 | + public const COMMAND_FOLDERCREATE = 10; |
|
43 | + public const COMMAND_FOLDERDELETE = 11; |
|
44 | + public const COMMAND_FOLDERUPDATE = 12; |
|
45 | + public const COMMAND_MOVEITEMS = 13; |
|
46 | + public const COMMAND_GETITEMESTIMATE = 14; |
|
47 | + public const COMMAND_MEETINGRESPONSE = 15; |
|
48 | + public const COMMAND_SEARCH = 16; |
|
49 | + public const COMMAND_SETTINGS = 17; |
|
50 | + public const COMMAND_PING = 18; |
|
51 | + public const COMMAND_ITEMOPERATIONS = 19; |
|
52 | + public const COMMAND_PROVISION = 20; |
|
53 | + public const COMMAND_RESOLVERECIPIENTS = 21; |
|
54 | + public const COMMAND_VALIDATECERT = 22; |
|
55 | + |
|
56 | + // Deprecated commands |
|
57 | + public const COMMAND_GETHIERARCHY = -1; |
|
58 | + public const COMMAND_CREATECOLLECTION = -2; |
|
59 | + public const COMMAND_DELETECOLLECTION = -3; |
|
60 | + public const COMMAND_MOVECOLLECTION = -4; |
|
61 | + public const COMMAND_NOTIFY = -5; |
|
62 | + |
|
63 | + // Latest supported State version |
|
64 | + public const STATE_VERSION = IStateMachine::STATEVERSION_02; |
|
65 | + |
|
66 | + // Versions 1.0, 2.0, 2.1 and 2.5 are deprecated (ZP-604) |
|
67 | + private static $supportedASVersions = [ |
|
68 | + self::ASV_12, |
|
69 | + self::ASV_121, |
|
70 | + self::ASV_14, |
|
71 | + self::ASV_141, |
|
72 | + ]; |
|
73 | + |
|
74 | + private static $supportedCommands = [ |
|
75 | + // COMMAND AS VERSION REQUESTHANDLER OTHER SETTINGS |
|
76 | + self::COMMAND_SYNC => [self::ASV_1, self::REQUESTHANDLER => "Sync"], |
|
77 | + self::COMMAND_SENDMAIL => [self::ASV_1, self::REQUESTHANDLER => "SendMail"], |
|
78 | + self::COMMAND_SMARTFORWARD => [self::ASV_1, self::REQUESTHANDLER => "SendMail"], |
|
79 | + self::COMMAND_SMARTREPLY => [self::ASV_1, self::REQUESTHANDLER => "SendMail"], |
|
80 | + self::COMMAND_GETATTACHMENT => [self::ASV_1, self::REQUESTHANDLER => "GetAttachment"], |
|
81 | + self::COMMAND_GETHIERARCHY => [self::ASV_1, self::REQUESTHANDLER => "GetHierarchy", self::HIERARCHYCOMMAND], // deprecated but implemented |
|
82 | + self::COMMAND_CREATECOLLECTION => [self::ASV_1], // deprecated & not implemented |
|
83 | + self::COMMAND_DELETECOLLECTION => [self::ASV_1], // deprecated & not implemented |
|
84 | + self::COMMAND_MOVECOLLECTION => [self::ASV_1], // deprecated & not implemented |
|
85 | + self::COMMAND_FOLDERSYNC => [self::ASV_2, self::REQUESTHANDLER => "FolderSync", self::HIERARCHYCOMMAND], |
|
86 | + self::COMMAND_FOLDERCREATE => [self::ASV_2, self::REQUESTHANDLER => "FolderChange", self::HIERARCHYCOMMAND], |
|
87 | + self::COMMAND_FOLDERDELETE => [self::ASV_2, self::REQUESTHANDLER => "FolderChange", self::HIERARCHYCOMMAND], |
|
88 | + self::COMMAND_FOLDERUPDATE => [self::ASV_2, self::REQUESTHANDLER => "FolderChange", self::HIERARCHYCOMMAND], |
|
89 | + self::COMMAND_MOVEITEMS => [self::ASV_1, self::REQUESTHANDLER => "MoveItems"], |
|
90 | + self::COMMAND_GETITEMESTIMATE => [self::ASV_1, self::REQUESTHANDLER => "GetItemEstimate"], |
|
91 | + self::COMMAND_MEETINGRESPONSE => [self::ASV_1, self::REQUESTHANDLER => "MeetingResponse"], |
|
92 | + self::COMMAND_RESOLVERECIPIENTS => [self::ASV_1, self::REQUESTHANDLER => "ResolveRecipients"], |
|
93 | + self::COMMAND_VALIDATECERT => [self::ASV_1, self::REQUESTHANDLER => "ValidateCert"], |
|
94 | + self::COMMAND_PROVISION => [self::ASV_25, self::REQUESTHANDLER => "Provisioning", self::UNAUTHENTICATED, self::UNPROVISIONED], |
|
95 | + self::COMMAND_SEARCH => [self::ASV_1, self::REQUESTHANDLER => "Search"], |
|
96 | + self::COMMAND_PING => [self::ASV_2, self::REQUESTHANDLER => "Ping", self::UNPROVISIONED], |
|
97 | + self::COMMAND_NOTIFY => [self::ASV_1, self::REQUESTHANDLER => "Notify"], // deprecated & not implemented |
|
98 | + self::COMMAND_ITEMOPERATIONS => [self::ASV_12, self::REQUESTHANDLER => "ItemOperations"], |
|
99 | + self::COMMAND_SETTINGS => [self::ASV_12, self::REQUESTHANDLER => "Settings"], |
|
100 | + ]; |
|
101 | + |
|
102 | + private static $classes = [ |
|
103 | + "Email" => [ |
|
104 | + self::CLASS_NAME => "SyncMail", |
|
105 | + self::CLASS_REQUIRESPROTOCOLVERSION => false, |
|
106 | + self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_INBOX, |
|
107 | + self::CLASS_OTHERTYPES => [ |
|
108 | + SYNC_FOLDER_TYPE_OTHER, |
|
109 | + SYNC_FOLDER_TYPE_DRAFTS, |
|
110 | + SYNC_FOLDER_TYPE_WASTEBASKET, |
|
111 | + SYNC_FOLDER_TYPE_SENTMAIL, |
|
112 | + SYNC_FOLDER_TYPE_OUTBOX, |
|
113 | + SYNC_FOLDER_TYPE_USER_MAIL, |
|
114 | + SYNC_FOLDER_TYPE_JOURNAL, |
|
115 | + SYNC_FOLDER_TYPE_USER_JOURNAL, |
|
116 | + ], |
|
117 | + ], |
|
118 | + "Contacts" => [ |
|
119 | + self::CLASS_NAME => "SyncContact", |
|
120 | + self::CLASS_REQUIRESPROTOCOLVERSION => true, |
|
121 | + self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_CONTACT, |
|
122 | + self::CLASS_OTHERTYPES => [SYNC_FOLDER_TYPE_USER_CONTACT, SYNC_FOLDER_TYPE_UNKNOWN], |
|
123 | + ], |
|
124 | + "Calendar" => [ |
|
125 | + self::CLASS_NAME => "SyncAppointment", |
|
126 | + self::CLASS_REQUIRESPROTOCOLVERSION => false, |
|
127 | + self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_APPOINTMENT, |
|
128 | + self::CLASS_OTHERTYPES => [SYNC_FOLDER_TYPE_USER_APPOINTMENT], |
|
129 | + ], |
|
130 | + "Tasks" => [ |
|
131 | + self::CLASS_NAME => "SyncTask", |
|
132 | + self::CLASS_REQUIRESPROTOCOLVERSION => false, |
|
133 | + self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_TASK, |
|
134 | + self::CLASS_OTHERTYPES => [SYNC_FOLDER_TYPE_USER_TASK], |
|
135 | + ], |
|
136 | + "Notes" => [ |
|
137 | + self::CLASS_NAME => "SyncNote", |
|
138 | + self::CLASS_REQUIRESPROTOCOLVERSION => false, |
|
139 | + self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_NOTE, |
|
140 | + self::CLASS_OTHERTYPES => [SYNC_FOLDER_TYPE_USER_NOTE], |
|
141 | + ], |
|
142 | + ]; |
|
143 | + |
|
144 | + private static $stateMachine; |
|
145 | + private static $deviceManager; |
|
146 | + private static $provisioningManager; |
|
147 | + private static $topCollector; |
|
148 | + private static $backend; |
|
149 | + private static $addSyncFolders; |
|
150 | + private static $policies; |
|
151 | + private static $redis; |
|
152 | + |
|
153 | + /** |
|
154 | + * Verifies configuration. |
|
155 | + * |
|
156 | + * @throws FatalMisconfigurationException |
|
157 | + * |
|
158 | + * @return bool |
|
159 | + */ |
|
160 | + public static function CheckConfig() { |
|
161 | + // check the php version |
|
162 | + if (version_compare(phpversion(), '5.4.0') < 0) { |
|
163 | + throw new FatalException("The configured PHP version is too old. Please make sure at least PHP 5.4 is used."); |
|
164 | + } |
|
165 | + |
|
166 | + // some basic checks |
|
167 | + if (!defined('BASE_PATH')) { |
|
168 | + throw new FatalMisconfigurationException("The BASE_PATH is not configured. Check if the config.php file is in place."); |
|
169 | + } |
|
170 | + |
|
171 | + if (substr(BASE_PATH, -1, 1) != "/") { |
|
172 | + throw new FatalMisconfigurationException("The BASE_PATH should terminate with a '/'"); |
|
173 | + } |
|
174 | + |
|
175 | + if (!file_exists(BASE_PATH)) { |
|
176 | + throw new FatalMisconfigurationException("The configured BASE_PATH does not exist or can not be accessed."); |
|
177 | + } |
|
178 | + |
|
179 | + if (defined('BASE_PATH_CLI') && file_exists(BASE_PATH_CLI)) { |
|
180 | + define('REAL_BASE_PATH', BASE_PATH_CLI); |
|
181 | + } |
|
182 | + else { |
|
183 | + define('REAL_BASE_PATH', BASE_PATH); |
|
184 | + } |
|
185 | + |
|
186 | + if (!defined('LOGBACKEND')) { |
|
187 | + define('LOGBACKEND', 'filelog'); |
|
188 | + } |
|
189 | + |
|
190 | + if (strtolower(LOGBACKEND) == 'syslog') { |
|
191 | + define('LOGBACKEND_CLASS', 'Syslog'); |
|
192 | + if (!defined('LOG_SYSLOG_FACILITY')) { |
|
193 | + define('LOG_SYSLOG_FACILITY', LOG_LOCAL0); |
|
194 | + } |
|
195 | + |
|
196 | + if (!defined('LOG_SYSLOG_HOST')) { |
|
197 | + define('LOG_SYSLOG_HOST', false); |
|
198 | + } |
|
199 | + |
|
200 | + if (!defined('LOG_SYSLOG_PORT')) { |
|
201 | + define('LOG_SYSLOG_PORT', 514); |
|
202 | + } |
|
203 | + |
|
204 | + if (!defined('LOG_SYSLOG_PROGRAM')) { |
|
205 | + define('LOG_SYSLOG_PROGRAM', 'grommunio-sync'); |
|
206 | + } |
|
207 | + |
|
208 | + if (!is_numeric(LOG_SYSLOG_PORT)) { |
|
209 | + throw new FatalMisconfigurationException("The LOG_SYSLOG_PORT must a be a number."); |
|
210 | + } |
|
211 | + |
|
212 | + if (LOG_SYSLOG_HOST && LOG_SYSLOG_PORT <= 0) { |
|
213 | + throw new FatalMisconfigurationException("LOG_SYSLOG_HOST is defined but the LOG_SYSLOG_PORT does not seem to be valid."); |
|
214 | + } |
|
215 | + } |
|
216 | + elseif (strtolower(LOGBACKEND) == 'filelog') { |
|
217 | + define('LOGBACKEND_CLASS', 'FileLog'); |
|
218 | + if (!defined('LOGFILEDIR')) { |
|
219 | + throw new FatalMisconfigurationException("The LOGFILEDIR is not configured. Check if the config.php file is in place."); |
|
220 | + } |
|
221 | + |
|
222 | + if (substr(LOGFILEDIR, -1, 1) != "/") { |
|
223 | + throw new FatalMisconfigurationException("The LOGFILEDIR should terminate with a '/'"); |
|
224 | + } |
|
225 | + |
|
226 | + if (!file_exists(LOGFILEDIR)) { |
|
227 | + throw new FatalMisconfigurationException("The configured LOGFILEDIR does not exist or can not be accessed."); |
|
228 | + } |
|
229 | + |
|
230 | + if ((!file_exists(LOGFILE) && !touch(LOGFILE)) || !is_writable(LOGFILE)) { |
|
231 | + throw new FatalMisconfigurationException("The configured LOGFILE can not be modified."); |
|
232 | + } |
|
233 | + |
|
234 | + if ((!file_exists(LOGERRORFILE) && !touch(LOGERRORFILE)) || !is_writable(LOGERRORFILE)) { |
|
235 | + throw new FatalMisconfigurationException("The configured LOGERRORFILE can not be modified."); |
|
236 | + } |
|
237 | + |
|
238 | + // check ownership on the (eventually) just created files |
|
239 | + Utils::FixFileOwner(LOGFILE); |
|
240 | + Utils::FixFileOwner(LOGERRORFILE); |
|
241 | + } |
|
242 | + else { |
|
243 | + define('LOGBACKEND_CLASS', LOGBACKEND); |
|
244 | + } |
|
245 | + |
|
246 | + // set time zone |
|
247 | + // code contributed by Robert Scheck (rsc) |
|
248 | + if (defined('TIMEZONE') ? constant('TIMEZONE') : false) { |
|
249 | + if (!@date_default_timezone_set(TIMEZONE)) { |
|
250 | + throw new FatalMisconfigurationException(sprintf("The configured TIMEZONE '%s' is not valid. Please check supported timezones at http://www.php.net/manual/en/timezones.php", constant('TIMEZONE'))); |
|
251 | + } |
|
252 | + } |
|
253 | + elseif (!ini_get('date.timezone')) { |
|
254 | + date_default_timezone_set('Europe/Vienna'); |
|
255 | + } |
|
256 | + |
|
257 | + if (defined('USE_X_FORWARDED_FOR_HEADER')) { |
|
258 | + SLog::Write(LOGLEVEL_INFO, "The configuration parameter 'USE_X_FORWARDED_FOR_HEADER' was deprecated in favor of 'USE_CUSTOM_REMOTE_IP_HEADER'. Please update your configuration."); |
|
259 | + } |
|
260 | + |
|
261 | + // check redis configuration - set defaults |
|
262 | + if (!defined('REDIS_HOST')) { |
|
263 | + define('REDIS_HOST', 'localhost'); |
|
264 | + } |
|
265 | + if (!defined('REDIS_PORT')) { |
|
266 | + define('REDIS_PORT', 6379); |
|
267 | + } |
|
268 | + if (!defined('REDIS_AUTH')) { |
|
269 | + define('REDIS_AUTH', ''); |
|
270 | + } |
|
271 | + |
|
272 | + return true; |
|
273 | + } |
|
274 | + |
|
275 | + /** |
|
276 | + * Verifies Timezone, StateMachine and Backend configuration. |
|
277 | + * |
|
278 | + * @return bool |
|
279 | + * @trows FatalMisconfigurationException |
|
280 | + */ |
|
281 | + public static function CheckAdvancedConfig() { |
|
282 | + global $specialLogUsers, $additionalFolders; |
|
283 | + |
|
284 | + if (!is_array($specialLogUsers)) { |
|
285 | + throw new FatalMisconfigurationException("The WBXML log users is not an array."); |
|
286 | + } |
|
287 | + |
|
288 | + if (!defined('SYNC_CONTACTS_MAXPICTURESIZE')) { |
|
289 | + define('SYNC_CONTACTS_MAXPICTURESIZE', 49152); |
|
290 | + } |
|
291 | + elseif ((!is_int(SYNC_CONTACTS_MAXPICTURESIZE) || SYNC_CONTACTS_MAXPICTURESIZE < 1)) { |
|
292 | + throw new FatalMisconfigurationException("The SYNC_CONTACTS_MAXPICTURESIZE value must be a number higher than 0."); |
|
293 | + } |
|
294 | + |
|
295 | + if (!defined('USE_PARTIAL_FOLDERSYNC')) { |
|
296 | + define('USE_PARTIAL_FOLDERSYNC', false); |
|
297 | + } |
|
298 | + |
|
299 | + if (!defined('PING_LOWER_BOUND_LIFETIME')) { |
|
300 | + define('PING_LOWER_BOUND_LIFETIME', false); |
|
301 | + } |
|
302 | + elseif (PING_LOWER_BOUND_LIFETIME !== false && (!is_int(PING_LOWER_BOUND_LIFETIME) || PING_LOWER_BOUND_LIFETIME < 1 || PING_LOWER_BOUND_LIFETIME > 3540)) { |
|
303 | + throw new FatalMisconfigurationException("The PING_LOWER_BOUND_LIFETIME value must be 'false' or a number between 1 and 3540 inclusively."); |
|
304 | + } |
|
305 | + if (!defined('PING_HIGHER_BOUND_LIFETIME')) { |
|
306 | + define('PING_HIGHER_BOUND_LIFETIME', false); |
|
307 | + } |
|
308 | + elseif (PING_HIGHER_BOUND_LIFETIME !== false && (!is_int(PING_HIGHER_BOUND_LIFETIME) || PING_HIGHER_BOUND_LIFETIME < 1 || PING_HIGHER_BOUND_LIFETIME > 3540)) { |
|
309 | + throw new FatalMisconfigurationException("The PING_HIGHER_BOUND_LIFETIME value must be 'false' or a number between 1 and 3540 inclusively."); |
|
310 | + } |
|
311 | + if (PING_HIGHER_BOUND_LIFETIME !== false && PING_LOWER_BOUND_LIFETIME !== false && PING_HIGHER_BOUND_LIFETIME < PING_LOWER_BOUND_LIFETIME) { |
|
312 | + throw new FatalMisconfigurationException("The PING_HIGHER_BOUND_LIFETIME value must be greater or equal to PING_LOWER_BOUND_LIFETIME."); |
|
313 | + } |
|
314 | + |
|
315 | + if (!defined('RETRY_AFTER_DELAY')) { |
|
316 | + define('RETRY_AFTER_DELAY', 300); |
|
317 | + } |
|
318 | + elseif (RETRY_AFTER_DELAY !== false && (!is_int(RETRY_AFTER_DELAY) || RETRY_AFTER_DELAY < 1)) { |
|
319 | + throw new FatalMisconfigurationException("The RETRY_AFTER_DELAY value must be 'false' or a number greater than 0."); |
|
320 | + } |
|
321 | + |
|
322 | + // set Grommunio backend defaults if not set |
|
323 | + if (!defined('MAPI_SERVER')) { |
|
324 | + define('MAPI_SERVER', 'default:'); |
|
325 | + } |
|
326 | + if (!defined('STORE_STATE_FOLDER')) { |
|
327 | + define('STORE_STATE_FOLDER', 'GS-SyncState'); |
|
328 | + } |
|
329 | + |
|
330 | + // the check on additional folders will not throw hard errors, as this is probably changed on live systems |
|
331 | + if (isset($additionalFolders) && !is_array($additionalFolders)) { |
|
332 | + SLog::Write(LOGLEVEL_ERROR, "GSync::CheckConfig(): The additional folders synchronization not available as array."); |
|
333 | + } |
|
334 | + else { |
|
335 | + // check configured data |
|
336 | + foreach ($additionalFolders as $af) { |
|
337 | + if (!is_array($af) || !isset($af['store']) || !isset($af['folderid']) || !isset($af['name']) || !isset($af['type'])) { |
|
338 | + SLog::Write(LOGLEVEL_ERROR, "GSync::CheckConfig(): the additional folder synchronization is not configured correctly. Missing parameters. Entry will be ignored."); |
|
339 | + |
|
340 | + continue; |
|
341 | + } |
|
342 | + |
|
343 | + if ($af['store'] == "" || $af['folderid'] == "" || $af['name'] == "" || $af['type'] == "") { |
|
344 | + SLog::Write(LOGLEVEL_WARN, "GSync::CheckConfig(): the additional folder synchronization is not configured correctly. Empty parameters. Entry will be ignored."); |
|
345 | + |
|
346 | + continue; |
|
347 | + } |
|
348 | + |
|
349 | + if (!in_array($af['type'], [SYNC_FOLDER_TYPE_USER_NOTE, SYNC_FOLDER_TYPE_USER_CONTACT, SYNC_FOLDER_TYPE_USER_APPOINTMENT, SYNC_FOLDER_TYPE_USER_TASK, SYNC_FOLDER_TYPE_USER_MAIL])) { |
|
350 | + SLog::Write(LOGLEVEL_ERROR, sprintf("GSync::CheckConfig(): the type of the additional synchronization folder '%s is not permitted.", $af['name'])); |
|
351 | + |
|
352 | + continue; |
|
353 | + } |
|
354 | + // the data will be initialized when used via self::getAddFolders() |
|
355 | + } |
|
356 | + } |
|
357 | + |
|
358 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("Used timezone '%s'", date_default_timezone_get())); |
|
359 | + |
|
360 | + // get the statemachine, which will also try to load the backend.. This could throw errors |
|
361 | + self::GetStateMachine(); |
|
362 | + |
|
363 | + return true; |
|
364 | + } |
|
365 | + |
|
366 | + /** |
|
367 | + * Returns the StateMachine object |
|
368 | + * which has to be an IStateMachine implementation. |
|
369 | + * |
|
370 | + * @throws FatalNotImplementedException |
|
371 | + * @throws HTTPReturnCodeException |
|
372 | + * |
|
373 | + * @return object implementation of IStateMachine |
|
374 | + */ |
|
375 | + public static function GetStateMachine() { |
|
376 | + if (!isset(GSync::$stateMachine)) { |
|
377 | + // the backend could also return an own IStateMachine implementation |
|
378 | + GSync::$stateMachine = self::GetBackend()->GetStateMachine(); |
|
379 | + |
|
380 | + if (GSync::$stateMachine->GetStateVersion() !== GSync::GetLatestStateVersion()) { |
|
381 | + if (class_exists("TopCollector")) { |
|
382 | + self::GetTopCollector()->AnnounceInformation("Run migration script!", true); |
|
383 | + } |
|
384 | + |
|
385 | + throw new ServiceUnavailableException(sprintf("The state version available to the %s is not the latest version - please run the state upgrade script. See release notes for more information.", get_class(GSync::$stateMachine))); |
|
386 | + } |
|
387 | + } |
|
388 | + |
|
389 | + return GSync::$stateMachine; |
|
390 | + } |
|
391 | + |
|
392 | + /** |
|
393 | + * Returns the Redis object. |
|
394 | + * |
|
395 | + * @return object Redis |
|
396 | + */ |
|
397 | + public static function GetRedis() { |
|
398 | + if (!isset(GSync::$redis)) { |
|
399 | + GSync::$redis = new RedisConnection(); |
|
400 | + } |
|
401 | + |
|
402 | + return GSync::$redis; |
|
403 | + } |
|
404 | + |
|
405 | + /** |
|
406 | + * Returns the latest version of supported states. |
|
407 | + * |
|
408 | + * @return int |
|
409 | + */ |
|
410 | + public static function GetLatestStateVersion() { |
|
411 | + return self::STATE_VERSION; |
|
412 | + } |
|
413 | + |
|
414 | + /** |
|
415 | + * Returns the ProvisioningManager object. |
|
416 | + * |
|
417 | + * @return object ProvisioningManager |
|
418 | + */ |
|
419 | + public static function GetProvisioningManager() { |
|
420 | + if (!isset(self::$provisioningManager)) { |
|
421 | + self::$provisioningManager = new ProvisioningManager(); |
|
422 | + } |
|
423 | + |
|
424 | + return self::$provisioningManager; |
|
425 | + } |
|
426 | + |
|
427 | + /** |
|
428 | + * Returns the DeviceManager object. |
|
429 | + * |
|
430 | + * @param bool $initialize (opt) default true: initializes the DeviceManager if not already done |
|
431 | + * |
|
432 | + * @return object DeviceManager |
|
433 | + */ |
|
434 | + public static function GetDeviceManager($initialize = true) { |
|
435 | + if (!isset(GSync::$deviceManager) && $initialize) { |
|
436 | + GSync::$deviceManager = new DeviceManager(); |
|
437 | + } |
|
438 | + |
|
439 | + return GSync::$deviceManager; |
|
440 | + } |
|
441 | + |
|
442 | + /** |
|
443 | + * Returns the Top data collector object. |
|
444 | + * |
|
445 | + * @return object TopCollector |
|
446 | + */ |
|
447 | + public static function GetTopCollector() { |
|
448 | + if (!isset(GSync::$topCollector)) { |
|
449 | + GSync::$topCollector = new TopCollector(); |
|
450 | + } |
|
451 | + |
|
452 | + return GSync::$topCollector; |
|
453 | + } |
|
454 | + |
|
455 | + /** |
|
456 | + * Loads a backend file. |
|
457 | + * |
|
458 | + * @param string $backendname |
|
459 | + * |
|
460 | + * @throws FatalNotImplementedException |
|
461 | + * |
|
462 | + * @return bool |
|
463 | + */ |
|
464 | + public static function IncludeBackend($backendname) { |
|
465 | + if ($backendname == false) { |
|
466 | + return false; |
|
467 | + } |
|
468 | + |
|
469 | + $backendname = strtolower($backendname); |
|
470 | + if (substr($backendname, 0, 7) !== 'backend') { |
|
471 | + throw new FatalNotImplementedException(sprintf("Backend '%s' is not allowed", $backendname)); |
|
472 | + } |
|
473 | + |
|
474 | + $rbn = substr($backendname, 7); |
|
475 | + |
|
476 | + $subdirbackend = REAL_BASE_PATH . "backend/" . $rbn . "/" . $rbn . ".php"; |
|
477 | + $stdbackend = REAL_BASE_PATH . "backend/" . $rbn . ".php"; |
|
478 | + |
|
479 | + if (is_file($subdirbackend)) { |
|
480 | + $toLoad = $subdirbackend; |
|
481 | + } |
|
482 | + elseif (is_file($stdbackend)) { |
|
483 | + $toLoad = $stdbackend; |
|
484 | + } |
|
485 | + else { |
|
486 | + return false; |
|
487 | + } |
|
488 | + |
|
489 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("Including backend file: '%s'", $toLoad)); |
|
490 | + |
|
491 | + return include_once $toLoad; |
|
492 | + } |
|
493 | + |
|
494 | + /** |
|
495 | + * Returns the Backend for this request |
|
496 | + * the backend has to be an IBackend implementation. |
|
497 | + * |
|
498 | + * @return object IBackend implementation |
|
499 | + */ |
|
500 | + public static function GetBackend() { |
|
501 | + // if the backend is not yet loaded, load backend drivers and instantiate it |
|
502 | + if (!isset(GSync::$backend)) { |
|
503 | + // Initialize Grommunio |
|
504 | + GSync::$backend = new Grommunio(); |
|
505 | + } |
|
506 | + |
|
507 | + return GSync::$backend; |
|
508 | + } |
|
509 | + |
|
510 | + /** |
|
511 | + * Returns additional folder objects which should be synchronized to the device. |
|
512 | + * |
|
513 | + * @param bool $backendIdsAsKeys if true the keys are backendids else folderids, default: true |
|
514 | + * |
|
515 | + * @return array |
|
516 | + */ |
|
517 | + public static function GetAdditionalSyncFolders($backendIdsAsKeys = true) { |
|
518 | + // get user based folders which should be synchronized |
|
519 | + $userFolder = self::GetDeviceManager()->GetAdditionalUserSyncFolders(); |
|
520 | + $addfolders = self::getAddSyncFolders() + $userFolder; |
|
521 | + // if requested, we rewrite the backendids to folderids here |
|
522 | + if ($backendIdsAsKeys === false && !empty($addfolders)) { |
|
523 | + SLog::Write(LOGLEVEL_DEBUG, "GSync::GetAdditionalSyncFolders(): Requested AS folderids as keys for additional folders array, converting"); |
|
524 | + $faddfolders = []; |
|
525 | + foreach ($addfolders as $backendId => $addFolder) { |
|
526 | + $fid = self::GetDeviceManager()->GetFolderIdForBackendId($backendId); |
|
527 | + $faddfolders[$fid] = $addFolder; |
|
528 | + } |
|
529 | + $addfolders = $faddfolders; |
|
530 | + } |
|
531 | + |
|
532 | + return $addfolders; |
|
533 | + } |
|
534 | + |
|
535 | + /** |
|
536 | + * Returns additional folder objects which should be synchronized to the device. |
|
537 | + * |
|
538 | + * @param string $backendid |
|
539 | + * @param bool $noDebug (opt) by default, debug message is shown |
|
540 | + * |
|
541 | + * @return string |
|
542 | + */ |
|
543 | + public static function GetAdditionalSyncFolderStore($backendid, $noDebug = false) { |
|
544 | + if (isset(self::getAddSyncFolders()[$backendid]->Store)) { |
|
545 | + $val = self::getAddSyncFolders()[$backendid]->Store; |
|
546 | + } |
|
547 | + else { |
|
548 | + $val = self::GetDeviceManager()->GetAdditionalUserSyncFolder($backendid); |
|
549 | + if (isset($val['store'])) { |
|
550 | + $val = $val['store']; |
|
551 | + } |
|
552 | + } |
|
553 | + |
|
554 | + if (!$noDebug) { |
|
555 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::GetAdditionalSyncFolderStore('%s'): '%s'", $backendid, Utils::PrintAsString($val))); |
|
556 | + } |
|
557 | + |
|
558 | + return $val; |
|
559 | + } |
|
560 | + |
|
561 | + /** |
|
562 | + * Returns a SyncObject class name for a folder class. |
|
563 | + * |
|
564 | + * @param string $folderclass |
|
565 | + * |
|
566 | + * @throws FatalNotImplementedException |
|
567 | + * |
|
568 | + * @return string |
|
569 | + */ |
|
570 | + public static function getSyncObjectFromFolderClass($folderclass) { |
|
571 | + if (!isset(self::$classes[$folderclass])) { |
|
572 | + throw new FatalNotImplementedException("Class '{$folderclass}' is not supported"); |
|
573 | + } |
|
574 | + |
|
575 | + $class = self::$classes[$folderclass][self::CLASS_NAME]; |
|
576 | + if (self::$classes[$folderclass][self::CLASS_REQUIRESPROTOCOLVERSION]) { |
|
577 | + return new $class(Request::GetProtocolVersion()); |
|
578 | + } |
|
579 | + |
|
580 | + return new $class(); |
|
581 | + } |
|
582 | + |
|
583 | + /** |
|
584 | + * Initializes the SyncObjects for additional folders on demand. |
|
585 | + * Uses DeviceManager->BuildSyncFolderObject() to do patching required for ZP-907. |
|
586 | + * |
|
587 | + * @return array |
|
588 | + */ |
|
589 | + private static function getAddSyncFolders() { |
|
590 | + global $additionalFolders; |
|
591 | + if (!isset(self::$addSyncFolders)) { |
|
592 | + self::$addSyncFolders = []; |
|
593 | + |
|
594 | + if (isset($additionalFolders) && !is_array($additionalFolders)) { |
|
595 | + SLog::Write(LOGLEVEL_ERROR, "GSync::getAddSyncFolders() : The additional folders synchronization not available as array."); |
|
596 | + } |
|
597 | + else { |
|
598 | + foreach ($additionalFolders as $af) { |
|
599 | + if (!is_array($af) || !isset($af['store']) || !isset($af['folderid']) || !isset($af['name']) || !isset($af['type'])) { |
|
600 | + SLog::Write(LOGLEVEL_ERROR, "GSync::getAddSyncFolders() : the additional folder synchronization is not configured correctly. Missing parameters. Entry will be ignored."); |
|
601 | + |
|
602 | + continue; |
|
603 | + } |
|
604 | + |
|
605 | + if ($af['store'] == "" || $af['folderid'] == "" || $af['name'] == "" || $af['type'] == "") { |
|
606 | + SLog::Write(LOGLEVEL_WARN, "GSync::getAddSyncFolders() : the additional folder synchronization is not configured correctly. Empty parameters. Entry will be ignored."); |
|
607 | + |
|
608 | + continue; |
|
609 | + } |
|
610 | + |
|
611 | + if (!in_array($af['type'], [SYNC_FOLDER_TYPE_USER_NOTE, SYNC_FOLDER_TYPE_USER_CONTACT, SYNC_FOLDER_TYPE_USER_APPOINTMENT, SYNC_FOLDER_TYPE_USER_TASK, SYNC_FOLDER_TYPE_USER_MAIL])) { |
|
612 | + SLog::Write(LOGLEVEL_ERROR, sprintf("GSync::getAddSyncFolders() : the type of the additional synchronization folder '%s is not permitted.", $af['name'])); |
|
613 | + |
|
614 | + continue; |
|
615 | + } |
|
616 | + |
|
617 | + // don't fail hard if no flags are set, but we at least warn about it |
|
618 | + if (!isset($af['flags'])) { |
|
619 | + SLog::Write(LOGLEVEL_WARN, sprintf("GSync::getAddSyncFolders() : the additional folder '%s' is not configured completely. Missing 'flags' parameter, defaulting to DeviceManager::FLD_FLAGS_NONE.", $af['name'])); |
|
620 | + $af['flags'] = DeviceManager::FLD_FLAGS_NONE; |
|
621 | + } |
|
622 | + |
|
623 | + $folder = self::GetDeviceManager()->BuildSyncFolderObject($af['store'], $af['folderid'], '0', $af['name'], $af['type'], $af['flags'], DeviceManager::FLD_ORIGIN_CONFIG); |
|
624 | + self::$addSyncFolders[$folder->BackendId] = $folder; |
|
625 | + } |
|
626 | + } |
|
627 | + } |
|
628 | + |
|
629 | + return self::$addSyncFolders; |
|
630 | + } |
|
631 | + |
|
632 | + /** |
|
633 | + * Returns the default foldertype for a folder class. |
|
634 | + * |
|
635 | + * @param string $folderclass folderclass sent by the mobile |
|
636 | + * |
|
637 | + * @return string |
|
638 | + */ |
|
639 | + public static function getDefaultFolderTypeFromFolderClass($folderclass) { |
|
640 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::getDefaultFolderTypeFromFolderClass('%s'): '%d'", $folderclass, self::$classes[$folderclass][self::CLASS_DEFAULTTYPE])); |
|
641 | + |
|
642 | + return self::$classes[$folderclass][self::CLASS_DEFAULTTYPE]; |
|
643 | + } |
|
644 | + |
|
645 | + /** |
|
646 | + * Returns the folder class for a foldertype. |
|
647 | + * |
|
648 | + * @param string $foldertype |
|
649 | + * |
|
650 | + * @return string/false false if no class for this type is available |
|
651 | + */ |
|
652 | + public static function GetFolderClassFromFolderType($foldertype) { |
|
653 | + $class = false; |
|
654 | + foreach (self::$classes as $aClass => $cprops) { |
|
655 | + if ($cprops[self::CLASS_DEFAULTTYPE] == $foldertype || in_array($foldertype, $cprops[self::CLASS_OTHERTYPES])) { |
|
656 | + $class = $aClass; |
|
657 | + |
|
658 | + break; |
|
659 | + } |
|
660 | + } |
|
661 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::GetFolderClassFromFolderType('%s'): %s", $foldertype, Utils::PrintAsString($class))); |
|
662 | + |
|
663 | + return $class; |
|
664 | + } |
|
665 | + |
|
666 | + /** |
|
667 | + * Prints the grommunio-sync legal header to STDOUT |
|
668 | + * Using this breaks ActiveSync synchronization if wbxml is expected. |
|
669 | + * |
|
670 | + * @param string $message (opt) message to be displayed |
|
671 | + * @param string $additionalMessage (opt) additional message to be displayed |
|
672 | + * |
|
673 | + * @return |
|
674 | + */ |
|
675 | + public static function PrintGrommunioSyncLegal($message = "", $additionalMessage = "") { |
|
676 | + SLog::Write(LOGLEVEL_DEBUG, "GSync::PrintGrommunioSyncLegal()"); |
|
677 | + |
|
678 | + if ($message) { |
|
679 | + $message = "<h3>" . $message . "</h3>"; |
|
680 | + } |
|
681 | + if ($additionalMessage) { |
|
682 | + $additionalMessage .= "<br>"; |
|
683 | + } |
|
684 | + |
|
685 | + header("Content-type: text/html"); |
|
686 | + echo <<<END |
|
687 | 687 | <html> |
688 | 688 | <header> |
689 | 689 | <title>grommunio-sync ActiveSync</title> |
@@ -699,198 +699,198 @@ discard block |
||
699 | 699 | </body> |
700 | 700 | </html> |
701 | 701 | END; |
702 | - } |
|
703 | - |
|
704 | - /** |
|
705 | - * Indicates the latest AS version supported by grommunio-sync. |
|
706 | - * |
|
707 | - * @return string |
|
708 | - */ |
|
709 | - public static function GetLatestSupportedASVersion() { |
|
710 | - return end(self::$supportedASVersions); |
|
711 | - } |
|
712 | - |
|
713 | - /** |
|
714 | - * Indicates which is the highest AS version supported by the backend. |
|
715 | - * |
|
716 | - * @throws FatalNotImplementedException if the backend returns an invalid version |
|
717 | - * |
|
718 | - * @return string |
|
719 | - */ |
|
720 | - public static function GetSupportedASVersion() { |
|
721 | - $version = self::GetBackend()->GetSupportedASVersion(); |
|
722 | - if (!in_array($version, self::$supportedASVersions)) { |
|
723 | - throw new FatalNotImplementedException(sprintf("AS version '%s' reported by the backend is not supported", $version)); |
|
724 | - } |
|
725 | - |
|
726 | - return $version; |
|
727 | - } |
|
728 | - |
|
729 | - /** |
|
730 | - * Returns AS server header. |
|
731 | - * |
|
732 | - * @return string |
|
733 | - */ |
|
734 | - public static function GetServerHeader() { |
|
735 | - if (self::GetSupportedASVersion() == self::ASV_25) { |
|
736 | - return "MS-Server-ActiveSync: 6.5.7638.1"; |
|
737 | - } |
|
738 | - |
|
739 | - return "MS-Server-ActiveSync: " . self::GetSupportedASVersion(); |
|
740 | - } |
|
741 | - |
|
742 | - /** |
|
743 | - * Returns AS protocol versions which are supported. |
|
744 | - * |
|
745 | - * @param bool $valueOnly (opt) default: false (also returns the header name) |
|
746 | - * |
|
747 | - * @return string |
|
748 | - */ |
|
749 | - public static function GetSupportedProtocolVersions($valueOnly = false) { |
|
750 | - $versions = implode(',', array_slice(self::$supportedASVersions, 0, (array_search(self::GetSupportedASVersion(), self::$supportedASVersions) + 1))); |
|
751 | - SLog::Write(LOGLEVEL_DEBUG, "GSync::GetSupportedProtocolVersions(): " . $versions); |
|
752 | - |
|
753 | - if ($valueOnly === true) { |
|
754 | - return $versions; |
|
755 | - } |
|
756 | - |
|
757 | - return "MS-ASProtocolVersions: " . $versions; |
|
758 | - } |
|
759 | - |
|
760 | - /** |
|
761 | - * Returns AS commands which are supported. |
|
762 | - * |
|
763 | - * @return string |
|
764 | - */ |
|
765 | - public static function GetSupportedCommands() { |
|
766 | - $asCommands = []; |
|
767 | - // filter all non-activesync commands |
|
768 | - foreach (self::$supportedCommands as $c => $v) { |
|
769 | - if (!self::checkCommandOptions($c, self::NOACTIVESYNCCOMMAND) && |
|
770 | - self::checkCommandOptions($c, self::GetSupportedASVersion())) { |
|
771 | - $asCommands[] = Utils::GetCommandFromCode($c); |
|
772 | - } |
|
773 | - } |
|
774 | - |
|
775 | - $commands = implode(',', $asCommands); |
|
776 | - SLog::Write(LOGLEVEL_DEBUG, "GSync::GetSupportedCommands(): " . $commands); |
|
777 | - |
|
778 | - return "MS-ASProtocolCommands: " . $commands; |
|
779 | - } |
|
780 | - |
|
781 | - /** |
|
782 | - * Loads and instantiates a request processor for a command. |
|
783 | - * |
|
784 | - * @param int $commandCode |
|
785 | - * |
|
786 | - * @return RequestProcessor sub-class |
|
787 | - */ |
|
788 | - public static function GetRequestHandlerForCommand($commandCode) { |
|
789 | - if (!array_key_exists($commandCode, self::$supportedCommands) || |
|
790 | - !array_key_exists(self::REQUESTHANDLER, self::$supportedCommands[$commandCode])) { |
|
791 | - throw new FatalNotImplementedException(sprintf("Command '%s' has no request handler or class", Utils::GetCommandFromCode($commandCode))); |
|
792 | - } |
|
793 | - |
|
794 | - $class = self::$supportedCommands[$commandCode][self::REQUESTHANDLER]; |
|
795 | - $handlerclass = REAL_BASE_PATH . "lib/request/" . strtolower($class) . ".php"; |
|
796 | - |
|
797 | - if (is_file($handlerclass)) { |
|
798 | - include $handlerclass; |
|
799 | - } |
|
800 | - |
|
801 | - if (class_exists($class)) { |
|
802 | - return new $class(); |
|
803 | - } |
|
804 | - |
|
805 | - throw new FatalNotImplementedException(sprintf("Request handler '%s' can not be loaded", $class)); |
|
806 | - } |
|
807 | - |
|
808 | - /** |
|
809 | - * Indicates if a commands requires authentication or not. |
|
810 | - * |
|
811 | - * @param int $commandCode |
|
812 | - * |
|
813 | - * @return bool |
|
814 | - */ |
|
815 | - public static function CommandNeedsAuthentication($commandCode) { |
|
816 | - $stat = !self::checkCommandOptions($commandCode, self::UNAUTHENTICATED); |
|
817 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::CommandNeedsAuthentication(%d): %s", $commandCode, Utils::PrintAsString($stat))); |
|
818 | - |
|
819 | - return $stat; |
|
820 | - } |
|
821 | - |
|
822 | - /** |
|
823 | - * Indicates if the Provisioning check has to be forced on these commands. |
|
824 | - * |
|
825 | - * @param string $commandCode |
|
826 | - * |
|
827 | - * @return bool |
|
828 | - */ |
|
829 | - public static function CommandNeedsProvisioning($commandCode) { |
|
830 | - $stat = !self::checkCommandOptions($commandCode, self::UNPROVISIONED); |
|
831 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::CommandNeedsProvisioning(%s): %s", $commandCode, Utils::PrintAsString($stat))); |
|
832 | - |
|
833 | - return $stat; |
|
834 | - } |
|
835 | - |
|
836 | - /** |
|
837 | - * Indicates if these commands expect plain text input instead of wbxml. |
|
838 | - * |
|
839 | - * @param string $commandCode |
|
840 | - * |
|
841 | - * @return bool |
|
842 | - */ |
|
843 | - public static function CommandNeedsPlainInput($commandCode) { |
|
844 | - $stat = self::checkCommandOptions($commandCode, self::PLAININPUT); |
|
845 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::CommandNeedsPlainInput(%d): %s", $commandCode, Utils::PrintAsString($stat))); |
|
846 | - |
|
847 | - return $stat; |
|
848 | - } |
|
849 | - |
|
850 | - /** |
|
851 | - * Indicates if the command to be executed operates on the hierarchy. |
|
852 | - * |
|
853 | - * @param int $commandCode |
|
854 | - * |
|
855 | - * @return bool |
|
856 | - */ |
|
857 | - public static function HierarchyCommand($commandCode) { |
|
858 | - $stat = self::checkCommandOptions($commandCode, self::HIERARCHYCOMMAND); |
|
859 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::HierarchyCommand(%d): %s", $commandCode, Utils::PrintAsString($stat))); |
|
860 | - |
|
861 | - return $stat; |
|
862 | - } |
|
863 | - |
|
864 | - /** |
|
865 | - * Checks access types of a command. |
|
866 | - * |
|
867 | - * @param string $commandCode a commandCode |
|
868 | - * @param string $option e.g. self::UNAUTHENTICATED |
|
869 | - * |
|
870 | - * @throws FatalNotImplementedException |
|
871 | - * |
|
872 | - * @return object StateMachine |
|
873 | - */ |
|
874 | - private static function checkCommandOptions($commandCode, $option) { |
|
875 | - if ($commandCode === false) { |
|
876 | - return false; |
|
877 | - } |
|
878 | - |
|
879 | - if (!array_key_exists($commandCode, self::$supportedCommands)) { |
|
880 | - throw new FatalNotImplementedException(sprintf("Command '%s' is not supported", Utils::GetCommandFromCode($commandCode))); |
|
881 | - } |
|
882 | - |
|
883 | - $capa = self::$supportedCommands[$commandCode]; |
|
884 | - $defcapa = in_array($option, $capa, true); |
|
885 | - |
|
886 | - // if not looking for a default capability, check if the command is supported since a previous AS version |
|
887 | - if (!$defcapa) { |
|
888 | - $verkey = array_search($option, self::$supportedASVersions, true); |
|
889 | - if ($verkey !== false && ($verkey >= array_search($capa[0], self::$supportedASVersions))) { |
|
890 | - $defcapa = true; |
|
891 | - } |
|
892 | - } |
|
893 | - |
|
894 | - return $defcapa; |
|
895 | - } |
|
702 | + } |
|
703 | + |
|
704 | + /** |
|
705 | + * Indicates the latest AS version supported by grommunio-sync. |
|
706 | + * |
|
707 | + * @return string |
|
708 | + */ |
|
709 | + public static function GetLatestSupportedASVersion() { |
|
710 | + return end(self::$supportedASVersions); |
|
711 | + } |
|
712 | + |
|
713 | + /** |
|
714 | + * Indicates which is the highest AS version supported by the backend. |
|
715 | + * |
|
716 | + * @throws FatalNotImplementedException if the backend returns an invalid version |
|
717 | + * |
|
718 | + * @return string |
|
719 | + */ |
|
720 | + public static function GetSupportedASVersion() { |
|
721 | + $version = self::GetBackend()->GetSupportedASVersion(); |
|
722 | + if (!in_array($version, self::$supportedASVersions)) { |
|
723 | + throw new FatalNotImplementedException(sprintf("AS version '%s' reported by the backend is not supported", $version)); |
|
724 | + } |
|
725 | + |
|
726 | + return $version; |
|
727 | + } |
|
728 | + |
|
729 | + /** |
|
730 | + * Returns AS server header. |
|
731 | + * |
|
732 | + * @return string |
|
733 | + */ |
|
734 | + public static function GetServerHeader() { |
|
735 | + if (self::GetSupportedASVersion() == self::ASV_25) { |
|
736 | + return "MS-Server-ActiveSync: 6.5.7638.1"; |
|
737 | + } |
|
738 | + |
|
739 | + return "MS-Server-ActiveSync: " . self::GetSupportedASVersion(); |
|
740 | + } |
|
741 | + |
|
742 | + /** |
|
743 | + * Returns AS protocol versions which are supported. |
|
744 | + * |
|
745 | + * @param bool $valueOnly (opt) default: false (also returns the header name) |
|
746 | + * |
|
747 | + * @return string |
|
748 | + */ |
|
749 | + public static function GetSupportedProtocolVersions($valueOnly = false) { |
|
750 | + $versions = implode(',', array_slice(self::$supportedASVersions, 0, (array_search(self::GetSupportedASVersion(), self::$supportedASVersions) + 1))); |
|
751 | + SLog::Write(LOGLEVEL_DEBUG, "GSync::GetSupportedProtocolVersions(): " . $versions); |
|
752 | + |
|
753 | + if ($valueOnly === true) { |
|
754 | + return $versions; |
|
755 | + } |
|
756 | + |
|
757 | + return "MS-ASProtocolVersions: " . $versions; |
|
758 | + } |
|
759 | + |
|
760 | + /** |
|
761 | + * Returns AS commands which are supported. |
|
762 | + * |
|
763 | + * @return string |
|
764 | + */ |
|
765 | + public static function GetSupportedCommands() { |
|
766 | + $asCommands = []; |
|
767 | + // filter all non-activesync commands |
|
768 | + foreach (self::$supportedCommands as $c => $v) { |
|
769 | + if (!self::checkCommandOptions($c, self::NOACTIVESYNCCOMMAND) && |
|
770 | + self::checkCommandOptions($c, self::GetSupportedASVersion())) { |
|
771 | + $asCommands[] = Utils::GetCommandFromCode($c); |
|
772 | + } |
|
773 | + } |
|
774 | + |
|
775 | + $commands = implode(',', $asCommands); |
|
776 | + SLog::Write(LOGLEVEL_DEBUG, "GSync::GetSupportedCommands(): " . $commands); |
|
777 | + |
|
778 | + return "MS-ASProtocolCommands: " . $commands; |
|
779 | + } |
|
780 | + |
|
781 | + /** |
|
782 | + * Loads and instantiates a request processor for a command. |
|
783 | + * |
|
784 | + * @param int $commandCode |
|
785 | + * |
|
786 | + * @return RequestProcessor sub-class |
|
787 | + */ |
|
788 | + public static function GetRequestHandlerForCommand($commandCode) { |
|
789 | + if (!array_key_exists($commandCode, self::$supportedCommands) || |
|
790 | + !array_key_exists(self::REQUESTHANDLER, self::$supportedCommands[$commandCode])) { |
|
791 | + throw new FatalNotImplementedException(sprintf("Command '%s' has no request handler or class", Utils::GetCommandFromCode($commandCode))); |
|
792 | + } |
|
793 | + |
|
794 | + $class = self::$supportedCommands[$commandCode][self::REQUESTHANDLER]; |
|
795 | + $handlerclass = REAL_BASE_PATH . "lib/request/" . strtolower($class) . ".php"; |
|
796 | + |
|
797 | + if (is_file($handlerclass)) { |
|
798 | + include $handlerclass; |
|
799 | + } |
|
800 | + |
|
801 | + if (class_exists($class)) { |
|
802 | + return new $class(); |
|
803 | + } |
|
804 | + |
|
805 | + throw new FatalNotImplementedException(sprintf("Request handler '%s' can not be loaded", $class)); |
|
806 | + } |
|
807 | + |
|
808 | + /** |
|
809 | + * Indicates if a commands requires authentication or not. |
|
810 | + * |
|
811 | + * @param int $commandCode |
|
812 | + * |
|
813 | + * @return bool |
|
814 | + */ |
|
815 | + public static function CommandNeedsAuthentication($commandCode) { |
|
816 | + $stat = !self::checkCommandOptions($commandCode, self::UNAUTHENTICATED); |
|
817 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::CommandNeedsAuthentication(%d): %s", $commandCode, Utils::PrintAsString($stat))); |
|
818 | + |
|
819 | + return $stat; |
|
820 | + } |
|
821 | + |
|
822 | + /** |
|
823 | + * Indicates if the Provisioning check has to be forced on these commands. |
|
824 | + * |
|
825 | + * @param string $commandCode |
|
826 | + * |
|
827 | + * @return bool |
|
828 | + */ |
|
829 | + public static function CommandNeedsProvisioning($commandCode) { |
|
830 | + $stat = !self::checkCommandOptions($commandCode, self::UNPROVISIONED); |
|
831 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::CommandNeedsProvisioning(%s): %s", $commandCode, Utils::PrintAsString($stat))); |
|
832 | + |
|
833 | + return $stat; |
|
834 | + } |
|
835 | + |
|
836 | + /** |
|
837 | + * Indicates if these commands expect plain text input instead of wbxml. |
|
838 | + * |
|
839 | + * @param string $commandCode |
|
840 | + * |
|
841 | + * @return bool |
|
842 | + */ |
|
843 | + public static function CommandNeedsPlainInput($commandCode) { |
|
844 | + $stat = self::checkCommandOptions($commandCode, self::PLAININPUT); |
|
845 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::CommandNeedsPlainInput(%d): %s", $commandCode, Utils::PrintAsString($stat))); |
|
846 | + |
|
847 | + return $stat; |
|
848 | + } |
|
849 | + |
|
850 | + /** |
|
851 | + * Indicates if the command to be executed operates on the hierarchy. |
|
852 | + * |
|
853 | + * @param int $commandCode |
|
854 | + * |
|
855 | + * @return bool |
|
856 | + */ |
|
857 | + public static function HierarchyCommand($commandCode) { |
|
858 | + $stat = self::checkCommandOptions($commandCode, self::HIERARCHYCOMMAND); |
|
859 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::HierarchyCommand(%d): %s", $commandCode, Utils::PrintAsString($stat))); |
|
860 | + |
|
861 | + return $stat; |
|
862 | + } |
|
863 | + |
|
864 | + /** |
|
865 | + * Checks access types of a command. |
|
866 | + * |
|
867 | + * @param string $commandCode a commandCode |
|
868 | + * @param string $option e.g. self::UNAUTHENTICATED |
|
869 | + * |
|
870 | + * @throws FatalNotImplementedException |
|
871 | + * |
|
872 | + * @return object StateMachine |
|
873 | + */ |
|
874 | + private static function checkCommandOptions($commandCode, $option) { |
|
875 | + if ($commandCode === false) { |
|
876 | + return false; |
|
877 | + } |
|
878 | + |
|
879 | + if (!array_key_exists($commandCode, self::$supportedCommands)) { |
|
880 | + throw new FatalNotImplementedException(sprintf("Command '%s' is not supported", Utils::GetCommandFromCode($commandCode))); |
|
881 | + } |
|
882 | + |
|
883 | + $capa = self::$supportedCommands[$commandCode]; |
|
884 | + $defcapa = in_array($option, $capa, true); |
|
885 | + |
|
886 | + // if not looking for a default capability, check if the command is supported since a previous AS version |
|
887 | + if (!$defcapa) { |
|
888 | + $verkey = array_search($option, self::$supportedASVersions, true); |
|
889 | + if ($verkey !== false && ($verkey >= array_search($capa[0], self::$supportedASVersions))) { |
|
890 | + $defcapa = true; |
|
891 | + } |
|
892 | + } |
|
893 | + |
|
894 | + return $defcapa; |
|
895 | + } |
|
896 | 896 | } |
@@ -11,7 +11,7 @@ discard block |
||
11 | 11 | public const UNAUTHENTICATED = 1; |
12 | 12 | public const UNPROVISIONED = 2; |
13 | 13 | public const NOACTIVESYNCCOMMAND = 3; |
14 | - public const WEBSERVICECOMMAND = 4; // DEPRECATED |
|
14 | + public const WEBSERVICECOMMAND = 4; // DEPRECATED |
|
15 | 15 | public const HIERARCHYCOMMAND = 5; |
16 | 16 | public const PLAININPUT = 6; |
17 | 17 | public const REQUESTHANDLER = 7; |
@@ -473,8 +473,8 @@ discard block |
||
473 | 473 | |
474 | 474 | $rbn = substr($backendname, 7); |
475 | 475 | |
476 | - $subdirbackend = REAL_BASE_PATH . "backend/" . $rbn . "/" . $rbn . ".php"; |
|
477 | - $stdbackend = REAL_BASE_PATH . "backend/" . $rbn . ".php"; |
|
476 | + $subdirbackend = REAL_BASE_PATH."backend/".$rbn."/".$rbn.".php"; |
|
477 | + $stdbackend = REAL_BASE_PATH."backend/".$rbn.".php"; |
|
478 | 478 | |
479 | 479 | if (is_file($subdirbackend)) { |
480 | 480 | $toLoad = $subdirbackend; |
@@ -676,7 +676,7 @@ discard block |
||
676 | 676 | SLog::Write(LOGLEVEL_DEBUG, "GSync::PrintGrommunioSyncLegal()"); |
677 | 677 | |
678 | 678 | if ($message) { |
679 | - $message = "<h3>" . $message . "</h3>"; |
|
679 | + $message = "<h3>".$message."</h3>"; |
|
680 | 680 | } |
681 | 681 | if ($additionalMessage) { |
682 | 682 | $additionalMessage .= "<br>"; |
@@ -736,7 +736,7 @@ discard block |
||
736 | 736 | return "MS-Server-ActiveSync: 6.5.7638.1"; |
737 | 737 | } |
738 | 738 | |
739 | - return "MS-Server-ActiveSync: " . self::GetSupportedASVersion(); |
|
739 | + return "MS-Server-ActiveSync: ".self::GetSupportedASVersion(); |
|
740 | 740 | } |
741 | 741 | |
742 | 742 | /** |
@@ -748,13 +748,13 @@ discard block |
||
748 | 748 | */ |
749 | 749 | public static function GetSupportedProtocolVersions($valueOnly = false) { |
750 | 750 | $versions = implode(',', array_slice(self::$supportedASVersions, 0, (array_search(self::GetSupportedASVersion(), self::$supportedASVersions) + 1))); |
751 | - SLog::Write(LOGLEVEL_DEBUG, "GSync::GetSupportedProtocolVersions(): " . $versions); |
|
751 | + SLog::Write(LOGLEVEL_DEBUG, "GSync::GetSupportedProtocolVersions(): ".$versions); |
|
752 | 752 | |
753 | 753 | if ($valueOnly === true) { |
754 | 754 | return $versions; |
755 | 755 | } |
756 | 756 | |
757 | - return "MS-ASProtocolVersions: " . $versions; |
|
757 | + return "MS-ASProtocolVersions: ".$versions; |
|
758 | 758 | } |
759 | 759 | |
760 | 760 | /** |
@@ -773,9 +773,9 @@ discard block |
||
773 | 773 | } |
774 | 774 | |
775 | 775 | $commands = implode(',', $asCommands); |
776 | - SLog::Write(LOGLEVEL_DEBUG, "GSync::GetSupportedCommands(): " . $commands); |
|
776 | + SLog::Write(LOGLEVEL_DEBUG, "GSync::GetSupportedCommands(): ".$commands); |
|
777 | 777 | |
778 | - return "MS-ASProtocolCommands: " . $commands; |
|
778 | + return "MS-ASProtocolCommands: ".$commands; |
|
779 | 779 | } |
780 | 780 | |
781 | 781 | /** |
@@ -792,7 +792,7 @@ discard block |
||
792 | 792 | } |
793 | 793 | |
794 | 794 | $class = self::$supportedCommands[$commandCode][self::REQUESTHANDLER]; |
795 | - $handlerclass = REAL_BASE_PATH . "lib/request/" . strtolower($class) . ".php"; |
|
795 | + $handlerclass = REAL_BASE_PATH."lib/request/".strtolower($class).".php"; |
|
796 | 796 | |
797 | 797 | if (is_file($handlerclass)) { |
798 | 798 | include $handlerclass; |
@@ -178,8 +178,7 @@ discard block |
||
178 | 178 | |
179 | 179 | if (defined('BASE_PATH_CLI') && file_exists(BASE_PATH_CLI)) { |
180 | 180 | define('REAL_BASE_PATH', BASE_PATH_CLI); |
181 | - } |
|
182 | - else { |
|
181 | + } else { |
|
183 | 182 | define('REAL_BASE_PATH', BASE_PATH); |
184 | 183 | } |
185 | 184 | |
@@ -212,8 +211,7 @@ discard block |
||
212 | 211 | if (LOG_SYSLOG_HOST && LOG_SYSLOG_PORT <= 0) { |
213 | 212 | throw new FatalMisconfigurationException("LOG_SYSLOG_HOST is defined but the LOG_SYSLOG_PORT does not seem to be valid."); |
214 | 213 | } |
215 | - } |
|
216 | - elseif (strtolower(LOGBACKEND) == 'filelog') { |
|
214 | + } elseif (strtolower(LOGBACKEND) == 'filelog') { |
|
217 | 215 | define('LOGBACKEND_CLASS', 'FileLog'); |
218 | 216 | if (!defined('LOGFILEDIR')) { |
219 | 217 | throw new FatalMisconfigurationException("The LOGFILEDIR is not configured. Check if the config.php file is in place."); |
@@ -238,8 +236,7 @@ discard block |
||
238 | 236 | // check ownership on the (eventually) just created files |
239 | 237 | Utils::FixFileOwner(LOGFILE); |
240 | 238 | Utils::FixFileOwner(LOGERRORFILE); |
241 | - } |
|
242 | - else { |
|
239 | + } else { |
|
243 | 240 | define('LOGBACKEND_CLASS', LOGBACKEND); |
244 | 241 | } |
245 | 242 | |
@@ -249,8 +246,7 @@ discard block |
||
249 | 246 | if (!@date_default_timezone_set(TIMEZONE)) { |
250 | 247 | throw new FatalMisconfigurationException(sprintf("The configured TIMEZONE '%s' is not valid. Please check supported timezones at http://www.php.net/manual/en/timezones.php", constant('TIMEZONE'))); |
251 | 248 | } |
252 | - } |
|
253 | - elseif (!ini_get('date.timezone')) { |
|
249 | + } elseif (!ini_get('date.timezone')) { |
|
254 | 250 | date_default_timezone_set('Europe/Vienna'); |
255 | 251 | } |
256 | 252 | |
@@ -287,8 +283,7 @@ discard block |
||
287 | 283 | |
288 | 284 | if (!defined('SYNC_CONTACTS_MAXPICTURESIZE')) { |
289 | 285 | define('SYNC_CONTACTS_MAXPICTURESIZE', 49152); |
290 | - } |
|
291 | - elseif ((!is_int(SYNC_CONTACTS_MAXPICTURESIZE) || SYNC_CONTACTS_MAXPICTURESIZE < 1)) { |
|
286 | + } elseif ((!is_int(SYNC_CONTACTS_MAXPICTURESIZE) || SYNC_CONTACTS_MAXPICTURESIZE < 1)) { |
|
292 | 287 | throw new FatalMisconfigurationException("The SYNC_CONTACTS_MAXPICTURESIZE value must be a number higher than 0."); |
293 | 288 | } |
294 | 289 | |
@@ -298,14 +293,12 @@ discard block |
||
298 | 293 | |
299 | 294 | if (!defined('PING_LOWER_BOUND_LIFETIME')) { |
300 | 295 | define('PING_LOWER_BOUND_LIFETIME', false); |
301 | - } |
|
302 | - elseif (PING_LOWER_BOUND_LIFETIME !== false && (!is_int(PING_LOWER_BOUND_LIFETIME) || PING_LOWER_BOUND_LIFETIME < 1 || PING_LOWER_BOUND_LIFETIME > 3540)) { |
|
296 | + } elseif (PING_LOWER_BOUND_LIFETIME !== false && (!is_int(PING_LOWER_BOUND_LIFETIME) || PING_LOWER_BOUND_LIFETIME < 1 || PING_LOWER_BOUND_LIFETIME > 3540)) { |
|
303 | 297 | throw new FatalMisconfigurationException("The PING_LOWER_BOUND_LIFETIME value must be 'false' or a number between 1 and 3540 inclusively."); |
304 | 298 | } |
305 | 299 | if (!defined('PING_HIGHER_BOUND_LIFETIME')) { |
306 | 300 | define('PING_HIGHER_BOUND_LIFETIME', false); |
307 | - } |
|
308 | - elseif (PING_HIGHER_BOUND_LIFETIME !== false && (!is_int(PING_HIGHER_BOUND_LIFETIME) || PING_HIGHER_BOUND_LIFETIME < 1 || PING_HIGHER_BOUND_LIFETIME > 3540)) { |
|
301 | + } elseif (PING_HIGHER_BOUND_LIFETIME !== false && (!is_int(PING_HIGHER_BOUND_LIFETIME) || PING_HIGHER_BOUND_LIFETIME < 1 || PING_HIGHER_BOUND_LIFETIME > 3540)) { |
|
309 | 302 | throw new FatalMisconfigurationException("The PING_HIGHER_BOUND_LIFETIME value must be 'false' or a number between 1 and 3540 inclusively."); |
310 | 303 | } |
311 | 304 | if (PING_HIGHER_BOUND_LIFETIME !== false && PING_LOWER_BOUND_LIFETIME !== false && PING_HIGHER_BOUND_LIFETIME < PING_LOWER_BOUND_LIFETIME) { |
@@ -314,8 +307,7 @@ discard block |
||
314 | 307 | |
315 | 308 | if (!defined('RETRY_AFTER_DELAY')) { |
316 | 309 | define('RETRY_AFTER_DELAY', 300); |
317 | - } |
|
318 | - elseif (RETRY_AFTER_DELAY !== false && (!is_int(RETRY_AFTER_DELAY) || RETRY_AFTER_DELAY < 1)) { |
|
310 | + } elseif (RETRY_AFTER_DELAY !== false && (!is_int(RETRY_AFTER_DELAY) || RETRY_AFTER_DELAY < 1)) { |
|
319 | 311 | throw new FatalMisconfigurationException("The RETRY_AFTER_DELAY value must be 'false' or a number greater than 0."); |
320 | 312 | } |
321 | 313 | |
@@ -330,8 +322,7 @@ discard block |
||
330 | 322 | // the check on additional folders will not throw hard errors, as this is probably changed on live systems |
331 | 323 | if (isset($additionalFolders) && !is_array($additionalFolders)) { |
332 | 324 | SLog::Write(LOGLEVEL_ERROR, "GSync::CheckConfig(): The additional folders synchronization not available as array."); |
333 | - } |
|
334 | - else { |
|
325 | + } else { |
|
335 | 326 | // check configured data |
336 | 327 | foreach ($additionalFolders as $af) { |
337 | 328 | if (!is_array($af) || !isset($af['store']) || !isset($af['folderid']) || !isset($af['name']) || !isset($af['type'])) { |
@@ -478,11 +469,9 @@ discard block |
||
478 | 469 | |
479 | 470 | if (is_file($subdirbackend)) { |
480 | 471 | $toLoad = $subdirbackend; |
481 | - } |
|
482 | - elseif (is_file($stdbackend)) { |
|
472 | + } elseif (is_file($stdbackend)) { |
|
483 | 473 | $toLoad = $stdbackend; |
484 | - } |
|
485 | - else { |
|
474 | + } else { |
|
486 | 475 | return false; |
487 | 476 | } |
488 | 477 | |
@@ -543,8 +532,7 @@ discard block |
||
543 | 532 | public static function GetAdditionalSyncFolderStore($backendid, $noDebug = false) { |
544 | 533 | if (isset(self::getAddSyncFolders()[$backendid]->Store)) { |
545 | 534 | $val = self::getAddSyncFolders()[$backendid]->Store; |
546 | - } |
|
547 | - else { |
|
535 | + } else { |
|
548 | 536 | $val = self::GetDeviceManager()->GetAdditionalUserSyncFolder($backendid); |
549 | 537 | if (isset($val['store'])) { |
550 | 538 | $val = $val['store']; |
@@ -593,8 +581,7 @@ discard block |
||
593 | 581 | |
594 | 582 | if (isset($additionalFolders) && !is_array($additionalFolders)) { |
595 | 583 | SLog::Write(LOGLEVEL_ERROR, "GSync::getAddSyncFolders() : The additional folders synchronization not available as array."); |
596 | - } |
|
597 | - else { |
|
584 | + } else { |
|
598 | 585 | foreach ($additionalFolders as $af) { |
599 | 586 | if (!is_array($af) || !isset($af['store']) || !isset($af['folderid']) || !isset($af['name']) || !isset($af['type'])) { |
600 | 587 | SLog::Write(LOGLEVEL_ERROR, "GSync::getAddSyncFolders() : the additional folder synchronization is not configured correctly. Missing parameters. Entry will be ignored."); |
@@ -8,232 +8,232 @@ |
||
8 | 8 | */ |
9 | 9 | |
10 | 10 | class StateObject implements JsonSerializable { |
11 | - private $SO_internalid; |
|
12 | - protected $data = []; |
|
13 | - protected $unsetdata = []; |
|
14 | - protected $changed = false; |
|
15 | - |
|
16 | - /** |
|
17 | - * Returns the unique id of that data object. |
|
18 | - * |
|
19 | - * @return array |
|
20 | - */ |
|
21 | - public function GetID() { |
|
22 | - if (!isset($this->SO_internalid)) { |
|
23 | - $this->SO_internalid = sprintf('%04x%04x%04x', mt_rand(0, 0xFFFF), mt_rand(0, 0xFFFF), mt_rand(0, 0xFFFF)); |
|
24 | - } |
|
25 | - |
|
26 | - return $this->SO_internalid; |
|
27 | - } |
|
28 | - |
|
29 | - /** |
|
30 | - * Indicates if the data contained in this object was modified. |
|
31 | - * |
|
32 | - * @return array |
|
33 | - */ |
|
34 | - public function IsDataChanged() { |
|
35 | - return $this->changed; |
|
36 | - } |
|
37 | - |
|
38 | - /** |
|
39 | - * PHP magic to set an instance variable. |
|
40 | - * |
|
41 | - * @param mixed $name |
|
42 | - * @param mixed $value |
|
43 | - * |
|
44 | - * @return |
|
45 | - */ |
|
46 | - public function __set($name, $value) { |
|
47 | - $lname = strtolower($name); |
|
48 | - if (isset($this->data[$lname]) && |
|
49 | - ( |
|
50 | - (is_scalar($value) && !is_array($value) && $this->data[$lname] === $value) || |
|
51 | - (is_array($value) && is_array($this->data[$lname]) && $this->data[$lname] === $value) |
|
52 | - )) { |
|
53 | - return false; |
|
54 | - } |
|
55 | - |
|
56 | - $this->data[$lname] = $value; |
|
57 | - $this->changed = true; |
|
58 | - } |
|
59 | - |
|
60 | - /** |
|
61 | - * PHP magic to get an instance variable |
|
62 | - * if the variable was not set previously, the value of the |
|
63 | - * Unsetdata array is returned. |
|
64 | - * |
|
65 | - * @param mixed $name |
|
66 | - * |
|
67 | - * @return |
|
68 | - */ |
|
69 | - public function __get($name) { |
|
70 | - $lname = strtolower($name); |
|
71 | - |
|
72 | - if (array_key_exists($lname, $this->data)) { |
|
73 | - return $this->data[$lname]; |
|
74 | - } |
|
75 | - |
|
76 | - if (isset($this->unsetdata) && is_array($this->unsetdata) && array_key_exists($lname, $this->unsetdata)) { |
|
77 | - return $this->unsetdata[$lname]; |
|
78 | - } |
|
79 | - |
|
80 | - return null; |
|
81 | - } |
|
82 | - |
|
83 | - /** |
|
84 | - * PHP magic to check if an instance variable is set. |
|
85 | - * |
|
86 | - * @param mixed $name |
|
87 | - * |
|
88 | - * @return |
|
89 | - */ |
|
90 | - public function __isset($name) { |
|
91 | - return isset($this->data[strtolower($name)]); |
|
92 | - } |
|
93 | - |
|
94 | - /** |
|
95 | - * PHP magic to remove an instance variable. |
|
96 | - * |
|
97 | - * @param mixed $name |
|
98 | - * |
|
99 | - * @return |
|
100 | - */ |
|
101 | - public function __unset($name) { |
|
102 | - if (isset($this->{$name})) { |
|
103 | - unset($this->data[strtolower($name)]); |
|
104 | - $this->changed = true; |
|
105 | - } |
|
106 | - } |
|
107 | - |
|
108 | - /** |
|
109 | - * PHP magic to implement any getter, setter, has and delete operations |
|
110 | - * on an instance variable. |
|
111 | - * Methods like e.g. "SetVariableName($x)" and "GetVariableName()" are supported. |
|
112 | - * |
|
113 | - * @param mixed $name |
|
114 | - * @param mixed $arguments |
|
115 | - * |
|
116 | - * @return mixed |
|
117 | - */ |
|
118 | - public function __call($name, $arguments) { |
|
119 | - $name = strtolower($name); |
|
120 | - $operator = substr($name, 0, 3); |
|
121 | - $var = substr($name, 3); |
|
122 | - |
|
123 | - if ($name == "postunserialize") { |
|
124 | - return $this->postUnserialize(); |
|
125 | - } |
|
126 | - |
|
127 | - if ($operator == "set" && count($arguments) == 1) { |
|
128 | - $this->{$var} = $arguments[0]; |
|
129 | - |
|
130 | - return true; |
|
131 | - } |
|
132 | - |
|
133 | - if ($operator == "set" && count($arguments) == 2 && $arguments[1] === false) { |
|
134 | - $this->data[$var] = $arguments[0]; |
|
135 | - |
|
136 | - return true; |
|
137 | - } |
|
138 | - |
|
139 | - // getter without argument = return variable, null if not set |
|
140 | - if ($operator == "get" && count($arguments) == 0) { |
|
141 | - return $this->{$var}; |
|
142 | - } |
|
143 | - |
|
144 | - // getter with one argument = return variable if set, else the argument |
|
145 | - if ($operator == "get" && count($arguments) == 1) { |
|
146 | - if (isset($this->{$var})) { |
|
147 | - return $this->{$var}; |
|
148 | - } |
|
149 | - |
|
150 | - return $arguments[0]; |
|
151 | - } |
|
152 | - |
|
153 | - if ($operator == "has" && count($arguments) == 0) { |
|
154 | - return isset($this->{$var}); |
|
155 | - } |
|
156 | - |
|
157 | - if ($operator == "del" && count($arguments) == 0) { |
|
158 | - unset($this->{$var}); |
|
159 | - |
|
160 | - return true; |
|
161 | - } |
|
162 | - |
|
163 | - throw new FatalNotImplementedException(sprintf("StateObject->__call('%s'): not implemented. op: {%s} args: %d", $name, $operator, count($arguments))); |
|
164 | - } |
|
165 | - |
|
166 | - /** |
|
167 | - * Called after the StateObject was unserialized. |
|
168 | - * |
|
169 | - * @return bool |
|
170 | - */ |
|
171 | - protected function postUnserialize() { |
|
172 | - $this->changed = false; |
|
173 | - |
|
174 | - return true; |
|
175 | - } |
|
176 | - |
|
177 | - /** |
|
178 | - * Callback function for failed unserialize. |
|
179 | - * |
|
180 | - * @throws StateInvalidException |
|
181 | - */ |
|
182 | - public static function ThrowStateInvalidException() { |
|
183 | - throw new StateInvalidException("Unserialization failed as class was not found or not compatible"); |
|
184 | - } |
|
185 | - |
|
186 | - /** |
|
187 | - * JsonSerializable interface method. |
|
188 | - * |
|
189 | - * Serializes the object to a value that can be serialized natively by json_encode() |
|
190 | - * |
|
191 | - * @return array |
|
192 | - */ |
|
193 | - public function jsonSerialize() { |
|
194 | - return [ |
|
195 | - 'gsSyncStateClass' => get_class($this), |
|
196 | - 'data' => $this->data, |
|
197 | - ]; |
|
198 | - } |
|
199 | - |
|
200 | - /** |
|
201 | - * Restores the object from a value provided by json_decode. |
|
202 | - * |
|
203 | - * @param $stdObj stdClass Object |
|
204 | - */ |
|
205 | - public function jsonDeserialize($stdObj) { |
|
206 | - foreach ($stdObj->data as $prop => $val) { |
|
207 | - if (is_object($val) && isset($val->gsSyncStateClass)) { |
|
208 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("StateObject->jsonDeserialize(): top class '%s'", $val->gsSyncStateClass)); |
|
209 | - $this->data[$prop] = new $val->gsSyncStateClass(); |
|
210 | - $this->data[$prop]->jsonDeserialize($val); |
|
211 | - $this->data[$prop]->postUnserialize(); |
|
212 | - } |
|
213 | - elseif (is_object($val)) { |
|
214 | - // json_decode converts arrays into objects, convert them back to arrays |
|
215 | - $this->data[$prop] = []; |
|
216 | - foreach ($val as $k => $v) { |
|
217 | - if (is_object($v) && isset($v->gsSyncStateClass)) { |
|
218 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("StateObject->jsonDeserialize(): sub class '%s'", $v->gsSyncStateClass)); |
|
219 | - // TODO: case should be removed when removing ASDevice backwards compatibility |
|
220 | - if (strcasecmp($v->gsSyncStateClass, "ASDevice") == 0) { |
|
221 | - $this->data[$prop][$k] = new ASDevice(Request::GetDeviceID(), Request::GetDeviceType(), Request::GetGETUser(), Request::GetUserAgent()); |
|
222 | - } |
|
223 | - else { |
|
224 | - $this->data[$prop][$k] = new $v->gsSyncStateClass(); |
|
225 | - } |
|
226 | - $this->data[$prop][$k]->jsonDeserialize($v); |
|
227 | - $this->data[$prop][$k]->postUnserialize(); |
|
228 | - } |
|
229 | - else { |
|
230 | - $this->data[$prop][$k] = $v; |
|
231 | - } |
|
232 | - } |
|
233 | - } |
|
234 | - else { |
|
235 | - $this->data[$prop] = $val; |
|
236 | - } |
|
237 | - } |
|
238 | - } |
|
11 | + private $SO_internalid; |
|
12 | + protected $data = []; |
|
13 | + protected $unsetdata = []; |
|
14 | + protected $changed = false; |
|
15 | + |
|
16 | + /** |
|
17 | + * Returns the unique id of that data object. |
|
18 | + * |
|
19 | + * @return array |
|
20 | + */ |
|
21 | + public function GetID() { |
|
22 | + if (!isset($this->SO_internalid)) { |
|
23 | + $this->SO_internalid = sprintf('%04x%04x%04x', mt_rand(0, 0xFFFF), mt_rand(0, 0xFFFF), mt_rand(0, 0xFFFF)); |
|
24 | + } |
|
25 | + |
|
26 | + return $this->SO_internalid; |
|
27 | + } |
|
28 | + |
|
29 | + /** |
|
30 | + * Indicates if the data contained in this object was modified. |
|
31 | + * |
|
32 | + * @return array |
|
33 | + */ |
|
34 | + public function IsDataChanged() { |
|
35 | + return $this->changed; |
|
36 | + } |
|
37 | + |
|
38 | + /** |
|
39 | + * PHP magic to set an instance variable. |
|
40 | + * |
|
41 | + * @param mixed $name |
|
42 | + * @param mixed $value |
|
43 | + * |
|
44 | + * @return |
|
45 | + */ |
|
46 | + public function __set($name, $value) { |
|
47 | + $lname = strtolower($name); |
|
48 | + if (isset($this->data[$lname]) && |
|
49 | + ( |
|
50 | + (is_scalar($value) && !is_array($value) && $this->data[$lname] === $value) || |
|
51 | + (is_array($value) && is_array($this->data[$lname]) && $this->data[$lname] === $value) |
|
52 | + )) { |
|
53 | + return false; |
|
54 | + } |
|
55 | + |
|
56 | + $this->data[$lname] = $value; |
|
57 | + $this->changed = true; |
|
58 | + } |
|
59 | + |
|
60 | + /** |
|
61 | + * PHP magic to get an instance variable |
|
62 | + * if the variable was not set previously, the value of the |
|
63 | + * Unsetdata array is returned. |
|
64 | + * |
|
65 | + * @param mixed $name |
|
66 | + * |
|
67 | + * @return |
|
68 | + */ |
|
69 | + public function __get($name) { |
|
70 | + $lname = strtolower($name); |
|
71 | + |
|
72 | + if (array_key_exists($lname, $this->data)) { |
|
73 | + return $this->data[$lname]; |
|
74 | + } |
|
75 | + |
|
76 | + if (isset($this->unsetdata) && is_array($this->unsetdata) && array_key_exists($lname, $this->unsetdata)) { |
|
77 | + return $this->unsetdata[$lname]; |
|
78 | + } |
|
79 | + |
|
80 | + return null; |
|
81 | + } |
|
82 | + |
|
83 | + /** |
|
84 | + * PHP magic to check if an instance variable is set. |
|
85 | + * |
|
86 | + * @param mixed $name |
|
87 | + * |
|
88 | + * @return |
|
89 | + */ |
|
90 | + public function __isset($name) { |
|
91 | + return isset($this->data[strtolower($name)]); |
|
92 | + } |
|
93 | + |
|
94 | + /** |
|
95 | + * PHP magic to remove an instance variable. |
|
96 | + * |
|
97 | + * @param mixed $name |
|
98 | + * |
|
99 | + * @return |
|
100 | + */ |
|
101 | + public function __unset($name) { |
|
102 | + if (isset($this->{$name})) { |
|
103 | + unset($this->data[strtolower($name)]); |
|
104 | + $this->changed = true; |
|
105 | + } |
|
106 | + } |
|
107 | + |
|
108 | + /** |
|
109 | + * PHP magic to implement any getter, setter, has and delete operations |
|
110 | + * on an instance variable. |
|
111 | + * Methods like e.g. "SetVariableName($x)" and "GetVariableName()" are supported. |
|
112 | + * |
|
113 | + * @param mixed $name |
|
114 | + * @param mixed $arguments |
|
115 | + * |
|
116 | + * @return mixed |
|
117 | + */ |
|
118 | + public function __call($name, $arguments) { |
|
119 | + $name = strtolower($name); |
|
120 | + $operator = substr($name, 0, 3); |
|
121 | + $var = substr($name, 3); |
|
122 | + |
|
123 | + if ($name == "postunserialize") { |
|
124 | + return $this->postUnserialize(); |
|
125 | + } |
|
126 | + |
|
127 | + if ($operator == "set" && count($arguments) == 1) { |
|
128 | + $this->{$var} = $arguments[0]; |
|
129 | + |
|
130 | + return true; |
|
131 | + } |
|
132 | + |
|
133 | + if ($operator == "set" && count($arguments) == 2 && $arguments[1] === false) { |
|
134 | + $this->data[$var] = $arguments[0]; |
|
135 | + |
|
136 | + return true; |
|
137 | + } |
|
138 | + |
|
139 | + // getter without argument = return variable, null if not set |
|
140 | + if ($operator == "get" && count($arguments) == 0) { |
|
141 | + return $this->{$var}; |
|
142 | + } |
|
143 | + |
|
144 | + // getter with one argument = return variable if set, else the argument |
|
145 | + if ($operator == "get" && count($arguments) == 1) { |
|
146 | + if (isset($this->{$var})) { |
|
147 | + return $this->{$var}; |
|
148 | + } |
|
149 | + |
|
150 | + return $arguments[0]; |
|
151 | + } |
|
152 | + |
|
153 | + if ($operator == "has" && count($arguments) == 0) { |
|
154 | + return isset($this->{$var}); |
|
155 | + } |
|
156 | + |
|
157 | + if ($operator == "del" && count($arguments) == 0) { |
|
158 | + unset($this->{$var}); |
|
159 | + |
|
160 | + return true; |
|
161 | + } |
|
162 | + |
|
163 | + throw new FatalNotImplementedException(sprintf("StateObject->__call('%s'): not implemented. op: {%s} args: %d", $name, $operator, count($arguments))); |
|
164 | + } |
|
165 | + |
|
166 | + /** |
|
167 | + * Called after the StateObject was unserialized. |
|
168 | + * |
|
169 | + * @return bool |
|
170 | + */ |
|
171 | + protected function postUnserialize() { |
|
172 | + $this->changed = false; |
|
173 | + |
|
174 | + return true; |
|
175 | + } |
|
176 | + |
|
177 | + /** |
|
178 | + * Callback function for failed unserialize. |
|
179 | + * |
|
180 | + * @throws StateInvalidException |
|
181 | + */ |
|
182 | + public static function ThrowStateInvalidException() { |
|
183 | + throw new StateInvalidException("Unserialization failed as class was not found or not compatible"); |
|
184 | + } |
|
185 | + |
|
186 | + /** |
|
187 | + * JsonSerializable interface method. |
|
188 | + * |
|
189 | + * Serializes the object to a value that can be serialized natively by json_encode() |
|
190 | + * |
|
191 | + * @return array |
|
192 | + */ |
|
193 | + public function jsonSerialize() { |
|
194 | + return [ |
|
195 | + 'gsSyncStateClass' => get_class($this), |
|
196 | + 'data' => $this->data, |
|
197 | + ]; |
|
198 | + } |
|
199 | + |
|
200 | + /** |
|
201 | + * Restores the object from a value provided by json_decode. |
|
202 | + * |
|
203 | + * @param $stdObj stdClass Object |
|
204 | + */ |
|
205 | + public function jsonDeserialize($stdObj) { |
|
206 | + foreach ($stdObj->data as $prop => $val) { |
|
207 | + if (is_object($val) && isset($val->gsSyncStateClass)) { |
|
208 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("StateObject->jsonDeserialize(): top class '%s'", $val->gsSyncStateClass)); |
|
209 | + $this->data[$prop] = new $val->gsSyncStateClass(); |
|
210 | + $this->data[$prop]->jsonDeserialize($val); |
|
211 | + $this->data[$prop]->postUnserialize(); |
|
212 | + } |
|
213 | + elseif (is_object($val)) { |
|
214 | + // json_decode converts arrays into objects, convert them back to arrays |
|
215 | + $this->data[$prop] = []; |
|
216 | + foreach ($val as $k => $v) { |
|
217 | + if (is_object($v) && isset($v->gsSyncStateClass)) { |
|
218 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("StateObject->jsonDeserialize(): sub class '%s'", $v->gsSyncStateClass)); |
|
219 | + // TODO: case should be removed when removing ASDevice backwards compatibility |
|
220 | + if (strcasecmp($v->gsSyncStateClass, "ASDevice") == 0) { |
|
221 | + $this->data[$prop][$k] = new ASDevice(Request::GetDeviceID(), Request::GetDeviceType(), Request::GetGETUser(), Request::GetUserAgent()); |
|
222 | + } |
|
223 | + else { |
|
224 | + $this->data[$prop][$k] = new $v->gsSyncStateClass(); |
|
225 | + } |
|
226 | + $this->data[$prop][$k]->jsonDeserialize($v); |
|
227 | + $this->data[$prop][$k]->postUnserialize(); |
|
228 | + } |
|
229 | + else { |
|
230 | + $this->data[$prop][$k] = $v; |
|
231 | + } |
|
232 | + } |
|
233 | + } |
|
234 | + else { |
|
235 | + $this->data[$prop] = $val; |
|
236 | + } |
|
237 | + } |
|
238 | + } |
|
239 | 239 | } |
@@ -209,8 +209,7 @@ discard block |
||
209 | 209 | $this->data[$prop] = new $val->gsSyncStateClass(); |
210 | 210 | $this->data[$prop]->jsonDeserialize($val); |
211 | 211 | $this->data[$prop]->postUnserialize(); |
212 | - } |
|
213 | - elseif (is_object($val)) { |
|
212 | + } elseif (is_object($val)) { |
|
214 | 213 | // json_decode converts arrays into objects, convert them back to arrays |
215 | 214 | $this->data[$prop] = []; |
216 | 215 | foreach ($val as $k => $v) { |
@@ -219,19 +218,16 @@ discard block |
||
219 | 218 | // TODO: case should be removed when removing ASDevice backwards compatibility |
220 | 219 | if (strcasecmp($v->gsSyncStateClass, "ASDevice") == 0) { |
221 | 220 | $this->data[$prop][$k] = new ASDevice(Request::GetDeviceID(), Request::GetDeviceType(), Request::GetGETUser(), Request::GetUserAgent()); |
222 | - } |
|
223 | - else { |
|
221 | + } else { |
|
224 | 222 | $this->data[$prop][$k] = new $v->gsSyncStateClass(); |
225 | 223 | } |
226 | 224 | $this->data[$prop][$k]->jsonDeserialize($v); |
227 | 225 | $this->data[$prop][$k]->postUnserialize(); |
228 | - } |
|
229 | - else { |
|
226 | + } else { |
|
230 | 227 | $this->data[$prop][$k] = $v; |
231 | 228 | } |
232 | 229 | } |
233 | - } |
|
234 | - else { |
|
230 | + } else { |
|
235 | 231 | $this->data[$prop] = $val; |
236 | 232 | } |
237 | 233 | } |
@@ -8,130 +8,130 @@ |
||
8 | 8 | */ |
9 | 9 | |
10 | 10 | class ContentParameters extends StateObject { |
11 | - protected $unsetdata = [ |
|
12 | - 'contentclass' => false, |
|
13 | - 'foldertype' => '', |
|
14 | - 'conflict' => false, |
|
15 | - 'deletesasmoves' => true, |
|
16 | - 'filtertype' => false, |
|
17 | - 'truncation' => false, |
|
18 | - 'rtftruncation' => false, |
|
19 | - 'mimesupport' => false, |
|
20 | - 'conversationmode' => false, |
|
21 | - ]; |
|
22 | - |
|
23 | - private $synckeyChanged = false; |
|
24 | - |
|
25 | - /** |
|
26 | - * Expected magic getters and setters. |
|
27 | - * |
|
28 | - * GetContentClass() + SetContentClass() |
|
29 | - * GetConflict() + SetConflict() |
|
30 | - * GetDeletesAsMoves() + SetDeletesAsMoves() |
|
31 | - * GetFilterType() + SetFilterType() |
|
32 | - * GetTruncation() + SetTruncation |
|
33 | - * GetRTFTruncation() + SetRTFTruncation() |
|
34 | - * GetMimeSupport () + SetMimeSupport() |
|
35 | - * GetMimeTruncation() + SetMimeTruncation() |
|
36 | - * GetConversationMode() + SetConversationMode() |
|
37 | - * |
|
38 | - * @param mixed $name |
|
39 | - * @param mixed $arguments |
|
40 | - */ |
|
41 | - |
|
42 | - /** |
|
43 | - * Overwrite StateObject->__call so we are able to handle ContentParameters->BodyPreference() |
|
44 | - * and ContentParameters->BodyPartPreference(). |
|
45 | - * |
|
46 | - * @return mixed |
|
47 | - */ |
|
48 | - public function __call($name, $arguments) { |
|
49 | - if ($name === "BodyPreference") { |
|
50 | - return $this->BodyPreference($arguments[0]); |
|
51 | - } |
|
52 | - |
|
53 | - if ($name === "BodyPartPreference") { |
|
54 | - return $this->BodyPartPreference($arguments[0]); |
|
55 | - } |
|
56 | - |
|
57 | - return parent::__call($name, $arguments); |
|
58 | - } |
|
59 | - |
|
60 | - /** |
|
61 | - * Instantiates/returns the bodypreference object for a type. |
|
62 | - * |
|
63 | - * @param int $type |
|
64 | - * |
|
65 | - * @return int/boolean returns false if value is not defined |
|
66 | - */ |
|
67 | - public function BodyPreference($type) { |
|
68 | - if (!isset($this->bodypref)) { |
|
69 | - $this->bodypref = []; |
|
70 | - } |
|
71 | - |
|
72 | - if (isset($this->bodypref[$type])) { |
|
73 | - return $this->bodypref[$type]; |
|
74 | - } |
|
75 | - |
|
76 | - $asb = new BodyPreference(); |
|
77 | - $arr = (array) $this->bodypref; |
|
78 | - $arr[$type] = $asb; |
|
79 | - $this->bodypref = $arr; |
|
80 | - |
|
81 | - return $asb; |
|
82 | - } |
|
83 | - |
|
84 | - /** |
|
85 | - * Instantiates/returns the bodypartpreference object for a type. |
|
86 | - * |
|
87 | - * @param int $type |
|
88 | - * |
|
89 | - * @return int/boolean returns false if value is not defined |
|
90 | - */ |
|
91 | - public function BodyPartPreference($type) { |
|
92 | - if (!isset($this->bodypartpref)) { |
|
93 | - $this->bodypartpref = []; |
|
94 | - } |
|
95 | - |
|
96 | - if (isset($this->bodypartpref[$type])) { |
|
97 | - return $this->bodypartpref[$type]; |
|
98 | - } |
|
99 | - |
|
100 | - $asb = new BodyPartPreference(); |
|
101 | - $arr = (array) $this->bodypartpref; |
|
102 | - $arr[$type] = $asb; |
|
103 | - $this->bodypartpref = $arr; |
|
104 | - |
|
105 | - return $asb; |
|
106 | - } |
|
107 | - |
|
108 | - /** |
|
109 | - * Returns available body preference objects. |
|
110 | - * |
|
111 | - * @return array/boolean returns false if the client's body preference is not available |
|
112 | - */ |
|
113 | - public function GetBodyPreference() { |
|
114 | - if (!isset($this->bodypref) || !(is_array($this->bodypref) || empty($this->bodypref))) { |
|
115 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("ContentParameters->GetBodyPreference(): bodypref is empty or not set")); |
|
116 | - |
|
117 | - return false; |
|
118 | - } |
|
119 | - |
|
120 | - return array_keys($this->bodypref); |
|
121 | - } |
|
122 | - |
|
123 | - /** |
|
124 | - * Returns available body part preference objects. |
|
125 | - * |
|
126 | - * @return array/boolean returns false if the client's body preference is not available |
|
127 | - */ |
|
128 | - public function GetBodyPartPreference() { |
|
129 | - if (!isset($this->bodypartpref) || !(is_array($this->bodypartpref) || empty($this->bodypartpref))) { |
|
130 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("ContentParameters->GetBodyPartPreference(): bodypartpref is empty or not set")); |
|
131 | - |
|
132 | - return false; |
|
133 | - } |
|
134 | - |
|
135 | - return array_keys($this->bodypartpref); |
|
136 | - } |
|
11 | + protected $unsetdata = [ |
|
12 | + 'contentclass' => false, |
|
13 | + 'foldertype' => '', |
|
14 | + 'conflict' => false, |
|
15 | + 'deletesasmoves' => true, |
|
16 | + 'filtertype' => false, |
|
17 | + 'truncation' => false, |
|
18 | + 'rtftruncation' => false, |
|
19 | + 'mimesupport' => false, |
|
20 | + 'conversationmode' => false, |
|
21 | + ]; |
|
22 | + |
|
23 | + private $synckeyChanged = false; |
|
24 | + |
|
25 | + /** |
|
26 | + * Expected magic getters and setters. |
|
27 | + * |
|
28 | + * GetContentClass() + SetContentClass() |
|
29 | + * GetConflict() + SetConflict() |
|
30 | + * GetDeletesAsMoves() + SetDeletesAsMoves() |
|
31 | + * GetFilterType() + SetFilterType() |
|
32 | + * GetTruncation() + SetTruncation |
|
33 | + * GetRTFTruncation() + SetRTFTruncation() |
|
34 | + * GetMimeSupport () + SetMimeSupport() |
|
35 | + * GetMimeTruncation() + SetMimeTruncation() |
|
36 | + * GetConversationMode() + SetConversationMode() |
|
37 | + * |
|
38 | + * @param mixed $name |
|
39 | + * @param mixed $arguments |
|
40 | + */ |
|
41 | + |
|
42 | + /** |
|
43 | + * Overwrite StateObject->__call so we are able to handle ContentParameters->BodyPreference() |
|
44 | + * and ContentParameters->BodyPartPreference(). |
|
45 | + * |
|
46 | + * @return mixed |
|
47 | + */ |
|
48 | + public function __call($name, $arguments) { |
|
49 | + if ($name === "BodyPreference") { |
|
50 | + return $this->BodyPreference($arguments[0]); |
|
51 | + } |
|
52 | + |
|
53 | + if ($name === "BodyPartPreference") { |
|
54 | + return $this->BodyPartPreference($arguments[0]); |
|
55 | + } |
|
56 | + |
|
57 | + return parent::__call($name, $arguments); |
|
58 | + } |
|
59 | + |
|
60 | + /** |
|
61 | + * Instantiates/returns the bodypreference object for a type. |
|
62 | + * |
|
63 | + * @param int $type |
|
64 | + * |
|
65 | + * @return int/boolean returns false if value is not defined |
|
66 | + */ |
|
67 | + public function BodyPreference($type) { |
|
68 | + if (!isset($this->bodypref)) { |
|
69 | + $this->bodypref = []; |
|
70 | + } |
|
71 | + |
|
72 | + if (isset($this->bodypref[$type])) { |
|
73 | + return $this->bodypref[$type]; |
|
74 | + } |
|
75 | + |
|
76 | + $asb = new BodyPreference(); |
|
77 | + $arr = (array) $this->bodypref; |
|
78 | + $arr[$type] = $asb; |
|
79 | + $this->bodypref = $arr; |
|
80 | + |
|
81 | + return $asb; |
|
82 | + } |
|
83 | + |
|
84 | + /** |
|
85 | + * Instantiates/returns the bodypartpreference object for a type. |
|
86 | + * |
|
87 | + * @param int $type |
|
88 | + * |
|
89 | + * @return int/boolean returns false if value is not defined |
|
90 | + */ |
|
91 | + public function BodyPartPreference($type) { |
|
92 | + if (!isset($this->bodypartpref)) { |
|
93 | + $this->bodypartpref = []; |
|
94 | + } |
|
95 | + |
|
96 | + if (isset($this->bodypartpref[$type])) { |
|
97 | + return $this->bodypartpref[$type]; |
|
98 | + } |
|
99 | + |
|
100 | + $asb = new BodyPartPreference(); |
|
101 | + $arr = (array) $this->bodypartpref; |
|
102 | + $arr[$type] = $asb; |
|
103 | + $this->bodypartpref = $arr; |
|
104 | + |
|
105 | + return $asb; |
|
106 | + } |
|
107 | + |
|
108 | + /** |
|
109 | + * Returns available body preference objects. |
|
110 | + * |
|
111 | + * @return array/boolean returns false if the client's body preference is not available |
|
112 | + */ |
|
113 | + public function GetBodyPreference() { |
|
114 | + if (!isset($this->bodypref) || !(is_array($this->bodypref) || empty($this->bodypref))) { |
|
115 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("ContentParameters->GetBodyPreference(): bodypref is empty or not set")); |
|
116 | + |
|
117 | + return false; |
|
118 | + } |
|
119 | + |
|
120 | + return array_keys($this->bodypref); |
|
121 | + } |
|
122 | + |
|
123 | + /** |
|
124 | + * Returns available body part preference objects. |
|
125 | + * |
|
126 | + * @return array/boolean returns false if the client's body preference is not available |
|
127 | + */ |
|
128 | + public function GetBodyPartPreference() { |
|
129 | + if (!isset($this->bodypartpref) || !(is_array($this->bodypartpref) || empty($this->bodypartpref))) { |
|
130 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("ContentParameters->GetBodyPartPreference(): bodypartpref is empty or not set")); |
|
131 | + |
|
132 | + return false; |
|
133 | + } |
|
134 | + |
|
135 | + return array_keys($this->bodypartpref); |
|
136 | + } |
|
137 | 137 | } |
@@ -74,7 +74,7 @@ discard block |
||
74 | 74 | } |
75 | 75 | |
76 | 76 | $asb = new BodyPreference(); |
77 | - $arr = (array) $this->bodypref; |
|
77 | + $arr = (array)$this->bodypref; |
|
78 | 78 | $arr[$type] = $asb; |
79 | 79 | $this->bodypref = $arr; |
80 | 80 | |
@@ -98,7 +98,7 @@ discard block |
||
98 | 98 | } |
99 | 99 | |
100 | 100 | $asb = new BodyPartPreference(); |
101 | - $arr = (array) $this->bodypartpref; |
|
101 | + $arr = (array)$this->bodypartpref; |
|
102 | 102 | $arr[$type] = $asb; |
103 | 103 | $this->bodypartpref = $arr; |
104 | 104 |
@@ -13,1106 +13,1106 @@ |
||
13 | 13 | */ |
14 | 14 | |
15 | 15 | class DeviceManager extends InterProcessData { |
16 | - // broken message indicators |
|
17 | - public const MSG_BROKEN_UNKNOWN = 1; |
|
18 | - public const MSG_BROKEN_CAUSINGLOOP = 2; |
|
19 | - public const MSG_BROKEN_SEMANTICERR = 4; |
|
20 | - |
|
21 | - public const FLD_SYNC_INITIALIZED = 1; |
|
22 | - public const FLD_SYNC_INPROGRESS = 2; |
|
23 | - public const FLD_SYNC_COMPLETED = 4; |
|
24 | - |
|
25 | - // new types need to be added to Request::HEX_EXTENDED2 filter |
|
26 | - public const FLD_ORIGIN_USER = "U"; |
|
27 | - public const FLD_ORIGIN_CONFIG = "C"; |
|
28 | - public const FLD_ORIGIN_SHARED = "S"; |
|
29 | - public const FLD_ORIGIN_GAB = "G"; |
|
30 | - public const FLD_ORIGIN_IMPERSONATED = "I"; |
|
31 | - |
|
32 | - public const FLD_FLAGS_NONE = 0; |
|
33 | - public const FLD_FLAGS_SENDASOWNER = 1; |
|
34 | - public const FLD_FLAGS_TRACKSHARENAME = 2; |
|
35 | - public const FLD_FLAGS_CALENDARREMINDERS = 4; |
|
36 | - |
|
37 | - private $device; |
|
38 | - private $deviceHash; |
|
39 | - private $saveDevice; |
|
40 | - private $statemachine; |
|
41 | - private $stateManager; |
|
42 | - private $incomingData = 0; |
|
43 | - private $outgoingData = 0; |
|
44 | - |
|
45 | - private $windowSize; |
|
46 | - private $latestFolder; |
|
47 | - |
|
48 | - private $loopdetection; |
|
49 | - private $hierarchySyncRequired; |
|
50 | - private $additionalFoldersHash; |
|
51 | - |
|
52 | - /** |
|
53 | - * Constructor. |
|
54 | - */ |
|
55 | - public function __construct() { |
|
56 | - $this->statemachine = GSync::GetStateMachine(); |
|
57 | - $this->deviceHash = false; |
|
58 | - $this->saveDevice = true; |
|
59 | - $this->windowSize = []; |
|
60 | - $this->latestFolder = false; |
|
61 | - $this->hierarchySyncRequired = false; |
|
62 | - |
|
63 | - // initialize InterProcess parameters |
|
64 | - $this->allocate = 0; |
|
65 | - $this->type = "grommunio-sync:devicesuser"; |
|
66 | - parent::__construct(); |
|
67 | - parent::initializeParams(); |
|
68 | - $this->stateManager = new StateManager(); |
|
69 | - |
|
70 | - // only continue if deviceid is set |
|
71 | - if (self::$devid) { |
|
72 | - $this->device = new ASDevice(); |
|
73 | - $this->device->Initialize(self::$devid, Request::GetDeviceType(), Request::GetGETUser(), Request::GetUserAgent()); |
|
74 | - $this->loadDeviceData(); |
|
75 | - |
|
76 | - GSync::GetTopCollector()->SetUserAgent($this->device->GetDeviceUserAgent()); |
|
77 | - } |
|
78 | - else { |
|
79 | - throw new FatalNotImplementedException("Can not proceed without a device id."); |
|
80 | - } |
|
81 | - |
|
82 | - $this->loopdetection = new LoopDetection(); |
|
83 | - $this->loopdetection->ProcessLoopDetectionInit(); |
|
84 | - $this->loopdetection->ProcessLoopDetectionPreviousConnectionFailed(); |
|
85 | - |
|
86 | - $this->additionalFoldersHash = $this->getAdditionalFoldersHash(); |
|
87 | - } |
|
88 | - |
|
89 | - /** |
|
90 | - * Load another different device. |
|
91 | - * |
|
92 | - * @param ASDevice $asDevice |
|
93 | - */ |
|
94 | - public function SetDevice($asDevice) { |
|
95 | - // TODO: this is broken and callers should be removed/updated. ASDevice is now always overwritten. |
|
96 | - $this->device = $asDevice; |
|
97 | - $this->loadDeviceData(); |
|
98 | - // $this->stateManager->SetDevice($this->device); |
|
99 | - } |
|
100 | - |
|
101 | - /** |
|
102 | - * Returns the StateManager for the current device. |
|
103 | - * |
|
104 | - * @return StateManager |
|
105 | - */ |
|
106 | - public function GetStateManager() { |
|
107 | - return $this->stateManager; |
|
108 | - } |
|
109 | - |
|
110 | - /*---------------------------------------------------------------------------------------------------------- |
|
16 | + // broken message indicators |
|
17 | + public const MSG_BROKEN_UNKNOWN = 1; |
|
18 | + public const MSG_BROKEN_CAUSINGLOOP = 2; |
|
19 | + public const MSG_BROKEN_SEMANTICERR = 4; |
|
20 | + |
|
21 | + public const FLD_SYNC_INITIALIZED = 1; |
|
22 | + public const FLD_SYNC_INPROGRESS = 2; |
|
23 | + public const FLD_SYNC_COMPLETED = 4; |
|
24 | + |
|
25 | + // new types need to be added to Request::HEX_EXTENDED2 filter |
|
26 | + public const FLD_ORIGIN_USER = "U"; |
|
27 | + public const FLD_ORIGIN_CONFIG = "C"; |
|
28 | + public const FLD_ORIGIN_SHARED = "S"; |
|
29 | + public const FLD_ORIGIN_GAB = "G"; |
|
30 | + public const FLD_ORIGIN_IMPERSONATED = "I"; |
|
31 | + |
|
32 | + public const FLD_FLAGS_NONE = 0; |
|
33 | + public const FLD_FLAGS_SENDASOWNER = 1; |
|
34 | + public const FLD_FLAGS_TRACKSHARENAME = 2; |
|
35 | + public const FLD_FLAGS_CALENDARREMINDERS = 4; |
|
36 | + |
|
37 | + private $device; |
|
38 | + private $deviceHash; |
|
39 | + private $saveDevice; |
|
40 | + private $statemachine; |
|
41 | + private $stateManager; |
|
42 | + private $incomingData = 0; |
|
43 | + private $outgoingData = 0; |
|
44 | + |
|
45 | + private $windowSize; |
|
46 | + private $latestFolder; |
|
47 | + |
|
48 | + private $loopdetection; |
|
49 | + private $hierarchySyncRequired; |
|
50 | + private $additionalFoldersHash; |
|
51 | + |
|
52 | + /** |
|
53 | + * Constructor. |
|
54 | + */ |
|
55 | + public function __construct() { |
|
56 | + $this->statemachine = GSync::GetStateMachine(); |
|
57 | + $this->deviceHash = false; |
|
58 | + $this->saveDevice = true; |
|
59 | + $this->windowSize = []; |
|
60 | + $this->latestFolder = false; |
|
61 | + $this->hierarchySyncRequired = false; |
|
62 | + |
|
63 | + // initialize InterProcess parameters |
|
64 | + $this->allocate = 0; |
|
65 | + $this->type = "grommunio-sync:devicesuser"; |
|
66 | + parent::__construct(); |
|
67 | + parent::initializeParams(); |
|
68 | + $this->stateManager = new StateManager(); |
|
69 | + |
|
70 | + // only continue if deviceid is set |
|
71 | + if (self::$devid) { |
|
72 | + $this->device = new ASDevice(); |
|
73 | + $this->device->Initialize(self::$devid, Request::GetDeviceType(), Request::GetGETUser(), Request::GetUserAgent()); |
|
74 | + $this->loadDeviceData(); |
|
75 | + |
|
76 | + GSync::GetTopCollector()->SetUserAgent($this->device->GetDeviceUserAgent()); |
|
77 | + } |
|
78 | + else { |
|
79 | + throw new FatalNotImplementedException("Can not proceed without a device id."); |
|
80 | + } |
|
81 | + |
|
82 | + $this->loopdetection = new LoopDetection(); |
|
83 | + $this->loopdetection->ProcessLoopDetectionInit(); |
|
84 | + $this->loopdetection->ProcessLoopDetectionPreviousConnectionFailed(); |
|
85 | + |
|
86 | + $this->additionalFoldersHash = $this->getAdditionalFoldersHash(); |
|
87 | + } |
|
88 | + |
|
89 | + /** |
|
90 | + * Load another different device. |
|
91 | + * |
|
92 | + * @param ASDevice $asDevice |
|
93 | + */ |
|
94 | + public function SetDevice($asDevice) { |
|
95 | + // TODO: this is broken and callers should be removed/updated. ASDevice is now always overwritten. |
|
96 | + $this->device = $asDevice; |
|
97 | + $this->loadDeviceData(); |
|
98 | + // $this->stateManager->SetDevice($this->device); |
|
99 | + } |
|
100 | + |
|
101 | + /** |
|
102 | + * Returns the StateManager for the current device. |
|
103 | + * |
|
104 | + * @return StateManager |
|
105 | + */ |
|
106 | + public function GetStateManager() { |
|
107 | + return $this->stateManager; |
|
108 | + } |
|
109 | + |
|
110 | + /*---------------------------------------------------------------------------------------------------------- |
|
111 | 111 | * Device operations |
112 | 112 | */ |
113 | 113 | |
114 | - /** |
|
115 | - * Announces amount of transmitted data to the DeviceManager. |
|
116 | - * |
|
117 | - * @param int $datacounter |
|
118 | - * |
|
119 | - * @return bool |
|
120 | - */ |
|
121 | - public function SentData($datacounter) { |
|
122 | - // TODO save this somewhere |
|
123 | - $this->incomingData = Request::GetContentLength(); |
|
124 | - $this->outgoingData = $datacounter; |
|
125 | - |
|
126 | - return true; |
|
127 | - } |
|
128 | - |
|
129 | - /** |
|
130 | - * Called at the end of the request |
|
131 | - * Statistics about received/sent data is saved here. |
|
132 | - * |
|
133 | - * @return bool |
|
134 | - */ |
|
135 | - public function Save() { |
|
136 | - // TODO save other stuff |
|
137 | - |
|
138 | - // check if previousily ignored messages were synchronized for the current folder |
|
139 | - // on multifolder operations of AS14 this is done by setLatestFolder() |
|
140 | - if ($this->latestFolder !== false) { |
|
141 | - $this->checkBrokenMessages($this->latestFolder); |
|
142 | - } |
|
143 | - |
|
144 | - // update the user agent and AS version on the device |
|
145 | - $this->device->SetUserAgent(Request::GetUserAgent()); |
|
146 | - $this->device->SetASVersion(Request::GetProtocolVersion()); |
|
147 | - |
|
148 | - // data to be saved |
|
149 | - if ($this->device->IsDataChanged() && Request::IsValidDeviceID() && $this->saveDevice) { |
|
150 | - SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->Save(): Device data changed"); |
|
151 | - |
|
152 | - try { |
|
153 | - // check if this is the first time the device data is saved and it is authenticated. If so, link the user to the device id |
|
154 | - if ($this->device->IsNewDevice() && RequestProcessor::isUserAuthenticated()) { |
|
155 | - SLog::Write(LOGLEVEL_INFO, sprintf("Linking device ID '%s' to user '%s'", self::$devid, $this->device->GetDeviceUser())); |
|
156 | - $this->statemachine->LinkUserDevice($this->device->GetDeviceUser(), self::$devid); |
|
157 | - } |
|
158 | - |
|
159 | - if (RequestProcessor::isUserAuthenticated() || $this->device->GetForceSave()) { |
|
160 | - $this->device->lastupdatetime = time(); |
|
161 | - $this->device->StripData(); |
|
162 | - $this->statemachine->SetState($this->device, self::$devid, IStateMachine::DEVICEDATA); |
|
163 | - |
|
164 | - // update deviceuser stat in redis as well |
|
165 | - $this->setDeviceUserData($this->type, [self::$user => $this->device], self::$devid, -1, $doCas = "merge"); |
|
166 | - SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->Save(): Device data saved"); |
|
167 | - } |
|
168 | - } |
|
169 | - catch (StateNotFoundException $snfex) { |
|
170 | - SLog::Write(LOGLEVEL_ERROR, "DeviceManager->Save(): Exception: " . $snfex->getMessage()); |
|
171 | - } |
|
172 | - } |
|
173 | - |
|
174 | - // remove old search data |
|
175 | - $oldpid = $this->loopdetection->ProcessLoopDetectionGetOutdatedSearchPID(); |
|
176 | - if ($oldpid) { |
|
177 | - GSync::GetBackend()->GetSearchProvider()->TerminateSearch($oldpid); |
|
178 | - } |
|
179 | - |
|
180 | - // we terminated this process |
|
181 | - if ($this->loopdetection) { |
|
182 | - $this->loopdetection->ProcessLoopDetectionTerminate(); |
|
183 | - } |
|
184 | - |
|
185 | - return true; |
|
186 | - } |
|
187 | - |
|
188 | - /** |
|
189 | - * Sets if the AS Device should automatically be saved when terminating the request. |
|
190 | - * |
|
191 | - * @param bool $doSave |
|
192 | - */ |
|
193 | - public function DoAutomaticASDeviceSaving($doSave) { |
|
194 | - SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->DoAutomaticASDeviceSaving(): save automatically: " . Utils::PrintAsString($doSave)); |
|
195 | - $this->saveDevice = $doSave; |
|
196 | - } |
|
197 | - |
|
198 | - /** |
|
199 | - * Newer mobiles send extensive device information with the Settings command |
|
200 | - * These information are saved in the ASDevice. |
|
201 | - * |
|
202 | - * @param SyncDeviceInformation $deviceinformation |
|
203 | - * |
|
204 | - * @return bool |
|
205 | - */ |
|
206 | - public function SaveDeviceInformation($deviceinformation) { |
|
207 | - SLog::Write(LOGLEVEL_DEBUG, "Saving submitted device information"); |
|
208 | - |
|
209 | - // set the user agent |
|
210 | - if (isset($deviceinformation->useragent)) { |
|
211 | - $this->device->SetUserAgent($deviceinformation->useragent); |
|
212 | - } |
|
213 | - |
|
214 | - // save other information |
|
215 | - foreach (["model", "imei", "friendlyname", "os", "oslanguage", "phonenumber", "mobileoperator", "enableoutboundsms"] as $info) { |
|
216 | - if (isset($deviceinformation->{$info}) && $deviceinformation->{$info} != "") { |
|
217 | - $this->device->__set("device" . $info, $deviceinformation->{$info}); |
|
218 | - } |
|
219 | - } |
|
220 | - |
|
221 | - return true; |
|
222 | - } |
|
223 | - |
|
224 | - /*---------------------------------------------------------------------------------------------------------- |
|
114 | + /** |
|
115 | + * Announces amount of transmitted data to the DeviceManager. |
|
116 | + * |
|
117 | + * @param int $datacounter |
|
118 | + * |
|
119 | + * @return bool |
|
120 | + */ |
|
121 | + public function SentData($datacounter) { |
|
122 | + // TODO save this somewhere |
|
123 | + $this->incomingData = Request::GetContentLength(); |
|
124 | + $this->outgoingData = $datacounter; |
|
125 | + |
|
126 | + return true; |
|
127 | + } |
|
128 | + |
|
129 | + /** |
|
130 | + * Called at the end of the request |
|
131 | + * Statistics about received/sent data is saved here. |
|
132 | + * |
|
133 | + * @return bool |
|
134 | + */ |
|
135 | + public function Save() { |
|
136 | + // TODO save other stuff |
|
137 | + |
|
138 | + // check if previousily ignored messages were synchronized for the current folder |
|
139 | + // on multifolder operations of AS14 this is done by setLatestFolder() |
|
140 | + if ($this->latestFolder !== false) { |
|
141 | + $this->checkBrokenMessages($this->latestFolder); |
|
142 | + } |
|
143 | + |
|
144 | + // update the user agent and AS version on the device |
|
145 | + $this->device->SetUserAgent(Request::GetUserAgent()); |
|
146 | + $this->device->SetASVersion(Request::GetProtocolVersion()); |
|
147 | + |
|
148 | + // data to be saved |
|
149 | + if ($this->device->IsDataChanged() && Request::IsValidDeviceID() && $this->saveDevice) { |
|
150 | + SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->Save(): Device data changed"); |
|
151 | + |
|
152 | + try { |
|
153 | + // check if this is the first time the device data is saved and it is authenticated. If so, link the user to the device id |
|
154 | + if ($this->device->IsNewDevice() && RequestProcessor::isUserAuthenticated()) { |
|
155 | + SLog::Write(LOGLEVEL_INFO, sprintf("Linking device ID '%s' to user '%s'", self::$devid, $this->device->GetDeviceUser())); |
|
156 | + $this->statemachine->LinkUserDevice($this->device->GetDeviceUser(), self::$devid); |
|
157 | + } |
|
158 | + |
|
159 | + if (RequestProcessor::isUserAuthenticated() || $this->device->GetForceSave()) { |
|
160 | + $this->device->lastupdatetime = time(); |
|
161 | + $this->device->StripData(); |
|
162 | + $this->statemachine->SetState($this->device, self::$devid, IStateMachine::DEVICEDATA); |
|
163 | + |
|
164 | + // update deviceuser stat in redis as well |
|
165 | + $this->setDeviceUserData($this->type, [self::$user => $this->device], self::$devid, -1, $doCas = "merge"); |
|
166 | + SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->Save(): Device data saved"); |
|
167 | + } |
|
168 | + } |
|
169 | + catch (StateNotFoundException $snfex) { |
|
170 | + SLog::Write(LOGLEVEL_ERROR, "DeviceManager->Save(): Exception: " . $snfex->getMessage()); |
|
171 | + } |
|
172 | + } |
|
173 | + |
|
174 | + // remove old search data |
|
175 | + $oldpid = $this->loopdetection->ProcessLoopDetectionGetOutdatedSearchPID(); |
|
176 | + if ($oldpid) { |
|
177 | + GSync::GetBackend()->GetSearchProvider()->TerminateSearch($oldpid); |
|
178 | + } |
|
179 | + |
|
180 | + // we terminated this process |
|
181 | + if ($this->loopdetection) { |
|
182 | + $this->loopdetection->ProcessLoopDetectionTerminate(); |
|
183 | + } |
|
184 | + |
|
185 | + return true; |
|
186 | + } |
|
187 | + |
|
188 | + /** |
|
189 | + * Sets if the AS Device should automatically be saved when terminating the request. |
|
190 | + * |
|
191 | + * @param bool $doSave |
|
192 | + */ |
|
193 | + public function DoAutomaticASDeviceSaving($doSave) { |
|
194 | + SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->DoAutomaticASDeviceSaving(): save automatically: " . Utils::PrintAsString($doSave)); |
|
195 | + $this->saveDevice = $doSave; |
|
196 | + } |
|
197 | + |
|
198 | + /** |
|
199 | + * Newer mobiles send extensive device information with the Settings command |
|
200 | + * These information are saved in the ASDevice. |
|
201 | + * |
|
202 | + * @param SyncDeviceInformation $deviceinformation |
|
203 | + * |
|
204 | + * @return bool |
|
205 | + */ |
|
206 | + public function SaveDeviceInformation($deviceinformation) { |
|
207 | + SLog::Write(LOGLEVEL_DEBUG, "Saving submitted device information"); |
|
208 | + |
|
209 | + // set the user agent |
|
210 | + if (isset($deviceinformation->useragent)) { |
|
211 | + $this->device->SetUserAgent($deviceinformation->useragent); |
|
212 | + } |
|
213 | + |
|
214 | + // save other information |
|
215 | + foreach (["model", "imei", "friendlyname", "os", "oslanguage", "phonenumber", "mobileoperator", "enableoutboundsms"] as $info) { |
|
216 | + if (isset($deviceinformation->{$info}) && $deviceinformation->{$info} != "") { |
|
217 | + $this->device->__set("device" . $info, $deviceinformation->{$info}); |
|
218 | + } |
|
219 | + } |
|
220 | + |
|
221 | + return true; |
|
222 | + } |
|
223 | + |
|
224 | + /*---------------------------------------------------------------------------------------------------------- |
|
225 | 225 | * LEGACY AS 1.0 and WRAPPER operations |
226 | 226 | */ |
227 | 227 | |
228 | - /** |
|
229 | - * Returns a wrapped Importer & Exporter to use the |
|
230 | - * HierarchyChache. |
|
231 | - * |
|
232 | - * @see ChangesMemoryWrapper |
|
233 | - * |
|
234 | - * @return object HierarchyCache |
|
235 | - */ |
|
236 | - public function GetHierarchyChangesWrapper() { |
|
237 | - return $this->device->GetHierarchyCache(); |
|
238 | - } |
|
239 | - |
|
240 | - /** |
|
241 | - * Initializes the HierarchyCache for legacy syncs |
|
242 | - * this is for AS 1.0 compatibility: |
|
243 | - * save folder information synched with GetHierarchy(). |
|
244 | - * |
|
245 | - * @param string $folders Array with folder information |
|
246 | - * |
|
247 | - * @return bool |
|
248 | - */ |
|
249 | - public function InitializeFolderCache($folders) { |
|
250 | - $this->stateManager->SetDevice($this->device); |
|
251 | - |
|
252 | - return $this->stateManager->InitializeFolderCache($folders); |
|
253 | - } |
|
254 | - |
|
255 | - /** |
|
256 | - * Returns the ActiveSync folder type for a FolderID. |
|
257 | - * |
|
258 | - * @param string $folderid |
|
259 | - * |
|
260 | - * @return int/boolean boolean if no type is found |
|
261 | - */ |
|
262 | - public function GetFolderTypeFromCacheById($folderid) { |
|
263 | - return $this->device->GetFolderType($folderid); |
|
264 | - } |
|
265 | - |
|
266 | - /** |
|
267 | - * Returns a FolderID of default classes |
|
268 | - * this is for AS 1.0 compatibility: |
|
269 | - * this information was made available during GetHierarchy(). |
|
270 | - * |
|
271 | - * @param string $class The class requested |
|
272 | - * |
|
273 | - * @throws NoHierarchyCacheAvailableException |
|
274 | - * |
|
275 | - * @return string |
|
276 | - */ |
|
277 | - public function GetFolderIdFromCacheByClass($class) { |
|
278 | - $folderidforClass = false; |
|
279 | - // look at the default foldertype for this class |
|
280 | - $type = GSync::getDefaultFolderTypeFromFolderClass($class); |
|
281 | - |
|
282 | - if ($type && $type > SYNC_FOLDER_TYPE_OTHER && $type < SYNC_FOLDER_TYPE_USER_MAIL) { |
|
283 | - $folderids = $this->device->GetAllFolderIds(); |
|
284 | - foreach ($folderids as $folderid) { |
|
285 | - if ($type == $this->device->GetFolderType($folderid)) { |
|
286 | - $folderidforClass = $folderid; |
|
287 | - |
|
288 | - break; |
|
289 | - } |
|
290 | - } |
|
291 | - |
|
292 | - // Old Palm Treos always do initial sync for calendar and contacts, even if they are not made available by the backend. |
|
293 | - // We need to fake these folderids, allowing a fake sync/ping, even if they are not supported by the backend |
|
294 | - // if the folderid would be available, they would already be returned in the above statement |
|
295 | - if ($folderidforClass == false && ($type == SYNC_FOLDER_TYPE_APPOINTMENT || $type == SYNC_FOLDER_TYPE_CONTACT)) { |
|
296 | - $folderidforClass = SYNC_FOLDER_TYPE_DUMMY; |
|
297 | - } |
|
298 | - } |
|
299 | - |
|
300 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetFolderIdFromCacheByClass('%s'): '%s' => '%s'", $class, $type, $folderidforClass)); |
|
301 | - |
|
302 | - return $folderidforClass; |
|
303 | - } |
|
304 | - |
|
305 | - /** |
|
306 | - * Returns a FolderClass for a FolderID which is known to the mobile. |
|
307 | - * |
|
308 | - * @param string $folderid |
|
309 | - * |
|
310 | - * @throws NoHierarchyCacheAvailableException, NotImplementedException |
|
311 | - * |
|
312 | - * @return int |
|
313 | - */ |
|
314 | - public function GetFolderClassFromCacheByID($folderid) { |
|
315 | - // TODO check if the parent folder exists and is also being synchronized |
|
316 | - $typeFromCache = $this->device->GetFolderType($folderid); |
|
317 | - if ($typeFromCache === false) { |
|
318 | - throw new NoHierarchyCacheAvailableException(sprintf("Folderid '%s' is not fully synchronized on the device", $folderid)); |
|
319 | - } |
|
320 | - |
|
321 | - $class = GSync::GetFolderClassFromFolderType($typeFromCache); |
|
322 | - if ($class === false) { |
|
323 | - throw new NotImplementedException(sprintf("Folderid '%s' is saved to be of type '%d' but this type is not implemented", $folderid, $typeFromCache)); |
|
324 | - } |
|
325 | - |
|
326 | - return $class; |
|
327 | - } |
|
328 | - |
|
329 | - /** |
|
330 | - * Returns the additional folders as SyncFolder objects. |
|
331 | - * |
|
332 | - * @return array of SyncFolder with backendids as keys |
|
333 | - */ |
|
334 | - public function GetAdditionalUserSyncFolders() { |
|
335 | - $folders = []; |
|
336 | - |
|
337 | - // In impersonated stores, no additional folders will be synchronized |
|
338 | - if (Request::GetImpersonatedUser()) { |
|
339 | - return $folders; |
|
340 | - } |
|
341 | - |
|
342 | - foreach ($this->device->GetAdditionalFolders() as $df) { |
|
343 | - if (!isset($df['flags'])) { |
|
344 | - $df['flags'] = 0; |
|
345 | - SLog::Write(LOGLEVEL_WARN, sprintf("DeviceManager->GetAdditionalUserSyncFolders(): Additional folder '%s' has no flags.", $df['name'])); |
|
346 | - } |
|
347 | - if (!isset($df['parentid'])) { |
|
348 | - $df['parentid'] = '0'; |
|
349 | - SLog::Write(LOGLEVEL_WARN, sprintf("DeviceManager->GetAdditionalUserSyncFolders(): Additional folder '%s' has no parentid.", $df['name'])); |
|
350 | - } |
|
351 | - |
|
352 | - $folder = $this->BuildSyncFolderObject($df['store'], $df['folderid'], $df['parentid'], $df['name'], $df['type'], $df['flags'], DeviceManager::FLD_ORIGIN_SHARED); |
|
353 | - $folders[$folder->BackendId] = $folder; |
|
354 | - } |
|
355 | - |
|
356 | - return $folders; |
|
357 | - } |
|
358 | - |
|
359 | - /** |
|
360 | - * Get the store of an additional folder. |
|
361 | - * |
|
362 | - * @param string $folderid |
|
363 | - * |
|
364 | - * @return bool|string |
|
365 | - */ |
|
366 | - public function GetAdditionalUserSyncFolder($folderid) { |
|
367 | - $f = $this->device->GetAdditionalFolder($folderid); |
|
368 | - if ($f) { |
|
369 | - return $f; |
|
370 | - } |
|
371 | - |
|
372 | - return false; |
|
373 | - } |
|
374 | - |
|
375 | - /** |
|
376 | - * Checks if the message should be streamed to a mobile |
|
377 | - * Should always be called before a message is sent to the mobile |
|
378 | - * Returns true if there is something wrong and the content could break the |
|
379 | - * synchronization. |
|
380 | - * |
|
381 | - * @param string $id message id |
|
382 | - * @param SyncObject &$message the method could edit the message to change the flags |
|
383 | - * |
|
384 | - * @return bool returns true if the message should NOT be send! |
|
385 | - */ |
|
386 | - public function DoNotStreamMessage($id, &$message) { |
|
387 | - $folderid = $this->getLatestFolder(); |
|
388 | - |
|
389 | - if (isset($message->parentid)) { |
|
390 | - $folder = $message->parentid; |
|
391 | - } |
|
392 | - |
|
393 | - // message was identified to be causing a loop |
|
394 | - if ($this->loopdetection->IgnoreNextMessage(true, $id, $folderid)) { |
|
395 | - $this->AnnounceIgnoredMessage($folderid, $id, $message, self::MSG_BROKEN_CAUSINGLOOP); |
|
396 | - |
|
397 | - return true; |
|
398 | - } |
|
399 | - |
|
400 | - // message is semantically incorrect |
|
401 | - if (!$message->Check(true)) { |
|
402 | - $this->AnnounceIgnoredMessage($folderid, $id, $message, self::MSG_BROKEN_SEMANTICERR); |
|
403 | - |
|
404 | - return true; |
|
405 | - } |
|
406 | - |
|
407 | - // check if this message is broken |
|
408 | - if ($this->device->HasIgnoredMessage($folderid, $id)) { |
|
409 | - // reset the flags so the message is always streamed with <Add> |
|
410 | - $message->flags = false; |
|
411 | - |
|
412 | - // track the broken message in the loop detection |
|
413 | - $this->loopdetection->SetBrokenMessage($folderid, $id); |
|
414 | - } |
|
415 | - |
|
416 | - return false; |
|
417 | - } |
|
418 | - |
|
419 | - /** |
|
420 | - * Removes device information about a broken message as it is been removed from the mobile. |
|
421 | - * |
|
422 | - * @param string $id message id |
|
423 | - * |
|
424 | - * @return bool |
|
425 | - */ |
|
426 | - public function RemoveBrokenMessage($id) { |
|
427 | - $folderid = $this->getLatestFolder(); |
|
428 | - if ($this->device->RemoveIgnoredMessage($folderid, $id)) { |
|
429 | - SLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->RemoveBrokenMessage('%s', '%s'): cleared data about previously ignored message", $folderid, $id)); |
|
430 | - |
|
431 | - return true; |
|
432 | - } |
|
433 | - |
|
434 | - return false; |
|
435 | - } |
|
436 | - |
|
437 | - /** |
|
438 | - * Amount of items to me synchronized. |
|
439 | - * |
|
440 | - * @param string $folderid |
|
441 | - * @param string $type |
|
442 | - * @param int $queuedmessages; |
|
443 | - * @param mixed $uuid |
|
444 | - * @param mixed $statecounter |
|
445 | - * |
|
446 | - * @return int |
|
447 | - */ |
|
448 | - public function GetWindowSize($folderid, $uuid, $statecounter, $queuedmessages) { |
|
449 | - if (isset($this->windowSize[$folderid])) { |
|
450 | - $items = $this->windowSize[$folderid]; |
|
451 | - } |
|
452 | - else { |
|
453 | - $items = WINDOW_SIZE_MAX; |
|
454 | - } // 512 by default |
|
455 | - |
|
456 | - $this->setLatestFolder($folderid); |
|
457 | - |
|
458 | - // detect if this is a loop condition |
|
459 | - $loop = $this->loopdetection->Detect($folderid, $uuid, $statecounter, $items, $queuedmessages); |
|
460 | - if ($loop !== false) { |
|
461 | - if ($loop === true) { |
|
462 | - $items = ($items == 0) ? 0 : 1 + ($this->loopdetection->IgnoreNextMessage(false) ? 1 : 0); |
|
463 | - } |
|
464 | - else { |
|
465 | - // we got a new suggested window size |
|
466 | - $items = $loop; |
|
467 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("Mobile loop pre stage detected! Forcing smaller window size of %d before entering loop detection mode", $items)); |
|
468 | - } |
|
469 | - } |
|
470 | - |
|
471 | - if ($items >= 0 && $items <= 2) { |
|
472 | - SLog::Write(LOGLEVEL_WARN, sprintf("Mobile loop detected! Messages sent to the mobile will be restricted to %d items in order to identify the conflict", $items)); |
|
473 | - } |
|
474 | - |
|
475 | - return $items; |
|
476 | - } |
|
477 | - |
|
478 | - /** |
|
479 | - * Sets the amount of items the device is requesting. |
|
480 | - * |
|
481 | - * @param string $folderid |
|
482 | - * @param int $maxItems |
|
483 | - * |
|
484 | - * @return bool |
|
485 | - */ |
|
486 | - public function SetWindowSize($folderid, $maxItems) { |
|
487 | - $this->windowSize[$folderid] = $maxItems; |
|
488 | - |
|
489 | - return true; |
|
490 | - } |
|
491 | - |
|
492 | - /** |
|
493 | - * Sets the supported fields transmitted by the device for a certain folder. |
|
494 | - * |
|
495 | - * @param string $folderid |
|
496 | - * @param array $fieldlist supported fields |
|
497 | - * |
|
498 | - * @return bool |
|
499 | - */ |
|
500 | - public function SetSupportedFields($folderid, $fieldlist) { |
|
501 | - return $this->device->SetSupportedFields($folderid, $fieldlist); |
|
502 | - } |
|
503 | - |
|
504 | - /** |
|
505 | - * Gets the supported fields transmitted previously by the device |
|
506 | - * for a certain folder. |
|
507 | - * |
|
508 | - * @param string $folderid |
|
509 | - * |
|
510 | - * @return array/boolean |
|
511 | - */ |
|
512 | - public function GetSupportedFields($folderid) { |
|
513 | - return $this->device->GetSupportedFields($folderid); |
|
514 | - } |
|
515 | - |
|
516 | - /** |
|
517 | - * Returns the maximum filter type for a folder. |
|
518 | - * This might be limited globally, per device or per folder. |
|
519 | - * |
|
520 | - * @param string $folderid |
|
521 | - * @param mixed $backendFolderId |
|
522 | - * |
|
523 | - * @return int |
|
524 | - */ |
|
525 | - public function GetFilterType($folderid, $backendFolderId) { |
|
526 | - global $specialSyncFilter; |
|
527 | - // either globally configured SYNC_FILTERTIME_MAX or ALL (no limit) |
|
528 | - $maxAllowed = (defined('SYNC_FILTERTIME_MAX') && SYNC_FILTERTIME_MAX > SYNC_FILTERTYPE_ALL) ? SYNC_FILTERTIME_MAX : SYNC_FILTERTYPE_ALL; |
|
529 | - |
|
530 | - // TODO we could/should check for a specific value for the folder, if it's available |
|
531 | - $maxDevice = $this->device->GetSyncFilterType(); |
|
532 | - |
|
533 | - // ALL has a value of 0, all limitations have higher integer values, see SYNC_FILTERTYPE_ALL definition |
|
534 | - if ($maxDevice !== false && $maxDevice > SYNC_FILTERTYPE_ALL && ($maxAllowed == SYNC_FILTERTYPE_ALL || $maxDevice < $maxAllowed)) { |
|
535 | - $maxAllowed = $maxDevice; |
|
536 | - } |
|
537 | - |
|
538 | - if (is_array($specialSyncFilter)) { |
|
539 | - $store = GSync::GetAdditionalSyncFolderStore($backendFolderId); |
|
540 | - // the store is only available when this is a shared folder (but might also be statically configured) |
|
541 | - if ($store) { |
|
542 | - $origin = Utils::GetFolderOriginFromId($folderid); |
|
543 | - // do not limit when the owner or impersonated user is syncing! |
|
544 | - if ($origin == DeviceManager::FLD_ORIGIN_USER || $origin == DeviceManager::FLD_ORIGIN_IMPERSONATED) { |
|
545 | - SLog::Write(LOGLEVEL_DEBUG, "Not checking for specific sync limit as this is the owner/impersonated user."); |
|
546 | - } |
|
547 | - else { |
|
548 | - $spKey = false; |
|
549 | - $spFilter = false; |
|
550 | - // 1. step: check if there is a general limitation for the store |
|
551 | - if (array_key_exists($store, $specialSyncFilter)) { |
|
552 | - $spFilter = $specialSyncFilter[$store]; |
|
553 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("Limit sync due to configured limitation on the store: '%s': %s", $store, $spFilter)); |
|
554 | - } |
|
555 | - |
|
556 | - // 2. step: check if there is a limitation for the hashed ID (for shared/configured stores) |
|
557 | - $spKey = $store . '/' . $folderid; |
|
558 | - if (array_key_exists($spKey, $specialSyncFilter)) { |
|
559 | - $spFilter = $specialSyncFilter[$spKey]; |
|
560 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("Limit sync due to configured limitation on the folder: '%s': %s", $spKey, $spFilter)); |
|
561 | - } |
|
562 | - |
|
563 | - // 3. step: check if there is a limitation for the backendId |
|
564 | - $spKey = $store . '/' . $backendFolderId; |
|
565 | - if (array_key_exists($spKey, $specialSyncFilter)) { |
|
566 | - $spFilter = $specialSyncFilter[$spKey]; |
|
567 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("Limit sync due to configured limitation on the folder: '%s': %s", $spKey, $spFilter)); |
|
568 | - } |
|
569 | - if ($spFilter) { |
|
570 | - $maxAllowed = $spFilter; |
|
571 | - } |
|
572 | - } |
|
573 | - } |
|
574 | - } |
|
575 | - |
|
576 | - return $maxAllowed; |
|
577 | - } |
|
578 | - |
|
579 | - /** |
|
580 | - * Removes all linked states of a specific folder. |
|
581 | - * During next request the folder is resynchronized. |
|
582 | - * |
|
583 | - * @param string $folderid |
|
584 | - * |
|
585 | - * @return bool |
|
586 | - */ |
|
587 | - public function ForceFolderResync($folderid) { |
|
588 | - SLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->ForceFolderResync('%s'): folder resync", $folderid)); |
|
589 | - |
|
590 | - // delete folder states |
|
591 | - StateManager::UnLinkState($this->device, $folderid); |
|
592 | - |
|
593 | - return true; |
|
594 | - } |
|
595 | - |
|
596 | - /** |
|
597 | - * Removes all linked states from a device. |
|
598 | - * During next requests a full resync is triggered. |
|
599 | - * |
|
600 | - * @return bool |
|
601 | - */ |
|
602 | - public function ForceFullResync() { |
|
603 | - SLog::Write(LOGLEVEL_INFO, "Full device resync requested"); |
|
604 | - |
|
605 | - // delete all other uuids |
|
606 | - foreach ($this->device->GetAllFolderIds() as $folderid) { |
|
607 | - $uuid = StateManager::UnLinkState($this->device, $folderid); |
|
608 | - } |
|
609 | - |
|
610 | - // delete hierarchy states |
|
611 | - StateManager::UnLinkState($this->device, false); |
|
612 | - |
|
613 | - return true; |
|
614 | - } |
|
615 | - |
|
616 | - /** |
|
617 | - * Indicates if the hierarchy should be resynchronized based on the general folder state and |
|
618 | - * if additional folders changed. |
|
619 | - * |
|
620 | - * @return bool |
|
621 | - */ |
|
622 | - public function IsHierarchySyncRequired() { |
|
623 | - $this->loadDeviceData(); |
|
624 | - |
|
625 | - if ($this->loopdetection->ProcessLoopDetectionIsHierarchySyncAdvised()) { |
|
626 | - return true; |
|
627 | - } |
|
628 | - |
|
629 | - // if the hash of the additional folders changed, we have to sync the hierarchy |
|
630 | - if ($this->additionalFoldersHash != $this->getAdditionalFoldersHash()) { |
|
631 | - $this->hierarchySyncRequired = true; |
|
632 | - } |
|
633 | - |
|
634 | - // check if a hierarchy sync might be necessary |
|
635 | - if ($this->device->GetFolderUUID(false) === false) { |
|
636 | - $this->hierarchySyncRequired = true; |
|
637 | - } |
|
638 | - |
|
639 | - return $this->hierarchySyncRequired; |
|
640 | - } |
|
641 | - |
|
642 | - private function getAdditionalFoldersHash() { |
|
643 | - return md5(serialize($this->device->GetAdditionalFolders())); |
|
644 | - } |
|
645 | - |
|
646 | - /** |
|
647 | - * Indicates if a full hierarchy resync should be triggered due to loops. |
|
648 | - * |
|
649 | - * @return bool |
|
650 | - */ |
|
651 | - public function IsHierarchyFullResyncRequired() { |
|
652 | - // do not check for loop detection, if the foldersync is not yet complete |
|
653 | - if ($this->GetFolderSyncComplete() === false) { |
|
654 | - SLog::Write(LOGLEVEL_INFO, "DeviceManager->IsHierarchyFullResyncRequired(): aborted, as exporting of folders has not yet completed"); |
|
655 | - |
|
656 | - return false; |
|
657 | - } |
|
658 | - // check for potential process loops like described in ZP-5 |
|
659 | - return $this->loopdetection->ProcessLoopDetectionIsHierarchyResyncRequired(); |
|
660 | - } |
|
661 | - |
|
662 | - /** |
|
663 | - * Adds an Exceptions to the process tracking. |
|
664 | - * |
|
665 | - * @param Exception $exception |
|
666 | - * |
|
667 | - * @return bool |
|
668 | - */ |
|
669 | - public function AnnounceProcessException($exception) { |
|
670 | - return $this->loopdetection->ProcessLoopDetectionAddException($exception); |
|
671 | - } |
|
672 | - |
|
673 | - /** |
|
674 | - * Adds a non-ok status for a folderid to the process tracking. |
|
675 | - * On 'false' a hierarchy status is assumed. |
|
676 | - * |
|
677 | - * @param mixed $folderid |
|
678 | - * @param mixed $status |
|
679 | - * |
|
680 | - * @return bool |
|
681 | - */ |
|
682 | - public function AnnounceProcessStatus($folderid, $status) { |
|
683 | - return $this->loopdetection->ProcessLoopDetectionAddStatus($folderid, $status); |
|
684 | - } |
|
685 | - |
|
686 | - /** |
|
687 | - * Announces that the current process is a push connection to the process loop |
|
688 | - * detection and to the Top collector. |
|
689 | - * |
|
690 | - * @return bool |
|
691 | - */ |
|
692 | - public function AnnounceProcessAsPush() { |
|
693 | - SLog::Write(LOGLEVEL_DEBUG, "Announce process as PUSH connection"); |
|
694 | - |
|
695 | - return $this->loopdetection->ProcessLoopDetectionSetAsPush() && GSync::GetTopCollector()->SetAsPushConnection(); |
|
696 | - } |
|
697 | - |
|
698 | - /** |
|
699 | - * Checks if the given counter for a certain uuid+folderid was already exported or modified. |
|
700 | - * This is called when a heartbeat request found changes to make sure that the same |
|
701 | - * changes are not exported twice, as during the heartbeat there could have been a normal |
|
702 | - * sync request. |
|
703 | - * |
|
704 | - * @param string $folderid folder id |
|
705 | - * @param string $uuid synkkey |
|
706 | - * @param string $counter synckey counter |
|
707 | - * |
|
708 | - * @return bool indicating if an uuid+counter were exported (with changes) before |
|
709 | - */ |
|
710 | - public function CheckHearbeatStateIntegrity($folderid, $uuid, $counter) { |
|
711 | - return $this->loopdetection->IsSyncStateObsolete($folderid, $uuid, $counter); |
|
712 | - } |
|
713 | - |
|
714 | - /** |
|
715 | - * Marks a syncstate as obsolete for Heartbeat, as e.g. an import was started using it. |
|
716 | - * |
|
717 | - * @param string $folderid folder id |
|
718 | - * @param string $uuid synkkey |
|
719 | - * @param string $counter synckey counter |
|
720 | - * |
|
721 | - * @return |
|
722 | - */ |
|
723 | - public function SetHeartbeatStateIntegrity($folderid, $uuid, $counter) { |
|
724 | - return $this->loopdetection->SetSyncStateUsage($folderid, $uuid, $counter); |
|
725 | - } |
|
726 | - |
|
727 | - /** |
|
728 | - * Checks the data integrity of the data in the hierarchy cache and the data of the content data (synchronized folders). |
|
729 | - * If a folder is deleted, the sync states could still be on the server (and being loaded by PING) while |
|
730 | - * the folder is not being synchronized anymore. See also https://jira.z-hub.io/browse/ZP-1077. |
|
731 | - * |
|
732 | - * @return bool |
|
733 | - */ |
|
734 | - public function CheckFolderData() { |
|
735 | - SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->CheckFolderData() checking integrity of hierarchy cache with synchronized folders"); |
|
736 | - |
|
737 | - $hc = $this->device->GetHierarchyCache(); |
|
738 | - $notInCache = []; |
|
739 | - foreach ($this->device->GetAllFolderIds() as $folderid) { |
|
740 | - $uuid = $this->device->GetFolderUUID($folderid); |
|
741 | - if ($uuid) { |
|
742 | - // has a UUID but is not in the cache?! This is deleted, remove the states. |
|
743 | - if (!$hc->GetFolder($folderid)) { |
|
744 | - SLog::Write(LOGLEVEL_WARN, sprintf("DeviceManager->CheckFolderData(): Folder '%s' has sync states but is not in the hierarchy cache. Removing states.", $folderid)); |
|
745 | - StateManager::UnLinkState($this->device, $folderid); |
|
746 | - } |
|
747 | - } |
|
748 | - } |
|
749 | - |
|
750 | - return true; |
|
751 | - } |
|
752 | - |
|
753 | - /** |
|
754 | - * Sets the current status of the folder. |
|
755 | - * |
|
756 | - * @param string $folderid folder id |
|
757 | - * @param int $statusflag current status: DeviceManager::FLD_SYNC_INITIALIZED, DeviceManager::FLD_SYNC_INPROGRESS, DeviceManager::FLD_SYNC_COMPLETED |
|
758 | - * |
|
759 | - * @return |
|
760 | - */ |
|
761 | - public function SetFolderSyncStatus($folderid, $statusflag) { |
|
762 | - $currentStatus = $this->device->GetFolderSyncStatus($folderid); |
|
763 | - |
|
764 | - // status available or just initialized |
|
765 | - if (isset($currentStatus->{ASDevice::FOLDERSYNCSTATUS}) || $statusflag == self::FLD_SYNC_INITIALIZED) { |
|
766 | - // only update if there is a change |
|
767 | - if ((!$currentStatus || (isset($currentStatus->{ASDevice::FOLDERSYNCSTATUS}) && $statusflag !== $currentStatus->{ASDevice::FOLDERSYNCSTATUS})) && |
|
768 | - $statusflag != self::FLD_SYNC_COMPLETED) { |
|
769 | - $this->device->SetFolderSyncStatus($folderid, $statusflag); |
|
770 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("SetFolderSyncStatus(): set %s for %s", $statusflag, $folderid)); |
|
771 | - } |
|
772 | - // if completed, remove the status |
|
773 | - elseif ($statusflag == self::FLD_SYNC_COMPLETED) { |
|
774 | - $this->device->SetFolderSyncStatus($folderid, false); |
|
775 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("SetFolderSyncStatus(): completed for %s", $folderid)); |
|
776 | - } |
|
777 | - } |
|
778 | - |
|
779 | - return true; |
|
780 | - } |
|
781 | - |
|
782 | - /** |
|
783 | - * Indicates if a folder is synchronizing by the saved status. |
|
784 | - * |
|
785 | - * @param string $folderid folder id |
|
786 | - * |
|
787 | - * @return bool |
|
788 | - */ |
|
789 | - public function HasFolderSyncStatus($folderid) { |
|
790 | - $currentStatus = $this->device->GetFolderSyncStatus($folderid); |
|
791 | - |
|
792 | - // status available ? |
|
793 | - $hasStatus = isset($currentStatus->{ASDevice::FOLDERSYNCSTATUS}); |
|
794 | - if ($hasStatus) { |
|
795 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("HasFolderSyncStatus(): saved folder status for %s: %s", $folderid, $currentStatus->{ASDevice::FOLDERSYNCSTATUS})); |
|
796 | - } |
|
797 | - |
|
798 | - return $hasStatus; |
|
799 | - } |
|
800 | - |
|
801 | - /** |
|
802 | - * Returns the indicator if the FolderSync was completed successfully (all folders synchronized). |
|
803 | - * |
|
804 | - * @return bool |
|
805 | - */ |
|
806 | - public function GetFolderSyncComplete() { |
|
807 | - return $this->device->GetFolderSyncComplete(); |
|
808 | - } |
|
809 | - |
|
810 | - /** |
|
811 | - * Sets if the FolderSync was completed successfully (all folders synchronized). |
|
812 | - * |
|
813 | - * @param bool $complete indicating if all folders were sent |
|
814 | - * @param mixed $user |
|
815 | - * @param mixed $devid |
|
816 | - * |
|
817 | - * @return bool |
|
818 | - */ |
|
819 | - public function SetFolderSyncComplete($complete, $user = false, $devid = false) { |
|
820 | - $this->device->SetFolderSyncComplete($complete); |
|
821 | - |
|
822 | - return true; |
|
823 | - } |
|
824 | - |
|
825 | - /** |
|
826 | - * Removes the Loop detection data for a user & device. |
|
827 | - * |
|
828 | - * @param string $user |
|
829 | - * @param string $devid |
|
830 | - * |
|
831 | - * @return bool |
|
832 | - */ |
|
833 | - public function ClearLoopDetectionData($user, $devid) { |
|
834 | - if ($user == false || $devid == false) { |
|
835 | - return false; |
|
836 | - } |
|
837 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->ClearLoopDetectionData(): clearing data for user '%s' and device '%s'", $user, $devid)); |
|
838 | - |
|
839 | - return $this->loopdetection->ClearData($user, $devid); |
|
840 | - } |
|
841 | - |
|
842 | - /** |
|
843 | - * Indicates if the device needs an AS version update. |
|
844 | - * |
|
845 | - * @return bool |
|
846 | - */ |
|
847 | - public function AnnounceASVersion() { |
|
848 | - $latest = GSync::GetSupportedASVersion(); |
|
849 | - $announced = $this->device->GetAnnouncedASversion(); |
|
850 | - $this->device->SetAnnouncedASversion($latest); |
|
851 | - |
|
852 | - return $announced != $latest; |
|
853 | - } |
|
854 | - |
|
855 | - /** |
|
856 | - * Returns the User Agent. This data is consolidated with data from Request::GetUserAgent() |
|
857 | - * and the data saved in the ASDevice. |
|
858 | - * |
|
859 | - * @return string |
|
860 | - */ |
|
861 | - public function GetUserAgent() { |
|
862 | - return $this->device->GetDeviceUserAgent(); |
|
863 | - } |
|
864 | - |
|
865 | - /** |
|
866 | - * Returns the backend folder id from the AS folderid known to the mobile. |
|
867 | - * If the id is not known, it's returned as is. |
|
868 | - * |
|
869 | - * @param mixed $folderid |
|
870 | - * |
|
871 | - * @return int/boolean returns false if the type is not set |
|
872 | - */ |
|
873 | - public function GetBackendIdForFolderId($folderid) { |
|
874 | - $backendId = $this->device->GetFolderBackendId($folderid); |
|
875 | - if (!$backendId) { |
|
876 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetBackendIdForFolderId(): no backend-folderid available for '%s', returning as is.", $folderid)); |
|
877 | - |
|
878 | - return $folderid; |
|
879 | - } |
|
880 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetBackendIdForFolderId(): folderid %s => %s", $folderid, $backendId)); |
|
881 | - |
|
882 | - return $backendId; |
|
883 | - } |
|
884 | - |
|
885 | - /** |
|
886 | - * Gets the AS folderid for a backendFolderId. |
|
887 | - * If there is no known AS folderId a new one is being created. |
|
888 | - * |
|
889 | - * @param string $backendid Backend folder id |
|
890 | - * @param bool $generateNewIdIfNew generates a new AS folderid for the case the backend folder is not known yet, default: false |
|
891 | - * @param string $folderOrigin Folder type is one of 'U' (user) |
|
892 | - * 'C' (configured) |
|
893 | - * 'S' (shared) |
|
894 | - * 'G' (global address book) |
|
895 | - * 'I' (impersonated) |
|
896 | - * @param string $folderName Folder name of the backend folder |
|
897 | - * |
|
898 | - * @return string/boolean returns false if there is folderid known for this backendid and $generateNewIdIfNew is not set or false |
|
899 | - */ |
|
900 | - public function GetFolderIdForBackendId($backendid, $generateNewIdIfNew = false, $folderOrigin = self::FLD_ORIGIN_USER, $folderName = null) { |
|
901 | - if (!in_array($folderOrigin, [DeviceManager::FLD_ORIGIN_CONFIG, DeviceManager::FLD_ORIGIN_GAB, DeviceManager::FLD_ORIGIN_SHARED, DeviceManager::FLD_ORIGIN_USER, DeviceManager::FLD_ORIGIN_IMPERSONATED])) { |
|
902 | - SLog::Write(LOGLEVEL_WARN, sprintf("ASDevice->GetFolderIdForBackendId(): folder type '%s' is unknown in DeviceManager", $folderOrigin)); |
|
903 | - } |
|
904 | - |
|
905 | - return $this->device->GetFolderIdForBackendId($backendid, $generateNewIdIfNew, $folderOrigin, $folderName); |
|
906 | - } |
|
907 | - |
|
908 | - /*---------------------------------------------------------------------------------------------------------- |
|
228 | + /** |
|
229 | + * Returns a wrapped Importer & Exporter to use the |
|
230 | + * HierarchyChache. |
|
231 | + * |
|
232 | + * @see ChangesMemoryWrapper |
|
233 | + * |
|
234 | + * @return object HierarchyCache |
|
235 | + */ |
|
236 | + public function GetHierarchyChangesWrapper() { |
|
237 | + return $this->device->GetHierarchyCache(); |
|
238 | + } |
|
239 | + |
|
240 | + /** |
|
241 | + * Initializes the HierarchyCache for legacy syncs |
|
242 | + * this is for AS 1.0 compatibility: |
|
243 | + * save folder information synched with GetHierarchy(). |
|
244 | + * |
|
245 | + * @param string $folders Array with folder information |
|
246 | + * |
|
247 | + * @return bool |
|
248 | + */ |
|
249 | + public function InitializeFolderCache($folders) { |
|
250 | + $this->stateManager->SetDevice($this->device); |
|
251 | + |
|
252 | + return $this->stateManager->InitializeFolderCache($folders); |
|
253 | + } |
|
254 | + |
|
255 | + /** |
|
256 | + * Returns the ActiveSync folder type for a FolderID. |
|
257 | + * |
|
258 | + * @param string $folderid |
|
259 | + * |
|
260 | + * @return int/boolean boolean if no type is found |
|
261 | + */ |
|
262 | + public function GetFolderTypeFromCacheById($folderid) { |
|
263 | + return $this->device->GetFolderType($folderid); |
|
264 | + } |
|
265 | + |
|
266 | + /** |
|
267 | + * Returns a FolderID of default classes |
|
268 | + * this is for AS 1.0 compatibility: |
|
269 | + * this information was made available during GetHierarchy(). |
|
270 | + * |
|
271 | + * @param string $class The class requested |
|
272 | + * |
|
273 | + * @throws NoHierarchyCacheAvailableException |
|
274 | + * |
|
275 | + * @return string |
|
276 | + */ |
|
277 | + public function GetFolderIdFromCacheByClass($class) { |
|
278 | + $folderidforClass = false; |
|
279 | + // look at the default foldertype for this class |
|
280 | + $type = GSync::getDefaultFolderTypeFromFolderClass($class); |
|
281 | + |
|
282 | + if ($type && $type > SYNC_FOLDER_TYPE_OTHER && $type < SYNC_FOLDER_TYPE_USER_MAIL) { |
|
283 | + $folderids = $this->device->GetAllFolderIds(); |
|
284 | + foreach ($folderids as $folderid) { |
|
285 | + if ($type == $this->device->GetFolderType($folderid)) { |
|
286 | + $folderidforClass = $folderid; |
|
287 | + |
|
288 | + break; |
|
289 | + } |
|
290 | + } |
|
291 | + |
|
292 | + // Old Palm Treos always do initial sync for calendar and contacts, even if they are not made available by the backend. |
|
293 | + // We need to fake these folderids, allowing a fake sync/ping, even if they are not supported by the backend |
|
294 | + // if the folderid would be available, they would already be returned in the above statement |
|
295 | + if ($folderidforClass == false && ($type == SYNC_FOLDER_TYPE_APPOINTMENT || $type == SYNC_FOLDER_TYPE_CONTACT)) { |
|
296 | + $folderidforClass = SYNC_FOLDER_TYPE_DUMMY; |
|
297 | + } |
|
298 | + } |
|
299 | + |
|
300 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetFolderIdFromCacheByClass('%s'): '%s' => '%s'", $class, $type, $folderidforClass)); |
|
301 | + |
|
302 | + return $folderidforClass; |
|
303 | + } |
|
304 | + |
|
305 | + /** |
|
306 | + * Returns a FolderClass for a FolderID which is known to the mobile. |
|
307 | + * |
|
308 | + * @param string $folderid |
|
309 | + * |
|
310 | + * @throws NoHierarchyCacheAvailableException, NotImplementedException |
|
311 | + * |
|
312 | + * @return int |
|
313 | + */ |
|
314 | + public function GetFolderClassFromCacheByID($folderid) { |
|
315 | + // TODO check if the parent folder exists and is also being synchronized |
|
316 | + $typeFromCache = $this->device->GetFolderType($folderid); |
|
317 | + if ($typeFromCache === false) { |
|
318 | + throw new NoHierarchyCacheAvailableException(sprintf("Folderid '%s' is not fully synchronized on the device", $folderid)); |
|
319 | + } |
|
320 | + |
|
321 | + $class = GSync::GetFolderClassFromFolderType($typeFromCache); |
|
322 | + if ($class === false) { |
|
323 | + throw new NotImplementedException(sprintf("Folderid '%s' is saved to be of type '%d' but this type is not implemented", $folderid, $typeFromCache)); |
|
324 | + } |
|
325 | + |
|
326 | + return $class; |
|
327 | + } |
|
328 | + |
|
329 | + /** |
|
330 | + * Returns the additional folders as SyncFolder objects. |
|
331 | + * |
|
332 | + * @return array of SyncFolder with backendids as keys |
|
333 | + */ |
|
334 | + public function GetAdditionalUserSyncFolders() { |
|
335 | + $folders = []; |
|
336 | + |
|
337 | + // In impersonated stores, no additional folders will be synchronized |
|
338 | + if (Request::GetImpersonatedUser()) { |
|
339 | + return $folders; |
|
340 | + } |
|
341 | + |
|
342 | + foreach ($this->device->GetAdditionalFolders() as $df) { |
|
343 | + if (!isset($df['flags'])) { |
|
344 | + $df['flags'] = 0; |
|
345 | + SLog::Write(LOGLEVEL_WARN, sprintf("DeviceManager->GetAdditionalUserSyncFolders(): Additional folder '%s' has no flags.", $df['name'])); |
|
346 | + } |
|
347 | + if (!isset($df['parentid'])) { |
|
348 | + $df['parentid'] = '0'; |
|
349 | + SLog::Write(LOGLEVEL_WARN, sprintf("DeviceManager->GetAdditionalUserSyncFolders(): Additional folder '%s' has no parentid.", $df['name'])); |
|
350 | + } |
|
351 | + |
|
352 | + $folder = $this->BuildSyncFolderObject($df['store'], $df['folderid'], $df['parentid'], $df['name'], $df['type'], $df['flags'], DeviceManager::FLD_ORIGIN_SHARED); |
|
353 | + $folders[$folder->BackendId] = $folder; |
|
354 | + } |
|
355 | + |
|
356 | + return $folders; |
|
357 | + } |
|
358 | + |
|
359 | + /** |
|
360 | + * Get the store of an additional folder. |
|
361 | + * |
|
362 | + * @param string $folderid |
|
363 | + * |
|
364 | + * @return bool|string |
|
365 | + */ |
|
366 | + public function GetAdditionalUserSyncFolder($folderid) { |
|
367 | + $f = $this->device->GetAdditionalFolder($folderid); |
|
368 | + if ($f) { |
|
369 | + return $f; |
|
370 | + } |
|
371 | + |
|
372 | + return false; |
|
373 | + } |
|
374 | + |
|
375 | + /** |
|
376 | + * Checks if the message should be streamed to a mobile |
|
377 | + * Should always be called before a message is sent to the mobile |
|
378 | + * Returns true if there is something wrong and the content could break the |
|
379 | + * synchronization. |
|
380 | + * |
|
381 | + * @param string $id message id |
|
382 | + * @param SyncObject &$message the method could edit the message to change the flags |
|
383 | + * |
|
384 | + * @return bool returns true if the message should NOT be send! |
|
385 | + */ |
|
386 | + public function DoNotStreamMessage($id, &$message) { |
|
387 | + $folderid = $this->getLatestFolder(); |
|
388 | + |
|
389 | + if (isset($message->parentid)) { |
|
390 | + $folder = $message->parentid; |
|
391 | + } |
|
392 | + |
|
393 | + // message was identified to be causing a loop |
|
394 | + if ($this->loopdetection->IgnoreNextMessage(true, $id, $folderid)) { |
|
395 | + $this->AnnounceIgnoredMessage($folderid, $id, $message, self::MSG_BROKEN_CAUSINGLOOP); |
|
396 | + |
|
397 | + return true; |
|
398 | + } |
|
399 | + |
|
400 | + // message is semantically incorrect |
|
401 | + if (!$message->Check(true)) { |
|
402 | + $this->AnnounceIgnoredMessage($folderid, $id, $message, self::MSG_BROKEN_SEMANTICERR); |
|
403 | + |
|
404 | + return true; |
|
405 | + } |
|
406 | + |
|
407 | + // check if this message is broken |
|
408 | + if ($this->device->HasIgnoredMessage($folderid, $id)) { |
|
409 | + // reset the flags so the message is always streamed with <Add> |
|
410 | + $message->flags = false; |
|
411 | + |
|
412 | + // track the broken message in the loop detection |
|
413 | + $this->loopdetection->SetBrokenMessage($folderid, $id); |
|
414 | + } |
|
415 | + |
|
416 | + return false; |
|
417 | + } |
|
418 | + |
|
419 | + /** |
|
420 | + * Removes device information about a broken message as it is been removed from the mobile. |
|
421 | + * |
|
422 | + * @param string $id message id |
|
423 | + * |
|
424 | + * @return bool |
|
425 | + */ |
|
426 | + public function RemoveBrokenMessage($id) { |
|
427 | + $folderid = $this->getLatestFolder(); |
|
428 | + if ($this->device->RemoveIgnoredMessage($folderid, $id)) { |
|
429 | + SLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->RemoveBrokenMessage('%s', '%s'): cleared data about previously ignored message", $folderid, $id)); |
|
430 | + |
|
431 | + return true; |
|
432 | + } |
|
433 | + |
|
434 | + return false; |
|
435 | + } |
|
436 | + |
|
437 | + /** |
|
438 | + * Amount of items to me synchronized. |
|
439 | + * |
|
440 | + * @param string $folderid |
|
441 | + * @param string $type |
|
442 | + * @param int $queuedmessages; |
|
443 | + * @param mixed $uuid |
|
444 | + * @param mixed $statecounter |
|
445 | + * |
|
446 | + * @return int |
|
447 | + */ |
|
448 | + public function GetWindowSize($folderid, $uuid, $statecounter, $queuedmessages) { |
|
449 | + if (isset($this->windowSize[$folderid])) { |
|
450 | + $items = $this->windowSize[$folderid]; |
|
451 | + } |
|
452 | + else { |
|
453 | + $items = WINDOW_SIZE_MAX; |
|
454 | + } // 512 by default |
|
455 | + |
|
456 | + $this->setLatestFolder($folderid); |
|
457 | + |
|
458 | + // detect if this is a loop condition |
|
459 | + $loop = $this->loopdetection->Detect($folderid, $uuid, $statecounter, $items, $queuedmessages); |
|
460 | + if ($loop !== false) { |
|
461 | + if ($loop === true) { |
|
462 | + $items = ($items == 0) ? 0 : 1 + ($this->loopdetection->IgnoreNextMessage(false) ? 1 : 0); |
|
463 | + } |
|
464 | + else { |
|
465 | + // we got a new suggested window size |
|
466 | + $items = $loop; |
|
467 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("Mobile loop pre stage detected! Forcing smaller window size of %d before entering loop detection mode", $items)); |
|
468 | + } |
|
469 | + } |
|
470 | + |
|
471 | + if ($items >= 0 && $items <= 2) { |
|
472 | + SLog::Write(LOGLEVEL_WARN, sprintf("Mobile loop detected! Messages sent to the mobile will be restricted to %d items in order to identify the conflict", $items)); |
|
473 | + } |
|
474 | + |
|
475 | + return $items; |
|
476 | + } |
|
477 | + |
|
478 | + /** |
|
479 | + * Sets the amount of items the device is requesting. |
|
480 | + * |
|
481 | + * @param string $folderid |
|
482 | + * @param int $maxItems |
|
483 | + * |
|
484 | + * @return bool |
|
485 | + */ |
|
486 | + public function SetWindowSize($folderid, $maxItems) { |
|
487 | + $this->windowSize[$folderid] = $maxItems; |
|
488 | + |
|
489 | + return true; |
|
490 | + } |
|
491 | + |
|
492 | + /** |
|
493 | + * Sets the supported fields transmitted by the device for a certain folder. |
|
494 | + * |
|
495 | + * @param string $folderid |
|
496 | + * @param array $fieldlist supported fields |
|
497 | + * |
|
498 | + * @return bool |
|
499 | + */ |
|
500 | + public function SetSupportedFields($folderid, $fieldlist) { |
|
501 | + return $this->device->SetSupportedFields($folderid, $fieldlist); |
|
502 | + } |
|
503 | + |
|
504 | + /** |
|
505 | + * Gets the supported fields transmitted previously by the device |
|
506 | + * for a certain folder. |
|
507 | + * |
|
508 | + * @param string $folderid |
|
509 | + * |
|
510 | + * @return array/boolean |
|
511 | + */ |
|
512 | + public function GetSupportedFields($folderid) { |
|
513 | + return $this->device->GetSupportedFields($folderid); |
|
514 | + } |
|
515 | + |
|
516 | + /** |
|
517 | + * Returns the maximum filter type for a folder. |
|
518 | + * This might be limited globally, per device or per folder. |
|
519 | + * |
|
520 | + * @param string $folderid |
|
521 | + * @param mixed $backendFolderId |
|
522 | + * |
|
523 | + * @return int |
|
524 | + */ |
|
525 | + public function GetFilterType($folderid, $backendFolderId) { |
|
526 | + global $specialSyncFilter; |
|
527 | + // either globally configured SYNC_FILTERTIME_MAX or ALL (no limit) |
|
528 | + $maxAllowed = (defined('SYNC_FILTERTIME_MAX') && SYNC_FILTERTIME_MAX > SYNC_FILTERTYPE_ALL) ? SYNC_FILTERTIME_MAX : SYNC_FILTERTYPE_ALL; |
|
529 | + |
|
530 | + // TODO we could/should check for a specific value for the folder, if it's available |
|
531 | + $maxDevice = $this->device->GetSyncFilterType(); |
|
532 | + |
|
533 | + // ALL has a value of 0, all limitations have higher integer values, see SYNC_FILTERTYPE_ALL definition |
|
534 | + if ($maxDevice !== false && $maxDevice > SYNC_FILTERTYPE_ALL && ($maxAllowed == SYNC_FILTERTYPE_ALL || $maxDevice < $maxAllowed)) { |
|
535 | + $maxAllowed = $maxDevice; |
|
536 | + } |
|
537 | + |
|
538 | + if (is_array($specialSyncFilter)) { |
|
539 | + $store = GSync::GetAdditionalSyncFolderStore($backendFolderId); |
|
540 | + // the store is only available when this is a shared folder (but might also be statically configured) |
|
541 | + if ($store) { |
|
542 | + $origin = Utils::GetFolderOriginFromId($folderid); |
|
543 | + // do not limit when the owner or impersonated user is syncing! |
|
544 | + if ($origin == DeviceManager::FLD_ORIGIN_USER || $origin == DeviceManager::FLD_ORIGIN_IMPERSONATED) { |
|
545 | + SLog::Write(LOGLEVEL_DEBUG, "Not checking for specific sync limit as this is the owner/impersonated user."); |
|
546 | + } |
|
547 | + else { |
|
548 | + $spKey = false; |
|
549 | + $spFilter = false; |
|
550 | + // 1. step: check if there is a general limitation for the store |
|
551 | + if (array_key_exists($store, $specialSyncFilter)) { |
|
552 | + $spFilter = $specialSyncFilter[$store]; |
|
553 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("Limit sync due to configured limitation on the store: '%s': %s", $store, $spFilter)); |
|
554 | + } |
|
555 | + |
|
556 | + // 2. step: check if there is a limitation for the hashed ID (for shared/configured stores) |
|
557 | + $spKey = $store . '/' . $folderid; |
|
558 | + if (array_key_exists($spKey, $specialSyncFilter)) { |
|
559 | + $spFilter = $specialSyncFilter[$spKey]; |
|
560 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("Limit sync due to configured limitation on the folder: '%s': %s", $spKey, $spFilter)); |
|
561 | + } |
|
562 | + |
|
563 | + // 3. step: check if there is a limitation for the backendId |
|
564 | + $spKey = $store . '/' . $backendFolderId; |
|
565 | + if (array_key_exists($spKey, $specialSyncFilter)) { |
|
566 | + $spFilter = $specialSyncFilter[$spKey]; |
|
567 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("Limit sync due to configured limitation on the folder: '%s': %s", $spKey, $spFilter)); |
|
568 | + } |
|
569 | + if ($spFilter) { |
|
570 | + $maxAllowed = $spFilter; |
|
571 | + } |
|
572 | + } |
|
573 | + } |
|
574 | + } |
|
575 | + |
|
576 | + return $maxAllowed; |
|
577 | + } |
|
578 | + |
|
579 | + /** |
|
580 | + * Removes all linked states of a specific folder. |
|
581 | + * During next request the folder is resynchronized. |
|
582 | + * |
|
583 | + * @param string $folderid |
|
584 | + * |
|
585 | + * @return bool |
|
586 | + */ |
|
587 | + public function ForceFolderResync($folderid) { |
|
588 | + SLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->ForceFolderResync('%s'): folder resync", $folderid)); |
|
589 | + |
|
590 | + // delete folder states |
|
591 | + StateManager::UnLinkState($this->device, $folderid); |
|
592 | + |
|
593 | + return true; |
|
594 | + } |
|
595 | + |
|
596 | + /** |
|
597 | + * Removes all linked states from a device. |
|
598 | + * During next requests a full resync is triggered. |
|
599 | + * |
|
600 | + * @return bool |
|
601 | + */ |
|
602 | + public function ForceFullResync() { |
|
603 | + SLog::Write(LOGLEVEL_INFO, "Full device resync requested"); |
|
604 | + |
|
605 | + // delete all other uuids |
|
606 | + foreach ($this->device->GetAllFolderIds() as $folderid) { |
|
607 | + $uuid = StateManager::UnLinkState($this->device, $folderid); |
|
608 | + } |
|
609 | + |
|
610 | + // delete hierarchy states |
|
611 | + StateManager::UnLinkState($this->device, false); |
|
612 | + |
|
613 | + return true; |
|
614 | + } |
|
615 | + |
|
616 | + /** |
|
617 | + * Indicates if the hierarchy should be resynchronized based on the general folder state and |
|
618 | + * if additional folders changed. |
|
619 | + * |
|
620 | + * @return bool |
|
621 | + */ |
|
622 | + public function IsHierarchySyncRequired() { |
|
623 | + $this->loadDeviceData(); |
|
624 | + |
|
625 | + if ($this->loopdetection->ProcessLoopDetectionIsHierarchySyncAdvised()) { |
|
626 | + return true; |
|
627 | + } |
|
628 | + |
|
629 | + // if the hash of the additional folders changed, we have to sync the hierarchy |
|
630 | + if ($this->additionalFoldersHash != $this->getAdditionalFoldersHash()) { |
|
631 | + $this->hierarchySyncRequired = true; |
|
632 | + } |
|
633 | + |
|
634 | + // check if a hierarchy sync might be necessary |
|
635 | + if ($this->device->GetFolderUUID(false) === false) { |
|
636 | + $this->hierarchySyncRequired = true; |
|
637 | + } |
|
638 | + |
|
639 | + return $this->hierarchySyncRequired; |
|
640 | + } |
|
641 | + |
|
642 | + private function getAdditionalFoldersHash() { |
|
643 | + return md5(serialize($this->device->GetAdditionalFolders())); |
|
644 | + } |
|
645 | + |
|
646 | + /** |
|
647 | + * Indicates if a full hierarchy resync should be triggered due to loops. |
|
648 | + * |
|
649 | + * @return bool |
|
650 | + */ |
|
651 | + public function IsHierarchyFullResyncRequired() { |
|
652 | + // do not check for loop detection, if the foldersync is not yet complete |
|
653 | + if ($this->GetFolderSyncComplete() === false) { |
|
654 | + SLog::Write(LOGLEVEL_INFO, "DeviceManager->IsHierarchyFullResyncRequired(): aborted, as exporting of folders has not yet completed"); |
|
655 | + |
|
656 | + return false; |
|
657 | + } |
|
658 | + // check for potential process loops like described in ZP-5 |
|
659 | + return $this->loopdetection->ProcessLoopDetectionIsHierarchyResyncRequired(); |
|
660 | + } |
|
661 | + |
|
662 | + /** |
|
663 | + * Adds an Exceptions to the process tracking. |
|
664 | + * |
|
665 | + * @param Exception $exception |
|
666 | + * |
|
667 | + * @return bool |
|
668 | + */ |
|
669 | + public function AnnounceProcessException($exception) { |
|
670 | + return $this->loopdetection->ProcessLoopDetectionAddException($exception); |
|
671 | + } |
|
672 | + |
|
673 | + /** |
|
674 | + * Adds a non-ok status for a folderid to the process tracking. |
|
675 | + * On 'false' a hierarchy status is assumed. |
|
676 | + * |
|
677 | + * @param mixed $folderid |
|
678 | + * @param mixed $status |
|
679 | + * |
|
680 | + * @return bool |
|
681 | + */ |
|
682 | + public function AnnounceProcessStatus($folderid, $status) { |
|
683 | + return $this->loopdetection->ProcessLoopDetectionAddStatus($folderid, $status); |
|
684 | + } |
|
685 | + |
|
686 | + /** |
|
687 | + * Announces that the current process is a push connection to the process loop |
|
688 | + * detection and to the Top collector. |
|
689 | + * |
|
690 | + * @return bool |
|
691 | + */ |
|
692 | + public function AnnounceProcessAsPush() { |
|
693 | + SLog::Write(LOGLEVEL_DEBUG, "Announce process as PUSH connection"); |
|
694 | + |
|
695 | + return $this->loopdetection->ProcessLoopDetectionSetAsPush() && GSync::GetTopCollector()->SetAsPushConnection(); |
|
696 | + } |
|
697 | + |
|
698 | + /** |
|
699 | + * Checks if the given counter for a certain uuid+folderid was already exported or modified. |
|
700 | + * This is called when a heartbeat request found changes to make sure that the same |
|
701 | + * changes are not exported twice, as during the heartbeat there could have been a normal |
|
702 | + * sync request. |
|
703 | + * |
|
704 | + * @param string $folderid folder id |
|
705 | + * @param string $uuid synkkey |
|
706 | + * @param string $counter synckey counter |
|
707 | + * |
|
708 | + * @return bool indicating if an uuid+counter were exported (with changes) before |
|
709 | + */ |
|
710 | + public function CheckHearbeatStateIntegrity($folderid, $uuid, $counter) { |
|
711 | + return $this->loopdetection->IsSyncStateObsolete($folderid, $uuid, $counter); |
|
712 | + } |
|
713 | + |
|
714 | + /** |
|
715 | + * Marks a syncstate as obsolete for Heartbeat, as e.g. an import was started using it. |
|
716 | + * |
|
717 | + * @param string $folderid folder id |
|
718 | + * @param string $uuid synkkey |
|
719 | + * @param string $counter synckey counter |
|
720 | + * |
|
721 | + * @return |
|
722 | + */ |
|
723 | + public function SetHeartbeatStateIntegrity($folderid, $uuid, $counter) { |
|
724 | + return $this->loopdetection->SetSyncStateUsage($folderid, $uuid, $counter); |
|
725 | + } |
|
726 | + |
|
727 | + /** |
|
728 | + * Checks the data integrity of the data in the hierarchy cache and the data of the content data (synchronized folders). |
|
729 | + * If a folder is deleted, the sync states could still be on the server (and being loaded by PING) while |
|
730 | + * the folder is not being synchronized anymore. See also https://jira.z-hub.io/browse/ZP-1077. |
|
731 | + * |
|
732 | + * @return bool |
|
733 | + */ |
|
734 | + public function CheckFolderData() { |
|
735 | + SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->CheckFolderData() checking integrity of hierarchy cache with synchronized folders"); |
|
736 | + |
|
737 | + $hc = $this->device->GetHierarchyCache(); |
|
738 | + $notInCache = []; |
|
739 | + foreach ($this->device->GetAllFolderIds() as $folderid) { |
|
740 | + $uuid = $this->device->GetFolderUUID($folderid); |
|
741 | + if ($uuid) { |
|
742 | + // has a UUID but is not in the cache?! This is deleted, remove the states. |
|
743 | + if (!$hc->GetFolder($folderid)) { |
|
744 | + SLog::Write(LOGLEVEL_WARN, sprintf("DeviceManager->CheckFolderData(): Folder '%s' has sync states but is not in the hierarchy cache. Removing states.", $folderid)); |
|
745 | + StateManager::UnLinkState($this->device, $folderid); |
|
746 | + } |
|
747 | + } |
|
748 | + } |
|
749 | + |
|
750 | + return true; |
|
751 | + } |
|
752 | + |
|
753 | + /** |
|
754 | + * Sets the current status of the folder. |
|
755 | + * |
|
756 | + * @param string $folderid folder id |
|
757 | + * @param int $statusflag current status: DeviceManager::FLD_SYNC_INITIALIZED, DeviceManager::FLD_SYNC_INPROGRESS, DeviceManager::FLD_SYNC_COMPLETED |
|
758 | + * |
|
759 | + * @return |
|
760 | + */ |
|
761 | + public function SetFolderSyncStatus($folderid, $statusflag) { |
|
762 | + $currentStatus = $this->device->GetFolderSyncStatus($folderid); |
|
763 | + |
|
764 | + // status available or just initialized |
|
765 | + if (isset($currentStatus->{ASDevice::FOLDERSYNCSTATUS}) || $statusflag == self::FLD_SYNC_INITIALIZED) { |
|
766 | + // only update if there is a change |
|
767 | + if ((!$currentStatus || (isset($currentStatus->{ASDevice::FOLDERSYNCSTATUS}) && $statusflag !== $currentStatus->{ASDevice::FOLDERSYNCSTATUS})) && |
|
768 | + $statusflag != self::FLD_SYNC_COMPLETED) { |
|
769 | + $this->device->SetFolderSyncStatus($folderid, $statusflag); |
|
770 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("SetFolderSyncStatus(): set %s for %s", $statusflag, $folderid)); |
|
771 | + } |
|
772 | + // if completed, remove the status |
|
773 | + elseif ($statusflag == self::FLD_SYNC_COMPLETED) { |
|
774 | + $this->device->SetFolderSyncStatus($folderid, false); |
|
775 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("SetFolderSyncStatus(): completed for %s", $folderid)); |
|
776 | + } |
|
777 | + } |
|
778 | + |
|
779 | + return true; |
|
780 | + } |
|
781 | + |
|
782 | + /** |
|
783 | + * Indicates if a folder is synchronizing by the saved status. |
|
784 | + * |
|
785 | + * @param string $folderid folder id |
|
786 | + * |
|
787 | + * @return bool |
|
788 | + */ |
|
789 | + public function HasFolderSyncStatus($folderid) { |
|
790 | + $currentStatus = $this->device->GetFolderSyncStatus($folderid); |
|
791 | + |
|
792 | + // status available ? |
|
793 | + $hasStatus = isset($currentStatus->{ASDevice::FOLDERSYNCSTATUS}); |
|
794 | + if ($hasStatus) { |
|
795 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("HasFolderSyncStatus(): saved folder status for %s: %s", $folderid, $currentStatus->{ASDevice::FOLDERSYNCSTATUS})); |
|
796 | + } |
|
797 | + |
|
798 | + return $hasStatus; |
|
799 | + } |
|
800 | + |
|
801 | + /** |
|
802 | + * Returns the indicator if the FolderSync was completed successfully (all folders synchronized). |
|
803 | + * |
|
804 | + * @return bool |
|
805 | + */ |
|
806 | + public function GetFolderSyncComplete() { |
|
807 | + return $this->device->GetFolderSyncComplete(); |
|
808 | + } |
|
809 | + |
|
810 | + /** |
|
811 | + * Sets if the FolderSync was completed successfully (all folders synchronized). |
|
812 | + * |
|
813 | + * @param bool $complete indicating if all folders were sent |
|
814 | + * @param mixed $user |
|
815 | + * @param mixed $devid |
|
816 | + * |
|
817 | + * @return bool |
|
818 | + */ |
|
819 | + public function SetFolderSyncComplete($complete, $user = false, $devid = false) { |
|
820 | + $this->device->SetFolderSyncComplete($complete); |
|
821 | + |
|
822 | + return true; |
|
823 | + } |
|
824 | + |
|
825 | + /** |
|
826 | + * Removes the Loop detection data for a user & device. |
|
827 | + * |
|
828 | + * @param string $user |
|
829 | + * @param string $devid |
|
830 | + * |
|
831 | + * @return bool |
|
832 | + */ |
|
833 | + public function ClearLoopDetectionData($user, $devid) { |
|
834 | + if ($user == false || $devid == false) { |
|
835 | + return false; |
|
836 | + } |
|
837 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->ClearLoopDetectionData(): clearing data for user '%s' and device '%s'", $user, $devid)); |
|
838 | + |
|
839 | + return $this->loopdetection->ClearData($user, $devid); |
|
840 | + } |
|
841 | + |
|
842 | + /** |
|
843 | + * Indicates if the device needs an AS version update. |
|
844 | + * |
|
845 | + * @return bool |
|
846 | + */ |
|
847 | + public function AnnounceASVersion() { |
|
848 | + $latest = GSync::GetSupportedASVersion(); |
|
849 | + $announced = $this->device->GetAnnouncedASversion(); |
|
850 | + $this->device->SetAnnouncedASversion($latest); |
|
851 | + |
|
852 | + return $announced != $latest; |
|
853 | + } |
|
854 | + |
|
855 | + /** |
|
856 | + * Returns the User Agent. This data is consolidated with data from Request::GetUserAgent() |
|
857 | + * and the data saved in the ASDevice. |
|
858 | + * |
|
859 | + * @return string |
|
860 | + */ |
|
861 | + public function GetUserAgent() { |
|
862 | + return $this->device->GetDeviceUserAgent(); |
|
863 | + } |
|
864 | + |
|
865 | + /** |
|
866 | + * Returns the backend folder id from the AS folderid known to the mobile. |
|
867 | + * If the id is not known, it's returned as is. |
|
868 | + * |
|
869 | + * @param mixed $folderid |
|
870 | + * |
|
871 | + * @return int/boolean returns false if the type is not set |
|
872 | + */ |
|
873 | + public function GetBackendIdForFolderId($folderid) { |
|
874 | + $backendId = $this->device->GetFolderBackendId($folderid); |
|
875 | + if (!$backendId) { |
|
876 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetBackendIdForFolderId(): no backend-folderid available for '%s', returning as is.", $folderid)); |
|
877 | + |
|
878 | + return $folderid; |
|
879 | + } |
|
880 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetBackendIdForFolderId(): folderid %s => %s", $folderid, $backendId)); |
|
881 | + |
|
882 | + return $backendId; |
|
883 | + } |
|
884 | + |
|
885 | + /** |
|
886 | + * Gets the AS folderid for a backendFolderId. |
|
887 | + * If there is no known AS folderId a new one is being created. |
|
888 | + * |
|
889 | + * @param string $backendid Backend folder id |
|
890 | + * @param bool $generateNewIdIfNew generates a new AS folderid for the case the backend folder is not known yet, default: false |
|
891 | + * @param string $folderOrigin Folder type is one of 'U' (user) |
|
892 | + * 'C' (configured) |
|
893 | + * 'S' (shared) |
|
894 | + * 'G' (global address book) |
|
895 | + * 'I' (impersonated) |
|
896 | + * @param string $folderName Folder name of the backend folder |
|
897 | + * |
|
898 | + * @return string/boolean returns false if there is folderid known for this backendid and $generateNewIdIfNew is not set or false |
|
899 | + */ |
|
900 | + public function GetFolderIdForBackendId($backendid, $generateNewIdIfNew = false, $folderOrigin = self::FLD_ORIGIN_USER, $folderName = null) { |
|
901 | + if (!in_array($folderOrigin, [DeviceManager::FLD_ORIGIN_CONFIG, DeviceManager::FLD_ORIGIN_GAB, DeviceManager::FLD_ORIGIN_SHARED, DeviceManager::FLD_ORIGIN_USER, DeviceManager::FLD_ORIGIN_IMPERSONATED])) { |
|
902 | + SLog::Write(LOGLEVEL_WARN, sprintf("ASDevice->GetFolderIdForBackendId(): folder type '%s' is unknown in DeviceManager", $folderOrigin)); |
|
903 | + } |
|
904 | + |
|
905 | + return $this->device->GetFolderIdForBackendId($backendid, $generateNewIdIfNew, $folderOrigin, $folderName); |
|
906 | + } |
|
907 | + |
|
908 | + /*---------------------------------------------------------------------------------------------------------- |
|
909 | 909 | * private DeviceManager methods |
910 | 910 | */ |
911 | 911 | |
912 | - /** |
|
913 | - * Loads devicedata from the StateMachine and loads it into the device. |
|
914 | - * |
|
915 | - * @return bool |
|
916 | - */ |
|
917 | - private function loadDeviceData() { |
|
918 | - if (!Request::IsValidDeviceID()) { |
|
919 | - return false; |
|
920 | - } |
|
921 | - |
|
922 | - try { |
|
923 | - $deviceHash = $this->statemachine->GetStateHash(self::$devid, IStateMachine::DEVICEDATA); |
|
924 | - if ($deviceHash != $this->deviceHash) { |
|
925 | - if ($this->deviceHash) { |
|
926 | - SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->loadDeviceData(): Device data was changed, reloading"); |
|
927 | - } |
|
928 | - $device = $this->statemachine->GetState(self::$devid, IStateMachine::DEVICEDATA); |
|
929 | - // TODO: case should be removed when removing ASDevice backwards compatibility |
|
930 | - // fallback for old grosync like devicedata |
|
931 | - if (($device instanceof StateObject) && isset($device->devices) && is_array($device->devices)) { |
|
932 | - SLog::Write(LOGLEVEL_INFO, "Found old style device, converting..."); |
|
933 | - list($_deviceuser, $_domain) = Utils::SplitDomainUser(Request::GetGETUser()); |
|
934 | - if (!isset($device->data->devices[$_deviceuser])) { |
|
935 | - SLog::Write(LOGLEVEL_INFO, "Using old style device for this request and updating when concluding"); |
|
936 | - $device = $device->devices[$_deviceuser]; |
|
937 | - $device->lastupdatetime = time(); |
|
938 | - } |
|
939 | - else { |
|
940 | - SLog::Write(LOGLEVEL_WARN, sprintf("Could not find '%s' in device state. Dropping previous device state!", $_deviceuser)); |
|
941 | - } |
|
942 | - } |
|
943 | - if (method_exists($device, 'LoadedDevice')) { |
|
944 | - $this->device = $device; |
|
945 | - $this->device->LoadedDevice(); |
|
946 | - $this->deviceHash = $deviceHash; |
|
947 | - } |
|
948 | - else { |
|
949 | - SLog::Write(LOGLEVEL_WARN, "Loaded device is not a device object. Dropping new loaded state and keeping initialized object!"); |
|
950 | - } |
|
951 | - $this->stateManager->SetDevice($this->device); |
|
952 | - } |
|
953 | - } |
|
954 | - catch (StateNotFoundException $snfex) { |
|
955 | - $this->hierarchySyncRequired = true; |
|
956 | - } |
|
957 | - catch (UnavailableException $uaex) { |
|
958 | - // This is temporary and can be ignored e.g. in PING - see https://jira.z-hub.io/browse/ZP-1054 |
|
959 | - // If the hash was not available before we treat it like a StateNotFoundException. |
|
960 | - if ($this->deviceHash === false) { |
|
961 | - $this->hierarchySyncRequired = true; |
|
962 | - } |
|
963 | - } |
|
964 | - |
|
965 | - return true; |
|
966 | - } |
|
967 | - |
|
968 | - /** |
|
969 | - * Called when a SyncObject is not being streamed to the mobile. |
|
970 | - * The user can be informed so he knows about this issue. |
|
971 | - * |
|
972 | - * @param string $folderid id of the parent folder (may be false if unknown) |
|
973 | - * @param string $id message id |
|
974 | - * @param SyncObject $message the broken message |
|
975 | - * @param string $reason (self::MSG_BROKEN_UNKNOWN, self::MSG_BROKEN_CAUSINGLOOP, self::MSG_BROKEN_SEMANTICERR) |
|
976 | - * |
|
977 | - * @return bool |
|
978 | - */ |
|
979 | - public function AnnounceIgnoredMessage($folderid, $id, SyncObject $message, $reason = self::MSG_BROKEN_UNKNOWN) { |
|
980 | - if ($folderid === false) { |
|
981 | - $folderid = $this->getLatestFolder(); |
|
982 | - } |
|
983 | - |
|
984 | - $class = get_class($message); |
|
985 | - |
|
986 | - $brokenMessage = new StateObject(); |
|
987 | - $brokenMessage->id = $id; |
|
988 | - $brokenMessage->folderid = $folderid; |
|
989 | - $brokenMessage->ASClass = $class; |
|
990 | - $brokenMessage->folderid = $folderid; |
|
991 | - $brokenMessage->reasonCode = $reason; |
|
992 | - $brokenMessage->reasonString = 'unknown cause'; |
|
993 | - $brokenMessage->timestamp = time(); |
|
994 | - $info = ""; |
|
995 | - if (isset($message->subject)) { |
|
996 | - $info .= sprintf("Subject: '%s'", $message->subject); |
|
997 | - } |
|
998 | - if (isset($message->fileas)) { |
|
999 | - $info .= sprintf("FileAs: '%s'", $message->fileas); |
|
1000 | - } |
|
1001 | - if (isset($message->from)) { |
|
1002 | - $info .= sprintf(" - From: '%s'", $message->from); |
|
1003 | - } |
|
1004 | - if (isset($message->starttime)) { |
|
1005 | - $info .= sprintf(" - On: '%s'", strftime("%Y-%m-%d %H:%M", $message->starttime)); |
|
1006 | - } |
|
1007 | - $brokenMessage->info = $info; |
|
1008 | - $brokenMessage->reasonString = SLog::GetLastMessage(LOGLEVEL_WARN); |
|
1009 | - |
|
1010 | - $this->device->AddIgnoredMessage($brokenMessage); |
|
1011 | - |
|
1012 | - SLog::Write(LOGLEVEL_ERROR, sprintf("Ignored broken message (%s). Reason: '%s' Folderid: '%s' message id '%s'", $class, $reason, $folderid, $id)); |
|
1013 | - |
|
1014 | - return true; |
|
1015 | - } |
|
1016 | - |
|
1017 | - /** |
|
1018 | - * Called when a SyncObject was streamed to the mobile. |
|
1019 | - * If the message could not be sent before this data is obsolete. |
|
1020 | - * |
|
1021 | - * @param string $folderid id of the parent folder |
|
1022 | - * @param string $id message id |
|
1023 | - * |
|
1024 | - * @return bool returns true if the message was ignored before |
|
1025 | - */ |
|
1026 | - private function announceAcceptedMessage($folderid, $id) { |
|
1027 | - if ($this->device->RemoveIgnoredMessage($folderid, $id)) { |
|
1028 | - SLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->announceAcceptedMessage('%s', '%s'): cleared previously ignored message as message is successfully streamed", $folderid, $id)); |
|
1029 | - |
|
1030 | - return true; |
|
1031 | - } |
|
1032 | - |
|
1033 | - return false; |
|
1034 | - } |
|
1035 | - |
|
1036 | - /** |
|
1037 | - * Checks if there were broken messages streamed to the mobile. |
|
1038 | - * If the sync completes/continues without further errors they are marked as accepted. |
|
1039 | - * |
|
1040 | - * @param string $folderid folderid which is to be checked |
|
1041 | - * |
|
1042 | - * @return bool |
|
1043 | - */ |
|
1044 | - private function checkBrokenMessages($folderid) { |
|
1045 | - // check for correctly synchronized messages of the folder |
|
1046 | - foreach ($this->loopdetection->GetSyncedButBeforeIgnoredMessages($folderid) as $okID) { |
|
1047 | - $this->announceAcceptedMessage($folderid, $okID); |
|
1048 | - } |
|
1049 | - |
|
1050 | - return true; |
|
1051 | - } |
|
1052 | - |
|
1053 | - /** |
|
1054 | - * Setter for the latest folder id |
|
1055 | - * on multi-folder operations of AS 14 this is used to set the new current folder id. |
|
1056 | - * |
|
1057 | - * @param string $folderid the current folder |
|
1058 | - * |
|
1059 | - * @return bool |
|
1060 | - */ |
|
1061 | - private function setLatestFolder($folderid) { |
|
1062 | - // this is a multi folder operation |
|
1063 | - // check on ignoredmessages before discaring the folderid |
|
1064 | - if ($this->latestFolder !== false) { |
|
1065 | - $this->checkBrokenMessages($this->latestFolder); |
|
1066 | - } |
|
1067 | - |
|
1068 | - $this->latestFolder = $folderid; |
|
1069 | - |
|
1070 | - return true; |
|
1071 | - } |
|
1072 | - |
|
1073 | - /** |
|
1074 | - * Getter for the latest folder id. |
|
1075 | - * |
|
1076 | - * @return string $folderid the current folder |
|
1077 | - */ |
|
1078 | - private function getLatestFolder() { |
|
1079 | - return $this->latestFolder; |
|
1080 | - } |
|
1081 | - |
|
1082 | - /** |
|
1083 | - * Generates and SyncFolder object and returns it. |
|
1084 | - * |
|
1085 | - * @param string $store |
|
1086 | - * @param string $folderid |
|
1087 | - * @param string $name |
|
1088 | - * @param int $type |
|
1089 | - * @param int $flags |
|
1090 | - * @param string $folderOrigin |
|
1091 | - * @param mixed $parentid |
|
1092 | - * |
|
1093 | - * @returns SyncFolder |
|
1094 | - */ |
|
1095 | - public function BuildSyncFolderObject($store, $folderid, $parentid, $name, $type, $flags, $folderOrigin) { |
|
1096 | - $folder = new SyncFolder(); |
|
1097 | - $folder->BackendId = $folderid; |
|
1098 | - $folder->serverid = $this->GetFolderIdForBackendId($folder->BackendId, true, $folderOrigin, $name); |
|
1099 | - $folder->parentid = $this->GetFolderIdForBackendId($parentid); |
|
1100 | - $folder->displayname = $name; |
|
1101 | - $folder->type = $type; |
|
1102 | - // save store as custom property which is not streamed directly to the device |
|
1103 | - $folder->NoBackendFolder = true; |
|
1104 | - $folder->Store = $store; |
|
1105 | - $folder->Flags = $flags; |
|
1106 | - |
|
1107 | - return $folder; |
|
1108 | - } |
|
1109 | - |
|
1110 | - /** |
|
1111 | - * Returns the device id. |
|
1112 | - * |
|
1113 | - * @return string |
|
1114 | - */ |
|
1115 | - public function GetDevid() { |
|
1116 | - return self::$devid; |
|
1117 | - } |
|
912 | + /** |
|
913 | + * Loads devicedata from the StateMachine and loads it into the device. |
|
914 | + * |
|
915 | + * @return bool |
|
916 | + */ |
|
917 | + private function loadDeviceData() { |
|
918 | + if (!Request::IsValidDeviceID()) { |
|
919 | + return false; |
|
920 | + } |
|
921 | + |
|
922 | + try { |
|
923 | + $deviceHash = $this->statemachine->GetStateHash(self::$devid, IStateMachine::DEVICEDATA); |
|
924 | + if ($deviceHash != $this->deviceHash) { |
|
925 | + if ($this->deviceHash) { |
|
926 | + SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->loadDeviceData(): Device data was changed, reloading"); |
|
927 | + } |
|
928 | + $device = $this->statemachine->GetState(self::$devid, IStateMachine::DEVICEDATA); |
|
929 | + // TODO: case should be removed when removing ASDevice backwards compatibility |
|
930 | + // fallback for old grosync like devicedata |
|
931 | + if (($device instanceof StateObject) && isset($device->devices) && is_array($device->devices)) { |
|
932 | + SLog::Write(LOGLEVEL_INFO, "Found old style device, converting..."); |
|
933 | + list($_deviceuser, $_domain) = Utils::SplitDomainUser(Request::GetGETUser()); |
|
934 | + if (!isset($device->data->devices[$_deviceuser])) { |
|
935 | + SLog::Write(LOGLEVEL_INFO, "Using old style device for this request and updating when concluding"); |
|
936 | + $device = $device->devices[$_deviceuser]; |
|
937 | + $device->lastupdatetime = time(); |
|
938 | + } |
|
939 | + else { |
|
940 | + SLog::Write(LOGLEVEL_WARN, sprintf("Could not find '%s' in device state. Dropping previous device state!", $_deviceuser)); |
|
941 | + } |
|
942 | + } |
|
943 | + if (method_exists($device, 'LoadedDevice')) { |
|
944 | + $this->device = $device; |
|
945 | + $this->device->LoadedDevice(); |
|
946 | + $this->deviceHash = $deviceHash; |
|
947 | + } |
|
948 | + else { |
|
949 | + SLog::Write(LOGLEVEL_WARN, "Loaded device is not a device object. Dropping new loaded state and keeping initialized object!"); |
|
950 | + } |
|
951 | + $this->stateManager->SetDevice($this->device); |
|
952 | + } |
|
953 | + } |
|
954 | + catch (StateNotFoundException $snfex) { |
|
955 | + $this->hierarchySyncRequired = true; |
|
956 | + } |
|
957 | + catch (UnavailableException $uaex) { |
|
958 | + // This is temporary and can be ignored e.g. in PING - see https://jira.z-hub.io/browse/ZP-1054 |
|
959 | + // If the hash was not available before we treat it like a StateNotFoundException. |
|
960 | + if ($this->deviceHash === false) { |
|
961 | + $this->hierarchySyncRequired = true; |
|
962 | + } |
|
963 | + } |
|
964 | + |
|
965 | + return true; |
|
966 | + } |
|
967 | + |
|
968 | + /** |
|
969 | + * Called when a SyncObject is not being streamed to the mobile. |
|
970 | + * The user can be informed so he knows about this issue. |
|
971 | + * |
|
972 | + * @param string $folderid id of the parent folder (may be false if unknown) |
|
973 | + * @param string $id message id |
|
974 | + * @param SyncObject $message the broken message |
|
975 | + * @param string $reason (self::MSG_BROKEN_UNKNOWN, self::MSG_BROKEN_CAUSINGLOOP, self::MSG_BROKEN_SEMANTICERR) |
|
976 | + * |
|
977 | + * @return bool |
|
978 | + */ |
|
979 | + public function AnnounceIgnoredMessage($folderid, $id, SyncObject $message, $reason = self::MSG_BROKEN_UNKNOWN) { |
|
980 | + if ($folderid === false) { |
|
981 | + $folderid = $this->getLatestFolder(); |
|
982 | + } |
|
983 | + |
|
984 | + $class = get_class($message); |
|
985 | + |
|
986 | + $brokenMessage = new StateObject(); |
|
987 | + $brokenMessage->id = $id; |
|
988 | + $brokenMessage->folderid = $folderid; |
|
989 | + $brokenMessage->ASClass = $class; |
|
990 | + $brokenMessage->folderid = $folderid; |
|
991 | + $brokenMessage->reasonCode = $reason; |
|
992 | + $brokenMessage->reasonString = 'unknown cause'; |
|
993 | + $brokenMessage->timestamp = time(); |
|
994 | + $info = ""; |
|
995 | + if (isset($message->subject)) { |
|
996 | + $info .= sprintf("Subject: '%s'", $message->subject); |
|
997 | + } |
|
998 | + if (isset($message->fileas)) { |
|
999 | + $info .= sprintf("FileAs: '%s'", $message->fileas); |
|
1000 | + } |
|
1001 | + if (isset($message->from)) { |
|
1002 | + $info .= sprintf(" - From: '%s'", $message->from); |
|
1003 | + } |
|
1004 | + if (isset($message->starttime)) { |
|
1005 | + $info .= sprintf(" - On: '%s'", strftime("%Y-%m-%d %H:%M", $message->starttime)); |
|
1006 | + } |
|
1007 | + $brokenMessage->info = $info; |
|
1008 | + $brokenMessage->reasonString = SLog::GetLastMessage(LOGLEVEL_WARN); |
|
1009 | + |
|
1010 | + $this->device->AddIgnoredMessage($brokenMessage); |
|
1011 | + |
|
1012 | + SLog::Write(LOGLEVEL_ERROR, sprintf("Ignored broken message (%s). Reason: '%s' Folderid: '%s' message id '%s'", $class, $reason, $folderid, $id)); |
|
1013 | + |
|
1014 | + return true; |
|
1015 | + } |
|
1016 | + |
|
1017 | + /** |
|
1018 | + * Called when a SyncObject was streamed to the mobile. |
|
1019 | + * If the message could not be sent before this data is obsolete. |
|
1020 | + * |
|
1021 | + * @param string $folderid id of the parent folder |
|
1022 | + * @param string $id message id |
|
1023 | + * |
|
1024 | + * @return bool returns true if the message was ignored before |
|
1025 | + */ |
|
1026 | + private function announceAcceptedMessage($folderid, $id) { |
|
1027 | + if ($this->device->RemoveIgnoredMessage($folderid, $id)) { |
|
1028 | + SLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->announceAcceptedMessage('%s', '%s'): cleared previously ignored message as message is successfully streamed", $folderid, $id)); |
|
1029 | + |
|
1030 | + return true; |
|
1031 | + } |
|
1032 | + |
|
1033 | + return false; |
|
1034 | + } |
|
1035 | + |
|
1036 | + /** |
|
1037 | + * Checks if there were broken messages streamed to the mobile. |
|
1038 | + * If the sync completes/continues without further errors they are marked as accepted. |
|
1039 | + * |
|
1040 | + * @param string $folderid folderid which is to be checked |
|
1041 | + * |
|
1042 | + * @return bool |
|
1043 | + */ |
|
1044 | + private function checkBrokenMessages($folderid) { |
|
1045 | + // check for correctly synchronized messages of the folder |
|
1046 | + foreach ($this->loopdetection->GetSyncedButBeforeIgnoredMessages($folderid) as $okID) { |
|
1047 | + $this->announceAcceptedMessage($folderid, $okID); |
|
1048 | + } |
|
1049 | + |
|
1050 | + return true; |
|
1051 | + } |
|
1052 | + |
|
1053 | + /** |
|
1054 | + * Setter for the latest folder id |
|
1055 | + * on multi-folder operations of AS 14 this is used to set the new current folder id. |
|
1056 | + * |
|
1057 | + * @param string $folderid the current folder |
|
1058 | + * |
|
1059 | + * @return bool |
|
1060 | + */ |
|
1061 | + private function setLatestFolder($folderid) { |
|
1062 | + // this is a multi folder operation |
|
1063 | + // check on ignoredmessages before discaring the folderid |
|
1064 | + if ($this->latestFolder !== false) { |
|
1065 | + $this->checkBrokenMessages($this->latestFolder); |
|
1066 | + } |
|
1067 | + |
|
1068 | + $this->latestFolder = $folderid; |
|
1069 | + |
|
1070 | + return true; |
|
1071 | + } |
|
1072 | + |
|
1073 | + /** |
|
1074 | + * Getter for the latest folder id. |
|
1075 | + * |
|
1076 | + * @return string $folderid the current folder |
|
1077 | + */ |
|
1078 | + private function getLatestFolder() { |
|
1079 | + return $this->latestFolder; |
|
1080 | + } |
|
1081 | + |
|
1082 | + /** |
|
1083 | + * Generates and SyncFolder object and returns it. |
|
1084 | + * |
|
1085 | + * @param string $store |
|
1086 | + * @param string $folderid |
|
1087 | + * @param string $name |
|
1088 | + * @param int $type |
|
1089 | + * @param int $flags |
|
1090 | + * @param string $folderOrigin |
|
1091 | + * @param mixed $parentid |
|
1092 | + * |
|
1093 | + * @returns SyncFolder |
|
1094 | + */ |
|
1095 | + public function BuildSyncFolderObject($store, $folderid, $parentid, $name, $type, $flags, $folderOrigin) { |
|
1096 | + $folder = new SyncFolder(); |
|
1097 | + $folder->BackendId = $folderid; |
|
1098 | + $folder->serverid = $this->GetFolderIdForBackendId($folder->BackendId, true, $folderOrigin, $name); |
|
1099 | + $folder->parentid = $this->GetFolderIdForBackendId($parentid); |
|
1100 | + $folder->displayname = $name; |
|
1101 | + $folder->type = $type; |
|
1102 | + // save store as custom property which is not streamed directly to the device |
|
1103 | + $folder->NoBackendFolder = true; |
|
1104 | + $folder->Store = $store; |
|
1105 | + $folder->Flags = $flags; |
|
1106 | + |
|
1107 | + return $folder; |
|
1108 | + } |
|
1109 | + |
|
1110 | + /** |
|
1111 | + * Returns the device id. |
|
1112 | + * |
|
1113 | + * @return string |
|
1114 | + */ |
|
1115 | + public function GetDevid() { |
|
1116 | + return self::$devid; |
|
1117 | + } |
|
1118 | 1118 | } |
@@ -167,7 +167,7 @@ discard block |
||
167 | 167 | } |
168 | 168 | } |
169 | 169 | catch (StateNotFoundException $snfex) { |
170 | - SLog::Write(LOGLEVEL_ERROR, "DeviceManager->Save(): Exception: " . $snfex->getMessage()); |
|
170 | + SLog::Write(LOGLEVEL_ERROR, "DeviceManager->Save(): Exception: ".$snfex->getMessage()); |
|
171 | 171 | } |
172 | 172 | } |
173 | 173 | |
@@ -191,7 +191,7 @@ discard block |
||
191 | 191 | * @param bool $doSave |
192 | 192 | */ |
193 | 193 | public function DoAutomaticASDeviceSaving($doSave) { |
194 | - SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->DoAutomaticASDeviceSaving(): save automatically: " . Utils::PrintAsString($doSave)); |
|
194 | + SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->DoAutomaticASDeviceSaving(): save automatically: ".Utils::PrintAsString($doSave)); |
|
195 | 195 | $this->saveDevice = $doSave; |
196 | 196 | } |
197 | 197 | |
@@ -214,7 +214,7 @@ discard block |
||
214 | 214 | // save other information |
215 | 215 | foreach (["model", "imei", "friendlyname", "os", "oslanguage", "phonenumber", "mobileoperator", "enableoutboundsms"] as $info) { |
216 | 216 | if (isset($deviceinformation->{$info}) && $deviceinformation->{$info} != "") { |
217 | - $this->device->__set("device" . $info, $deviceinformation->{$info}); |
|
217 | + $this->device->__set("device".$info, $deviceinformation->{$info}); |
|
218 | 218 | } |
219 | 219 | } |
220 | 220 | |
@@ -554,14 +554,14 @@ discard block |
||
554 | 554 | } |
555 | 555 | |
556 | 556 | // 2. step: check if there is a limitation for the hashed ID (for shared/configured stores) |
557 | - $spKey = $store . '/' . $folderid; |
|
557 | + $spKey = $store.'/'.$folderid; |
|
558 | 558 | if (array_key_exists($spKey, $specialSyncFilter)) { |
559 | 559 | $spFilter = $specialSyncFilter[$spKey]; |
560 | 560 | SLog::Write(LOGLEVEL_DEBUG, sprintf("Limit sync due to configured limitation on the folder: '%s': %s", $spKey, $spFilter)); |
561 | 561 | } |
562 | 562 | |
563 | 563 | // 3. step: check if there is a limitation for the backendId |
564 | - $spKey = $store . '/' . $backendFolderId; |
|
564 | + $spKey = $store.'/'.$backendFolderId; |
|
565 | 565 | if (array_key_exists($spKey, $specialSyncFilter)) { |
566 | 566 | $spFilter = $specialSyncFilter[$spKey]; |
567 | 567 | SLog::Write(LOGLEVEL_DEBUG, sprintf("Limit sync due to configured limitation on the folder: '%s': %s", $spKey, $spFilter)); |
@@ -74,8 +74,7 @@ discard block |
||
74 | 74 | $this->loadDeviceData(); |
75 | 75 | |
76 | 76 | GSync::GetTopCollector()->SetUserAgent($this->device->GetDeviceUserAgent()); |
77 | - } |
|
78 | - else { |
|
77 | + } else { |
|
79 | 78 | throw new FatalNotImplementedException("Can not proceed without a device id."); |
80 | 79 | } |
81 | 80 | |
@@ -165,8 +164,7 @@ discard block |
||
165 | 164 | $this->setDeviceUserData($this->type, [self::$user => $this->device], self::$devid, -1, $doCas = "merge"); |
166 | 165 | SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->Save(): Device data saved"); |
167 | 166 | } |
168 | - } |
|
169 | - catch (StateNotFoundException $snfex) { |
|
167 | + } catch (StateNotFoundException $snfex) { |
|
170 | 168 | SLog::Write(LOGLEVEL_ERROR, "DeviceManager->Save(): Exception: " . $snfex->getMessage()); |
171 | 169 | } |
172 | 170 | } |
@@ -448,8 +446,7 @@ discard block |
||
448 | 446 | public function GetWindowSize($folderid, $uuid, $statecounter, $queuedmessages) { |
449 | 447 | if (isset($this->windowSize[$folderid])) { |
450 | 448 | $items = $this->windowSize[$folderid]; |
451 | - } |
|
452 | - else { |
|
449 | + } else { |
|
453 | 450 | $items = WINDOW_SIZE_MAX; |
454 | 451 | } // 512 by default |
455 | 452 | |
@@ -460,8 +457,7 @@ discard block |
||
460 | 457 | if ($loop !== false) { |
461 | 458 | if ($loop === true) { |
462 | 459 | $items = ($items == 0) ? 0 : 1 + ($this->loopdetection->IgnoreNextMessage(false) ? 1 : 0); |
463 | - } |
|
464 | - else { |
|
460 | + } else { |
|
465 | 461 | // we got a new suggested window size |
466 | 462 | $items = $loop; |
467 | 463 | SLog::Write(LOGLEVEL_DEBUG, sprintf("Mobile loop pre stage detected! Forcing smaller window size of %d before entering loop detection mode", $items)); |
@@ -543,8 +539,7 @@ discard block |
||
543 | 539 | // do not limit when the owner or impersonated user is syncing! |
544 | 540 | if ($origin == DeviceManager::FLD_ORIGIN_USER || $origin == DeviceManager::FLD_ORIGIN_IMPERSONATED) { |
545 | 541 | SLog::Write(LOGLEVEL_DEBUG, "Not checking for specific sync limit as this is the owner/impersonated user."); |
546 | - } |
|
547 | - else { |
|
542 | + } else { |
|
548 | 543 | $spKey = false; |
549 | 544 | $spFilter = false; |
550 | 545 | // 1. step: check if there is a general limitation for the store |
@@ -935,8 +930,7 @@ discard block |
||
935 | 930 | SLog::Write(LOGLEVEL_INFO, "Using old style device for this request and updating when concluding"); |
936 | 931 | $device = $device->devices[$_deviceuser]; |
937 | 932 | $device->lastupdatetime = time(); |
938 | - } |
|
939 | - else { |
|
933 | + } else { |
|
940 | 934 | SLog::Write(LOGLEVEL_WARN, sprintf("Could not find '%s' in device state. Dropping previous device state!", $_deviceuser)); |
941 | 935 | } |
942 | 936 | } |
@@ -944,17 +938,14 @@ discard block |
||
944 | 938 | $this->device = $device; |
945 | 939 | $this->device->LoadedDevice(); |
946 | 940 | $this->deviceHash = $deviceHash; |
947 | - } |
|
948 | - else { |
|
941 | + } else { |
|
949 | 942 | SLog::Write(LOGLEVEL_WARN, "Loaded device is not a device object. Dropping new loaded state and keeping initialized object!"); |
950 | 943 | } |
951 | 944 | $this->stateManager->SetDevice($this->device); |
952 | 945 | } |
953 | - } |
|
954 | - catch (StateNotFoundException $snfex) { |
|
946 | + } catch (StateNotFoundException $snfex) { |
|
955 | 947 | $this->hierarchySyncRequired = true; |
956 | - } |
|
957 | - catch (UnavailableException $uaex) { |
|
948 | + } catch (UnavailableException $uaex) { |
|
958 | 949 | // This is temporary and can be ignored e.g. in PING - see https://jira.z-hub.io/browse/ZP-1054 |
959 | 950 | // If the hash was not available before we treat it like a StateNotFoundException. |
960 | 951 | if ($this->deviceHash === false) { |
@@ -16,515 +16,515 @@ |
||
16 | 16 | */ |
17 | 17 | |
18 | 18 | class StateManager { |
19 | - public const FIXEDHIERARCHYCOUNTER = 99999; |
|
20 | - |
|
21 | - // backend storage types |
|
22 | - public const BACKENDSTORAGE_PERMANENT = 1; |
|
23 | - public const BACKENDSTORAGE_STATE = 2; |
|
24 | - |
|
25 | - private $statemachine; |
|
26 | - private $device; |
|
27 | - private $hierarchyOperation = false; |
|
28 | - private $deleteOldStates = false; |
|
29 | - |
|
30 | - private $foldertype; |
|
31 | - private $uuid; |
|
32 | - private $oldStateCounter; |
|
33 | - private $newStateCounter; |
|
34 | - private $synchedFolders; |
|
35 | - |
|
36 | - /** |
|
37 | - * Constructor. |
|
38 | - */ |
|
39 | - public function __construct() { |
|
40 | - $this->statemachine = GSync::GetStateMachine(); |
|
41 | - $this->hierarchyOperation = GSync::HierarchyCommand(Request::GetCommandCode()); |
|
42 | - $this->deleteOldStates = (Request::GetCommandCode() === GSync::COMMAND_SYNC || $this->hierarchyOperation); |
|
43 | - $this->synchedFolders = []; |
|
44 | - } |
|
45 | - |
|
46 | - /** |
|
47 | - * Prevents the StateMachine from removing old states. |
|
48 | - */ |
|
49 | - public function DoNotDeleteOldStates() { |
|
50 | - $this->deleteOldStates = false; |
|
51 | - } |
|
52 | - |
|
53 | - /** |
|
54 | - * Sets an ASDevice for the Statemanager to work with. |
|
55 | - * |
|
56 | - * @param ASDevice $device |
|
57 | - * |
|
58 | - * @return bool |
|
59 | - */ |
|
60 | - public function SetDevice(&$device) { |
|
61 | - $this->device = $device; |
|
62 | - |
|
63 | - return true; |
|
64 | - } |
|
65 | - |
|
66 | - /** |
|
67 | - * Returns an array will all synchronized folderids. |
|
68 | - * |
|
69 | - * @return array |
|
70 | - */ |
|
71 | - public function GetSynchedFolders() { |
|
72 | - $synched = []; |
|
73 | - foreach ($this->device->GetAllFolderIds() as $folderid) { |
|
74 | - $uuid = $this->device->GetFolderUUID($folderid); |
|
75 | - if ($uuid) { |
|
76 | - $synched[] = $folderid; |
|
77 | - } |
|
78 | - } |
|
79 | - |
|
80 | - return $synched; |
|
81 | - } |
|
82 | - |
|
83 | - /** |
|
84 | - * Returns a folder state (SyncParameters) for a folder id. |
|
85 | - * |
|
86 | - * @param string $folderid |
|
87 | - * @param bool $fromCacheIfAvailable if set to false, the folderdata is always reloaded, default: true |
|
88 | - * |
|
89 | - * @return SyncParameters |
|
90 | - */ |
|
91 | - public function GetSynchedFolderState($folderid, $fromCacheIfAvailable = true) { |
|
92 | - // new SyncParameters are cached |
|
93 | - if ($fromCacheIfAvailable && isset($this->synchedFolders[$folderid])) { |
|
94 | - return $this->synchedFolders[$folderid]; |
|
95 | - } |
|
96 | - |
|
97 | - $uuid = $this->device->GetFolderUUID($folderid); |
|
98 | - if ($uuid) { |
|
99 | - try { |
|
100 | - $data = $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::FOLDERDATA, $uuid); |
|
101 | - if ($data !== false) { |
|
102 | - $this->synchedFolders[$folderid] = $data; |
|
103 | - } |
|
104 | - } |
|
105 | - catch (StateNotFoundException $ex) { |
|
106 | - } |
|
107 | - } |
|
108 | - |
|
109 | - if (!isset($this->synchedFolders[$folderid])) { |
|
110 | - $this->synchedFolders[$folderid] = new SyncParameters(); |
|
111 | - } |
|
112 | - |
|
113 | - return $this->synchedFolders[$folderid]; |
|
114 | - } |
|
115 | - |
|
116 | - /** |
|
117 | - * Saves a folder state - SyncParameters object. |
|
118 | - * |
|
119 | - * @param SyncParamerters $spa |
|
120 | - * |
|
121 | - * @return bool |
|
122 | - */ |
|
123 | - public function SetSynchedFolderState($spa) { |
|
124 | - // make sure the current uuid is linked on the device for the folder. |
|
125 | - // if not, old states will be automatically removed and the new ones linked |
|
126 | - self::LinkState($this->device, $spa->GetUuid(), $spa->GetFolderId()); |
|
127 | - |
|
128 | - $spa->SetReferencePolicyKey($this->device->GetPolicyKey()); |
|
129 | - |
|
130 | - return $this->statemachine->SetState($spa, $this->device->GetDeviceId(), IStateMachine::FOLDERDATA, $spa->GetUuid()); |
|
131 | - } |
|
132 | - |
|
133 | - /** |
|
134 | - * Gets the new sync key for a specified sync key. The new sync state must be |
|
135 | - * associated to this sync key when calling SetSyncState(). |
|
136 | - * |
|
137 | - * @param string $synckey |
|
138 | - * |
|
139 | - * @return string |
|
140 | - */ |
|
141 | - public function GetNewSyncKey($synckey) { |
|
142 | - if (!isset($synckey) || $synckey == "0" || $synckey == false) { |
|
143 | - $this->uuid = $this->getNewUuid(); |
|
144 | - $this->newStateCounter = 1; |
|
145 | - } |
|
146 | - else { |
|
147 | - list($uuid, $counter) = self::ParseStateKey($synckey); |
|
148 | - $this->uuid = $uuid; |
|
149 | - $this->newStateCounter = $counter + 1; |
|
150 | - } |
|
151 | - |
|
152 | - return self::BuildStateKey($this->uuid, $this->newStateCounter); |
|
153 | - } |
|
154 | - |
|
155 | - /** |
|
156 | - * Returns a counter zero SyncKey. |
|
157 | - * |
|
158 | - * @return string |
|
159 | - */ |
|
160 | - public function GetZeroSyncKey() { |
|
161 | - return self::BuildStateKey($this->getNewUuid(), 0); |
|
162 | - } |
|
163 | - |
|
164 | - /** |
|
165 | - * Gets the state for a specified synckey (uuid + counter). |
|
166 | - * |
|
167 | - * @param string $synckey |
|
168 | - * @param bool $forceHierarchyLoading, default: false |
|
169 | - * |
|
170 | - * @throws StateInvalidException, StateNotFoundException |
|
171 | - * |
|
172 | - * @return string |
|
173 | - */ |
|
174 | - public function GetSyncState($synckey, $forceHierarchyLoading = false) { |
|
175 | - // No sync state for sync key '0' |
|
176 | - if ($synckey == "0") { |
|
177 | - $this->oldStateCounter = 0; |
|
178 | - |
|
179 | - return ""; |
|
180 | - } |
|
181 | - |
|
182 | - // Check if synckey is allowed and set uuid and counter |
|
183 | - list($this->uuid, $this->oldStateCounter) = self::ParseStateKey($synckey); |
|
184 | - |
|
185 | - // make sure the hierarchy cache is in place |
|
186 | - if ($this->hierarchyOperation || $forceHierarchyLoading) { |
|
187 | - $this->loadHierarchyCache($forceHierarchyLoading); |
|
188 | - } |
|
189 | - |
|
190 | - // the state machine will discard any sync states before this one, as they are no longer required |
|
191 | - return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::DEFTYPE, $this->uuid, $this->oldStateCounter, $this->deleteOldStates); |
|
192 | - } |
|
193 | - |
|
194 | - /** |
|
195 | - * Writes the sync state to a new synckey. |
|
196 | - * |
|
197 | - * @param string $synckey |
|
198 | - * @param string $syncstate |
|
199 | - * @param string $folderid (opt) the synckey is associated with the folder - should always be set when performing CONTENT operations |
|
200 | - * |
|
201 | - * @throws StateInvalidException |
|
202 | - * |
|
203 | - * @return bool |
|
204 | - */ |
|
205 | - public function SetSyncState($synckey, $syncstate, $folderid = false) { |
|
206 | - $internalkey = self::BuildStateKey($this->uuid, $this->newStateCounter); |
|
207 | - if ($this->oldStateCounter != 0 && $synckey != $internalkey) { |
|
208 | - throw new StateInvalidException(sprintf("Unexpected synckey value oldcounter: '%s' synckey: '%s' internal key: '%s'", $this->oldStateCounter, $synckey, $internalkey)); |
|
209 | - } |
|
210 | - |
|
211 | - // make sure the hierarchy cache is also saved |
|
212 | - if ($this->hierarchyOperation) { |
|
213 | - $this->saveHierarchyCache(); |
|
214 | - } |
|
215 | - |
|
216 | - // announce this uuid to the device, while old uuid/states should be deleted |
|
217 | - self::LinkState($this->device, $this->uuid, $folderid); |
|
218 | - |
|
219 | - return $this->statemachine->SetState($syncstate, $this->device->GetDeviceId(), IStateMachine::DEFTYPE, $this->uuid, $this->newStateCounter); |
|
220 | - } |
|
221 | - |
|
222 | - /** |
|
223 | - * Gets the failsave sync state for the current synckey. |
|
224 | - * |
|
225 | - * @return array/boolean false if not available |
|
226 | - */ |
|
227 | - public function GetSyncFailState() { |
|
228 | - if (!$this->uuid) { |
|
229 | - return false; |
|
230 | - } |
|
231 | - |
|
232 | - try { |
|
233 | - return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::FAILSAVE, $this->uuid, $this->oldStateCounter, $this->deleteOldStates); |
|
234 | - } |
|
235 | - catch (StateNotFoundException $snfex) { |
|
236 | - return false; |
|
237 | - } |
|
238 | - } |
|
239 | - |
|
240 | - /** |
|
241 | - * Writes the failsave sync state for the current (old) synckey. |
|
242 | - * |
|
243 | - * @param mixed $syncstate |
|
244 | - * |
|
245 | - * @return bool |
|
246 | - */ |
|
247 | - public function SetSyncFailState($syncstate) { |
|
248 | - if ($this->oldStateCounter == 0) { |
|
249 | - return false; |
|
250 | - } |
|
251 | - |
|
252 | - return $this->statemachine->SetState($syncstate, $this->device->GetDeviceId(), IStateMachine::FAILSAVE, $this->uuid, $this->oldStateCounter); |
|
253 | - } |
|
254 | - |
|
255 | - /** |
|
256 | - * Gets the backendstorage data. |
|
257 | - * |
|
258 | - * @param int $type permanent or state related storage |
|
259 | - * |
|
260 | - * @throws StateNotYetAvailableException, StateNotFoundException |
|
261 | - * |
|
262 | - * @return mixed |
|
263 | - */ |
|
264 | - public function GetBackendStorage($type = self::BACKENDSTORAGE_PERMANENT) { |
|
265 | - if ($type == self::BACKENDSTORAGE_STATE) { |
|
266 | - if (!$this->uuid) { |
|
267 | - throw new StateNotYetAvailableException(); |
|
268 | - } |
|
269 | - |
|
270 | - return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, $this->uuid, $this->oldStateCounter, $this->deleteOldStates); |
|
271 | - } |
|
272 | - |
|
273 | - return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, false, $this->device->GetFirstSyncTime(), false); |
|
274 | - } |
|
275 | - |
|
276 | - /** |
|
277 | - * Writes the backendstorage data. |
|
278 | - * |
|
279 | - * @param mixed $data |
|
280 | - * @param int $type permanent or state related storage |
|
281 | - * |
|
282 | - * @throws StateNotYetAvailableException, StateNotFoundException |
|
283 | - * |
|
284 | - * @return int amount of bytes saved |
|
285 | - */ |
|
286 | - public function SetBackendStorage($data, $type = self::BACKENDSTORAGE_PERMANENT) { |
|
287 | - if ($type == self::BACKENDSTORAGE_STATE) { |
|
288 | - if (!$this->uuid) { |
|
289 | - throw new StateNotYetAvailableException(); |
|
290 | - } |
|
291 | - |
|
292 | - // TODO serialization should be done in the StateMachine |
|
293 | - return $this->statemachine->SetState($data, $this->device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, $this->uuid, $this->newStateCounter); |
|
294 | - } |
|
295 | - |
|
296 | - return $this->statemachine->SetState($data, $this->device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, false, $this->device->GetFirstSyncTime()); |
|
297 | - } |
|
298 | - |
|
299 | - /** |
|
300 | - * Initializes the HierarchyCache for legacy syncs |
|
301 | - * this is for AS 1.0 compatibility: |
|
302 | - * save folder information synched with GetHierarchy() |
|
303 | - * handled by StateManager. |
|
304 | - * |
|
305 | - * @param string $folders Array with folder information |
|
306 | - * |
|
307 | - * @return bool |
|
308 | - */ |
|
309 | - public function InitializeFolderCache($folders) { |
|
310 | - if (!is_array($folders)) { |
|
311 | - return false; |
|
312 | - } |
|
313 | - |
|
314 | - if (!isset($this->device)) { |
|
315 | - throw new FatalException("ASDevice not initialized"); |
|
316 | - } |
|
317 | - |
|
318 | - // redeclare this operation as hierarchyOperation |
|
319 | - $this->hierarchyOperation = true; |
|
320 | - |
|
321 | - // as there is no hierarchy uuid, we have to create one |
|
322 | - $this->uuid = $this->getNewUuid(); |
|
323 | - $this->newStateCounter = self::FIXEDHIERARCHYCOUNTER; |
|
324 | - |
|
325 | - // initialize legacy HierarchCache |
|
326 | - $this->device->SetHierarchyCache($folders); |
|
327 | - |
|
328 | - // force saving the hierarchy cache! |
|
329 | - return $this->saveHierarchyCache(true); |
|
330 | - } |
|
331 | - |
|
332 | - /*---------------------------------------------------------------------------------------------------------- |
|
19 | + public const FIXEDHIERARCHYCOUNTER = 99999; |
|
20 | + |
|
21 | + // backend storage types |
|
22 | + public const BACKENDSTORAGE_PERMANENT = 1; |
|
23 | + public const BACKENDSTORAGE_STATE = 2; |
|
24 | + |
|
25 | + private $statemachine; |
|
26 | + private $device; |
|
27 | + private $hierarchyOperation = false; |
|
28 | + private $deleteOldStates = false; |
|
29 | + |
|
30 | + private $foldertype; |
|
31 | + private $uuid; |
|
32 | + private $oldStateCounter; |
|
33 | + private $newStateCounter; |
|
34 | + private $synchedFolders; |
|
35 | + |
|
36 | + /** |
|
37 | + * Constructor. |
|
38 | + */ |
|
39 | + public function __construct() { |
|
40 | + $this->statemachine = GSync::GetStateMachine(); |
|
41 | + $this->hierarchyOperation = GSync::HierarchyCommand(Request::GetCommandCode()); |
|
42 | + $this->deleteOldStates = (Request::GetCommandCode() === GSync::COMMAND_SYNC || $this->hierarchyOperation); |
|
43 | + $this->synchedFolders = []; |
|
44 | + } |
|
45 | + |
|
46 | + /** |
|
47 | + * Prevents the StateMachine from removing old states. |
|
48 | + */ |
|
49 | + public function DoNotDeleteOldStates() { |
|
50 | + $this->deleteOldStates = false; |
|
51 | + } |
|
52 | + |
|
53 | + /** |
|
54 | + * Sets an ASDevice for the Statemanager to work with. |
|
55 | + * |
|
56 | + * @param ASDevice $device |
|
57 | + * |
|
58 | + * @return bool |
|
59 | + */ |
|
60 | + public function SetDevice(&$device) { |
|
61 | + $this->device = $device; |
|
62 | + |
|
63 | + return true; |
|
64 | + } |
|
65 | + |
|
66 | + /** |
|
67 | + * Returns an array will all synchronized folderids. |
|
68 | + * |
|
69 | + * @return array |
|
70 | + */ |
|
71 | + public function GetSynchedFolders() { |
|
72 | + $synched = []; |
|
73 | + foreach ($this->device->GetAllFolderIds() as $folderid) { |
|
74 | + $uuid = $this->device->GetFolderUUID($folderid); |
|
75 | + if ($uuid) { |
|
76 | + $synched[] = $folderid; |
|
77 | + } |
|
78 | + } |
|
79 | + |
|
80 | + return $synched; |
|
81 | + } |
|
82 | + |
|
83 | + /** |
|
84 | + * Returns a folder state (SyncParameters) for a folder id. |
|
85 | + * |
|
86 | + * @param string $folderid |
|
87 | + * @param bool $fromCacheIfAvailable if set to false, the folderdata is always reloaded, default: true |
|
88 | + * |
|
89 | + * @return SyncParameters |
|
90 | + */ |
|
91 | + public function GetSynchedFolderState($folderid, $fromCacheIfAvailable = true) { |
|
92 | + // new SyncParameters are cached |
|
93 | + if ($fromCacheIfAvailable && isset($this->synchedFolders[$folderid])) { |
|
94 | + return $this->synchedFolders[$folderid]; |
|
95 | + } |
|
96 | + |
|
97 | + $uuid = $this->device->GetFolderUUID($folderid); |
|
98 | + if ($uuid) { |
|
99 | + try { |
|
100 | + $data = $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::FOLDERDATA, $uuid); |
|
101 | + if ($data !== false) { |
|
102 | + $this->synchedFolders[$folderid] = $data; |
|
103 | + } |
|
104 | + } |
|
105 | + catch (StateNotFoundException $ex) { |
|
106 | + } |
|
107 | + } |
|
108 | + |
|
109 | + if (!isset($this->synchedFolders[$folderid])) { |
|
110 | + $this->synchedFolders[$folderid] = new SyncParameters(); |
|
111 | + } |
|
112 | + |
|
113 | + return $this->synchedFolders[$folderid]; |
|
114 | + } |
|
115 | + |
|
116 | + /** |
|
117 | + * Saves a folder state - SyncParameters object. |
|
118 | + * |
|
119 | + * @param SyncParamerters $spa |
|
120 | + * |
|
121 | + * @return bool |
|
122 | + */ |
|
123 | + public function SetSynchedFolderState($spa) { |
|
124 | + // make sure the current uuid is linked on the device for the folder. |
|
125 | + // if not, old states will be automatically removed and the new ones linked |
|
126 | + self::LinkState($this->device, $spa->GetUuid(), $spa->GetFolderId()); |
|
127 | + |
|
128 | + $spa->SetReferencePolicyKey($this->device->GetPolicyKey()); |
|
129 | + |
|
130 | + return $this->statemachine->SetState($spa, $this->device->GetDeviceId(), IStateMachine::FOLDERDATA, $spa->GetUuid()); |
|
131 | + } |
|
132 | + |
|
133 | + /** |
|
134 | + * Gets the new sync key for a specified sync key. The new sync state must be |
|
135 | + * associated to this sync key when calling SetSyncState(). |
|
136 | + * |
|
137 | + * @param string $synckey |
|
138 | + * |
|
139 | + * @return string |
|
140 | + */ |
|
141 | + public function GetNewSyncKey($synckey) { |
|
142 | + if (!isset($synckey) || $synckey == "0" || $synckey == false) { |
|
143 | + $this->uuid = $this->getNewUuid(); |
|
144 | + $this->newStateCounter = 1; |
|
145 | + } |
|
146 | + else { |
|
147 | + list($uuid, $counter) = self::ParseStateKey($synckey); |
|
148 | + $this->uuid = $uuid; |
|
149 | + $this->newStateCounter = $counter + 1; |
|
150 | + } |
|
151 | + |
|
152 | + return self::BuildStateKey($this->uuid, $this->newStateCounter); |
|
153 | + } |
|
154 | + |
|
155 | + /** |
|
156 | + * Returns a counter zero SyncKey. |
|
157 | + * |
|
158 | + * @return string |
|
159 | + */ |
|
160 | + public function GetZeroSyncKey() { |
|
161 | + return self::BuildStateKey($this->getNewUuid(), 0); |
|
162 | + } |
|
163 | + |
|
164 | + /** |
|
165 | + * Gets the state for a specified synckey (uuid + counter). |
|
166 | + * |
|
167 | + * @param string $synckey |
|
168 | + * @param bool $forceHierarchyLoading, default: false |
|
169 | + * |
|
170 | + * @throws StateInvalidException, StateNotFoundException |
|
171 | + * |
|
172 | + * @return string |
|
173 | + */ |
|
174 | + public function GetSyncState($synckey, $forceHierarchyLoading = false) { |
|
175 | + // No sync state for sync key '0' |
|
176 | + if ($synckey == "0") { |
|
177 | + $this->oldStateCounter = 0; |
|
178 | + |
|
179 | + return ""; |
|
180 | + } |
|
181 | + |
|
182 | + // Check if synckey is allowed and set uuid and counter |
|
183 | + list($this->uuid, $this->oldStateCounter) = self::ParseStateKey($synckey); |
|
184 | + |
|
185 | + // make sure the hierarchy cache is in place |
|
186 | + if ($this->hierarchyOperation || $forceHierarchyLoading) { |
|
187 | + $this->loadHierarchyCache($forceHierarchyLoading); |
|
188 | + } |
|
189 | + |
|
190 | + // the state machine will discard any sync states before this one, as they are no longer required |
|
191 | + return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::DEFTYPE, $this->uuid, $this->oldStateCounter, $this->deleteOldStates); |
|
192 | + } |
|
193 | + |
|
194 | + /** |
|
195 | + * Writes the sync state to a new synckey. |
|
196 | + * |
|
197 | + * @param string $synckey |
|
198 | + * @param string $syncstate |
|
199 | + * @param string $folderid (opt) the synckey is associated with the folder - should always be set when performing CONTENT operations |
|
200 | + * |
|
201 | + * @throws StateInvalidException |
|
202 | + * |
|
203 | + * @return bool |
|
204 | + */ |
|
205 | + public function SetSyncState($synckey, $syncstate, $folderid = false) { |
|
206 | + $internalkey = self::BuildStateKey($this->uuid, $this->newStateCounter); |
|
207 | + if ($this->oldStateCounter != 0 && $synckey != $internalkey) { |
|
208 | + throw new StateInvalidException(sprintf("Unexpected synckey value oldcounter: '%s' synckey: '%s' internal key: '%s'", $this->oldStateCounter, $synckey, $internalkey)); |
|
209 | + } |
|
210 | + |
|
211 | + // make sure the hierarchy cache is also saved |
|
212 | + if ($this->hierarchyOperation) { |
|
213 | + $this->saveHierarchyCache(); |
|
214 | + } |
|
215 | + |
|
216 | + // announce this uuid to the device, while old uuid/states should be deleted |
|
217 | + self::LinkState($this->device, $this->uuid, $folderid); |
|
218 | + |
|
219 | + return $this->statemachine->SetState($syncstate, $this->device->GetDeviceId(), IStateMachine::DEFTYPE, $this->uuid, $this->newStateCounter); |
|
220 | + } |
|
221 | + |
|
222 | + /** |
|
223 | + * Gets the failsave sync state for the current synckey. |
|
224 | + * |
|
225 | + * @return array/boolean false if not available |
|
226 | + */ |
|
227 | + public function GetSyncFailState() { |
|
228 | + if (!$this->uuid) { |
|
229 | + return false; |
|
230 | + } |
|
231 | + |
|
232 | + try { |
|
233 | + return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::FAILSAVE, $this->uuid, $this->oldStateCounter, $this->deleteOldStates); |
|
234 | + } |
|
235 | + catch (StateNotFoundException $snfex) { |
|
236 | + return false; |
|
237 | + } |
|
238 | + } |
|
239 | + |
|
240 | + /** |
|
241 | + * Writes the failsave sync state for the current (old) synckey. |
|
242 | + * |
|
243 | + * @param mixed $syncstate |
|
244 | + * |
|
245 | + * @return bool |
|
246 | + */ |
|
247 | + public function SetSyncFailState($syncstate) { |
|
248 | + if ($this->oldStateCounter == 0) { |
|
249 | + return false; |
|
250 | + } |
|
251 | + |
|
252 | + return $this->statemachine->SetState($syncstate, $this->device->GetDeviceId(), IStateMachine::FAILSAVE, $this->uuid, $this->oldStateCounter); |
|
253 | + } |
|
254 | + |
|
255 | + /** |
|
256 | + * Gets the backendstorage data. |
|
257 | + * |
|
258 | + * @param int $type permanent or state related storage |
|
259 | + * |
|
260 | + * @throws StateNotYetAvailableException, StateNotFoundException |
|
261 | + * |
|
262 | + * @return mixed |
|
263 | + */ |
|
264 | + public function GetBackendStorage($type = self::BACKENDSTORAGE_PERMANENT) { |
|
265 | + if ($type == self::BACKENDSTORAGE_STATE) { |
|
266 | + if (!$this->uuid) { |
|
267 | + throw new StateNotYetAvailableException(); |
|
268 | + } |
|
269 | + |
|
270 | + return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, $this->uuid, $this->oldStateCounter, $this->deleteOldStates); |
|
271 | + } |
|
272 | + |
|
273 | + return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, false, $this->device->GetFirstSyncTime(), false); |
|
274 | + } |
|
275 | + |
|
276 | + /** |
|
277 | + * Writes the backendstorage data. |
|
278 | + * |
|
279 | + * @param mixed $data |
|
280 | + * @param int $type permanent or state related storage |
|
281 | + * |
|
282 | + * @throws StateNotYetAvailableException, StateNotFoundException |
|
283 | + * |
|
284 | + * @return int amount of bytes saved |
|
285 | + */ |
|
286 | + public function SetBackendStorage($data, $type = self::BACKENDSTORAGE_PERMANENT) { |
|
287 | + if ($type == self::BACKENDSTORAGE_STATE) { |
|
288 | + if (!$this->uuid) { |
|
289 | + throw new StateNotYetAvailableException(); |
|
290 | + } |
|
291 | + |
|
292 | + // TODO serialization should be done in the StateMachine |
|
293 | + return $this->statemachine->SetState($data, $this->device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, $this->uuid, $this->newStateCounter); |
|
294 | + } |
|
295 | + |
|
296 | + return $this->statemachine->SetState($data, $this->device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, false, $this->device->GetFirstSyncTime()); |
|
297 | + } |
|
298 | + |
|
299 | + /** |
|
300 | + * Initializes the HierarchyCache for legacy syncs |
|
301 | + * this is for AS 1.0 compatibility: |
|
302 | + * save folder information synched with GetHierarchy() |
|
303 | + * handled by StateManager. |
|
304 | + * |
|
305 | + * @param string $folders Array with folder information |
|
306 | + * |
|
307 | + * @return bool |
|
308 | + */ |
|
309 | + public function InitializeFolderCache($folders) { |
|
310 | + if (!is_array($folders)) { |
|
311 | + return false; |
|
312 | + } |
|
313 | + |
|
314 | + if (!isset($this->device)) { |
|
315 | + throw new FatalException("ASDevice not initialized"); |
|
316 | + } |
|
317 | + |
|
318 | + // redeclare this operation as hierarchyOperation |
|
319 | + $this->hierarchyOperation = true; |
|
320 | + |
|
321 | + // as there is no hierarchy uuid, we have to create one |
|
322 | + $this->uuid = $this->getNewUuid(); |
|
323 | + $this->newStateCounter = self::FIXEDHIERARCHYCOUNTER; |
|
324 | + |
|
325 | + // initialize legacy HierarchCache |
|
326 | + $this->device->SetHierarchyCache($folders); |
|
327 | + |
|
328 | + // force saving the hierarchy cache! |
|
329 | + return $this->saveHierarchyCache(true); |
|
330 | + } |
|
331 | + |
|
332 | + /*---------------------------------------------------------------------------------------------------------- |
|
333 | 333 | * static StateManager methods |
334 | 334 | */ |
335 | 335 | |
336 | - /** |
|
337 | - * Links a folderid to the a UUID |
|
338 | - * Old states are removed if an folderid is linked to a new UUID |
|
339 | - * assisting the StateMachine to get rid of old data. |
|
340 | - * |
|
341 | - * @param ASDevice $device |
|
342 | - * @param string $uuid the uuid to link to |
|
343 | - * @param string $folderid (opt) if not set, hierarchy state is linked |
|
344 | - * @param mixed $newUuid |
|
345 | - * |
|
346 | - * @return bool |
|
347 | - */ |
|
348 | - public static function LinkState(&$device, $newUuid, $folderid = false) { |
|
349 | - $savedUuid = $device->GetFolderUUID($folderid); |
|
350 | - // delete 'old' states! |
|
351 | - if ($savedUuid != $newUuid) { |
|
352 | - // remove states but no need to notify device |
|
353 | - self::UnLinkState($device, $folderid, false); |
|
354 | - |
|
355 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("StateManager::linkState(#ASDevice, '%s','%s'): linked to uuid '%s'.", $newUuid, (($folderid === false) ? 'HierarchyCache' : $folderid), $newUuid)); |
|
356 | - |
|
357 | - return $device->SetFolderUUID($newUuid, $folderid); |
|
358 | - } |
|
359 | - |
|
360 | - return true; |
|
361 | - } |
|
362 | - |
|
363 | - /** |
|
364 | - * UnLinks all states from a folder id |
|
365 | - * Old states are removed assisting the StateMachine to get rid of old data. |
|
366 | - * The UUID is then removed from the device. |
|
367 | - * |
|
368 | - * @param ASDevice $device |
|
369 | - * @param string $folderid |
|
370 | - * @param bool $removeFromDevice indicates if the device should be |
|
371 | - * notified that the state was removed |
|
372 | - * @param bool $retrieveUUIDFromDevice indicates if the UUID should be retrieved from |
|
373 | - * device. If not true this parameter will be used as UUID. |
|
374 | - * |
|
375 | - * @return bool |
|
376 | - */ |
|
377 | - public static function UnLinkState(&$device, $folderid, $removeFromDevice = true, $retrieveUUIDFromDevice = true) { |
|
378 | - if ($retrieveUUIDFromDevice === true) { |
|
379 | - $savedUuid = $device->GetFolderUUID($folderid); |
|
380 | - } |
|
381 | - else { |
|
382 | - $savedUuid = $retrieveUUIDFromDevice; |
|
383 | - } |
|
384 | - |
|
385 | - if ($savedUuid) { |
|
386 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("StateManager::UnLinkState('%s'): saved state '%s' will be deleted.", $folderid, $savedUuid)); |
|
387 | - GSync::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::DEFTYPE, $savedUuid, self::FIXEDHIERARCHYCOUNTER * 2); |
|
388 | - GSync::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::FOLDERDATA, $savedUuid, false, true); // CPO |
|
389 | - GSync::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::FAILSAVE, $savedUuid, self::FIXEDHIERARCHYCOUNTER * 2); |
|
390 | - GSync::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, $savedUuid, self::FIXEDHIERARCHYCOUNTER * 2); |
|
391 | - |
|
392 | - // remove all messages which could not be synched before |
|
393 | - $device->RemoveIgnoredMessage($folderid, false); |
|
394 | - |
|
395 | - if ($folderid === false && $savedUuid !== false) { |
|
396 | - GSync::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::HIERARCHY, $savedUuid, self::FIXEDHIERARCHYCOUNTER * 2); |
|
397 | - } |
|
398 | - } |
|
399 | - // delete this id from the uuid cache |
|
400 | - if ($removeFromDevice) { |
|
401 | - return $device->SetFolderUUID(false, $folderid); |
|
402 | - } |
|
403 | - |
|
404 | - return true; |
|
405 | - } |
|
406 | - |
|
407 | - /** |
|
408 | - * Parses a SyncKey and returns UUID and counter. |
|
409 | - * |
|
410 | - * @param string $synckey |
|
411 | - * |
|
412 | - * @throws StateInvalidException |
|
413 | - * |
|
414 | - * @return array uuid, counter |
|
415 | - */ |
|
416 | - public static function ParseStateKey($synckey) { |
|
417 | - $matches = []; |
|
418 | - if (!preg_match('/^\{([0-9A-Za-z-]+)\}([0-9]+)$/', $synckey, $matches)) { |
|
419 | - throw new StateInvalidException(sprintf("SyncKey '%s' is invalid", $synckey)); |
|
420 | - } |
|
421 | - |
|
422 | - return [$matches[1], (int) $matches[2]]; |
|
423 | - } |
|
424 | - |
|
425 | - /** |
|
426 | - * Builds a SyncKey from a UUID and counter. |
|
427 | - * |
|
428 | - * @param string $uuid |
|
429 | - * @param int $counter |
|
430 | - * |
|
431 | - * @throws StateInvalidException |
|
432 | - * |
|
433 | - * @return string syncKey |
|
434 | - */ |
|
435 | - public static function BuildStateKey($uuid, $counter) { |
|
436 | - if (!preg_match('/^([0-9A-Za-z-]+)$/', $uuid, $matches)) { |
|
437 | - throw new StateInvalidException(sprintf("UUID '%s' is invalid", $uuid)); |
|
438 | - } |
|
439 | - |
|
440 | - return "{" . $uuid . "}" . $counter; |
|
441 | - } |
|
442 | - |
|
443 | - /*---------------------------------------------------------------------------------------------------------- |
|
336 | + /** |
|
337 | + * Links a folderid to the a UUID |
|
338 | + * Old states are removed if an folderid is linked to a new UUID |
|
339 | + * assisting the StateMachine to get rid of old data. |
|
340 | + * |
|
341 | + * @param ASDevice $device |
|
342 | + * @param string $uuid the uuid to link to |
|
343 | + * @param string $folderid (opt) if not set, hierarchy state is linked |
|
344 | + * @param mixed $newUuid |
|
345 | + * |
|
346 | + * @return bool |
|
347 | + */ |
|
348 | + public static function LinkState(&$device, $newUuid, $folderid = false) { |
|
349 | + $savedUuid = $device->GetFolderUUID($folderid); |
|
350 | + // delete 'old' states! |
|
351 | + if ($savedUuid != $newUuid) { |
|
352 | + // remove states but no need to notify device |
|
353 | + self::UnLinkState($device, $folderid, false); |
|
354 | + |
|
355 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("StateManager::linkState(#ASDevice, '%s','%s'): linked to uuid '%s'.", $newUuid, (($folderid === false) ? 'HierarchyCache' : $folderid), $newUuid)); |
|
356 | + |
|
357 | + return $device->SetFolderUUID($newUuid, $folderid); |
|
358 | + } |
|
359 | + |
|
360 | + return true; |
|
361 | + } |
|
362 | + |
|
363 | + /** |
|
364 | + * UnLinks all states from a folder id |
|
365 | + * Old states are removed assisting the StateMachine to get rid of old data. |
|
366 | + * The UUID is then removed from the device. |
|
367 | + * |
|
368 | + * @param ASDevice $device |
|
369 | + * @param string $folderid |
|
370 | + * @param bool $removeFromDevice indicates if the device should be |
|
371 | + * notified that the state was removed |
|
372 | + * @param bool $retrieveUUIDFromDevice indicates if the UUID should be retrieved from |
|
373 | + * device. If not true this parameter will be used as UUID. |
|
374 | + * |
|
375 | + * @return bool |
|
376 | + */ |
|
377 | + public static function UnLinkState(&$device, $folderid, $removeFromDevice = true, $retrieveUUIDFromDevice = true) { |
|
378 | + if ($retrieveUUIDFromDevice === true) { |
|
379 | + $savedUuid = $device->GetFolderUUID($folderid); |
|
380 | + } |
|
381 | + else { |
|
382 | + $savedUuid = $retrieveUUIDFromDevice; |
|
383 | + } |
|
384 | + |
|
385 | + if ($savedUuid) { |
|
386 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("StateManager::UnLinkState('%s'): saved state '%s' will be deleted.", $folderid, $savedUuid)); |
|
387 | + GSync::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::DEFTYPE, $savedUuid, self::FIXEDHIERARCHYCOUNTER * 2); |
|
388 | + GSync::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::FOLDERDATA, $savedUuid, false, true); // CPO |
|
389 | + GSync::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::FAILSAVE, $savedUuid, self::FIXEDHIERARCHYCOUNTER * 2); |
|
390 | + GSync::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, $savedUuid, self::FIXEDHIERARCHYCOUNTER * 2); |
|
391 | + |
|
392 | + // remove all messages which could not be synched before |
|
393 | + $device->RemoveIgnoredMessage($folderid, false); |
|
394 | + |
|
395 | + if ($folderid === false && $savedUuid !== false) { |
|
396 | + GSync::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::HIERARCHY, $savedUuid, self::FIXEDHIERARCHYCOUNTER * 2); |
|
397 | + } |
|
398 | + } |
|
399 | + // delete this id from the uuid cache |
|
400 | + if ($removeFromDevice) { |
|
401 | + return $device->SetFolderUUID(false, $folderid); |
|
402 | + } |
|
403 | + |
|
404 | + return true; |
|
405 | + } |
|
406 | + |
|
407 | + /** |
|
408 | + * Parses a SyncKey and returns UUID and counter. |
|
409 | + * |
|
410 | + * @param string $synckey |
|
411 | + * |
|
412 | + * @throws StateInvalidException |
|
413 | + * |
|
414 | + * @return array uuid, counter |
|
415 | + */ |
|
416 | + public static function ParseStateKey($synckey) { |
|
417 | + $matches = []; |
|
418 | + if (!preg_match('/^\{([0-9A-Za-z-]+)\}([0-9]+)$/', $synckey, $matches)) { |
|
419 | + throw new StateInvalidException(sprintf("SyncKey '%s' is invalid", $synckey)); |
|
420 | + } |
|
421 | + |
|
422 | + return [$matches[1], (int) $matches[2]]; |
|
423 | + } |
|
424 | + |
|
425 | + /** |
|
426 | + * Builds a SyncKey from a UUID and counter. |
|
427 | + * |
|
428 | + * @param string $uuid |
|
429 | + * @param int $counter |
|
430 | + * |
|
431 | + * @throws StateInvalidException |
|
432 | + * |
|
433 | + * @return string syncKey |
|
434 | + */ |
|
435 | + public static function BuildStateKey($uuid, $counter) { |
|
436 | + if (!preg_match('/^([0-9A-Za-z-]+)$/', $uuid, $matches)) { |
|
437 | + throw new StateInvalidException(sprintf("UUID '%s' is invalid", $uuid)); |
|
438 | + } |
|
439 | + |
|
440 | + return "{" . $uuid . "}" . $counter; |
|
441 | + } |
|
442 | + |
|
443 | + /*---------------------------------------------------------------------------------------------------------- |
|
444 | 444 | * private StateManager methods |
445 | 445 | */ |
446 | 446 | |
447 | - /** |
|
448 | - * Loads the HierarchyCacheState and initializes the HierarchyChache |
|
449 | - * if this is an hierarchy operation. |
|
450 | - * |
|
451 | - * @param bool $forceLoading, default: false |
|
452 | - * |
|
453 | - * @throws StateNotFoundException |
|
454 | - * |
|
455 | - * @return bool |
|
456 | - */ |
|
457 | - private function loadHierarchyCache($forceLoading = false) { |
|
458 | - if (!$this->hierarchyOperation && $forceLoading == false) { |
|
459 | - return false; |
|
460 | - } |
|
461 | - |
|
462 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("StateManager->loadHierarchyCache(): '%s-%s-%s-%d'", $this->device->GetDeviceId(), $this->uuid, IStateMachine::HIERARCHY, $this->oldStateCounter)); |
|
463 | - |
|
464 | - // check if a full hierarchy sync might be necessary |
|
465 | - if ($this->device->GetFolderUUID(false) === false) { |
|
466 | - self::UnLinkState($this->device, false, false, $this->uuid); |
|
467 | - |
|
468 | - throw new StateNotFoundException("No hierarchy UUID linked to device. Requesting folder resync."); |
|
469 | - } |
|
470 | - |
|
471 | - $hierarchydata = $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::HIERARCHY, $this->uuid, $this->oldStateCounter, $this->deleteOldStates); |
|
472 | - $this->device->SetHierarchyCache($hierarchydata); |
|
473 | - |
|
474 | - return true; |
|
475 | - } |
|
476 | - |
|
477 | - /** |
|
478 | - * Saves the HierarchyCacheState of the HierarchyChache |
|
479 | - * if this is an hierarchy operation. |
|
480 | - * |
|
481 | - * @param bool $forceLoad indicates if the cache should be saved also if not a hierary operation |
|
482 | - * @param mixed $forceSaving |
|
483 | - * |
|
484 | - * @throws StateInvalidException |
|
485 | - * |
|
486 | - * @return bool |
|
487 | - */ |
|
488 | - private function saveHierarchyCache($forceSaving = false) { |
|
489 | - if (!$this->hierarchyOperation && !$forceSaving) { |
|
490 | - return false; |
|
491 | - } |
|
492 | - |
|
493 | - // link the hierarchy cache again, if the UUID does not match the UUID saved in the devicedata |
|
494 | - if (($this->uuid != $this->device->GetFolderUUID() || $forceSaving)) { |
|
495 | - self::LinkState($this->device, $this->uuid); |
|
496 | - } |
|
497 | - |
|
498 | - // check all folders and deleted folders to update data of ASDevice and delete old states |
|
499 | - $hc = $this->device->getHierarchyCache(); |
|
500 | - foreach ($hc->GetDeletedFolders() as $delfolder) { |
|
501 | - self::UnLinkState($this->device, $delfolder->serverid); |
|
502 | - } |
|
503 | - |
|
504 | - foreach ($hc->ExportFolders() as $folder) { |
|
505 | - $this->device->SetFolderType($folder->serverid, $folder->type); |
|
506 | - $this->device->SetFolderBackendId($folder->serverid, $folder->BackendId); |
|
507 | - } |
|
508 | - |
|
509 | - return $this->statemachine->SetState($this->device->GetHierarchyCacheData(), $this->device->GetDeviceId(), IStateMachine::HIERARCHY, $this->uuid, $this->newStateCounter); |
|
510 | - } |
|
511 | - |
|
512 | - /** |
|
513 | - * Generates a new UUID. |
|
514 | - * |
|
515 | - * @return string |
|
516 | - */ |
|
517 | - private function getNewUuid() { |
|
518 | - return sprintf( |
|
519 | - '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', |
|
520 | - mt_rand(0, 0xFFFF), |
|
521 | - mt_rand(0, 0xFFFF), |
|
522 | - mt_rand(0, 0xFFFF), |
|
523 | - mt_rand(0, 0x0FFF) | 0x4000, |
|
524 | - mt_rand(0, 0x3FFF) | 0x8000, |
|
525 | - mt_rand(0, 0xFFFF), |
|
526 | - mt_rand(0, 0xFFFF), |
|
527 | - mt_rand(0, 0xFFFF) |
|
528 | - ); |
|
529 | - } |
|
447 | + /** |
|
448 | + * Loads the HierarchyCacheState and initializes the HierarchyChache |
|
449 | + * if this is an hierarchy operation. |
|
450 | + * |
|
451 | + * @param bool $forceLoading, default: false |
|
452 | + * |
|
453 | + * @throws StateNotFoundException |
|
454 | + * |
|
455 | + * @return bool |
|
456 | + */ |
|
457 | + private function loadHierarchyCache($forceLoading = false) { |
|
458 | + if (!$this->hierarchyOperation && $forceLoading == false) { |
|
459 | + return false; |
|
460 | + } |
|
461 | + |
|
462 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("StateManager->loadHierarchyCache(): '%s-%s-%s-%d'", $this->device->GetDeviceId(), $this->uuid, IStateMachine::HIERARCHY, $this->oldStateCounter)); |
|
463 | + |
|
464 | + // check if a full hierarchy sync might be necessary |
|
465 | + if ($this->device->GetFolderUUID(false) === false) { |
|
466 | + self::UnLinkState($this->device, false, false, $this->uuid); |
|
467 | + |
|
468 | + throw new StateNotFoundException("No hierarchy UUID linked to device. Requesting folder resync."); |
|
469 | + } |
|
470 | + |
|
471 | + $hierarchydata = $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::HIERARCHY, $this->uuid, $this->oldStateCounter, $this->deleteOldStates); |
|
472 | + $this->device->SetHierarchyCache($hierarchydata); |
|
473 | + |
|
474 | + return true; |
|
475 | + } |
|
476 | + |
|
477 | + /** |
|
478 | + * Saves the HierarchyCacheState of the HierarchyChache |
|
479 | + * if this is an hierarchy operation. |
|
480 | + * |
|
481 | + * @param bool $forceLoad indicates if the cache should be saved also if not a hierary operation |
|
482 | + * @param mixed $forceSaving |
|
483 | + * |
|
484 | + * @throws StateInvalidException |
|
485 | + * |
|
486 | + * @return bool |
|
487 | + */ |
|
488 | + private function saveHierarchyCache($forceSaving = false) { |
|
489 | + if (!$this->hierarchyOperation && !$forceSaving) { |
|
490 | + return false; |
|
491 | + } |
|
492 | + |
|
493 | + // link the hierarchy cache again, if the UUID does not match the UUID saved in the devicedata |
|
494 | + if (($this->uuid != $this->device->GetFolderUUID() || $forceSaving)) { |
|
495 | + self::LinkState($this->device, $this->uuid); |
|
496 | + } |
|
497 | + |
|
498 | + // check all folders and deleted folders to update data of ASDevice and delete old states |
|
499 | + $hc = $this->device->getHierarchyCache(); |
|
500 | + foreach ($hc->GetDeletedFolders() as $delfolder) { |
|
501 | + self::UnLinkState($this->device, $delfolder->serverid); |
|
502 | + } |
|
503 | + |
|
504 | + foreach ($hc->ExportFolders() as $folder) { |
|
505 | + $this->device->SetFolderType($folder->serverid, $folder->type); |
|
506 | + $this->device->SetFolderBackendId($folder->serverid, $folder->BackendId); |
|
507 | + } |
|
508 | + |
|
509 | + return $this->statemachine->SetState($this->device->GetHierarchyCacheData(), $this->device->GetDeviceId(), IStateMachine::HIERARCHY, $this->uuid, $this->newStateCounter); |
|
510 | + } |
|
511 | + |
|
512 | + /** |
|
513 | + * Generates a new UUID. |
|
514 | + * |
|
515 | + * @return string |
|
516 | + */ |
|
517 | + private function getNewUuid() { |
|
518 | + return sprintf( |
|
519 | + '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', |
|
520 | + mt_rand(0, 0xFFFF), |
|
521 | + mt_rand(0, 0xFFFF), |
|
522 | + mt_rand(0, 0xFFFF), |
|
523 | + mt_rand(0, 0x0FFF) | 0x4000, |
|
524 | + mt_rand(0, 0x3FFF) | 0x8000, |
|
525 | + mt_rand(0, 0xFFFF), |
|
526 | + mt_rand(0, 0xFFFF), |
|
527 | + mt_rand(0, 0xFFFF) |
|
528 | + ); |
|
529 | + } |
|
530 | 530 | } |
@@ -419,7 +419,7 @@ discard block |
||
419 | 419 | throw new StateInvalidException(sprintf("SyncKey '%s' is invalid", $synckey)); |
420 | 420 | } |
421 | 421 | |
422 | - return [$matches[1], (int) $matches[2]]; |
|
422 | + return [$matches[1], (int)$matches[2]]; |
|
423 | 423 | } |
424 | 424 | |
425 | 425 | /** |
@@ -437,7 +437,7 @@ discard block |
||
437 | 437 | throw new StateInvalidException(sprintf("UUID '%s' is invalid", $uuid)); |
438 | 438 | } |
439 | 439 | |
440 | - return "{" . $uuid . "}" . $counter; |
|
440 | + return "{".$uuid."}".$counter; |
|
441 | 441 | } |
442 | 442 | |
443 | 443 | /*---------------------------------------------------------------------------------------------------------- |
@@ -520,8 +520,8 @@ discard block |
||
520 | 520 | mt_rand(0, 0xFFFF), |
521 | 521 | mt_rand(0, 0xFFFF), |
522 | 522 | mt_rand(0, 0xFFFF), |
523 | - mt_rand(0, 0x0FFF) | 0x4000, |
|
524 | - mt_rand(0, 0x3FFF) | 0x8000, |
|
523 | + mt_rand(0, 0x0FFF)|0x4000, |
|
524 | + mt_rand(0, 0x3FFF)|0x8000, |
|
525 | 525 | mt_rand(0, 0xFFFF), |
526 | 526 | mt_rand(0, 0xFFFF), |
527 | 527 | mt_rand(0, 0xFFFF) |
@@ -101,8 +101,7 @@ discard block |
||
101 | 101 | if ($data !== false) { |
102 | 102 | $this->synchedFolders[$folderid] = $data; |
103 | 103 | } |
104 | - } |
|
105 | - catch (StateNotFoundException $ex) { |
|
104 | + } catch (StateNotFoundException $ex) { |
|
106 | 105 | } |
107 | 106 | } |
108 | 107 | |
@@ -142,8 +141,7 @@ discard block |
||
142 | 141 | if (!isset($synckey) || $synckey == "0" || $synckey == false) { |
143 | 142 | $this->uuid = $this->getNewUuid(); |
144 | 143 | $this->newStateCounter = 1; |
145 | - } |
|
146 | - else { |
|
144 | + } else { |
|
147 | 145 | list($uuid, $counter) = self::ParseStateKey($synckey); |
148 | 146 | $this->uuid = $uuid; |
149 | 147 | $this->newStateCounter = $counter + 1; |
@@ -231,8 +229,7 @@ discard block |
||
231 | 229 | |
232 | 230 | try { |
233 | 231 | return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::FAILSAVE, $this->uuid, $this->oldStateCounter, $this->deleteOldStates); |
234 | - } |
|
235 | - catch (StateNotFoundException $snfex) { |
|
232 | + } catch (StateNotFoundException $snfex) { |
|
236 | 233 | return false; |
237 | 234 | } |
238 | 235 | } |
@@ -377,8 +374,7 @@ discard block |
||
377 | 374 | public static function UnLinkState(&$device, $folderid, $removeFromDevice = true, $retrieveUUIDFromDevice = true) { |
378 | 375 | if ($retrieveUUIDFromDevice === true) { |
379 | 376 | $savedUuid = $device->GetFolderUUID($folderid); |
380 | - } |
|
381 | - else { |
|
377 | + } else { |
|
382 | 378 | $savedUuid = $retrieveUUIDFromDevice; |
383 | 379 | } |
384 | 380 |
@@ -6,58 +6,58 @@ |
||
6 | 6 | */ |
7 | 7 | |
8 | 8 | class PingTracking extends InterProcessData { |
9 | - /** |
|
10 | - * Constructor. |
|
11 | - */ |
|
12 | - public function __construct() { |
|
13 | - // initialize super parameters |
|
14 | - $this->allocate = 0; |
|
15 | - $this->type = "grommunio-sync:pingtracking"; |
|
16 | - parent::__construct(); |
|
9 | + /** |
|
10 | + * Constructor. |
|
11 | + */ |
|
12 | + public function __construct() { |
|
13 | + // initialize super parameters |
|
14 | + $this->allocate = 0; |
|
15 | + $this->type = "grommunio-sync:pingtracking"; |
|
16 | + parent::__construct(); |
|
17 | 17 | |
18 | - $this->initPing(); |
|
19 | - } |
|
18 | + $this->initPing(); |
|
19 | + } |
|
20 | 20 | |
21 | - /** |
|
22 | - * Destructor |
|
23 | - * Used to remove the current ping data from shared memory. |
|
24 | - */ |
|
25 | - public function __destruct() { |
|
26 | - return $this->setDeviceUserData($this->type, ["pid:" . self::$pid], self::$devid, self::$user, $subkey = -1, $doCas = "deletekeys"); |
|
27 | - } |
|
21 | + /** |
|
22 | + * Destructor |
|
23 | + * Used to remove the current ping data from shared memory. |
|
24 | + */ |
|
25 | + public function __destruct() { |
|
26 | + return $this->setDeviceUserData($this->type, ["pid:" . self::$pid], self::$devid, self::$user, $subkey = -1, $doCas = "deletekeys"); |
|
27 | + } |
|
28 | 28 | |
29 | - /** |
|
30 | - * Initialized the current request. |
|
31 | - * |
|
32 | - * @return bool |
|
33 | - */ |
|
34 | - protected function initPing() { |
|
35 | - // initialize params |
|
36 | - $this->initializeParams(); |
|
37 | - // need microtime as connections sometimes start at the same second |
|
38 | - self::$start = microtime(true); |
|
39 | - $pingtracking = ["pid:" . self::$pid => self::$start]; |
|
29 | + /** |
|
30 | + * Initialized the current request. |
|
31 | + * |
|
32 | + * @return bool |
|
33 | + */ |
|
34 | + protected function initPing() { |
|
35 | + // initialize params |
|
36 | + $this->initializeParams(); |
|
37 | + // need microtime as connections sometimes start at the same second |
|
38 | + self::$start = microtime(true); |
|
39 | + $pingtracking = ["pid:" . self::$pid => self::$start]; |
|
40 | 40 | |
41 | - return $this->setDeviceUserData($this->type, $pingtracking, self::$devid, self::$user, $subkey = -1, $doCas = "merge"); |
|
42 | - } |
|
41 | + return $this->setDeviceUserData($this->type, $pingtracking, self::$devid, self::$user, $subkey = -1, $doCas = "merge"); |
|
42 | + } |
|
43 | 43 | |
44 | - /** |
|
45 | - * Checks if there are newer ping requests for the same device & user so |
|
46 | - * the current process could be terminated. |
|
47 | - * |
|
48 | - * @return bool true if the current process is obsolete |
|
49 | - */ |
|
50 | - public function DoForcePingTimeout() { |
|
51 | - $pings = $this->getDeviceUserData($this->type, self::$devid, self::$user); |
|
52 | - // check if there is another (and newer) active ping connection |
|
53 | - if (count($pings) > 1) { |
|
54 | - foreach ($pings as $pid => $starttime) { |
|
55 | - if ($starttime > self::$start) { |
|
56 | - return true; |
|
57 | - } |
|
58 | - } |
|
59 | - } |
|
44 | + /** |
|
45 | + * Checks if there are newer ping requests for the same device & user so |
|
46 | + * the current process could be terminated. |
|
47 | + * |
|
48 | + * @return bool true if the current process is obsolete |
|
49 | + */ |
|
50 | + public function DoForcePingTimeout() { |
|
51 | + $pings = $this->getDeviceUserData($this->type, self::$devid, self::$user); |
|
52 | + // check if there is another (and newer) active ping connection |
|
53 | + if (count($pings) > 1) { |
|
54 | + foreach ($pings as $pid => $starttime) { |
|
55 | + if ($starttime > self::$start) { |
|
56 | + return true; |
|
57 | + } |
|
58 | + } |
|
59 | + } |
|
60 | 60 | |
61 | - return false; |
|
62 | - } |
|
61 | + return false; |
|
62 | + } |
|
63 | 63 | } |
@@ -23,7 +23,7 @@ discard block |
||
23 | 23 | * Used to remove the current ping data from shared memory. |
24 | 24 | */ |
25 | 25 | public function __destruct() { |
26 | - return $this->setDeviceUserData($this->type, ["pid:" . self::$pid], self::$devid, self::$user, $subkey = -1, $doCas = "deletekeys"); |
|
26 | + return $this->setDeviceUserData($this->type, ["pid:".self::$pid], self::$devid, self::$user, $subkey = -1, $doCas = "deletekeys"); |
|
27 | 27 | } |
28 | 28 | |
29 | 29 | /** |
@@ -36,7 +36,7 @@ discard block |
||
36 | 36 | $this->initializeParams(); |
37 | 37 | // need microtime as connections sometimes start at the same second |
38 | 38 | self::$start = microtime(true); |
39 | - $pingtracking = ["pid:" . self::$pid => self::$start]; |
|
39 | + $pingtracking = ["pid:".self::$pid => self::$start]; |
|
40 | 40 | |
41 | 41 | return $this->setDeviceUserData($this->type, $pingtracking, self::$devid, self::$user, $subkey = -1, $doCas = "merge"); |
42 | 42 | } |
@@ -8,231 +8,231 @@ |
||
8 | 8 | */ |
9 | 9 | |
10 | 10 | class ProvisioningManager extends InterProcessData { |
11 | - public const KEY_POLICYKEY = "policykey"; |
|
12 | - public const KEY_POLICYHASH = "policyhash"; |
|
13 | - public const KEY_UPDATETIME = "updatetime"; |
|
14 | - |
|
15 | - private $policies = []; |
|
16 | - private $policyKey = ASDevice::UNDEFINED; |
|
17 | - private $policyHash = ASDevice::UNDEFINED; |
|
18 | - private $updatetime = 0; |
|
19 | - private $loadtime = 0; |
|
20 | - private $typePolicyCacheId = false; |
|
21 | - |
|
22 | - /** |
|
23 | - * Constructor. |
|
24 | - */ |
|
25 | - public function __construct() { |
|
26 | - // initialize super parameters |
|
27 | - $this->allocate = 0; |
|
28 | - $this->type = "grommunio-sync:provisioningcache"; |
|
29 | - parent::__construct(); |
|
30 | - // initialize params |
|
31 | - $this->initializeParams(); |
|
32 | - |
|
33 | - $this->typePolicyCacheId = sprintf("grommunio-sync:policycache-%s", self::$user); |
|
34 | - |
|
35 | - // Remote wipe requested ? |
|
36 | - // If there is an entry in the provisioningcache for this user+device we assume that there is **NO** remote wipe requested. |
|
37 | - // If a device is to be remotewipe'd, the entry from the provisioningcache will be removed by the admin api. |
|
38 | - // This will trigger a provisioning operation and retrieve the status via GetProvisioningWipeStatus(). |
|
39 | - |
|
40 | - // get provisioning data from redis |
|
41 | - $p = $this->getData($this->typePolicyCacheId); |
|
42 | - if (!empty($p)) { |
|
43 | - $this->policies = $p; |
|
44 | - } |
|
45 | - // no policies cached in redis, get policies from admin API |
|
46 | - else { |
|
47 | - $policies = false; |
|
48 | - $api_response = file_get_contents(ADMIN_API_POLICY_ENDPOINT . self::$user); |
|
49 | - if ($api_response) { |
|
50 | - $data = json_decode($api_response); |
|
51 | - if (isset($data->data)) { |
|
52 | - $policies = $data->data; |
|
53 | - } |
|
54 | - } |
|
55 | - |
|
56 | - // failed to retrieve: use default "empty" policy |
|
57 | - if (!$policies) { |
|
58 | - // failed to retrieve: use default "empty" policy |
|
59 | - $policies = []; |
|
60 | - } |
|
61 | - $this->policies = $policies; |
|
62 | - // cache policies for 24h |
|
63 | - $this->setData($this->policies, $this->typePolicyCacheId, 3600 * 24); |
|
64 | - } |
|
65 | - |
|
66 | - // get policykey and hash |
|
67 | - $this->loadPolicyCache(); |
|
68 | - } |
|
69 | - |
|
70 | - private function loadPolicyCache() { |
|
71 | - if ($this->loadtime + 29 > time()) { |
|
72 | - return; |
|
73 | - } |
|
74 | - // get provisioning data from redis |
|
75 | - $d = $this->getDeviceUserData($this->type, self::$devid, self::$user); |
|
76 | - if (!empty($d)) { |
|
77 | - $this->policyKey = $d[self::KEY_POLICYKEY]; |
|
78 | - $this->policyHash = $d[self::KEY_POLICYHASH]; |
|
79 | - $this->updatetime = $d[self::KEY_UPDATETIME]; |
|
80 | - $this->loadtime = time(); |
|
81 | - } |
|
82 | - else { |
|
83 | - $this->policyKey = ASDevice::UNDEFINED; |
|
84 | - $this->policyHash = ASDevice::UNDEFINED; |
|
85 | - $this->updatetime = 0; |
|
86 | - $this->loadtime = time(); |
|
87 | - } |
|
88 | - } |
|
89 | - |
|
90 | - private function updatePolicyCache() { |
|
91 | - $p = []; |
|
92 | - $p[self::KEY_POLICYKEY] = $this->policyKey; |
|
93 | - $p[self::KEY_POLICYHASH] = $this->policyHash; |
|
94 | - $p[self::KEY_UPDATETIME] = time(); |
|
95 | - |
|
96 | - return $this->setDeviceUserData($this->type, $p, self::$devid, self::$user); |
|
97 | - } |
|
98 | - |
|
99 | - /** |
|
100 | - * Checks if the sent policykey matches the latest policykey |
|
101 | - * saved for the device. |
|
102 | - * |
|
103 | - * @param string $policykey |
|
104 | - * @param bool $noDebug (opt) by default, debug message is shown |
|
105 | - * @param bool $checkPolicies (opt) by default check if the provisioning policies changed |
|
106 | - * |
|
107 | - * @return bool |
|
108 | - */ |
|
109 | - public function ProvisioningRequired($policykey, $noDebug = false, $checkPolicies = true) { |
|
110 | - // get latest policykey and hash |
|
111 | - $this->loadPolicyCache(); |
|
112 | - |
|
113 | - // check if policiykey matches |
|
114 | - $p = (($policykey !== ASDevice::UNDEFINED && $policykey != $this->policyKey) || $this->policyKey == ASDevice::UNDEFINED); |
|
115 | - |
|
116 | - if (!$noDebug || $p) { |
|
117 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("ProvisioningManager->ProvisioningRequired('%s') saved device key '%s': %s", $policykey, $this->policyKey, Utils::PrintAsString($p))); |
|
118 | - } |
|
119 | - |
|
120 | - if ($checkPolicies) { |
|
121 | - $policyHash = $this->GetProvisioningObject()->GetPolicyHash(); |
|
122 | - if ($this->policyHash !== ASDevice::UNDEFINED && $this->policyHash != $policyHash) { |
|
123 | - $p = true; |
|
124 | - SLog::Write(LOGLEVEL_INFO, sprintf("ProvisioningManager->ProvisioningRequired(): saved policy hash '%s' changed to '%s'. Provisioning required.", $this->policyHash, $policyHash)); |
|
125 | - } |
|
126 | - elseif (!$noDebug) { |
|
127 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("ProvisioningManager->ProvisioningRequired() saved policy hash '%s' matches", $policyHash)); |
|
128 | - } |
|
129 | - } |
|
130 | - |
|
131 | - return $p; |
|
132 | - } |
|
133 | - |
|
134 | - /** |
|
135 | - * Generates a new Policykey. |
|
136 | - * |
|
137 | - * @return int |
|
138 | - */ |
|
139 | - public function GenerateProvisioningPolicyKey() { |
|
140 | - return mt_rand(100000000, 999999999); |
|
141 | - } |
|
142 | - |
|
143 | - /** |
|
144 | - * Attributes a provisioned policykey to a device. |
|
145 | - * |
|
146 | - * @param int $policykey |
|
147 | - * |
|
148 | - * @return bool status |
|
149 | - */ |
|
150 | - public function SetProvisioningPolicyKey($policykey) { |
|
151 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("ProvisioningManager->SetPolicyKey('%s')", $policykey)); |
|
152 | - $this->policyKey = $policykey; |
|
153 | - $this->updatePolicyCache(); |
|
154 | - |
|
155 | - // tell the Admin API that the policies were successfully deployed |
|
156 | - return $this->SetProvisioningWipeStatus(SYNC_PROVISION_RWSTATUS_OK); |
|
157 | - } |
|
158 | - |
|
159 | - /** |
|
160 | - * Builds a Provisioning SyncObject with policies. |
|
161 | - * |
|
162 | - * @param bool $logPolicies optional, determines if the policies and values should be logged. Default: false |
|
163 | - * |
|
164 | - * @return SyncProvisioning |
|
165 | - */ |
|
166 | - public function GetProvisioningObject($logPolicies = false) { |
|
167 | - return SyncProvisioning::GetObjectWithPolicies($this->policies, $logPolicies); |
|
168 | - } |
|
169 | - |
|
170 | - /** |
|
171 | - * Returns the status of the remote wipe policy. |
|
172 | - * |
|
173 | - * @return int returns the current status of the device - SYNC_PROVISION_RWSTATUS_* |
|
174 | - */ |
|
175 | - public function GetProvisioningWipeStatus() { |
|
176 | - $status = SYNC_PROVISION_RWSTATUS_NA; |
|
177 | - |
|
178 | - // retrieve the WIPE STATUS from the Admin API |
|
179 | - $api_response = file_get_contents(ADMIN_API_WIPE_ENDPOINT . self::$user . "?devices=" . self::$devid); |
|
180 | - if ($api_response) { |
|
181 | - $data = json_decode($api_response, true); |
|
182 | - if (isset($data['data'][self::$devid]["status"])) { |
|
183 | - $status = $data['data'][self::$devid]["status"]; |
|
184 | - // reset status to pending if it was already executed |
|
185 | - if ($status >= SYNC_PROVISION_RWSTATUS_PENDING) { |
|
186 | - SLog::Write(LOGLEVEL_INFO, sprintf("ProvisioningManager->GetProvisioningWipeStatus(): REMOTE WIPE due for user '%s' on device '%s' - status: '%s'", self::$user, self::$devid, $status)); |
|
187 | - $status = SYNC_PROVISION_RWSTATUS_PENDING; |
|
188 | - } |
|
189 | - else { |
|
190 | - SLog::Write(LOGLEVEL_INFO, sprintf("ProvisioningManager->GetProvisioningWipeStatus(): no remote wipe pending - status: '%s'", $status)); |
|
191 | - } |
|
192 | - } |
|
193 | - } |
|
194 | - |
|
195 | - return $status; |
|
196 | - } |
|
197 | - |
|
198 | - /** |
|
199 | - * Updates the status of the remote wipe. |
|
200 | - * |
|
201 | - * @param int $status - SYNC_PROVISION_RWSTATUS_* |
|
202 | - * |
|
203 | - * @return bool could fail if trying to update status to a wipe status which was not requested before |
|
204 | - */ |
|
205 | - public function SetProvisioningWipeStatus($status) { |
|
206 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("ProvisioningManager->SetProvisioningWipeStatus() set to '%d'", $status)); |
|
207 | - $opts = ['http' => [ |
|
208 | - 'method' => 'POST', |
|
209 | - 'header' => 'Content-Type: application/json', |
|
210 | - 'ignore_errors' => true, |
|
211 | - 'content' => json_encode( |
|
212 | - [ |
|
213 | - 'remoteIP' => Request::GetRemoteAddr(), |
|
214 | - 'status' => $status, |
|
215 | - 'time' => time(), |
|
216 | - ] |
|
217 | - ), |
|
218 | - ], |
|
219 | - ]; |
|
220 | - $ret = file_get_contents(ADMIN_API_WIPE_ENDPOINT . self::$user . "?devices=" . self::$devid, false, stream_context_create($opts)); |
|
221 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("ProvisioningManager->SetProvisioningWipeStatus() admin API response: %s", trim(Utils::PrintAsString($ret)))); |
|
222 | - |
|
223 | - return strpos($http_response_header[0], "201") !== false; |
|
224 | - } |
|
225 | - |
|
226 | - /** |
|
227 | - * Saves the policy hash and name in device's state. |
|
228 | - * |
|
229 | - * @param SyncProvisioning $provisioning |
|
230 | - */ |
|
231 | - public function SavePolicyHash($provisioning) { |
|
232 | - // save policies' hash |
|
233 | - $this->policyHash = $provisioning->GetPolicyHash(); |
|
234 | - $this->updatePolicyCache(); |
|
235 | - |
|
236 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("ProvisioningManager->SavePolicyHash(): Set policy with hash: %s", $this->policyHash)); |
|
237 | - } |
|
11 | + public const KEY_POLICYKEY = "policykey"; |
|
12 | + public const KEY_POLICYHASH = "policyhash"; |
|
13 | + public const KEY_UPDATETIME = "updatetime"; |
|
14 | + |
|
15 | + private $policies = []; |
|
16 | + private $policyKey = ASDevice::UNDEFINED; |
|
17 | + private $policyHash = ASDevice::UNDEFINED; |
|
18 | + private $updatetime = 0; |
|
19 | + private $loadtime = 0; |
|
20 | + private $typePolicyCacheId = false; |
|
21 | + |
|
22 | + /** |
|
23 | + * Constructor. |
|
24 | + */ |
|
25 | + public function __construct() { |
|
26 | + // initialize super parameters |
|
27 | + $this->allocate = 0; |
|
28 | + $this->type = "grommunio-sync:provisioningcache"; |
|
29 | + parent::__construct(); |
|
30 | + // initialize params |
|
31 | + $this->initializeParams(); |
|
32 | + |
|
33 | + $this->typePolicyCacheId = sprintf("grommunio-sync:policycache-%s", self::$user); |
|
34 | + |
|
35 | + // Remote wipe requested ? |
|
36 | + // If there is an entry in the provisioningcache for this user+device we assume that there is **NO** remote wipe requested. |
|
37 | + // If a device is to be remotewipe'd, the entry from the provisioningcache will be removed by the admin api. |
|
38 | + // This will trigger a provisioning operation and retrieve the status via GetProvisioningWipeStatus(). |
|
39 | + |
|
40 | + // get provisioning data from redis |
|
41 | + $p = $this->getData($this->typePolicyCacheId); |
|
42 | + if (!empty($p)) { |
|
43 | + $this->policies = $p; |
|
44 | + } |
|
45 | + // no policies cached in redis, get policies from admin API |
|
46 | + else { |
|
47 | + $policies = false; |
|
48 | + $api_response = file_get_contents(ADMIN_API_POLICY_ENDPOINT . self::$user); |
|
49 | + if ($api_response) { |
|
50 | + $data = json_decode($api_response); |
|
51 | + if (isset($data->data)) { |
|
52 | + $policies = $data->data; |
|
53 | + } |
|
54 | + } |
|
55 | + |
|
56 | + // failed to retrieve: use default "empty" policy |
|
57 | + if (!$policies) { |
|
58 | + // failed to retrieve: use default "empty" policy |
|
59 | + $policies = []; |
|
60 | + } |
|
61 | + $this->policies = $policies; |
|
62 | + // cache policies for 24h |
|
63 | + $this->setData($this->policies, $this->typePolicyCacheId, 3600 * 24); |
|
64 | + } |
|
65 | + |
|
66 | + // get policykey and hash |
|
67 | + $this->loadPolicyCache(); |
|
68 | + } |
|
69 | + |
|
70 | + private function loadPolicyCache() { |
|
71 | + if ($this->loadtime + 29 > time()) { |
|
72 | + return; |
|
73 | + } |
|
74 | + // get provisioning data from redis |
|
75 | + $d = $this->getDeviceUserData($this->type, self::$devid, self::$user); |
|
76 | + if (!empty($d)) { |
|
77 | + $this->policyKey = $d[self::KEY_POLICYKEY]; |
|
78 | + $this->policyHash = $d[self::KEY_POLICYHASH]; |
|
79 | + $this->updatetime = $d[self::KEY_UPDATETIME]; |
|
80 | + $this->loadtime = time(); |
|
81 | + } |
|
82 | + else { |
|
83 | + $this->policyKey = ASDevice::UNDEFINED; |
|
84 | + $this->policyHash = ASDevice::UNDEFINED; |
|
85 | + $this->updatetime = 0; |
|
86 | + $this->loadtime = time(); |
|
87 | + } |
|
88 | + } |
|
89 | + |
|
90 | + private function updatePolicyCache() { |
|
91 | + $p = []; |
|
92 | + $p[self::KEY_POLICYKEY] = $this->policyKey; |
|
93 | + $p[self::KEY_POLICYHASH] = $this->policyHash; |
|
94 | + $p[self::KEY_UPDATETIME] = time(); |
|
95 | + |
|
96 | + return $this->setDeviceUserData($this->type, $p, self::$devid, self::$user); |
|
97 | + } |
|
98 | + |
|
99 | + /** |
|
100 | + * Checks if the sent policykey matches the latest policykey |
|
101 | + * saved for the device. |
|
102 | + * |
|
103 | + * @param string $policykey |
|
104 | + * @param bool $noDebug (opt) by default, debug message is shown |
|
105 | + * @param bool $checkPolicies (opt) by default check if the provisioning policies changed |
|
106 | + * |
|
107 | + * @return bool |
|
108 | + */ |
|
109 | + public function ProvisioningRequired($policykey, $noDebug = false, $checkPolicies = true) { |
|
110 | + // get latest policykey and hash |
|
111 | + $this->loadPolicyCache(); |
|
112 | + |
|
113 | + // check if policiykey matches |
|
114 | + $p = (($policykey !== ASDevice::UNDEFINED && $policykey != $this->policyKey) || $this->policyKey == ASDevice::UNDEFINED); |
|
115 | + |
|
116 | + if (!$noDebug || $p) { |
|
117 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("ProvisioningManager->ProvisioningRequired('%s') saved device key '%s': %s", $policykey, $this->policyKey, Utils::PrintAsString($p))); |
|
118 | + } |
|
119 | + |
|
120 | + if ($checkPolicies) { |
|
121 | + $policyHash = $this->GetProvisioningObject()->GetPolicyHash(); |
|
122 | + if ($this->policyHash !== ASDevice::UNDEFINED && $this->policyHash != $policyHash) { |
|
123 | + $p = true; |
|
124 | + SLog::Write(LOGLEVEL_INFO, sprintf("ProvisioningManager->ProvisioningRequired(): saved policy hash '%s' changed to '%s'. Provisioning required.", $this->policyHash, $policyHash)); |
|
125 | + } |
|
126 | + elseif (!$noDebug) { |
|
127 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("ProvisioningManager->ProvisioningRequired() saved policy hash '%s' matches", $policyHash)); |
|
128 | + } |
|
129 | + } |
|
130 | + |
|
131 | + return $p; |
|
132 | + } |
|
133 | + |
|
134 | + /** |
|
135 | + * Generates a new Policykey. |
|
136 | + * |
|
137 | + * @return int |
|
138 | + */ |
|
139 | + public function GenerateProvisioningPolicyKey() { |
|
140 | + return mt_rand(100000000, 999999999); |
|
141 | + } |
|
142 | + |
|
143 | + /** |
|
144 | + * Attributes a provisioned policykey to a device. |
|
145 | + * |
|
146 | + * @param int $policykey |
|
147 | + * |
|
148 | + * @return bool status |
|
149 | + */ |
|
150 | + public function SetProvisioningPolicyKey($policykey) { |
|
151 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("ProvisioningManager->SetPolicyKey('%s')", $policykey)); |
|
152 | + $this->policyKey = $policykey; |
|
153 | + $this->updatePolicyCache(); |
|
154 | + |
|
155 | + // tell the Admin API that the policies were successfully deployed |
|
156 | + return $this->SetProvisioningWipeStatus(SYNC_PROVISION_RWSTATUS_OK); |
|
157 | + } |
|
158 | + |
|
159 | + /** |
|
160 | + * Builds a Provisioning SyncObject with policies. |
|
161 | + * |
|
162 | + * @param bool $logPolicies optional, determines if the policies and values should be logged. Default: false |
|
163 | + * |
|
164 | + * @return SyncProvisioning |
|
165 | + */ |
|
166 | + public function GetProvisioningObject($logPolicies = false) { |
|
167 | + return SyncProvisioning::GetObjectWithPolicies($this->policies, $logPolicies); |
|
168 | + } |
|
169 | + |
|
170 | + /** |
|
171 | + * Returns the status of the remote wipe policy. |
|
172 | + * |
|
173 | + * @return int returns the current status of the device - SYNC_PROVISION_RWSTATUS_* |
|
174 | + */ |
|
175 | + public function GetProvisioningWipeStatus() { |
|
176 | + $status = SYNC_PROVISION_RWSTATUS_NA; |
|
177 | + |
|
178 | + // retrieve the WIPE STATUS from the Admin API |
|
179 | + $api_response = file_get_contents(ADMIN_API_WIPE_ENDPOINT . self::$user . "?devices=" . self::$devid); |
|
180 | + if ($api_response) { |
|
181 | + $data = json_decode($api_response, true); |
|
182 | + if (isset($data['data'][self::$devid]["status"])) { |
|
183 | + $status = $data['data'][self::$devid]["status"]; |
|
184 | + // reset status to pending if it was already executed |
|
185 | + if ($status >= SYNC_PROVISION_RWSTATUS_PENDING) { |
|
186 | + SLog::Write(LOGLEVEL_INFO, sprintf("ProvisioningManager->GetProvisioningWipeStatus(): REMOTE WIPE due for user '%s' on device '%s' - status: '%s'", self::$user, self::$devid, $status)); |
|
187 | + $status = SYNC_PROVISION_RWSTATUS_PENDING; |
|
188 | + } |
|
189 | + else { |
|
190 | + SLog::Write(LOGLEVEL_INFO, sprintf("ProvisioningManager->GetProvisioningWipeStatus(): no remote wipe pending - status: '%s'", $status)); |
|
191 | + } |
|
192 | + } |
|
193 | + } |
|
194 | + |
|
195 | + return $status; |
|
196 | + } |
|
197 | + |
|
198 | + /** |
|
199 | + * Updates the status of the remote wipe. |
|
200 | + * |
|
201 | + * @param int $status - SYNC_PROVISION_RWSTATUS_* |
|
202 | + * |
|
203 | + * @return bool could fail if trying to update status to a wipe status which was not requested before |
|
204 | + */ |
|
205 | + public function SetProvisioningWipeStatus($status) { |
|
206 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("ProvisioningManager->SetProvisioningWipeStatus() set to '%d'", $status)); |
|
207 | + $opts = ['http' => [ |
|
208 | + 'method' => 'POST', |
|
209 | + 'header' => 'Content-Type: application/json', |
|
210 | + 'ignore_errors' => true, |
|
211 | + 'content' => json_encode( |
|
212 | + [ |
|
213 | + 'remoteIP' => Request::GetRemoteAddr(), |
|
214 | + 'status' => $status, |
|
215 | + 'time' => time(), |
|
216 | + ] |
|
217 | + ), |
|
218 | + ], |
|
219 | + ]; |
|
220 | + $ret = file_get_contents(ADMIN_API_WIPE_ENDPOINT . self::$user . "?devices=" . self::$devid, false, stream_context_create($opts)); |
|
221 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("ProvisioningManager->SetProvisioningWipeStatus() admin API response: %s", trim(Utils::PrintAsString($ret)))); |
|
222 | + |
|
223 | + return strpos($http_response_header[0], "201") !== false; |
|
224 | + } |
|
225 | + |
|
226 | + /** |
|
227 | + * Saves the policy hash and name in device's state. |
|
228 | + * |
|
229 | + * @param SyncProvisioning $provisioning |
|
230 | + */ |
|
231 | + public function SavePolicyHash($provisioning) { |
|
232 | + // save policies' hash |
|
233 | + $this->policyHash = $provisioning->GetPolicyHash(); |
|
234 | + $this->updatePolicyCache(); |
|
235 | + |
|
236 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("ProvisioningManager->SavePolicyHash(): Set policy with hash: %s", $this->policyHash)); |
|
237 | + } |
|
238 | 238 | } |
@@ -45,7 +45,7 @@ discard block |
||
45 | 45 | // no policies cached in redis, get policies from admin API |
46 | 46 | else { |
47 | 47 | $policies = false; |
48 | - $api_response = file_get_contents(ADMIN_API_POLICY_ENDPOINT . self::$user); |
|
48 | + $api_response = file_get_contents(ADMIN_API_POLICY_ENDPOINT.self::$user); |
|
49 | 49 | if ($api_response) { |
50 | 50 | $data = json_decode($api_response); |
51 | 51 | if (isset($data->data)) { |
@@ -176,7 +176,7 @@ discard block |
||
176 | 176 | $status = SYNC_PROVISION_RWSTATUS_NA; |
177 | 177 | |
178 | 178 | // retrieve the WIPE STATUS from the Admin API |
179 | - $api_response = file_get_contents(ADMIN_API_WIPE_ENDPOINT . self::$user . "?devices=" . self::$devid); |
|
179 | + $api_response = file_get_contents(ADMIN_API_WIPE_ENDPOINT.self::$user."?devices=".self::$devid); |
|
180 | 180 | if ($api_response) { |
181 | 181 | $data = json_decode($api_response, true); |
182 | 182 | if (isset($data['data'][self::$devid]["status"])) { |
@@ -217,7 +217,7 @@ discard block |
||
217 | 217 | ), |
218 | 218 | ], |
219 | 219 | ]; |
220 | - $ret = file_get_contents(ADMIN_API_WIPE_ENDPOINT . self::$user . "?devices=" . self::$devid, false, stream_context_create($opts)); |
|
220 | + $ret = file_get_contents(ADMIN_API_WIPE_ENDPOINT.self::$user."?devices=".self::$devid, false, stream_context_create($opts)); |
|
221 | 221 | SLog::Write(LOGLEVEL_DEBUG, sprintf("ProvisioningManager->SetProvisioningWipeStatus() admin API response: %s", trim(Utils::PrintAsString($ret)))); |
222 | 222 | |
223 | 223 | return strpos($http_response_header[0], "201") !== false; |
@@ -78,8 +78,7 @@ discard block |
||
78 | 78 | $this->policyHash = $d[self::KEY_POLICYHASH]; |
79 | 79 | $this->updatetime = $d[self::KEY_UPDATETIME]; |
80 | 80 | $this->loadtime = time(); |
81 | - } |
|
82 | - else { |
|
81 | + } else { |
|
83 | 82 | $this->policyKey = ASDevice::UNDEFINED; |
84 | 83 | $this->policyHash = ASDevice::UNDEFINED; |
85 | 84 | $this->updatetime = 0; |
@@ -122,8 +121,7 @@ discard block |
||
122 | 121 | if ($this->policyHash !== ASDevice::UNDEFINED && $this->policyHash != $policyHash) { |
123 | 122 | $p = true; |
124 | 123 | SLog::Write(LOGLEVEL_INFO, sprintf("ProvisioningManager->ProvisioningRequired(): saved policy hash '%s' changed to '%s'. Provisioning required.", $this->policyHash, $policyHash)); |
125 | - } |
|
126 | - elseif (!$noDebug) { |
|
124 | + } elseif (!$noDebug) { |
|
127 | 125 | SLog::Write(LOGLEVEL_DEBUG, sprintf("ProvisioningManager->ProvisioningRequired() saved policy hash '%s' matches", $policyHash)); |
128 | 126 | } |
129 | 127 | } |
@@ -185,8 +183,7 @@ discard block |
||
185 | 183 | if ($status >= SYNC_PROVISION_RWSTATUS_PENDING) { |
186 | 184 | SLog::Write(LOGLEVEL_INFO, sprintf("ProvisioningManager->GetProvisioningWipeStatus(): REMOTE WIPE due for user '%s' on device '%s' - status: '%s'", self::$user, self::$devid, $status)); |
187 | 185 | $status = SYNC_PROVISION_RWSTATUS_PENDING; |
188 | - } |
|
189 | - else { |
|
186 | + } else { |
|
190 | 187 | SLog::Write(LOGLEVEL_INFO, sprintf("ProvisioningManager->GetProvisioningWipeStatus(): no remote wipe pending - status: '%s'", $status)); |
191 | 188 | } |
192 | 189 | } |