ASDevice   F
last analyzed

Complexity

Total Complexity 180

Size/Duplication

Total Lines 1057
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 394
c 3
b 0
f 0
dl 0
loc 1057
rs 2
wmc 180

35 Methods

Rating   Name   Duplication   Size   Complexity  
A GetFolderSyncStatus() 0 7 3
C EditAdditionalFolder() 0 47 14
A SetFolderType() 0 15 4
A GetFolderType() 0 6 2
B SetAdditionalFolderList() 0 62 11
B SetFolderBackendId() 0 23 8
A GetAdditionalFolder() 0 7 2
B AddIgnoredMessage() 0 37 7
A GetSupportedFields() 0 8 4
A LoadedDevice() 0 16 3
A SetFolderSyncStatus() 0 16 4
C RemoveIgnoredMessage() 0 41 12
A StripData() 0 19 4
A GetHierarchyCacheData() 0 8 2
A SetHierarchyCache() 0 14 4
A GetAdditionalFolders() 0 6 2
B SetFolderUUID() 0 36 7
A SetUserAgent() 0 20 6
A GetHierarchyCache() 0 8 2
A orderAdditionalFoldersHierarchically() 0 15 4
A GetDeviceUserAgent() 0 6 3
A GetFolderBackendId() 0 6 2
A RemoveAdditionalFolder() 0 21 3
A __construct() 0 5 1
C GetFolderIdForBackendId() 0 49 15
A SetSupportedFields() 0 10 2
D AddAdditionalFolder() 0 83 19
B HasIgnoredMessage() 0 21 7
A GetDeviceUserAgentHistory() 0 2 1
A GetFolderUUID() 0 9 5
A IsNewDevice() 0 2 2
A GetAllFolderIds() 0 6 3
A generateFolderHash() 0 16 6
A HasFolderIdMapping() 0 14 5
A Initialize() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like ASDevice often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ASDevice, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * SPDX-License-Identifier: AGPL-3.0-only
4
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
5
 * SPDX-FileCopyrightText: Copyright 2020-2024 grommunio GmbH
6
 *
7
 * The ASDevice holds basic data about a device, its users and
8
 * the linked states
9
 */
10
11
class ASDevice extends StateObject {
12
	public const UNDEFINED = -1;
13
	// content data
14
	public const FOLDERUUID = 1;
15
	public const FOLDERTYPE = 2;
16
	public const FOLDERSUPPORTEDFIELDS = 3;
17
	public const FOLDERSYNCSTATUS = 4;
18
	public const FOLDERBACKENDID = 5;
19
20
	// expected values for not set member variables
21
	protected $unsetdata = [
22
		'useragenthistory' => [],
23
		'hierarchyuuid' => false,
24
		'contentdata' => [],
25
		'wipestatus' => SYNC_PROVISION_RWSTATUS_NA,
26
		'wiperequestedby' => false,
27
		'wiperequestedon' => false,
28
		'wipeactionon' => false,
29
		'lastupdatetime' => 0,
30
		'conversationmode' => false,
31
		'policyhash' => false,
32
		'policykey' => self::UNDEFINED,
33
		'forcesave' => false,
34
		'asversion' => false,
35
		'ignoredmessages' => [],
36
		'announcedASversion' => false,
37
		'foldersynccomplete' => true,
38
		'additionalfolders' => [],
39
		'syncfiltertype' => false,
40
	];
41
42
	protected $newdevice;
43
	protected $hierarchyCache;
44
	protected $ignoredMessageIds;
45
	protected $backend2folderidCache;
46
47
	/**
48
	 * AS Device constructor.
49
	 */
50
	public function __construct() {
51
		$this->firstsynctime = time();
0 ignored issues
show
Bug Best Practice introduced by
The property firstsynctime does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
52
		$this->newdevice = true;
53
		$this->ignoredMessageIds = [];
54
		$this->backend2folderidCache = false;
55
	}
56
57
	public function Initialize($devid, $devicetype, $getuser, $useragent) {
58
		$this->deviceid = $devid;
0 ignored issues
show
Bug Best Practice introduced by
The property deviceid does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
59
		$this->devicetype = $devicetype;
0 ignored issues
show
Bug Best Practice introduced by
The property devicetype does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
60
		list($this->deviceuser, $this->domain) = Utils::SplitDomainUser($getuser);
0 ignored issues
show
Bug Best Practice introduced by
The property domain does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
Bug Best Practice introduced by
The property deviceuser does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
61
		$this->useragent = $useragent;
0 ignored issues
show
Bug Best Practice introduced by
The property useragent does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
62
	}
63
64
	/**
65
	 * Removes internal data from the object, so this data can not be exposed.
66
	 *
67
	 * @param bool $stripHierarchyCache (opt) strips the hierarchy cache - default: true
68
	 *
69
	 * @return bool
70
	 */
71
	public function StripData($stripHierarchyCache = true) {
72
		unset(
73
			$this->changed,
74
			$this->unsetdata,
75
			$this->forceSave,
0 ignored issues
show
Bug Best Practice introduced by
The property forceSave does not exist on ASDevice. Since you implemented __get, consider adding a @property annotation.
Loading history...
76
			$this->newdevice,
77
			$this->ignoredMessageIds
78
		);
79
80
		$this->backend2folderidCache = false;
81
82
		if (!$stripHierarchyCache && $this->hierarchyCache !== false && $this->hierarchyCache instanceof ChangesMemoryWrapper) {
83
			$this->hierarchyCache->StripData();
84
		}
85
		else {
86
			unset($this->hierarchyCache);
87
		}
88
89
		return true;
90
	}
91
92
	/**
93
	 * Indicates if the object was just created.
94
	 *
95
	 * @return bool
96
	 */
97
	public function IsNewDevice() {
98
		return isset($this->newdevice) && $this->newdevice === true;
99
	}
100
101
	/**
102
	 * Marked as loaded device.
103
	 *
104
	 * @return bool
105
	 */
106
	public function LoadedDevice() {
107
		$this->newdevice = false;
108
109
		// Gsync Issue #52
110
		// TODO: Remove fallback code for fix missing properties
111
		if (!isset($this->data['deviceid']) || !$this->data['deviceid']) {
112
			$this->deviceid = Request::GetDeviceID();
0 ignored issues
show
Bug Best Practice introduced by
The property deviceid does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
113
			$this->devicetype = Request::GetDeviceType();
0 ignored issues
show
Bug Best Practice introduced by
The property devicetype does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
114
			$this->deviceuser = Request::GetUser();
0 ignored issues
show
Bug Best Practice introduced by
The property deviceuser does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
115
			$this->domain = Request::GetAuthDomain();
0 ignored issues
show
Bug Best Practice introduced by
The property domain does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
116
			$this->useragent = Request::GetUserAgent();
0 ignored issues
show
Bug Best Practice introduced by
The property useragent does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
117
			$this->firstsynctime = time();
0 ignored issues
show
Bug Best Practice introduced by
The property firstsynctime does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
118
			SLog::Write(LOGLEVEL_INFO, "Successfulyy set missing properties (GSync #52). Requesting data to be saved.");
119
		}
120
121
		return true;
122
	}
123
124
	/*----------------------------------------------------------------------------------------------------------
125
	 * Non-standard Getter and Setter
126
	 */
127
128
	/**
129
	 * Returns the user agent of this device.
130
	 *
131
	 * @return string
132
	 */
133
	public function GetDeviceUserAgent() {
134
		if (!isset($this->useragent) || !$this->useragent) {
135
			return "unknown";
136
		}
137
138
		return $this->useragent;
139
	}
140
141
	/**
142
	 * Returns the user agent history of this device.
143
	 *
144
	 * @return string
145
	 */
146
	public function GetDeviceUserAgentHistory() {
147
		return $this->useragentHistory;
148
	}
149
150
	/**
151
	 * Sets the useragent of the current request
152
	 * If this value is already available, no update is done.
153
	 *
154
	 * @param string $useragent
155
	 *
156
	 * @return bool
157
	 */
158
	public function SetUserAgent($useragent) {
159
		if ($useragent == $this->useragent || $useragent === false || $useragent === Request::UNKNOWN) {
160
			return true;
161
		}
162
163
		// save the old user agent, if available
164
		if ($this->useragent != "") {
165
			// [] = changedate, previous user agent
166
			$a = $this->useragentHistory;
167
168
			// only add if this agent was not seen before
169
			if (!in_array([true, $this->useragent], $a)) {
170
				$a[] = [time(), $this->useragent];
171
				$this->useragentHistory = $a;
0 ignored issues
show
Bug Best Practice introduced by
The property useragentHistory does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
172
				$this->changed = true;
173
			}
174
		}
175
		$this->useragent = $useragent;
0 ignored issues
show
Bug Best Practice introduced by
The property useragent does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
176
177
		return true;
178
	}
179
180
	/**
181
	 * Adds a messages which was ignored to the device data.
182
	 *
183
	 * @param StateObject $ignoredMessage
184
	 *
185
	 * @return bool
186
	 */
187
	public function AddIgnoredMessage($ignoredMessage) {
188
		// we should have all previously ignored messages in an id array
189
		if (count($this->ignoredMessages) != count($this->ignoredMessageIds)) {
190
			foreach ($this->ignoredMessages as $oldMessage) {
191
				if (!isset($this->ignoredMessageIds[$oldMessage->folderid])) {
192
					$this->ignoredMessageIds[$oldMessage->folderid] = [];
193
				}
194
				$this->ignoredMessageIds[$oldMessage->folderid][] = $oldMessage->id;
195
			}
196
		}
197
198
		// try not to add the same message several times
199
		if (isset($ignoredMessage->folderid, $ignoredMessage->id)) {
0 ignored issues
show
Bug Best Practice introduced by
The property folderid does not exist on StateObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property id does not exist on StateObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
200
			if (!isset($this->ignoredMessageIds[$ignoredMessage->folderid])) {
201
				$this->ignoredMessageIds[$ignoredMessage->folderid] = [];
202
			}
203
204
			if (in_array($ignoredMessage->id, $this->ignoredMessageIds[$ignoredMessage->folderid])) {
205
				$this->RemoveIgnoredMessage($ignoredMessage->folderid, $ignoredMessage->id);
206
			}
207
208
			$this->ignoredMessageIds[$ignoredMessage->folderid][] = $ignoredMessage->id;
209
			$msges = $this->ignoredMessages;
210
			$msges[] = $ignoredMessage;
211
			$this->ignoredMessages = $msges;
0 ignored issues
show
Bug Best Practice introduced by
The property ignoredMessages does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
212
			$this->changed = true;
213
214
			return true;
215
		}
216
217
		$msges = $this->ignoredMessages;
218
		$msges[] = $ignoredMessage;
219
		$this->ignoredMessages = $msges;
220
		$this->changed = true;
221
		SLog::Write(LOGLEVEL_WARN, "ASDevice->AddIgnoredMessage(): added message has no folder/id");
222
223
		return true;
224
	}
225
226
	/**
227
	 * Removes message in the list of ignored messages.
228
	 *
229
	 * @param string $folderid parent folder id of the message
230
	 * @param string $id       message id
231
	 *
232
	 * @return bool
233
	 */
234
	public function RemoveIgnoredMessage($folderid, $id) {
235
		// we should have all previously ignored messages in an id array
236
		if (count($this->ignoredMessages) != count($this->ignoredMessageIds)) {
237
			foreach ($this->ignoredMessages as $oldMessage) {
238
				if (!isset($this->ignoredMessageIds[$oldMessage->folderid])) {
239
					$this->ignoredMessageIds[$oldMessage->folderid] = [];
240
				}
241
				$this->ignoredMessageIds[$oldMessage->folderid][] = $oldMessage->id;
242
			}
243
		}
244
245
		$foundMessage = false;
246
		// there are ignored messages in that folder
247
		if (isset($this->ignoredMessageIds[$folderid])) {
248
			// resync of a folder. we should remove all previously ignored messages
249
			if ($id === false || in_array($id, $this->ignoredMessageIds[$folderid], true)) {
250
				$ignored = $this->ignoredMessages;
251
				$newMessages = [];
252
				foreach ($ignored as $im) {
253
					if ($im->folderid == $folderid) {
254
						if ($id === false || $im->id === $id) {
255
							$foundMessage = true;
256
							if (count($this->ignoredMessageIds[$folderid]) == 1) {
257
								unset($this->ignoredMessageIds[$folderid]);
258
							}
259
							else {
260
								unset($this->ignoredMessageIds[$folderid][array_search($id, $this->ignoredMessageIds[$folderid])]);
261
							}
262
263
							continue;
264
						}
265
266
						$newMessages[] = $im;
267
					}
268
				}
269
				$this->ignoredMessages = $newMessages;
0 ignored issues
show
Bug Best Practice introduced by
The property ignoredMessages does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
270
				$this->changed = true;
271
			}
272
		}
273
274
		return $foundMessage;
275
	}
276
277
	/**
278
	 * Indicates if a message is in the list of ignored messages.
279
	 *
280
	 * @param string $folderid parent folder id of the message
281
	 * @param string $id       message id
282
	 *
283
	 * @return bool
284
	 */
285
	public function HasIgnoredMessage($folderid, $id) {
286
		// we should have all previously ignored messages in an id array
287
		if (count($this->ignoredMessages) != count($this->ignoredMessageIds)) {
288
			foreach ($this->ignoredMessages as $oldMessage) {
289
				if (!isset($this->ignoredMessageIds[$oldMessage->folderid])) {
290
					$this->ignoredMessageIds[$oldMessage->folderid] = [];
291
				}
292
				$this->ignoredMessageIds[$oldMessage->folderid][] = $oldMessage->id;
293
			}
294
		}
295
296
		$foundMessage = false;
297
		// there are ignored messages in that folder
298
		if (isset($this->ignoredMessageIds[$folderid])) {
299
			// resync of a folder. we should remove all previously ignored messages
300
			if ($id === false || in_array($id, $this->ignoredMessageIds[$folderid], true)) {
301
				$foundMessage = true;
302
			}
303
		}
304
305
		return $foundMessage;
306
	}
307
308
	/*----------------------------------------------------------------------------------------------------------
309
	 * HierarchyCache and ContentData operations
310
	 */
311
312
	/**
313
	 * Sets the HierarchyCache
314
	 * The hierarchydata, can be:
315
	 *  - false     a new HierarchyCache is initialized
316
	 *  - array()   new HierarchyCache is initialized and data from GetHierarchy is loaded
317
	 *  - string    previously serialized data is loaded.
318
	 *
319
	 * @param string $hierarchydata (opt)
320
	 *
321
	 * @return bool
322
	 */
323
	public function SetHierarchyCache($hierarchydata = false) {
324
		if ($hierarchydata !== false && $hierarchydata instanceof ChangesMemoryWrapper) {
0 ignored issues
show
introduced by
$hierarchydata is never a sub-type of ChangesMemoryWrapper.
Loading history...
325
			$this->hierarchyCache = $hierarchydata;
326
			$this->hierarchyCache->CopyOldState();
327
		}
328
		else {
329
			$this->hierarchyCache = new ChangesMemoryWrapper();
330
		}
331
332
		if (is_array($hierarchydata)) {
0 ignored issues
show
introduced by
The condition is_array($hierarchydata) is always false.
Loading history...
333
			return $this->hierarchyCache->ImportFolders($hierarchydata);
334
		}
335
336
		return true;
337
	}
338
339
	/**
340
	 * Returns serialized data of the HierarchyCache.
341
	 *
342
	 * @return string
343
	 */
344
	public function GetHierarchyCacheData() {
345
		if (isset($this->hierarchyCache)) {
346
			return $this->hierarchyCache;
347
		}
348
349
		SLog::Write(LOGLEVEL_WARN, "ASDevice->GetHierarchyCacheData() has no data! HierarchyCache probably never initialized.");
350
351
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
352
	}
353
354
	/**
355
	 * Returns the HierarchyCache Object.
356
	 *
357
	 * @return object HierarchyCache
358
	 */
359
	public function GetHierarchyCache() {
360
		if (!isset($this->hierarchyCache)) {
361
			$this->SetHierarchyCache();
362
		}
363
364
		SLog::Write(LOGLEVEL_DEBUG, "ASDevice->GetHierarchyCache(): " . $this->hierarchyCache->GetStat());
365
366
		return $this->hierarchyCache;
367
	}
368
369
	/**
370
	 * Returns all known folderids.
371
	 *
372
	 * @return array
373
	 */
374
	public function GetAllFolderIds() {
375
		if (isset($this->contentData) && is_array($this->contentData)) {
376
			return array_keys($this->contentData);
377
		}
378
379
		return [];
380
	}
381
382
	/**
383
	 * Returns a linked UUID for a folder id.
384
	 *
385
	 * @param string $folderid (opt) if not set, Hierarchy UUID is returned
386
	 *
387
	 * @return string
388
	 */
389
	public function GetFolderUUID($folderid = false) {
390
		if ($folderid === false) {
391
			return (isset($this->hierarchyUuid) && $this->hierarchyUuid !== self::UNDEFINED) ? $this->hierarchyUuid : false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return IssetNode && $thi...->hierarchyUuid : false could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
392
		}
393
		if (isset($this->contentData[$folderid]->{self::FOLDERUUID})) {
394
			return $this->contentData[$folderid]->{self::FOLDERUUID};
395
		}
396
397
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
398
	}
399
400
	/**
401
	 * Link a UUID to a folder id
402
	 * If a boolean false UUID is sent, the relation is removed.
403
	 *
404
	 * @param string $uuid
405
	 * @param string $folderid (opt) if not set Hierarchy UUID is linked
406
	 *
407
	 * @return bool
408
	 */
409
	public function SetFolderUUID($uuid, $folderid = false) {
410
		if ($folderid === false) {
411
			$this->hierarchyUuid = $uuid;
0 ignored issues
show
Bug Best Practice introduced by
The property hierarchyUuid does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
412
			// when unsetting the hierarchycache, also remove saved contentdata and ignoredmessages
413
			if ($folderid === false && $uuid === false) {
0 ignored issues
show
introduced by
The condition $uuid === false is always false.
Loading history...
414
				$this->contentData = [];
415
				$this->ignoredMessageIds = [];
416
				$this->ignoredMessages = [];
417
				$this->backend2folderidCache = false;
418
			}
419
			$this->changed = true;
420
		}
421
		else {
422
			$contentData = $this->contentData;
423
424
			if (!isset($contentData[$folderid])) {
425
				$contentData[$folderid] = new stdClass();
426
			}
427
428
			// check if the foldertype is set. This has to be available at this point, as generated during the first HierarchySync
429
			if (!isset($contentData[$folderid]->{self::FOLDERTYPE})) {
430
				return false;
431
			}
432
433
			if ($uuid) {
434
				$contentData[$folderid]->{self::FOLDERUUID} = $uuid;
435
			}
436
			else {
437
				$contentData[$folderid]->{self::FOLDERUUID} = false;
438
			}
439
440
			$this->contentData = $contentData;
0 ignored issues
show
Bug Best Practice introduced by
The property contentData does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
441
			$this->changed = true;
442
		}
443
444
		return true;
445
	}
446
447
	/**
448
	 * Returns a foldertype for a folder already known to the mobile.
449
	 *
450
	 * @param string $folderid
451
	 *
452
	 * @return bool|int returns false if the type is not set
453
	 */
454
	public function GetFolderType($folderid) {
455
		if (isset($this->contentData[$folderid]->{self::FOLDERTYPE})) {
456
			return $this->contentData[$folderid]->{self::FOLDERTYPE};
457
		}
458
459
		return false;
460
	}
461
462
	/**
463
	 * Sets the foldertype of a folder id.
464
	 *
465
	 * @param string $folderid
466
	 * @param int    $foldertype ActiveSync folder type (as on the mobile)
467
	 *
468
	 * @return bool true if the type was set or updated
469
	 */
470
	public function SetFolderType($folderid, $foldertype) {
471
		$contentData = $this->contentData;
472
473
		if (!isset($contentData[$folderid])) {
474
			$contentData[$folderid] = new stdClass();
475
		}
476
		if (!isset($contentData[$folderid]->{self::FOLDERTYPE}) || $contentData[$folderid]->{self::FOLDERTYPE} != $foldertype) {
477
			$contentData[$folderid]->{self::FOLDERTYPE} = $foldertype;
478
			$this->contentData = $contentData;
0 ignored issues
show
Bug Best Practice introduced by
The property contentData does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
479
			$this->changed = true;
480
481
			return true;
482
		}
483
484
		return false;
485
	}
486
487
	/**
488
	 * Returns the backend folder id from the AS folderid known to the mobile.
489
	 *
490
	 * @param int $folderid
491
	 *
492
	 * @return bool|int returns false if the type is not set
493
	 */
494
	public function GetFolderBackendId($folderid) {
495
		if (isset($this->contentData[$folderid]->{self::FOLDERBACKENDID})) {
496
			return $this->contentData[$folderid]->{self::FOLDERBACKENDID};
497
		}
498
499
		return false;
500
	}
501
502
	/**
503
	 * Sets the backend folder id of an AS folderid.
504
	 *
505
	 * @param string $folderid        the AS folder id
506
	 * @param string $backendfolderid the backend folder id
507
	 *
508
	 * @return bool true if the type was set or updated
509
	 */
510
	public function SetFolderBackendId($folderid, $backendfolderid) {
511
		if ($folderid === $backendfolderid || $folderid === false || $backendfolderid === false) {
512
			return false;
513
		}
514
515
		$contentData = $this->contentData;
516
		if (!isset($contentData[$folderid])) {
517
			$contentData[$folderid] = new stdClass();
518
		}
519
		if (!isset($contentData[$folderid]->{self::FOLDERBACKENDID}) || $contentData[$folderid]->{self::FOLDERBACKENDID} != $backendfolderid) {
520
			$contentData[$folderid]->{self::FOLDERBACKENDID} = $backendfolderid;
521
			$this->contentData = $contentData;
0 ignored issues
show
Bug Best Practice introduced by
The property contentData does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
522
			$this->changed = true;
523
524
			// update the reverse cache as well
525
			if (is_array($this->backend2folderidCache)) {
0 ignored issues
show
introduced by
The condition is_array($this->backend2folderidCache) is always false.
Loading history...
526
				$this->backend2folderidCache[$backendfolderid] = $folderid;
527
			}
528
529
			return true;
530
		}
531
532
		return false;
533
	}
534
535
	/**
536
	 * Gets the AS folderid for a backendFolderId.
537
	 * If there is no known AS folderId a new one is being created.
538
	 *
539
	 * @param string $backendid          Backend folder id
540
	 * @param bool   $generateNewIdIfNew generates a new AS folderid for the case the backend folder is not known yet
541
	 * @param string $folderOrigin       Folder type is one of   'U' (user)
542
	 *                                   'C' (configured)
543
	 *                                   'S' (shared)
544
	 *                                   'G' (global address book)
545
	 *                                   'I' (impersonated)
546
	 * @param string $folderName         Folder name of the backend folder
547
	 *
548
	 * @return string
549
	 */
550
	public function GetFolderIdForBackendId($backendid, $generateNewIdIfNew, $folderOrigin, $folderName) {
551
		// build the backend-to-folderId backwards cache once
552
		if ($this->backend2folderidCache === false) {
553
			$this->backend2folderidCache = [];
554
			foreach ($this->contentData as $folderid => $data) {
555
				if (isset($data->{self::FOLDERBACKENDID})) {
556
					$this->backend2folderidCache[$data->{self::FOLDERBACKENDID}] = $folderid;
557
				}
558
			}
559
560
			// if we couldn't find any backend-folderids but there is data in contentdata, then this is an old profile.
561
			// do not generate new folderids in this case
562
			if (empty($this->backend2folderidCache) && !empty($this->contentData)) {
563
				SLog::Write(LOGLEVEL_DEBUG, "ASDevice->GetFolderIdForBackendId(): this is a profile without backend-folderid mapping. Returning folderids as is.");
564
				$this->backend2folderidCache = true;
565
			}
566
		}
567
		if (is_array($this->backend2folderidCache) && isset($this->backend2folderidCache[$backendid])) {
568
			// Use cached version only if the folderOrigin matches
569
			if (Utils::GetFolderOriginFromId($this->backend2folderidCache[$backendid]) == $folderOrigin) {
570
				return $this->backend2folderidCache[$backendid];
571
			}
572
			// if we have a different origin, we need to actively search for all synchronized folders, as they might be synched with a different origin
573
			// the short-id is only used if the folder is being synchronized (in contentdata) - else any cached (temporarily) ids are NOT used
574
575
			foreach ($this->contentData as $folderid => $data) {
576
				if (isset($data->{self::FOLDERBACKENDID}) && $data->{self::FOLDERBACKENDID} == $backendid) {
577
					SLog::Write(LOGLEVEL_DEBUG, sprintf("ASDevice->GetFolderIdForBackendId(): found backendid in contentdata but with different folder type. Lookup '%s' - synchronized id '%s'", $folderOrigin, $folderid));
578
579
					return $folderid;
580
				}
581
			}
582
		}
583
584
		// nothing found? Then it's a new one, get and add it
585
		if (is_array($this->backend2folderidCache) && $generateNewIdIfNew) {
586
			if ($folderName == null) {
587
				SLog::Write(LOGLEVEL_INFO, "ASDevice->GetFolderIdForBackendId(): generating a new folder id for the folder without a name");
588
			}
589
			$newHash = $this->generateFolderHash($backendid, $folderOrigin, $folderName);
590
			SLog::Write(LOGLEVEL_DEBUG, sprintf("ASDevice->GetFolderIdForBackendId(): generated new folderid '%s' for backend-folderid '%s'", $newHash, $backendid));
591
			// temporarily save the new hash also in the cache (new folders will only be saved at the end of request and could be requested before that
592
			$this->backend2folderidCache[$backendid] = $newHash;
593
594
			return $newHash;
595
		}
596
		SLog::Write(LOGLEVEL_DEBUG, sprintf("ASDevice->GetFolderIdForBackendId(): no valid condition found for determining folderid for backendid '%s'. Returning as is!", Utils::PrintAsString($backendid)));
597
598
		return $backendid;
599
	}
600
601
	/**
602
	 * Indicates if the device has a folderid mapping (short ids).
603
	 *
604
	 * @return bool
605
	 */
606
	public function HasFolderIdMapping() {
607
		if (is_array($this->backend2folderidCache)) {
0 ignored issues
show
introduced by
The condition is_array($this->backend2folderidCache) is always false.
Loading history...
608
			return true;
609
		}
610
		if (!is_array($this->contentData)) {
611
			return false;
612
		}
613
		foreach ($this->contentData as $folderid => $data) {
614
			if (isset($data->{self::FOLDERBACKENDID})) {
615
				return true;
616
			}
617
		}
618
619
		return false;
620
	}
621
622
	/**
623
	 * Gets the supported fields transmitted previously by the device
624
	 * for a certain folder.
625
	 *
626
	 * @param string $folderid
627
	 *
628
	 * @return array|bool false means no supportedFields are available
629
	 */
630
	public function GetSupportedFields($folderid) {
631
		if (isset($this->contentData, $this->contentData[$folderid], $this->contentData[$folderid]->{self::FOLDERUUID}) &&
632
				$this->contentData[$folderid]->{self::FOLDERUUID} !== false &&
633
				isset($this->contentData[$folderid]->{self::FOLDERSUPPORTEDFIELDS})) {
634
			return $this->contentData[$folderid]->{self::FOLDERSUPPORTEDFIELDS};
635
		}
636
637
		return false;
638
	}
639
640
	/**
641
	 * Sets the set of supported fields transmitted by the device for a certain folder.
642
	 *
643
	 * @param string $folderid
644
	 * @param array  $fieldlist supported fields
645
	 *
646
	 * @return bool
647
	 */
648
	public function SetSupportedFields($folderid, $fieldlist) {
649
		$contentData = $this->contentData;
650
		if (!isset($contentData[$folderid])) {
651
			$contentData[$folderid] = new stdClass();
652
		}
653
		$contentData[$folderid]->{self::FOLDERSUPPORTEDFIELDS} = $fieldlist;
654
		$this->contentData = $contentData;
0 ignored issues
show
Bug Best Practice introduced by
The property contentData does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
655
		$this->changed = true;
656
657
		return true;
658
	}
659
660
	/**
661
	 * Gets the current sync status of a certain folder.
662
	 *
663
	 * @param string $folderid
664
	 *
665
	 * @return bool|mixed false means the status is not available
666
	 */
667
	public function GetFolderSyncStatus($folderid) {
668
		if (isset($this->contentData[$folderid]->{self::FOLDERUUID}, $this->contentData[$folderid]->{self::FOLDERSYNCSTATUS}) &&
669
				$this->contentData[$folderid]->{self::FOLDERUUID} !== false) {
670
			return $this->contentData[$folderid]->{self::FOLDERSYNCSTATUS};
671
		}
672
673
		return false;
674
	}
675
676
	/**
677
	 * Sets the current sync status of a certain folder.
678
	 *
679
	 * @param string $folderid
680
	 * @param mixed  $status   if set to false the current status is deleted
681
	 *
682
	 * @return bool
683
	 */
684
	public function SetFolderSyncStatus($folderid, $status) {
685
		$contentData = $this->contentData;
686
		if (!isset($contentData[$folderid])) {
687
			$contentData[$folderid] = new stdClass();
688
		}
689
		if ($status !== false) {
690
			$contentData[$folderid]->{self::FOLDERSYNCSTATUS} = $status;
691
		}
692
		elseif (isset($contentData[$folderid]->{self::FOLDERSYNCSTATUS})) {
693
			unset($contentData[$folderid]->{self::FOLDERSYNCSTATUS});
694
		}
695
696
		$this->contentData = $contentData;
0 ignored issues
show
Bug Best Practice introduced by
The property contentData does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
697
		$this->changed = true;
698
699
		return true;
700
	}
701
702
	/*----------------------------------------------------------------------------------------------------------
703
	 * Additional Folders operations
704
	  TODO: All these methods are not being used in the code currently and could be removed at a later point if this
705
	  functionality is not being made available anymore.
706
	 */
707
708
	/**
709
	 * Returns a list of all additional folders of this device.
710
	 *
711
	 * @return array
712
	 */
713
	public function GetAdditionalFolders() {
714
		if (is_array($this->additionalfolders)) {
715
			return array_values($this->additionalfolders);
716
		}
717
718
		return [];
719
	}
720
721
	/**
722
	 * Returns an additional folder by folder ID.
723
	 *
724
	 * @param string $folderid
725
	 *
726
	 * @return array|false Returns a list of properties. Else false if folder id is unknown.
727
	 */
728
	// TODO: not used
729
	public function GetAdditionalFolder($folderid) {
730
		// check if the $folderid is one of our own - this will in mostly NOT be the case, so we do not log here
731
		if (!isset($this->additionalfolders[$folderid])) {
732
			return false;
733
		}
734
735
		return $this->additionalfolders[$folderid];
736
	}
737
738
	/**
739
	 * Adds an additional folder to this device & user.
740
	 *
741
	 * @param string $store     the store where this folder is located, e.g. "SYSTEM" (for public folder) or a username.
742
	 * @param string $folderid  the folder id of the additional folder
743
	 * @param string $name      the name of the additional folder (has to be unique for all folders on the device)
744
	 * @param string $type      AS foldertype of SYNC_FOLDER_TYPE_USER_*
745
	 * @param int    $flags     Additional flags, like DeviceManager::FLD_FLAGS_SENDASOWNER
746
	 * @param string $parentid  the parentid of this folder
747
	 * @param bool   $checkDups indicates if duplicate names and ids should be verified. Default: true
748
	 *
749
	 * @return bool
750
	 */
751
	// TODO: not used
752
	public function AddAdditionalFolder($store, $folderid, $name, $type, $flags, $parentid = 0, $checkDups = true) {
753
		// check if a folderid and name were sent
754
		if (!$folderid || !$name) {
755
			SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->AddAdditionalFolder(): No valid folderid ('%s') or name ('%s') sent. Aborting. ", $folderid, $name));
756
757
			return false;
758
		}
759
760
		// check if type is of a additional user type
761
		if (!in_array($type, [SYNC_FOLDER_TYPE_USER_CONTACT, SYNC_FOLDER_TYPE_USER_APPOINTMENT, SYNC_FOLDER_TYPE_USER_TASK, SYNC_FOLDER_TYPE_USER_MAIL, SYNC_FOLDER_TYPE_USER_NOTE, SYNC_FOLDER_TYPE_USER_JOURNAL, SYNC_FOLDER_TYPE_OTHER])) {
762
			SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->AddAdditionalFolder(): folder can not be added because the specified type '%s' is not a permitted user type.", $type));
763
764
			return false;
765
		}
766
767
		// check if a folder with this ID is already in the list
768
		if (isset($this->additionalfolders[$folderid])) {
769
			SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->AddAdditionalFolder(): folder can not be added because there is already an additional folder with the same folder id: '%s'", $folderid));
770
771
			return false;
772
		}
773
774
		// check if a folder with that Name is already in the list and that its parent exists
775
		$parentFound = false;
776
		foreach ($this->additionalfolders as $k => $folder) {
777
			// This is fixed in fixstates, but we could keep this here a while longer.
778
			// TODO: remove line at a later point.
779
			if (!isset($folder['parentid'])) {
780
				$folder['parentid'] = "0";
781
			}
782
783
			if ($folder['name'] == $name && $folder['parentid'] == $parentid) {
784
				SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->AddAdditionalFolder(): folder can not be added because there is already an additional folder with the same name in the same folder: '%s'", $name));
785
786
				return false;
787
			}
788
			if ($folder['folderid'] == $parentid) {
789
				$parentFound = true;
790
			}
791
		}
792
		if ($parentid != '0' && !$parentFound) {
793
			SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->AddAdditionalFolder(): folder '%s' ('%s') can not be added because the parent folder '%s' can not be found'", $name, $folderid, $parentid));
794
795
			return false;
796
		}
797
798
		// check if a folder with this ID or Name is already known on the device (regular folder)
799
		if ($checkDups) {
800
			// in order to check for the parent-ids we need a shortid
801
			$parentShortId = $this->GetFolderIdForBackendId($parentid, false, null, null);
802
			foreach ($this->GetHierarchyCache()->ExportFolders() as $syncedFolderid => $folder) {
803
				if ($syncedFolderid === $folderid || $folder->BackendId === $folderid) {
804
					SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->AddAdditionalFolder(): folder can not be added because there is already a folder with the same folder id synchronized: '%s'", $folderid));
805
806
					return false;
807
				}
808
809
				// $folder is a SyncFolder object here
810
				if ($folder->displayname == $name && ($folder->parentid == $parentid || $folder->parentid == $parentShortId)) {
811
					SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->AddAdditionalFolder(): folder can not be added because there is already a folder with the same name synchronized in the same parent: '%s'", $name));
812
813
					return false;
814
				}
815
			}
816
		}
817
818
		// add the folder
819
		$af = $this->additionalfolders;
820
		$af[$folderid] = [
821
			'store' => $store,
822
			'folderid' => $folderid,
823
			'parentid' => $parentid,
824
			'name' => $name,
825
			'type' => $type,
826
			'flags' => $flags,
827
		];
828
		$this->additionalfolders = $af;
0 ignored issues
show
Bug Best Practice introduced by
The property additionalfolders does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
829
		$this->changed = true;
830
831
		// generate an integer folderid for it
832
		$this->GetFolderIdForBackendId($folderid, true, DeviceManager::FLD_ORIGIN_SHARED, $name);
833
834
		return true;
835
	}
836
837
	/**
838
	 * Edits (sets a new name) for an additional folder. Store, folderid and type can not be edited. Remove and add instead.
839
	 *
840
	 * @param string $folderid  the folder id of the additional folder
841
	 * @param string $name      the name of the additional folder (has to be unique for all folders on the device)
842
	 * @param int    $flags     Additional flags, like DeviceManager::FLD_FLAGS_SENDASOWNER
843
	 * @param string $parentid  the parentid of this folder
844
	 * @param bool   $checkDups indicates if duplicate names and ids should be verified. Default: true
845
	 *
846
	 * @return bool
847
	 */
848
	// TODO: not used
849
	public function EditAdditionalFolder($folderid, $name, $flags, $parentid = 0, $checkDups = true) {
850
		// check if a folderid and name were sent
851
		if (!$folderid || !$name) {
852
			SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->EditAdditionalFolder(): No valid folderid ('%s') or name ('%s') sent. Aborting. ", $folderid, $name));
853
854
			return false;
855
		}
856
857
		// check if a folder with this ID is known
858
		if (!isset($this->additionalfolders[$folderid])) {
859
			SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->EditAdditionalFolder(): folder can not be edited because there is no folder known with this folder id: '%s'. Add the folder first.", $folderid));
860
861
			return false;
862
		}
863
864
		// check if a folder with the new name is already in the list
865
		foreach ($this->additionalfolders as $existingFolderid => $folder) {
866
			if ($folder['name'] == $name && $folderid !== $existingFolderid) {
867
				SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->EditAdditionalFolder(): folder can not be edited because there is already an additional folder with the same name: '%s'", $name));
868
869
				return false;
870
			}
871
		}
872
873
		// check if a folder with the new name is already known on the device (regular folder)
874
		if ($checkDups) {
875
			// in order to check for the parent-ids we need a shortid
876
			$parentShortId = $this->GetFolderIdForBackendId($parentid, false, null, null);
877
			foreach ($this->GetHierarchyCache()->ExportFolders() as $syncedFolderid => $folder) {
878
				// $folder is a SyncFolder object here
879
				if ($folder->displayname == $name && $folderid !== $folder->BackendId && $folderid !== $syncedFolderid && ($folder->parentid == $parentid || $folder->parentid == $parentShortId)) {
880
					SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->EditAdditionalFolder(): folder can not be edited because there is already a folder with the same name synchronized: '%s'", $folderid));
881
882
					return false;
883
				}
884
			}
885
		}
886
887
		// update the name
888
		$af = $this->additionalfolders;
889
		$af[$folderid]['name'] = $name;
890
		$af[$folderid]['flags'] = $flags;
891
		$af[$folderid]['parentid'] = $parentid;
892
		$this->additionalfolders = $af;
0 ignored issues
show
Bug Best Practice introduced by
The property additionalfolders does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
893
		$this->changed = true;
894
895
		return true;
896
	}
897
898
	/**
899
	 * Removes an additional folder from this device & user.
900
	 *
901
	 * @param mixed $folderid
902
	 *
903
	 * @return bool
904
	 */
905
	// TODO: not used
906
	public function RemoveAdditionalFolder($folderid) {
907
		// check if a folderid were sent
908
		if (!$folderid) {
909
			SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->RemoveAdditionalFolder(): No valid folderid ('%s') sent. Aborting. ", $folderid));
910
911
			return false;
912
		}
913
		// check if a folder with this ID is known
914
		if (!isset($this->additionalfolders[$folderid])) {
915
			SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->RemoveAdditionalFolder(): folder can not be removed because there is no folder known with this folder id: '%s'", $folderid));
916
917
			return false;
918
		}
919
920
		// remove the folder
921
		$af = $this->additionalfolders;
922
		unset($af[$folderid]);
923
		$this->additionalfolders = $af;
0 ignored issues
show
Bug Best Practice introduced by
The property additionalfolders does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
924
		$this->changed = true;
925
926
		return true;
927
	}
928
929
	/**
930
	 * Sets a list of additional folders of one store to the device.
931
	 * If there are additional folders for the set_store, that are not in the list they will be removed.
932
	 *
933
	 * @param string $store   the store where this folder is located, e.g. "SYSTEM" (for public folder) or an username/email address.
934
	 * @param array  $folders a list of folders to be set for this user. Other existing additional folders (that are not in this list)
935
	 *                        will be removed. The list is an array containing folders, where each folder is an array with the following keys:
936
	 *                        'folderid'  (string) the folder id of the additional folder.
937
	 *                        'parentid'  (string) the folderid of the parent folder. If no parent folder is set or the parent folder is not defined, '0' (main folder) is used.
938
	 *                        'name'      (string) the name of the additional folder (has to be unique for all folders on the device).
939
	 *                        'type'      (string) AS foldertype of SYNC_FOLDER_TYPE_USER_*
940
	 *                        'flags'     (int)    Additional flags, like DeviceManager::FLD_FLAGS_SENDASOWNER
941
	 *
942
	 * @return bool
943
	 */
944
	// TODO: not used
945
	public function SetAdditionalFolderList($store, $folders) {
946
		// remove all folders already shared for this store
947
		$newAF = [];
948
		$noDupsCheck = [];
949
		foreach ($this->additionalfolders as $keepFolder) {
950
			if ($keepFolder['store'] !== $store) {
951
				$newAF[$keepFolder['folderid']] = $keepFolder;
952
			}
953
			else {
954
				$noDupsCheck[$keepFolder['folderid']] = true;
955
			}
956
		}
957
		SLog::Write(LOGLEVEL_DEBUG, sprintf("ASDevice->SetAdditionalFolderList(): cleared additional folder lists of store '%s', total %d folders, kept %d and removed %d", $store, count($this->additionalfolders), count($newAF), count($noDupsCheck)));
958
		// set remaining additional folders
959
		$this->additionalfolders = $newAF;
0 ignored issues
show
Bug Best Practice introduced by
The property additionalfolders does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
960
		$this->changed = true;
961
962
		// transform our array in a key/value array where folderids are keys and do some basic checks
963
		$toOrder = [];
964
		$ordered = [];
965
		$validTypes = [SYNC_FOLDER_TYPE_USER_CONTACT, SYNC_FOLDER_TYPE_USER_APPOINTMENT, SYNC_FOLDER_TYPE_USER_TASK, SYNC_FOLDER_TYPE_USER_MAIL, SYNC_FOLDER_TYPE_USER_NOTE, SYNC_FOLDER_TYPE_USER_JOURNAL, SYNC_FOLDER_TYPE_OTHER];
966
		foreach ($folders as $f) {
967
			// fail early
968
			if (!$f['folderid'] || !$f['name']) {
969
				SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->SetAdditionalFolderList(): No valid folderid ('%s') or name ('%s') sent. Aborting. ", $f['folderid'], $f['name']));
970
971
				return false;
972
			}
973
974
			// check if type is of a additional user type
975
			if (!in_array($f['type'], $validTypes)) {
976
				SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->SetAdditionalFolderList(): folder (id: '%s' - name: '%s') can not be added because the specified type '%s' is not a permitted user type.", $f['folderid'], $f['name'], $f['type']));
977
978
				return false;
979
			}
980
			$toOrder[$f['folderid']] = $f;
981
		}
982
983
		// order the array, so folders with leafs come first
984
		$this->orderAdditionalFoldersHierarchically($toOrder, $ordered);
985
986
		// if there are folders that are not be positioned in the tree, we can't add them!
987
		if (!empty($toOrder)) {
988
			$s = "";
989
			foreach ($toOrder as $f) {
990
				$s .= sprintf("'%s'('%s') ", $f['name'], $f['folderid']);
991
			}
992
			SLog::Write(LOGLEVEL_ERROR, "ASDevice->SetAdditionalFolderList(): cannot proceed as these folders have invalid parentids (not found): " . $s);
993
994
			return false;
995
		}
996
997
		foreach ($ordered as $f) {
998
			$status = $this->AddAdditionalFolder($store, $f['folderid'], $f['name'], $f['type'], $f['flags'], $f['parentid'], !isset($noDupsCheck[$f['folderid']]));
999
			SLog::Write(LOGLEVEL_DEBUG, sprintf("ASDevice->SetAdditionalFolderList(): set folder '%s' in additional folders list with status: %s", $f['name'], Utils::PrintAsString($status)));
1000
			// break if a folder can not be added
1001
			if (!$status) {
1002
				return false;
1003
			}
1004
		}
1005
1006
		return true;
1007
	}
1008
1009
	/**
1010
	 * Orders a list of folders so the parents are first in the array, all leaves come afterwards.
1011
	 *
1012
	 * @param array  $toOrderFolders an array of folders, where the folderids are keys. This array should be empty at the end.
1013
	 * @param array  $orderedFolders the ordered array
1014
	 * @param string $parentid       the parentid to start with, if not set '0' (main folders) is used
1015
	 */
1016
	// TODO: not used
1017
	private function orderAdditionalFoldersHierarchically(&$toOrderFolders, &$orderedFolders, $parentid = '0') {
1018
		$stepInto = [];
1019
		// loop through the remaining folders that need to be ordered
1020
		foreach ($toOrderFolders as $folder) {
1021
			// move folders with the matching parentid to the ordered array
1022
			if ($folder['parentid'] == $parentid) {
1023
				$fid = $folder['folderid'];
1024
				$orderedFolders[$fid] = $folder;
1025
				unset($toOrderFolders[$fid]);
1026
				$stepInto[] = $fid;
1027
			}
1028
		}
1029
		// call recursively to move/order the leaves as well
1030
		foreach ($stepInto as $fid) {
1031
			$this->orderAdditionalFoldersHierarchically($toOrderFolders, $orderedFolders, $fid);
1032
		}
1033
	}
1034
1035
	/*----------------------------------------------------------------------------------------------------------
1036
	 * Additional Folders operations - END
1037
	 */
1038
1039
	/**
1040
	 * Generates the AS folder hash from the backend folder id, type and name.
1041
	 *
1042
	 * @param string $backendid    Backend folder id
1043
	 * @param string $folderOrigin Folder type is one of   'U' (user)
1044
	 *                             'C' (configured)
1045
	 *                             'S' (shared)
1046
	 *                             'G' (global address book)
1047
	 *                             'I' (impersonated)
1048
	 * @param string $folderName   Folder name of the backend folder
1049
	 *
1050
	 * @return string
1051
	 */
1052
	private function generateFolderHash($backendid, $folderOrigin, $folderName) {
1053
		// Hash backendid with crc32 and get the hex representation of it.
1054
		// 5 chars of hash + $folderOrigin should still be enough to avoid collisions.
1055
		$folderId = substr($folderOrigin . dechex(crc32($backendid)), 0, 6);
1056
		$cnt = 0;
1057
		// Collision avoiding. Append an increasing number to the string to hash
1058
		// until there aren't any collisions. Probably a smaller number is also sufficient.
1059
		while ((isset($this->contentData[$folderId]) || (is_array($this->backend2folderidCache) && in_array($folderId, $this->backend2folderidCache, true))) && $cnt < 10000) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (IssetNode || is_array($... true)) && $cnt < 10000, Probably Intended Meaning: IssetNode || (is_array($... true) && $cnt < 10000)
Loading history...
1060
			$folderId = substr($folderOrigin . dechex(crc32($backendid . $folderName . $cnt++)), 0, 6);
1061
			SLog::Write(LOGLEVEL_WARN, sprintf("ASDevice->generateFolderHash(): collision avoiding nr %05d. Generated hash: '%s'", $cnt, $folderId));
1062
		}
1063
		if ($cnt >= 10000) {
1064
			throw new FatalException("ASDevice->generateFolderHash(): too many colissions while generating folder hash.");
1065
		}
1066
1067
		return $folderId;
1068
	}
1069
}
1070