ASDevice   F
last analyzed

Complexity

Total Complexity 177

Size/Duplication

Total Lines 1046
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 388
c 3
b 0
f 0
dl 0
loc 1046
rs 2
wmc 177

35 Methods

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

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
/*
4
 * SPDX-License-Identifier: AGPL-3.0-only
5
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
6
 * SPDX-FileCopyrightText: Copyright 2020-2024 grommunio GmbH
7
 *
8
 * The ASDevice holds basic data about a device, its users and
9
 * the linked states
10
 */
11
12
class ASDevice extends StateObject {
13
	public const UNDEFINED = -1;
14
	// content data
15
	public const FOLDERUUID = 1;
16
	public const FOLDERTYPE = 2;
17
	public const FOLDERSUPPORTEDFIELDS = 3;
18
	public const FOLDERSYNCSTATUS = 4;
19
	public const FOLDERBACKENDID = 5;
20
21
	// expected values for not set member variables
22
	protected $unsetdata = [
23
		'useragenthistory' => [],
24
		'hierarchyuuid' => false,
25
		'contentdata' => [],
26
		'wipestatus' => SYNC_PROVISION_RWSTATUS_NA,
27
		'wiperequestedby' => false,
28
		'wiperequestedon' => false,
29
		'wipeactionon' => false,
30
		'lastupdatetime' => 0,
31
		'conversationmode' => false,
32
		'policyhash' => false,
33
		'policykey' => self::UNDEFINED,
34
		'forcesave' => false,
35
		'asversion' => false,
36
		'ignoredmessages' => [],
37
		'announcedASversion' => false,
38
		'foldersynccomplete' => true,
39
		'additionalfolders' => [],
40
		'syncfiltertype' => false,
41
	];
42
43
	protected $newdevice;
44
	protected $hierarchyCache;
45
	protected $ignoredMessageIds;
46
	protected $backend2folderidCache;
47
48
	/**
49
	 * AS Device constructor.
50
	 */
51
	public function __construct() {
52
		$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...
53
		$this->newdevice = true;
54
		$this->ignoredMessageIds = [];
55
		$this->backend2folderidCache = false;
56
	}
57
58
	public function Initialize($devid, $devicetype, $getuser, $useragent) {
59
		$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...
60
		$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...
61
		[$this->deviceuser, $this->domain] = Utils::SplitDomainUser($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...
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...
62
		$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...
63
	}
64
65
	/**
66
	 * Removes internal data from the object, so this data can not be exposed.
67
	 *
68
	 * @param bool $stripHierarchyCache (opt) strips the hierarchy cache - default: true
69
	 *
70
	 * @return bool
71
	 */
72
	public function StripData($stripHierarchyCache = true) {
73
		unset(
74
			$this->changed,
75
			$this->unsetdata,
76
			$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...
77
			$this->newdevice,
78
			$this->ignoredMessageIds
79
		);
80
81
		$this->backend2folderidCache = false;
82
83
		if (!$stripHierarchyCache && $this->hierarchyCache !== false && $this->hierarchyCache instanceof ChangesMemoryWrapper) {
84
			$this->hierarchyCache->StripData();
85
		}
86
		else {
87
			unset($this->hierarchyCache);
88
		}
89
90
		return true;
91
	}
92
93
	/**
94
	 * Indicates if the object was just created.
95
	 *
96
	 * @return bool
97
	 */
98
	public function IsNewDevice() {
99
		return isset($this->newdevice) && $this->newdevice === true;
100
	}
101
102
	/**
103
	 * Marked as loaded device.
104
	 *
105
	 * @return bool
106
	 */
107
	public function LoadedDevice() {
108
		$this->newdevice = false;
109
110
		// Gsync Issue #52
111
		// TODO: Remove fallback code for fix missing properties
112
		if (!isset($this->data['deviceid']) || !$this->data['deviceid']) {
113
			$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...
114
			$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...
115
			$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...
116
			$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...
117
			$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...
118
			$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...
119
			SLog::Write(LOGLEVEL_INFO, "Successfulyy set missing properties (GSync #52). Requesting data to be saved.");
120
		}
121
122
		return true;
123
	}
124
125
	/*----------------------------------------------------------------------------------------------------------
126
	 * Non-standard Getter and Setter
127
	 */
128
129
	/**
130
	 * Returns the user agent of this device.
131
	 *
132
	 * @return string
133
	 */
134
	public function GetDeviceUserAgent() {
135
		if (!isset($this->useragent) || !$this->useragent) {
136
			return "unknown";
137
		}
138
139
		return $this->useragent;
140
	}
141
142
	/**
143
	 * Returns the user agent history of this device.
144
	 *
145
	 * @return string
146
	 */
147
	public function GetDeviceUserAgentHistory() {
148
		return $this->useragentHistory;
149
	}
150
151
	/**
152
	 * Sets the useragent of the current request
153
	 * If this value is already available, no update is done.
154
	 *
155
	 * @param string $useragent
156
	 *
157
	 * @return bool
158
	 */
159
	public function SetUserAgent($useragent) {
160
		if ($useragent == $this->useragent || $useragent === false || $useragent === Request::UNKNOWN) {
161
			return true;
162
		}
163
164
		// save the old user agent, if available
165
		if ($this->useragent != "") {
166
			// [] = changedate, previous user agent
167
			$a = $this->useragentHistory;
168
169
			// only add if this agent was not seen before
170
			if (!in_array([true, $this->useragent], $a)) {
171
				$a[] = [time(), $this->useragent];
172
				$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...
173
				$this->changed = true;
174
			}
175
		}
176
		$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...
177
178
		return true;
179
	}
180
181
	/**
182
	 * Adds a messages which was ignored to the device data.
183
	 *
184
	 * @param StateObject $ignoredMessage
185
	 *
186
	 * @return bool
187
	 */
188
	public function AddIgnoredMessage($ignoredMessage) {
189
		// we should have all previously ignored messages in an id array
190
		if (count($this->ignoredMessages) != count($this->ignoredMessageIds)) {
191
			foreach ($this->ignoredMessages as $oldMessage) {
192
				if (!isset($this->ignoredMessageIds[$oldMessage->folderid])) {
193
					$this->ignoredMessageIds[$oldMessage->folderid] = [];
194
				}
195
				$this->ignoredMessageIds[$oldMessage->folderid][] = $oldMessage->id;
196
			}
197
		}
198
199
		// try not to add the same message several times
200
		if (isset($ignoredMessage->folderid, $ignoredMessage->id)) {
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on StateObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property folderid does not exist on StateObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
201
			if (!isset($this->ignoredMessageIds[$ignoredMessage->folderid])) {
202
				$this->ignoredMessageIds[$ignoredMessage->folderid] = [];
203
			}
204
205
			if (in_array($ignoredMessage->id, $this->ignoredMessageIds[$ignoredMessage->folderid])) {
206
				$this->RemoveIgnoredMessage($ignoredMessage->folderid, $ignoredMessage->id);
207
			}
208
209
			$this->ignoredMessageIds[$ignoredMessage->folderid][] = $ignoredMessage->id;
210
			$msges = $this->ignoredMessages;
211
			$msges[] = $ignoredMessage;
212
			$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...
213
			$this->changed = true;
214
215
			return true;
216
		}
217
218
		$msges = $this->ignoredMessages;
219
		$msges[] = $ignoredMessage;
220
		$this->ignoredMessages = $msges;
221
		$this->changed = true;
222
		SLog::Write(LOGLEVEL_WARN, "ASDevice->AddIgnoredMessage(): added message has no folder/id");
223
224
		return true;
225
	}
226
227
	/**
228
	 * Removes message in the list of ignored messages.
229
	 *
230
	 * @param string $folderid parent folder id of the message
231
	 * @param string $id       message id
232
	 *
233
	 * @return bool
234
	 */
235
	public function RemoveIgnoredMessage($folderid, $id) {
236
		// we should have all previously ignored messages in an id array
237
		if (count($this->ignoredMessages) != count($this->ignoredMessageIds)) {
238
			foreach ($this->ignoredMessages as $oldMessage) {
239
				if (!isset($this->ignoredMessageIds[$oldMessage->folderid])) {
240
					$this->ignoredMessageIds[$oldMessage->folderid] = [];
241
				}
242
				$this->ignoredMessageIds[$oldMessage->folderid][] = $oldMessage->id;
243
			}
244
		}
245
246
		$foundMessage = false;
247
		// there are ignored messages in that folder
248
		if (isset($this->ignoredMessageIds[$folderid])) {
249
			// resync of a folder. we should remove all previously ignored messages
250
			if ($id === false || in_array($id, $this->ignoredMessageIds[$folderid], true)) {
251
				$ignored = $this->ignoredMessages;
252
				$newMessages = [];
253
				foreach ($ignored as $im) {
254
					if ($im->folderid == $folderid) {
255
						if ($id === false || $im->id === $id) {
256
							$foundMessage = true;
257
							if (count($this->ignoredMessageIds[$folderid]) == 1) {
258
								unset($this->ignoredMessageIds[$folderid]);
259
							}
260
							else {
261
								unset($this->ignoredMessageIds[$folderid][array_search($id, $this->ignoredMessageIds[$folderid])]);
262
							}
263
264
							continue;
265
						}
266
267
						$newMessages[] = $im;
268
					}
269
				}
270
				$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...
271
				$this->changed = true;
272
			}
273
		}
274
275
		return $foundMessage;
276
	}
277
278
	/**
279
	 * Indicates if a message is in the list of ignored messages.
280
	 *
281
	 * @param string $folderid parent folder id of the message
282
	 * @param string $id       message id
283
	 *
284
	 * @return bool
285
	 */
286
	public function HasIgnoredMessage($folderid, $id) {
287
		// we should have all previously ignored messages in an id array
288
		if (count($this->ignoredMessages) != count($this->ignoredMessageIds)) {
289
			foreach ($this->ignoredMessages as $oldMessage) {
290
				if (!isset($this->ignoredMessageIds[$oldMessage->folderid])) {
291
					$this->ignoredMessageIds[$oldMessage->folderid] = [];
292
				}
293
				$this->ignoredMessageIds[$oldMessage->folderid][] = $oldMessage->id;
294
			}
295
		}
296
297
		$foundMessage = false;
298
		// there are ignored messages in that folder
299
		if (isset($this->ignoredMessageIds[$folderid])) {
300
			// resync of a folder. we should remove all previously ignored messages
301
			if ($id === false || in_array($id, $this->ignoredMessageIds[$folderid], true)) {
302
				$foundMessage = true;
303
			}
304
		}
305
306
		return $foundMessage;
307
	}
308
309
	/*----------------------------------------------------------------------------------------------------------
310
	 * HierarchyCache and ContentData operations
311
	 */
312
313
	/**
314
	 * Sets the HierarchyCache
315
	 * The hierarchydata, can be:
316
	 *  - false     a new HierarchyCache is initialized
317
	 *  - array()   new HierarchyCache is initialized and data from GetHierarchy is loaded
318
	 *  - string    previously serialized data is loaded.
319
	 *
320
	 * @param string $hierarchydata (opt)
321
	 *
322
	 * @return bool
323
	 */
324
	public function SetHierarchyCache($hierarchydata = false) {
325
		if ($hierarchydata !== false && $hierarchydata instanceof ChangesMemoryWrapper) {
0 ignored issues
show
introduced by
$hierarchydata is never a sub-type of ChangesMemoryWrapper.
Loading history...
326
			$this->hierarchyCache = $hierarchydata;
327
			$this->hierarchyCache->CopyOldState();
328
		}
329
		else {
330
			$this->hierarchyCache = new ChangesMemoryWrapper();
331
		}
332
333
		if (is_array($hierarchydata)) {
0 ignored issues
show
introduced by
The condition is_array($hierarchydata) is always false.
Loading history...
334
			return $this->hierarchyCache->ImportFolders($hierarchydata);
335
		}
336
337
		return true;
338
	}
339
340
	/**
341
	 * Returns serialized data of the HierarchyCache.
342
	 *
343
	 * @return string
344
	 */
345
	public function GetHierarchyCacheData() {
346
		if (isset($this->hierarchyCache)) {
347
			return $this->hierarchyCache;
348
		}
349
350
		SLog::Write(LOGLEVEL_WARN, "ASDevice->GetHierarchyCacheData() has no data! HierarchyCache probably never initialized.");
351
352
		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...
353
	}
354
355
	/**
356
	 * Returns the HierarchyCache Object.
357
	 *
358
	 * @return object HierarchyCache
359
	 */
360
	public function GetHierarchyCache() {
361
		if (!isset($this->hierarchyCache)) {
362
			$this->SetHierarchyCache();
363
		}
364
365
		SLog::Write(LOGLEVEL_DEBUG, "ASDevice->GetHierarchyCache(): " . $this->hierarchyCache->GetStat());
366
367
		return $this->hierarchyCache;
368
	}
369
370
	/**
371
	 * Returns all known folderids.
372
	 *
373
	 * @return array
374
	 */
375
	public function GetAllFolderIds() {
376
		if (isset($this->contentData) && is_array($this->contentData)) {
377
			return array_keys($this->contentData);
378
		}
379
380
		return [];
381
	}
382
383
	/**
384
	 * Returns a linked UUID for a folder id.
385
	 *
386
	 * @param string $folderid (opt) if not set, Hierarchy UUID is returned
387
	 *
388
	 * @return string
389
	 */
390
	public function GetFolderUUID($folderid = false) {
391
		if ($folderid === false) {
392
			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...
393
		}
394
395
		return $this->contentData[$folderid]->{self::FOLDERUUID} ?? false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->contentDat...lf::FOLDERUUID ?? 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...
396
	}
397
398
	/**
399
	 * Link a UUID to a folder id
400
	 * If a boolean false UUID is sent, the relation is removed.
401
	 *
402
	 * @param string $uuid
403
	 * @param string $folderid (opt) if not set Hierarchy UUID is linked
404
	 *
405
	 * @return bool
406
	 */
407
	public function SetFolderUUID($uuid, $folderid = false) {
408
		if ($folderid === false) {
409
			$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...
410
			// when unsetting the hierarchycache, also remove saved contentdata and ignoredmessages
411
			if ($folderid === false && $uuid === false) {
0 ignored issues
show
introduced by
The condition $uuid === false is always false.
Loading history...
412
				$this->contentData = [];
413
				$this->ignoredMessageIds = [];
414
				$this->ignoredMessages = [];
415
				$this->backend2folderidCache = false;
416
			}
417
			$this->changed = true;
418
		}
419
		else {
420
			$contentData = $this->contentData;
421
422
			if (!isset($contentData[$folderid])) {
423
				$contentData[$folderid] = new stdClass();
424
			}
425
426
			// check if the foldertype is set. This has to be available at this point, as generated during the first HierarchySync
427
			if (!isset($contentData[$folderid]->{self::FOLDERTYPE})) {
428
				return false;
429
			}
430
431
			if ($uuid) {
432
				$contentData[$folderid]->{self::FOLDERUUID} = $uuid;
433
			}
434
			else {
435
				$contentData[$folderid]->{self::FOLDERUUID} = false;
436
			}
437
438
			$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...
439
			$this->changed = true;
440
		}
441
442
		return true;
443
	}
444
445
	/**
446
	 * Returns a foldertype for a folder already known to the mobile.
447
	 *
448
	 * @param string $folderid
449
	 *
450
	 * @return bool|int returns false if the type is not set
451
	 */
452
	public function GetFolderType($folderid) {
453
		return $this->contentData[$folderid]->{self::FOLDERTYPE} ?? false;
454
	}
455
456
	/**
457
	 * Sets the foldertype of a folder id.
458
	 *
459
	 * @param string $folderid
460
	 * @param int    $foldertype ActiveSync folder type (as on the mobile)
461
	 *
462
	 * @return bool true if the type was set or updated
463
	 */
464
	public function SetFolderType($folderid, $foldertype) {
465
		$contentData = $this->contentData;
466
467
		if (!isset($contentData[$folderid])) {
468
			$contentData[$folderid] = new stdClass();
469
		}
470
		if (!isset($contentData[$folderid]->{self::FOLDERTYPE}) || $contentData[$folderid]->{self::FOLDERTYPE} != $foldertype) {
471
			$contentData[$folderid]->{self::FOLDERTYPE} = $foldertype;
472
			$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...
473
			$this->changed = true;
474
475
			return true;
476
		}
477
478
		return false;
479
	}
480
481
	/**
482
	 * Returns the backend folder id from the AS folderid known to the mobile.
483
	 *
484
	 * @param int $folderid
485
	 *
486
	 * @return bool|int returns false if the type is not set
487
	 */
488
	public function GetFolderBackendId($folderid) {
489
		return $this->contentData[$folderid]->{self::FOLDERBACKENDID} ?? false;
490
	}
491
492
	/**
493
	 * Sets the backend folder id of an AS folderid.
494
	 *
495
	 * @param string $folderid        the AS folder id
496
	 * @param string $backendfolderid the backend folder id
497
	 *
498
	 * @return bool true if the type was set or updated
499
	 */
500
	public function SetFolderBackendId($folderid, $backendfolderid) {
501
		if ($folderid === $backendfolderid || $folderid === false || $backendfolderid === false) {
502
			return false;
503
		}
504
505
		$contentData = $this->contentData;
506
		if (!isset($contentData[$folderid])) {
507
			$contentData[$folderid] = new stdClass();
508
		}
509
		if (!isset($contentData[$folderid]->{self::FOLDERBACKENDID}) || $contentData[$folderid]->{self::FOLDERBACKENDID} != $backendfolderid) {
510
			$contentData[$folderid]->{self::FOLDERBACKENDID} = $backendfolderid;
511
			$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...
512
			$this->changed = true;
513
514
			// update the reverse cache as well
515
			if (is_array($this->backend2folderidCache)) {
0 ignored issues
show
introduced by
The condition is_array($this->backend2folderidCache) is always false.
Loading history...
516
				$this->backend2folderidCache[$backendfolderid] = $folderid;
517
			}
518
519
			return true;
520
		}
521
522
		return false;
523
	}
524
525
	/**
526
	 * Gets the AS folderid for a backendFolderId.
527
	 * If there is no known AS folderId a new one is being created.
528
	 *
529
	 * @param string $backendid          Backend folder id
530
	 * @param bool   $generateNewIdIfNew generates a new AS folderid for the case the backend folder is not known yet
531
	 * @param string $folderOrigin       Folder type is one of   'U' (user)
532
	 *                                   'C' (configured)
533
	 *                                   'S' (shared)
534
	 *                                   'G' (global address book)
535
	 *                                   'I' (impersonated)
536
	 * @param string $folderName         Folder name of the backend folder
537
	 *
538
	 * @return string
539
	 */
540
	public function GetFolderIdForBackendId($backendid, $generateNewIdIfNew, $folderOrigin, $folderName) {
541
		// build the backend-to-folderId backwards cache once
542
		if ($this->backend2folderidCache === false) {
543
			$this->backend2folderidCache = [];
544
			foreach ($this->contentData as $folderid => $data) {
545
				if (isset($data->{self::FOLDERBACKENDID})) {
546
					$this->backend2folderidCache[$data->{self::FOLDERBACKENDID}] = $folderid;
547
				}
548
			}
549
550
			// if we couldn't find any backend-folderids but there is data in contentdata, then this is an old profile.
551
			// do not generate new folderids in this case
552
			if (empty($this->backend2folderidCache) && !empty($this->contentData)) {
553
				SLog::Write(LOGLEVEL_DEBUG, "ASDevice->GetFolderIdForBackendId(): this is a profile without backend-folderid mapping. Returning folderids as is.");
554
				$this->backend2folderidCache = true;
555
			}
556
		}
557
		if (is_array($this->backend2folderidCache) && isset($this->backend2folderidCache[$backendid])) {
558
			// Use cached version only if the folderOrigin matches
559
			if (Utils::GetFolderOriginFromId($this->backend2folderidCache[$backendid]) == $folderOrigin) {
560
				return $this->backend2folderidCache[$backendid];
561
			}
562
			// if we have a different origin, we need to actively search for all synchronized folders, as they might be synched with a different origin
563
			// the short-id is only used if the folder is being synchronized (in contentdata) - else any cached (temporarily) ids are NOT used
564
565
			foreach ($this->contentData as $folderid => $data) {
566
				if (isset($data->{self::FOLDERBACKENDID}) && $data->{self::FOLDERBACKENDID} == $backendid) {
567
					SLog::Write(LOGLEVEL_DEBUG, sprintf("ASDevice->GetFolderIdForBackendId(): found backendid in contentdata but with different folder type. Lookup '%s' - synchronized id '%s'", $folderOrigin, $folderid));
568
569
					return $folderid;
570
				}
571
			}
572
		}
573
574
		// nothing found? Then it's a new one, get and add it
575
		if (is_array($this->backend2folderidCache) && $generateNewIdIfNew) {
576
			if ($folderName == null) {
577
				SLog::Write(LOGLEVEL_INFO, "ASDevice->GetFolderIdForBackendId(): generating a new folder id for the folder without a name");
578
			}
579
			$newHash = $this->generateFolderHash($backendid, $folderOrigin, $folderName);
580
			SLog::Write(LOGLEVEL_DEBUG, sprintf("ASDevice->GetFolderIdForBackendId(): generated new folderid '%s' for backend-folderid '%s'", $newHash, $backendid));
581
			// 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
582
			$this->backend2folderidCache[$backendid] = $newHash;
583
584
			return $newHash;
585
		}
586
		SLog::Write(LOGLEVEL_DEBUG, sprintf("ASDevice->GetFolderIdForBackendId(): no valid condition found for determining folderid for backendid '%s'. Returning as is!", Utils::PrintAsString($backendid)));
587
588
		return $backendid;
589
	}
590
591
	/**
592
	 * Indicates if the device has a folderid mapping (short ids).
593
	 *
594
	 * @return bool
595
	 */
596
	public function HasFolderIdMapping() {
597
		if (is_array($this->backend2folderidCache)) {
0 ignored issues
show
introduced by
The condition is_array($this->backend2folderidCache) is always false.
Loading history...
598
			return true;
599
		}
600
		if (!is_array($this->contentData)) {
601
			return false;
602
		}
603
		foreach ($this->contentData as $folderid => $data) {
604
			if (isset($data->{self::FOLDERBACKENDID})) {
605
				return true;
606
			}
607
		}
608
609
		return false;
610
	}
611
612
	/**
613
	 * Gets the supported fields transmitted previously by the device
614
	 * for a certain folder.
615
	 *
616
	 * @param string $folderid
617
	 *
618
	 * @return array|bool false means no supportedFields are available
619
	 */
620
	public function GetSupportedFields($folderid) {
621
		if (isset($this->contentData, $this->contentData[$folderid], $this->contentData[$folderid]->{self::FOLDERUUID}) &&
622
				$this->contentData[$folderid]->{self::FOLDERUUID} !== false &&
623
				isset($this->contentData[$folderid]->{self::FOLDERSUPPORTEDFIELDS})) {
624
			return $this->contentData[$folderid]->{self::FOLDERSUPPORTEDFIELDS};
625
		}
626
627
		return false;
628
	}
629
630
	/**
631
	 * Sets the set of supported fields transmitted by the device for a certain folder.
632
	 *
633
	 * @param string $folderid
634
	 * @param array  $fieldlist supported fields
635
	 *
636
	 * @return bool
637
	 */
638
	public function SetSupportedFields($folderid, $fieldlist) {
639
		$contentData = $this->contentData;
640
		if (!isset($contentData[$folderid])) {
641
			$contentData[$folderid] = new stdClass();
642
		}
643
		$contentData[$folderid]->{self::FOLDERSUPPORTEDFIELDS} = $fieldlist;
644
		$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...
645
		$this->changed = true;
646
647
		return true;
648
	}
649
650
	/**
651
	 * Gets the current sync status of a certain folder.
652
	 *
653
	 * @param string $folderid
654
	 *
655
	 * @return bool|mixed false means the status is not available
656
	 */
657
	public function GetFolderSyncStatus($folderid) {
658
		if (isset($this->contentData[$folderid]->{self::FOLDERUUID}, $this->contentData[$folderid]->{self::FOLDERSYNCSTATUS}) &&
659
				$this->contentData[$folderid]->{self::FOLDERUUID} !== false) {
660
			return $this->contentData[$folderid]->{self::FOLDERSYNCSTATUS};
661
		}
662
663
		return false;
664
	}
665
666
	/**
667
	 * Sets the current sync status of a certain folder.
668
	 *
669
	 * @param string $folderid
670
	 * @param mixed  $status   if set to false the current status is deleted
671
	 *
672
	 * @return bool
673
	 */
674
	public function SetFolderSyncStatus($folderid, $status) {
675
		$contentData = $this->contentData;
676
		if (!isset($contentData[$folderid])) {
677
			$contentData[$folderid] = new stdClass();
678
		}
679
		if ($status !== false) {
680
			$contentData[$folderid]->{self::FOLDERSYNCSTATUS} = $status;
681
		}
682
		elseif (isset($contentData[$folderid]->{self::FOLDERSYNCSTATUS})) {
683
			unset($contentData[$folderid]->{self::FOLDERSYNCSTATUS});
684
		}
685
686
		$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...
687
		$this->changed = true;
688
689
		return true;
690
	}
691
692
	/*----------------------------------------------------------------------------------------------------------
693
	 * Additional Folders operations
694
	  TODO: All these methods are not being used in the code currently and could be removed at a later point if this
695
	  functionality is not being made available anymore.
696
	 */
697
698
	/**
699
	 * Returns a list of all additional folders of this device.
700
	 *
701
	 * @return array
702
	 */
703
	public function GetAdditionalFolders() {
704
		if (is_array($this->additionalfolders)) {
705
			return array_values($this->additionalfolders);
706
		}
707
708
		return [];
709
	}
710
711
	/**
712
	 * Returns an additional folder by folder ID.
713
	 *
714
	 * @param string $folderid
715
	 *
716
	 * @return array|false Returns a list of properties. Else false if folder id is unknown.
717
	 */
718
	// TODO: not used
719
	public function GetAdditionalFolder($folderid) {
720
		// check if the $folderid is one of our own - this will in mostly NOT be the case, so we do not log here
721
		if (!isset($this->additionalfolders[$folderid])) {
722
			return false;
723
		}
724
725
		return $this->additionalfolders[$folderid];
726
	}
727
728
	/**
729
	 * Adds an additional folder to this device & user.
730
	 *
731
	 * @param string $store     the store where this folder is located, e.g. "SYSTEM" (for public folder) or a username.
732
	 * @param string $folderid  the folder id of the additional folder
733
	 * @param string $name      the name of the additional folder (has to be unique for all folders on the device)
734
	 * @param string $type      AS foldertype of SYNC_FOLDER_TYPE_USER_*
735
	 * @param int    $flags     Additional flags, like DeviceManager::FLD_FLAGS_SENDASOWNER
736
	 * @param string $parentid  the parentid of this folder
737
	 * @param bool   $checkDups indicates if duplicate names and ids should be verified. Default: true
738
	 *
739
	 * @return bool
740
	 */
741
	// TODO: not used
742
	public function AddAdditionalFolder($store, $folderid, $name, $type, $flags, $parentid = 0, $checkDups = true) {
743
		// check if a folderid and name were sent
744
		if (!$folderid || !$name) {
745
			SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->AddAdditionalFolder(): No valid folderid ('%s') or name ('%s') sent. Aborting. ", $folderid, $name));
746
747
			return false;
748
		}
749
750
		// check if type is of a additional user type
751
		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])) {
752
			SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->AddAdditionalFolder(): folder can not be added because the specified type '%s' is not a permitted user type.", $type));
753
754
			return false;
755
		}
756
757
		// check if a folder with this ID is already in the list
758
		if (isset($this->additionalfolders[$folderid])) {
759
			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));
760
761
			return false;
762
		}
763
764
		// check if a folder with that Name is already in the list and that its parent exists
765
		$parentFound = false;
766
		foreach ($this->additionalfolders as $k => $folder) {
767
			// This is fixed in fixstates, but we could keep this here a while longer.
768
			// TODO: remove line at a later point.
769
			if (!isset($folder['parentid'])) {
770
				$folder['parentid'] = "0";
771
			}
772
773
			if ($folder['name'] == $name && $folder['parentid'] == $parentid) {
774
				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));
775
776
				return false;
777
			}
778
			if ($folder['folderid'] == $parentid) {
779
				$parentFound = true;
780
			}
781
		}
782
		if ($parentid != '0' && !$parentFound) {
783
			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));
784
785
			return false;
786
		}
787
788
		// check if a folder with this ID or Name is already known on the device (regular folder)
789
		if ($checkDups) {
790
			// in order to check for the parent-ids we need a shortid
791
			$parentShortId = $this->GetFolderIdForBackendId($parentid, false, null, null);
792
			foreach ($this->GetHierarchyCache()->ExportFolders() as $syncedFolderid => $folder) {
793
				if ($syncedFolderid === $folderid || $folder->BackendId === $folderid) {
794
					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));
795
796
					return false;
797
				}
798
799
				// $folder is a SyncFolder object here
800
				if ($folder->displayname == $name && ($folder->parentid == $parentid || $folder->parentid == $parentShortId)) {
801
					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));
802
803
					return false;
804
				}
805
			}
806
		}
807
808
		// add the folder
809
		$af = $this->additionalfolders;
810
		$af[$folderid] = [
811
			'store' => $store,
812
			'folderid' => $folderid,
813
			'parentid' => $parentid,
814
			'name' => $name,
815
			'type' => $type,
816
			'flags' => $flags,
817
		];
818
		$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...
819
		$this->changed = true;
820
821
		// generate an integer folderid for it
822
		$this->GetFolderIdForBackendId($folderid, true, DeviceManager::FLD_ORIGIN_SHARED, $name);
823
824
		return true;
825
	}
826
827
	/**
828
	 * Edits (sets a new name) for an additional folder. Store, folderid and type can not be edited. Remove and add instead.
829
	 *
830
	 * @param string $folderid  the folder id of the additional folder
831
	 * @param string $name      the name of the additional folder (has to be unique for all folders on the device)
832
	 * @param int    $flags     Additional flags, like DeviceManager::FLD_FLAGS_SENDASOWNER
833
	 * @param string $parentid  the parentid of this folder
834
	 * @param bool   $checkDups indicates if duplicate names and ids should be verified. Default: true
835
	 *
836
	 * @return bool
837
	 */
838
	// TODO: not used
839
	public function EditAdditionalFolder($folderid, $name, $flags, $parentid = 0, $checkDups = true) {
840
		// check if a folderid and name were sent
841
		if (!$folderid || !$name) {
842
			SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->EditAdditionalFolder(): No valid folderid ('%s') or name ('%s') sent. Aborting. ", $folderid, $name));
843
844
			return false;
845
		}
846
847
		// check if a folder with this ID is known
848
		if (!isset($this->additionalfolders[$folderid])) {
849
			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));
850
851
			return false;
852
		}
853
854
		// check if a folder with the new name is already in the list
855
		foreach ($this->additionalfolders as $existingFolderid => $folder) {
856
			if ($folder['name'] == $name && $folderid !== $existingFolderid) {
857
				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));
858
859
				return false;
860
			}
861
		}
862
863
		// check if a folder with the new name is already known on the device (regular folder)
864
		if ($checkDups) {
865
			// in order to check for the parent-ids we need a shortid
866
			$parentShortId = $this->GetFolderIdForBackendId($parentid, false, null, null);
867
			foreach ($this->GetHierarchyCache()->ExportFolders() as $syncedFolderid => $folder) {
868
				// $folder is a SyncFolder object here
869
				if ($folder->displayname == $name && $folderid !== $folder->BackendId && $folderid !== $syncedFolderid && ($folder->parentid == $parentid || $folder->parentid == $parentShortId)) {
870
					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));
871
872
					return false;
873
				}
874
			}
875
		}
876
877
		// update the name
878
		$af = $this->additionalfolders;
879
		$af[$folderid]['name'] = $name;
880
		$af[$folderid]['flags'] = $flags;
881
		$af[$folderid]['parentid'] = $parentid;
882
		$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...
883
		$this->changed = true;
884
885
		return true;
886
	}
887
888
	/**
889
	 * Removes an additional folder from this device & user.
890
	 *
891
	 * @param mixed $folderid
892
	 *
893
	 * @return bool
894
	 */
895
	// TODO: not used
896
	public function RemoveAdditionalFolder($folderid) {
897
		// check if a folderid were sent
898
		if (!$folderid) {
899
			SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->RemoveAdditionalFolder(): No valid folderid ('%s') sent. Aborting. ", $folderid));
900
901
			return false;
902
		}
903
		// check if a folder with this ID is known
904
		if (!isset($this->additionalfolders[$folderid])) {
905
			SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->RemoveAdditionalFolder(): folder can not be removed because there is no folder known with this folder id: '%s'", $folderid));
906
907
			return false;
908
		}
909
910
		// remove the folder
911
		$af = $this->additionalfolders;
912
		unset($af[$folderid]);
913
		$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...
914
		$this->changed = true;
915
916
		return true;
917
	}
918
919
	/**
920
	 * Sets a list of additional folders of one store to the device.
921
	 * If there are additional folders for the set_store, that are not in the list they will be removed.
922
	 *
923
	 * @param string $store   the store where this folder is located, e.g. "SYSTEM" (for public folder) or an username/email address.
924
	 * @param array  $folders a list of folders to be set for this user. Other existing additional folders (that are not in this list)
925
	 *                        will be removed. The list is an array containing folders, where each folder is an array with the following keys:
926
	 *                        'folderid'  (string) the folder id of the additional folder.
927
	 *                        '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.
928
	 *                        'name'      (string) the name of the additional folder (has to be unique for all folders on the device).
929
	 *                        'type'      (string) AS foldertype of SYNC_FOLDER_TYPE_USER_*
930
	 *                        'flags'     (int)    Additional flags, like DeviceManager::FLD_FLAGS_SENDASOWNER
931
	 *
932
	 * @return bool
933
	 */
934
	// TODO: not used
935
	public function SetAdditionalFolderList($store, $folders) {
936
		// remove all folders already shared for this store
937
		$newAF = [];
938
		$noDupsCheck = [];
939
		foreach ($this->additionalfolders as $keepFolder) {
940
			if ($keepFolder['store'] !== $store) {
941
				$newAF[$keepFolder['folderid']] = $keepFolder;
942
			}
943
			else {
944
				$noDupsCheck[$keepFolder['folderid']] = true;
945
			}
946
		}
947
		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)));
948
		// set remaining additional folders
949
		$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...
950
		$this->changed = true;
951
952
		// transform our array in a key/value array where folderids are keys and do some basic checks
953
		$toOrder = [];
954
		$ordered = [];
955
		$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];
956
		foreach ($folders as $f) {
957
			// fail early
958
			if (!$f['folderid'] || !$f['name']) {
959
				SLog::Write(LOGLEVEL_ERROR, sprintf("ASDevice->SetAdditionalFolderList(): No valid folderid ('%s') or name ('%s') sent. Aborting. ", $f['folderid'], $f['name']));
960
961
				return false;
962
			}
963
964
			// check if type is of a additional user type
965
			if (!in_array($f['type'], $validTypes)) {
966
				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']));
967
968
				return false;
969
			}
970
			$toOrder[$f['folderid']] = $f;
971
		}
972
973
		// order the array, so folders with leafs come first
974
		$this->orderAdditionalFoldersHierarchically($toOrder, $ordered);
975
976
		// if there are folders that are not be positioned in the tree, we can't add them!
977
		if (!empty($toOrder)) {
978
			$s = "";
979
			foreach ($toOrder as $f) {
980
				$s .= sprintf("'%s'('%s') ", $f['name'], $f['folderid']);
981
			}
982
			SLog::Write(LOGLEVEL_ERROR, "ASDevice->SetAdditionalFolderList(): cannot proceed as these folders have invalid parentids (not found): " . $s);
983
984
			return false;
985
		}
986
987
		foreach ($ordered as $f) {
988
			$status = $this->AddAdditionalFolder($store, $f['folderid'], $f['name'], $f['type'], $f['flags'], $f['parentid'], !isset($noDupsCheck[$f['folderid']]));
989
			SLog::Write(LOGLEVEL_DEBUG, sprintf("ASDevice->SetAdditionalFolderList(): set folder '%s' in additional folders list with status: %s", $f['name'], Utils::PrintAsString($status)));
990
			// break if a folder can not be added
991
			if (!$status) {
992
				return false;
993
			}
994
		}
995
996
		return true;
997
	}
998
999
	/**
1000
	 * Orders a list of folders so the parents are first in the array, all leaves come afterwards.
1001
	 *
1002
	 * @param array  $toOrderFolders an array of folders, where the folderids are keys. This array should be empty at the end.
1003
	 * @param array  $orderedFolders the ordered array
1004
	 * @param string $parentid       the parentid to start with, if not set '0' (main folders) is used
1005
	 */
1006
	// TODO: not used
1007
	private function orderAdditionalFoldersHierarchically(&$toOrderFolders, &$orderedFolders, $parentid = '0') {
1008
		$stepInto = [];
1009
		// loop through the remaining folders that need to be ordered
1010
		foreach ($toOrderFolders as $folder) {
1011
			// move folders with the matching parentid to the ordered array
1012
			if ($folder['parentid'] == $parentid) {
1013
				$fid = $folder['folderid'];
1014
				$orderedFolders[$fid] = $folder;
1015
				unset($toOrderFolders[$fid]);
1016
				$stepInto[] = $fid;
1017
			}
1018
		}
1019
		// call recursively to move/order the leaves as well
1020
		foreach ($stepInto as $fid) {
1021
			$this->orderAdditionalFoldersHierarchically($toOrderFolders, $orderedFolders, $fid);
1022
		}
1023
	}
1024
1025
	/*----------------------------------------------------------------------------------------------------------
1026
	 * Additional Folders operations - END
1027
	 */
1028
1029
	/**
1030
	 * Generates the AS folder hash from the backend folder id, type and name.
1031
	 *
1032
	 * @param string $backendid    Backend folder id
1033
	 * @param string $folderOrigin Folder type is one of   'U' (user)
1034
	 *                             'C' (configured)
1035
	 *                             'S' (shared)
1036
	 *                             'G' (global address book)
1037
	 *                             'I' (impersonated)
1038
	 * @param string $folderName   Folder name of the backend folder
1039
	 *
1040
	 * @return string
1041
	 */
1042
	private function generateFolderHash($backendid, $folderOrigin, $folderName) {
1043
		// Hash backendid with crc32 and get the hex representation of it.
1044
		// 5 chars of hash + $folderOrigin should still be enough to avoid collisions.
1045
		$folderId = substr($folderOrigin . dechex(crc32($backendid)), 0, 6);
1046
		$cnt = 0;
1047
		// Collision avoiding. Append an increasing number to the string to hash
1048
		// until there aren't any collisions. Probably a smaller number is also sufficient.
1049
		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...
1050
			$folderId = substr($folderOrigin . dechex(crc32($backendid . $folderName . $cnt++)), 0, 6);
1051
			SLog::Write(LOGLEVEL_WARN, sprintf("ASDevice->generateFolderHash(): collision avoiding nr %05d. Generated hash: '%s'", $cnt, $folderId));
1052
		}
1053
		if ($cnt >= 10000) {
1054
			throw new FatalException("ASDevice->generateFolderHash(): too many colissions while generating folder hash.");
1055
		}
1056
1057
		return $folderId;
1058
	}
1059
}
1060