Passed
Push — master ( 34e8da...f497d2 )
by
unknown
06:10 queued 02:50
created
lib/core/userstoreinfo.php 1 patch
Indentation   +59 added lines, -59 removed lines patch added patch discarded remove patch
@@ -8,69 +8,69 @@
 block discarded – undo
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
 }
Please login to merge, or discard this patch.
lib/core/loopdetection.php 3 patches
Indentation   +935 added lines, -935 removed lines patch added patch discarded remove patch
@@ -12,389 +12,389 @@  discard block
 block discarded – undo
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
 block discarded – undo
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
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -424,7 +424,7 @@  discard block
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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));
Please login to merge, or discard this patch.
Braces   +11 added lines, -22 removed lines patch added patch discarded remove patch
@@ -264,8 +264,7 @@  discard block
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 			}
Please login to merge, or discard this patch.
lib/core/gsync.php 3 patches
Indentation   +870 added lines, -870 removed lines patch added patch discarded remove patch
@@ -8,682 +8,682 @@  discard block
 block discarded – undo
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
 block discarded – undo
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
 }
Please login to merge, or discard this patch.
Spacing   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -11,7 +11,7 @@  discard block
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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;
Please login to merge, or discard this patch.
Braces   +13 added lines, -26 removed lines patch added patch discarded remove patch
@@ -178,8 +178,7 @@  discard block
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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.");
Please login to merge, or discard this patch.
lib/core/stateobject.php 2 patches
Indentation   +228 added lines, -228 removed lines patch added patch discarded remove patch
@@ -8,232 +8,232 @@
 block discarded – undo
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
 }
Please login to merge, or discard this patch.
Braces   +4 added lines, -8 removed lines patch added patch discarded remove patch
@@ -209,8 +209,7 @@  discard block
 block discarded – undo
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
 block discarded – undo
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
 		}
Please login to merge, or discard this patch.
lib/core/contentparameters.php 2 patches
Indentation   +126 added lines, -126 removed lines patch added patch discarded remove patch
@@ -8,130 +8,130 @@
 block discarded – undo
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
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -74,7 +74,7 @@  discard block
 block discarded – undo
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
 block discarded – undo
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
 
Please login to merge, or discard this patch.
lib/core/devicemanager.php 3 patches
Indentation   +1093 added lines, -1093 removed lines patch added patch discarded remove patch
@@ -13,1106 +13,1106 @@
 block discarded – undo
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
 }
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -167,7 +167,7 @@  discard block
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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));
Please login to merge, or discard this patch.
Braces   +9 added lines, -18 removed lines patch added patch discarded remove patch
@@ -74,8 +74,7 @@  discard block
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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) {
Please login to merge, or discard this patch.
lib/core/statemanager.php 3 patches
Indentation   +505 added lines, -505 removed lines patch added patch discarded remove patch
@@ -16,515 +16,515 @@
 block discarded – undo
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
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -419,7 +419,7 @@  discard block
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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)
Please login to merge, or discard this patch.
Braces   +4 added lines, -8 removed lines patch added patch discarded remove patch
@@ -101,8 +101,7 @@  discard block
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 
Please login to merge, or discard this patch.
lib/core/pingtracking.php 2 patches
Indentation   +48 added lines, -48 removed lines patch added patch discarded remove patch
@@ -6,58 +6,58 @@
 block discarded – undo
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
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -23,7 +23,7 @@  discard block
 block discarded – undo
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
 block discarded – undo
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
 	}
Please login to merge, or discard this patch.
lib/core/provisioningmanager.php 3 patches
Indentation   +227 added lines, -227 removed lines patch added patch discarded remove patch
@@ -8,231 +8,231 @@
 block discarded – undo
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
 }
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -45,7 +45,7 @@  discard block
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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;
Please login to merge, or discard this patch.
Braces   +3 added lines, -6 removed lines patch added patch discarded remove patch
@@ -78,8 +78,7 @@  discard block
 block discarded – undo
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
 block discarded – undo
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
 block discarded – undo
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
 			}
Please login to merge, or discard this patch.