grommunio /
grommunio-sync
| 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
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
|
|||
| 60 | $this->devicetype = $devicetype; |
||
|
0 ignored issues
–
show
|
|||
| 61 | [$this->deviceuser, $this->domain] = Utils::SplitDomainUser($getuser); |
||
|
0 ignored issues
–
show
|
|||
| 62 | $this->useragent = $useragent; |
||
|
0 ignored issues
–
show
|
|||
| 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
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
|
|||
| 114 | $this->devicetype = Request::GetDeviceType(); |
||
|
0 ignored issues
–
show
|
|||
| 115 | $this->deviceuser = Request::GetUser(); |
||
|
0 ignored issues
–
show
|
|||
| 116 | $this->domain = Request::GetAuthDomain(); |
||
|
0 ignored issues
–
show
|
|||
| 117 | $this->useragent = Request::GetUserAgent(); |
||
|
0 ignored issues
–
show
|
|||
| 118 | $this->firstsynctime = time(); |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 173 | $this->changed = true; |
||
| 174 | } |
||
| 175 | } |
||
| 176 | $this->useragent = $useragent; |
||
|
0 ignored issues
–
show
|
|||
| 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
The property
id does not exist on StateObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
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
|
|||
| 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
|
|||
| 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
|
|||
| 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
|
|||
| 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
|
|||
| 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
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
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
|
|||
| 410 | // when unsetting the hierarchycache, also remove saved contentdata and ignoredmessages |
||
| 411 | if ($folderid === false && $uuid === false) { |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 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
|
|||
| 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
|
|||
| 512 | $this->changed = true; |
||
| 513 | |||
| 514 | // update the reverse cache as well |
||
| 515 | if (is_array($this->backend2folderidCache)) { |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 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
|
|||
| 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
|
|||
| 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
|
|||
| 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
|
|||
| 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
|
|||
| 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
|
|||
| 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
|
|||
| 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 |