1<?php 2/*********************************************** 3* File : devicemanager.php 4* Project : Z-Push 5* Descr : Manages device relevant data, provisioning, 6* loop detection and device states. 7* The DeviceManager uses a IStateMachine 8* implementation with IStateMachine::DEVICEDATA 9* to save device relevant data. 10* 11* Created : 11.04.2011 12* 13* Copyright 2007 - 2016 Zarafa Deutschland GmbH 14* 15* This program is free software: you can redistribute it and/or modify 16* it under the terms of the GNU Affero General Public License, version 3, 17* as published by the Free Software Foundation. 18* 19* This program is distributed in the hope that it will be useful, 20* but WITHOUT ANY WARRANTY; without even the implied warranty of 21* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22* GNU Affero General Public License for more details. 23* 24* You should have received a copy of the GNU Affero General Public License 25* along with this program. If not, see <http://www.gnu.org/licenses/>. 26* 27* Consult LICENSE file for details 28************************************************/ 29 30class DeviceManager { 31 // broken message indicators 32 const MSG_BROKEN_UNKNOWN = 1; 33 const MSG_BROKEN_CAUSINGLOOP = 2; 34 const MSG_BROKEN_SEMANTICERR = 4; 35 36 const FLD_SYNC_INITIALIZED = 1; 37 const FLD_SYNC_INPROGRESS = 2; 38 const FLD_SYNC_COMPLETED = 4; 39 40 // new types need to be added to Request::HEX_EXTENDED2 filter 41 const FLD_ORIGIN_USER = "U"; 42 const FLD_ORIGIN_CONFIG = "C"; 43 const FLD_ORIGIN_SHARED = "S"; 44 const FLD_ORIGIN_GAB = "G"; 45 const FLD_ORIGIN_IMPERSONATED = "I"; 46 47 const FLD_FLAGS_NONE = 0; 48 const FLD_FLAGS_SENDASOWNER = 1; 49 const FLD_FLAGS_TRACKSHARENAME = 2; 50 const FLD_FLAGS_CALENDARREMINDERS = 4; 51 const FLD_FLAGS_NOREADONLYNOTIFY = 8; 52 53 private $device; 54 private $deviceHash; 55 private $saveDevice; 56 private $statemachine; 57 private $stateManager; 58 private $incomingData = 0; 59 private $outgoingData = 0; 60 61 private $windowSize; 62 private $latestFolder; 63 64 private $loopdetection; 65 private $hierarchySyncRequired; 66 private $additionalFoldersHash; 67 68 /** 69 * Constructor 70 * 71 * @access public 72 */ 73 public function __construct() { 74 $this->statemachine = ZPush::GetStateMachine(); 75 $this->deviceHash = false; 76 $this->devid = Request::GetDeviceID(); 77 $this->saveDevice = true; 78 $this->windowSize = array(); 79 $this->latestFolder = false; 80 $this->hierarchySyncRequired = false; 81 82 // only continue if deviceid is set 83 if ($this->devid) { 84 $this->device = new ASDevice($this->devid, Request::GetDeviceType(), Request::GetGETUser(), Request::GetUserAgent()); 85 $this->loadDeviceData(); 86 87 ZPush::GetTopCollector()->SetUserAgent($this->device->GetDeviceUserAgent()); 88 } 89 else 90 throw new FatalNotImplementedException("Can not proceed without a device id."); 91 92 $this->loopdetection = new LoopDetection(); 93 $this->loopdetection->ProcessLoopDetectionInit(); 94 $this->loopdetection->ProcessLoopDetectionPreviousConnectionFailed(); 95 96 $this->stateManager = new StateManager(); 97 $this->stateManager->SetDevice($this->device); 98 99 $this->additionalFoldersHash = $this->getAdditionalFoldersHash(); 100 101 if ($this->IsKoe() && $this->device->GetKoeVersion() !== false) { 102 ZLog::Write(LOGLEVEL_DEBUG, sprintf("KOE: %s / %s / %s", $this->device->GetKoeVersion(), $this->device->GetKoeBuild(), strftime("%Y-%m-%d %H:%M", $this->device->GetKoeBuildDate()))); 103 ZLog::Write(LOGLEVEL_DEBUG, sprintf("KOE Capabilities: %s ", count($this->device->GetKoeCapabilities()) ? implode(',', $this->device->GetKoeCapabilities()) : 'unknown')); 104 ZLog::Write(LOGLEVEL_DEBUG, sprintf("KOE Last confirmed access: %s (may be up to 7h old)", ($this->device->GetKoeLastAccess() ? strftime("%Y-%m-%d %H:%M", $this->device->GetKoeLastAccess()) : 'unknown'))); 105 } 106 } 107 108 /** 109 * Load another different device. 110 * @param ASDevice $asDevice 111 */ 112 public function SetDevice($asDevice) { 113 $this->device = $asDevice; 114 $this->loadDeviceData(); 115 $this->stateManager->SetDevice($this->device); 116 } 117 118 /** 119 * Returns the StateManager for the current device 120 * 121 * @access public 122 * @return StateManager 123 */ 124 public function GetStateManager() { 125 return $this->stateManager; 126 } 127 128 /**---------------------------------------------------------------------------------------------------------- 129 * Device operations 130 */ 131 132 /** 133 * Announces amount of transmitted data to the DeviceManager 134 * 135 * @param int $datacounter 136 * 137 * @access public 138 * @return boolean 139 */ 140 public function SentData($datacounter) { 141 // TODO save this somewhere 142 $this->incomingData = Request::GetContentLength(); 143 $this->outgoingData = $datacounter; 144 } 145 146 /** 147 * Called at the end of the request 148 * Statistics about received/sent data is saved here 149 * 150 * @access public 151 * @return boolean 152 */ 153 public function Save() { 154 // TODO save other stuff 155 156 // check if previousily ignored messages were synchronized for the current folder 157 // on multifolder operations of AS14 this is done by setLatestFolder() 158 if ($this->latestFolder !== false) 159 $this->checkBrokenMessages($this->latestFolder); 160 161 // update the user agent and AS version on the device 162 $this->device->SetUserAgent(Request::GetUserAgent()); 163 $this->device->SetASVersion(Request::GetProtocolVersion()); 164 165 // update data from the OL plugin (if available) 166 if (Request::HasKoeStats()) { 167 $this->device->SetKoeVersion(Request::GetKoeVersion()); 168 $this->device->SetKoeBuild(Request::GetKoeBuild()); 169 $this->device->SetKoeBuildDate(Request::GetKoeBuildDate()); 170 $this->device->SetKoeCapabilities(Request::GetKoeCapabilities()); 171 // update KOE last access time if it's at least 6h old 172 if ($this->device->GetKoeLastAccess() < time() - 21600) { 173 $this->device->SetKoeLastAccess(time()); 174 } 175 } 176 177 // data to be saved 178 $data = $this->device->GetData(); 179 if ($data && Request::IsValidDeviceID() && $this->saveDevice) { 180 ZLog::Write(LOGLEVEL_DEBUG, "DeviceManager->Save(): Device data changed"); 181 182 try { 183 // check if this is the first time the device data is saved and it is authenticated. If so, link the user to the device id 184 if ($this->device->IsNewDevice() && RequestProcessor::isUserAuthenticated()) { 185 ZLog::Write(LOGLEVEL_INFO, sprintf("Linking device ID '%s' to user '%s'", $this->devid, $this->device->GetDeviceUser())); 186 $this->statemachine->LinkUserDevice($this->device->GetDeviceUser(), $this->devid); 187 } 188 189 if (RequestProcessor::isUserAuthenticated() || $this->device->GetForceSave() ) { 190 $this->statemachine->SetState($data, $this->devid, IStateMachine::DEVICEDATA); 191 ZLog::Write(LOGLEVEL_DEBUG, "DeviceManager->Save(): Device data saved"); 192 } 193 } 194 catch (StateNotFoundException $snfex) { 195 ZLog::Write(LOGLEVEL_ERROR, "DeviceManager->Save(): Exception: ". $snfex->getMessage()); 196 } 197 } 198 199 // remove old search data 200 $oldpid = $this->loopdetection->ProcessLoopDetectionGetOutdatedSearchPID(); 201 if ($oldpid) { 202 ZPush::GetBackend()->GetSearchProvider()->TerminateSearch($oldpid); 203 } 204 205 // we terminated this process 206 if ($this->loopdetection) 207 $this->loopdetection->ProcessLoopDetectionTerminate(); 208 209 return true; 210 } 211 212 /** 213 * Sets if the AS Device should automatically be saved when terminating the request. 214 * 215 * @param boolean $doSave 216 * 217 * @access public 218 * @return void 219 */ 220 public function DoAutomaticASDeviceSaving($doSave) { 221 ZLog::Write(LOGLEVEL_DEBUG, "DeviceManager->DoAutomaticASDeviceSaving(): save automatically: ". Utils::PrintAsString($doSave)); 222 $this->saveDevice = $doSave; 223 } 224 225 /** 226 * Newer mobiles send extensive device informations with the Settings command 227 * These informations are saved in the ASDevice 228 * 229 * @param SyncDeviceInformation $deviceinformation 230 * 231 * @access public 232 * @return boolean 233 */ 234 public function SaveDeviceInformation($deviceinformation) { 235 ZLog::Write(LOGLEVEL_DEBUG, "Saving submitted device information"); 236 237 // set the user agent 238 if (isset($deviceinformation->useragent)) 239 $this->device->SetUserAgent($deviceinformation->useragent); 240 241 // save other informations 242 foreach (array("model", "imei", "friendlyname", "os", "oslanguage", "phonenumber", "mobileoperator", "enableoutboundsms") as $info) { 243 if (isset($deviceinformation->$info) && $deviceinformation->$info != "") { 244 $this->device->__set("device".$info, $deviceinformation->$info); 245 } 246 } 247 return true; 248 } 249 250 /**---------------------------------------------------------------------------------------------------------- 251 * Provisioning operations 252 */ 253 254 /** 255 * Checks if the sent policykey matches the latest policykey 256 * saved for the device 257 * 258 * @param string $policykey 259 * @param boolean $noDebug (opt) by default, debug message is shown 260 * @param boolean $checkPolicies (opt) by default check if the provisioning policies changed 261 * 262 * @access public 263 * @return boolean 264 */ 265 public function ProvisioningRequired($policykey, $noDebug = false, $checkPolicies = true) { 266 $this->loadDeviceData(); 267 268 // check if a remote wipe is required 269 if ($this->device->GetWipeStatus() > SYNC_PROVISION_RWSTATUS_OK) { 270 ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->ProvisioningRequired('%s'): YES, remote wipe requested", $policykey)); 271 return true; 272 } 273 274 $p = ( ($this->device->GetWipeStatus() != SYNC_PROVISION_RWSTATUS_NA && $policykey != $this->device->GetPolicyKey()) || 275 Request::WasPolicyKeySent() && $this->device->GetPolicyKey() == ASDevice::UNDEFINED ); 276 277 if (!$noDebug || $p) 278 ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->ProvisioningRequired('%s') saved device key '%s': %s", $policykey, $this->device->GetPolicyKey(), Utils::PrintAsString($p))); 279 280 if ($checkPolicies) { 281 $policyHash = $this->GetProvisioningObject()->GetPolicyHash(); 282 if ($this->device->hasPolicyhash() && $this->device->getPolicyhash() != $policyHash) { 283 $p = true; 284 ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->ProvisioningRequired(): saved policy hash '%s' changed '%s'. Provisioning required.", $this->device->getPolicyhash(), $policyHash)); 285 } 286 } 287 288 return $p; 289 } 290 291 /** 292 * Generates a new Policykey 293 * 294 * @access public 295 * @return int 296 */ 297 public function GenerateProvisioningPolicyKey() { 298 return mt_rand(100000000, 999999999); 299 } 300 301 /** 302 * Attributes a provisioned policykey to a device 303 * 304 * @param int $policykey 305 * 306 * @access public 307 * @return boolean status 308 */ 309 public function SetProvisioningPolicyKey($policykey) { 310 ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->SetPolicyKey('%s')", $policykey)); 311 return $this->device->SetPolicyKey($policykey); 312 } 313 314 /** 315 * Builds a Provisioning SyncObject with policies 316 * 317 * @param boolean $logPolicies optional, determines if the policies and values should be logged. Default: false 318 * 319 * @access public 320 * @return SyncProvisioning 321 */ 322 public function GetProvisioningObject($logPolicies = false) { 323 $policyName = $this->getPolicyName(); 324 $p = SyncProvisioning::GetObjectWithPolicies($this->getProvisioningPolicies($policyName), $logPolicies); 325 $p->PolicyName = $policyName; 326 return $p; 327 } 328 329 /** 330 * Returns the status of the remote wipe policy 331 * 332 * @access public 333 * @return int returns the current status of the device - SYNC_PROVISION_RWSTATUS_* 334 */ 335 public function GetProvisioningWipeStatus() { 336 return $this->device->GetWipeStatus(); 337 } 338 339 /** 340 * Updates the status of the remote wipe 341 * 342 * @param int $status - SYNC_PROVISION_RWSTATUS_* 343 * 344 * @access public 345 * @return boolean could fail if trying to update status to a wipe status which was not requested before 346 */ 347 public function SetProvisioningWipeStatus($status) { 348 ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->SetProvisioningWipeStatus() change from '%d' to '%d'",$this->device->GetWipeStatus(), $status)); 349 350 if ($status > SYNC_PROVISION_RWSTATUS_OK && !($this->device->GetWipeStatus() > SYNC_PROVISION_RWSTATUS_OK)) { 351 ZLog::Write(LOGLEVEL_ERROR, "Not permitted to update remote wipe status to a higher value as remote wipe was not initiated!"); 352 return false; 353 } 354 $this->device->SetWipeStatus($status); 355 return true; 356 } 357 358 /** 359 * Saves the policy hash and name in device's state. 360 * 361 * @param SyncProvisioning $provisioning 362 * 363 * @access public 364 * @return void 365 */ 366 public function SavePolicyHashAndName($provisioning) { 367 // save policies' hash and name 368 $this->device->SetPolicyname($provisioning->PolicyName); 369 $this->device->SetPolicyhash($provisioning->GetPolicyHash()); 370 ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->SavePolicyHashAndName(): Set policy: %s with hash: %s", $this->device->GetPolicyname(), $this->device->GetPolicyhash())); 371 } 372 373 374 /**---------------------------------------------------------------------------------------------------------- 375 * LEGACY AS 1.0 and WRAPPER operations 376 */ 377 378 /** 379 * Returns a wrapped Importer & Exporter to use the 380 * HierarchyChache 381 * 382 * @see ChangesMemoryWrapper 383 * @access public 384 * @return object HierarchyCache 385 */ 386 public function GetHierarchyChangesWrapper() { 387 return $this->device->GetHierarchyCache(); 388 } 389 390 /** 391 * Initializes the HierarchyCache for legacy syncs 392 * this is for AS 1.0 compatibility: 393 * save folder information synched with GetHierarchy() 394 * 395 * @param string $folders Array with folder information 396 * 397 * @access public 398 * @return boolean 399 */ 400 public function InitializeFolderCache($folders) { 401 $this->stateManager->SetDevice($this->device); 402 return $this->stateManager->InitializeFolderCache($folders); 403 } 404 405 /** 406 * Returns the ActiveSync folder type for a FolderID 407 * 408 * @param string $folderid 409 * 410 * @access public 411 * @return int/boolean boolean if no type is found 412 */ 413 public function GetFolderTypeFromCacheById($folderid) { 414 return $this->device->GetFolderType($folderid); 415 } 416 417 /** 418 * Returns a FolderID of default classes 419 * this is for AS 1.0 compatibility: 420 * this information was made available during GetHierarchy() 421 * 422 * @param string $class The class requested 423 * 424 * @access public 425 * @return string 426 * @throws NoHierarchyCacheAvailableException 427 */ 428 public function GetFolderIdFromCacheByClass($class) { 429 $folderidforClass = false; 430 // look at the default foldertype for this class 431 $type = ZPush::getDefaultFolderTypeFromFolderClass($class); 432 433 if ($type && $type > SYNC_FOLDER_TYPE_OTHER && $type < SYNC_FOLDER_TYPE_USER_MAIL) { 434 $folderids = $this->device->GetAllFolderIds(); 435 foreach ($folderids as $folderid) { 436 if ($type == $this->device->GetFolderType($folderid)) { 437 $folderidforClass = $folderid; 438 break; 439 } 440 } 441 442 // Old Palm Treos always do initial sync for calendar and contacts, even if they are not made available by the backend. 443 // We need to fake these folderids, allowing a fake sync/ping, even if they are not supported by the backend 444 // if the folderid would be available, they would already be returned in the above statement 445 if ($folderidforClass == false && ($type == SYNC_FOLDER_TYPE_APPOINTMENT || $type == SYNC_FOLDER_TYPE_CONTACT)) 446 $folderidforClass = SYNC_FOLDER_TYPE_DUMMY; 447 } 448 449 ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetFolderIdFromCacheByClass('%s'): '%s' => '%s'", $class, $type, $folderidforClass)); 450 return $folderidforClass; 451 } 452 453 /** 454 * Returns a FolderClass for a FolderID which is known to the mobile 455 * 456 * @param string $folderid 457 * 458 * @access public 459 * @return int 460 * @throws NoHierarchyCacheAvailableException, NotImplementedException 461 */ 462 public function GetFolderClassFromCacheByID($folderid) { 463 //TODO check if the parent folder exists and is also beeing synchronized 464 $typeFromCache = $this->device->GetFolderType($folderid); 465 if ($typeFromCache === false) 466 throw new NoHierarchyCacheAvailableException(sprintf("Folderid '%s' is not fully synchronized on the device", $folderid)); 467 468 $class = ZPush::GetFolderClassFromFolderType($typeFromCache); 469 if ($class === false) 470 throw new NotImplementedException(sprintf("Folderid '%s' is saved to be of type '%d' but this type is not implemented", $folderid, $typeFromCache)); 471 472 return $class; 473 } 474 475 /** 476 * Returns the backend folder id of the KOE GAB folder. 477 * This comes either from the configuration or from the device data. 478 * 479 * @return string|boolean returns false if not set or found 480 */ 481 public function GetKoeGabBackendFolderId() { 482 $gabid = false; 483 if (KOE_CAPABILITY_GAB) { 484 if (KOE_GAB_FOLDERID) { 485 $gabid = KOE_GAB_FOLDERID; 486 } 487 else if (KOE_GAB_STORE && KOE_GAB_NAME) { 488 $gabid = $this->device->GetKoeGabBackendFolderId(); 489 } 490 } 491 return $gabid; 492 } 493 494 /** 495 * Returns the additional folders as SyncFolder objects. 496 * 497 * @access public 498 * @return array of SyncFolder with backendids as keys 499 */ 500 public function GetAdditionalUserSyncFolders() { 501 $folders = array(); 502 503 // In impersonated stores, no additional folders will be synchronized 504 if (Request::GetImpersonatedUser()) { 505 return $folders; 506 } 507 508 foreach($this->device->GetAdditionalFolders() as $df) { 509 if (!isset($df['flags'])) { 510 $df['flags'] = 0; 511 ZLog::Write(LOGLEVEL_WARN, sprintf("DeviceManager->GetAdditionalUserSyncFolders(): Additional folder '%s' has no flags. Please run 'z-push-admin -a fixstates' to fix this issue.", $df['name'])); 512 } 513 if (!isset($df['parentid'])) { 514 $df['parentid'] = '0'; 515 ZLog::Write(LOGLEVEL_WARN, sprintf("DeviceManager->GetAdditionalUserSyncFolders(): Additional folder '%s' has no parentid. Please run 'z-push-admin -a fixstates' to fix this issue.", $df['name'])); 516 } 517 518 $folder = $this->BuildSyncFolderObject($df['store'], $df['folderid'], $df['parentid'], $df['name'], $df['type'], $df['flags'], DeviceManager::FLD_ORIGIN_SHARED); 519 $folders[$folder->BackendId] = $folder; 520 } 521 522 // ZO-40: add KOE GAB folder 523 if (KOE_CAPABILITY_GAB && $this->IsKoe() && KOE_GAB_STORE != "" && KOE_GAB_NAME != "") { 524 // if KOE_GAB_FOLDERID is set, use it 525 if (KOE_GAB_FOLDERID != "") { 526 $folder = $this->BuildSyncFolderObject(KOE_GAB_STORE, KOE_GAB_FOLDERID, '0', KOE_GAB_NAME, SYNC_FOLDER_TYPE_USER_APPOINTMENT, 0, DeviceManager::FLD_ORIGIN_GAB); 527 $folders[$folder->BackendId] = $folder; 528 } 529 else { 530 // get the GAB id from the device and from the backend 531 $deviceGabId = $this->device->GetKoeGabBackendFolderId(); 532 if (!ZPush::GetBackend()->Setup(KOE_GAB_STORE)) { 533 ZLog::Write(LOGLEVEL_WARN, sprintf("DeviceManager->GetAdditionalUserSyncFolders(): setup for store '%s' failed. Unable to search for KOE GAB folder.", KOE_GAB_STORE)); 534 } 535 else { 536 $backendGabId = ZPush::GetBackend()->GetKoeGabBackendFolderId(KOE_GAB_NAME); 537 if ($deviceGabId !== $backendGabId) { 538 ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetAdditionalUserSyncFolders(): Backend found different KOE GAB backend folderid: '%s'. Updating ASDevice.", $backendGabId)); 539 $this->device->SetKoeGabBackendFolderId($backendGabId); 540 } 541 542 if ($backendGabId) { 543 $folders[$backendGabId] = $this->BuildSyncFolderObject(KOE_GAB_STORE, $backendGabId, '0', KOE_GAB_NAME, SYNC_FOLDER_TYPE_USER_APPOINTMENT, 0, DeviceManager::FLD_ORIGIN_GAB); 544 } 545 } 546 } 547 } 548 return $folders; 549 } 550 551 /** 552 * Get the store of an additional folder. 553 * 554 * @param string $folderid 555 * 556 * @access public 557 * @return boolean|string 558 */ 559 public function GetAdditionalUserSyncFolder($folderid) { 560 // is this the KOE GAB folder? 561 if ($folderid && $folderid === $this->GetKoeGabBackendFolderId()) { 562 return KOE_GAB_STORE; 563 } 564 565 $f = $this->device->GetAdditionalFolder($folderid); 566 if ($f) { 567 return $f; 568 } 569 570 return false; 571 } 572 573 /** 574 * Checks if the message should be streamed to a mobile 575 * Should always be called before a message is sent to the mobile 576 * Returns true if there is something wrong and the content could break the 577 * synchronization 578 * 579 * @param string $id message id 580 * @param SyncObject &$message the method could edit the message to change the flags 581 * 582 * @access public 583 * @return boolean returns true if the message should NOT be send! 584 */ 585 public function DoNotStreamMessage($id, &$message) { 586 $folderid = $this->getLatestFolder(); 587 588 if (isset($message->parentid)) 589 $folder = $message->parentid; 590 591 // message was identified to be causing a loop 592 if ($this->loopdetection->IgnoreNextMessage(true, $id, $folderid)) { 593 $this->AnnounceIgnoredMessage($folderid, $id, $message, self::MSG_BROKEN_CAUSINGLOOP); 594 return true; 595 } 596 597 // message is semantically incorrect 598 if (!$message->Check(true)) { 599 $this->AnnounceIgnoredMessage($folderid, $id, $message, self::MSG_BROKEN_SEMANTICERR); 600 return true; 601 } 602 603 // check if this message is broken 604 if ($this->device->HasIgnoredMessage($folderid, $id)) { 605 // reset the flags so the message is always streamed with <Add> 606 $message->flags = false; 607 608 // track the broken message in the loop detection 609 $this->loopdetection->SetBrokenMessage($folderid, $id); 610 } 611 return false; 612 } 613 614 /** 615 * Removes device information about a broken message as it is been removed from the mobile. 616 * 617 * @param string $id message id 618 * 619 * @access public 620 * @return boolean 621 */ 622 public function RemoveBrokenMessage($id) { 623 $folderid = $this->getLatestFolder(); 624 if ($this->device->RemoveIgnoredMessage($folderid, $id)) { 625 ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->RemoveBrokenMessage('%s', '%s'): cleared data about previously ignored message", $folderid, $id)); 626 return true; 627 } 628 return false; 629 } 630 631 /** 632 * Amount of items to me synchronized 633 * 634 * @param string $folderid 635 * @param string $type 636 * @param int $queuedmessages; 637 * @access public 638 * @return int 639 */ 640 public function GetWindowSize($folderid, $uuid, $statecounter, $queuedmessages) { 641 if (isset($this->windowSize[$folderid])) 642 $items = $this->windowSize[$folderid]; 643 else 644 $items = WINDOW_SIZE_MAX; // 512 by default 645 646 $this->setLatestFolder($folderid); 647 648 // detect if this is a loop condition 649 $loop = $this->loopdetection->Detect($folderid, $uuid, $statecounter, $items, $queuedmessages); 650 if ($loop !== false) { 651 if ($loop === true) { 652 $items = ($items == 0) ? 0: 1+($this->loopdetection->IgnoreNextMessage(false)?1:0) ; 653 } 654 else { 655 // we got a new suggested window size 656 $items = $loop; 657 ZLog::Write(LOGLEVEL_DEBUG, sprintf("Mobile loop pre stage detected! Forcing smaller window size of %d before entering loop detection mode", $items)); 658 } 659 } 660 661 if ($items >= 0 && $items <= 2) 662 ZLog::Write(LOGLEVEL_WARN, sprintf("Mobile loop detected! Messages sent to the mobile will be restricted to %d items in order to identify the conflict", $items)); 663 664 return $items; 665 } 666 667 /** 668 * Sets the amount of items the device is requesting 669 * 670 * @param string $folderid 671 * @param int $maxItems 672 * 673 * @access public 674 * @return boolean 675 */ 676 public function SetWindowSize($folderid, $maxItems) { 677 $this->windowSize[$folderid] = $maxItems; 678 679 return true; 680 } 681 682 /** 683 * Sets the supported fields transmitted by the device for a certain folder 684 * 685 * @param string $folderid 686 * @param array $fieldlist supported fields 687 * 688 * @access public 689 * @return boolean 690 */ 691 public function SetSupportedFields($folderid, $fieldlist) { 692 return $this->device->SetSupportedFields($folderid, $fieldlist); 693 } 694 695 /** 696 * Gets the supported fields transmitted previousely by the device 697 * for a certain folder 698 * 699 * @param string $folderid 700 * 701 * @access public 702 * @return array/boolean 703 */ 704 public function GetSupportedFields($folderid) { 705 return $this->device->GetSupportedFields($folderid); 706 } 707 708 /** 709 * Returns the maximum filter type for a folder. 710 * This might be limited globally, per device or per folder. 711 * 712 * @param string $folderid 713 * 714 * @access public 715 * @return int 716 */ 717 public function GetFilterType($folderid, $backendFolderId) { 718 global $specialSyncFilter; 719 // either globally configured SYNC_FILTERTIME_MAX or ALL (no limit) 720 $maxAllowed = (defined('SYNC_FILTERTIME_MAX') && SYNC_FILTERTIME_MAX > SYNC_FILTERTYPE_ALL) ? SYNC_FILTERTIME_MAX : SYNC_FILTERTYPE_ALL; 721 722 // TODO we could/should check for a specific value for the folder, if it's available 723 $maxDevice = $this->device->GetSyncFilterType(); 724 725 // ALL has a value of 0, all limitations have higher integer values, see SYNC_FILTERTYPE_ALL definition 726 if ($maxDevice !== false && $maxDevice > SYNC_FILTERTYPE_ALL && ($maxAllowed == SYNC_FILTERTYPE_ALL || $maxDevice < $maxAllowed)) { 727 $maxAllowed = $maxDevice; 728 } 729 730 if (is_array($specialSyncFilter)) { 731 $store = ZPush::GetAdditionalSyncFolderStore($backendFolderId); 732 // the store is only available when this is a shared folder (but might also be statically configured) 733 if ($store) { 734 $origin = Utils::GetFolderOriginFromId($folderid); 735 // do not limit when the owner or impersonated user is synching! 736 if ($origin == DeviceManager::FLD_ORIGIN_USER || $origin == DeviceManager::FLD_ORIGIN_IMPERSONATED) { 737 ZLog::Write(LOGLEVEL_DEBUG, "Not checking for specific sync limit as this is the owner/impersonated user."); 738 } 739 else { 740 $spKey = false; 741 $spFilter = false; 742 // 1. step: check if there is a general limitation for the store 743 if (array_key_exists($store, $specialSyncFilter)) { 744 $spFilter = $specialSyncFilter[$store]; 745 ZLog::Write(LOGLEVEL_DEBUG, sprintf("Limit sync due to configured limitation on the store: '%s': %s",$store, $spFilter)); 746 } 747 748 // 2. step: check if there is a limitation for the hashed ID (for shared/configured stores) 749 $spKey= $store .'/'. $folderid; 750 if (array_key_exists($spKey, $specialSyncFilter)) { 751 $spFilter = $specialSyncFilter[$spKey]; 752 ZLog::Write(LOGLEVEL_DEBUG, sprintf("Limit sync due to configured limitation on the folder: '%s': %s", $spKey, $spFilter)); 753 } 754 755 // 3. step: check if there is a limitation for the backendId 756 $spKey= $store .'/'. $backendFolderId; 757 if (array_key_exists($spKey, $specialSyncFilter)) { 758 $spFilter = $specialSyncFilter[$spKey]; 759 ZLog::Write(LOGLEVEL_DEBUG, sprintf("Limit sync due to configured limitation on the folder: '%s': %s", $spKey, $spFilter)); 760 } 761 if ($spFilter) { 762 $maxAllowed = $spFilter; 763 } 764 } 765 } 766 } 767 768 return $maxAllowed; 769 } 770 771 /** 772 * Removes all linked states of a specific folder. 773 * During next request the folder is resynchronized. 774 * 775 * @param string $folderid 776 * 777 * @access public 778 * @return boolean 779 */ 780 public function ForceFolderResync($folderid) { 781 ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->ForceFolderResync('%s'): folder resync", $folderid)); 782 783 // delete folder states 784 StateManager::UnLinkState($this->device, $folderid); 785 786 return true; 787 } 788 789 /** 790 * Removes all linked states from a device. 791 * During next requests a full resync is triggered. 792 * 793 * @access public 794 * @return boolean 795 */ 796 public function ForceFullResync() { 797 ZLog::Write(LOGLEVEL_INFO, "Full device resync requested"); 798 799 // delete all other uuids 800 foreach ($this->device->GetAllFolderIds() as $folderid) 801 $uuid = StateManager::UnLinkState($this->device, $folderid); 802 803 // delete hierarchy states 804 StateManager::UnLinkState($this->device, false); 805 806 return true; 807 } 808 809 /** 810 * Indicates if the hierarchy should be resynchronized based on the general folder state and 811 * if additional folders changed. 812 * 813 * @access public 814 * @return boolean 815 */ 816 public function IsHierarchySyncRequired() { 817 $this->loadDeviceData(); 818 819 if ($this->loopdetection->ProcessLoopDetectionIsHierarchySyncAdvised()) { 820 return true; 821 } 822 823 // if the hash of the additional folders changed, we have to sync the hierarchy 824 if ($this->additionalFoldersHash != $this->getAdditionalFoldersHash()) { 825 $this->hierarchySyncRequired = true; 826 } 827 828 // check if a hierarchy sync might be necessary 829 if ($this->device->GetFolderUUID(false) === false) 830 $this->hierarchySyncRequired = true; 831 832 return $this->hierarchySyncRequired; 833 } 834 835 private function getAdditionalFoldersHash() { 836 return md5(serialize($this->device->GetAdditionalFolders())); 837 } 838 839 /** 840 * Indicates if a full hierarchy resync should be triggered due to loops 841 * 842 * @access public 843 * @return boolean 844 */ 845 public function IsHierarchyFullResyncRequired() { 846 // do not check for loop detection, if the foldersync is not yet complete 847 if ($this->GetFolderSyncComplete() === false) { 848 ZLog::Write(LOGLEVEL_INFO, "DeviceManager->IsHierarchyFullResyncRequired(): aborted, as exporting of folders has not yet completed"); 849 return false; 850 } 851 // check for potential process loops like described in ZP-5 852 return $this->loopdetection->ProcessLoopDetectionIsHierarchyResyncRequired(); 853 } 854 855 /** 856 * Indicates if an Outlook via ActiveSync is connected. 857 * 858 * @access public 859 * @return boolean 860 */ 861 public function IsKoe() { 862 if (Request::IsOutlook() && ($this->device->GetKoeVersion() !== false || Request::HasKoeStats())) { 863 return true; 864 } 865 return false; 866 } 867 868 /** 869 * Indicates if the KOE client supports a feature. 870 * 871 * @param string $feature 872 * 873 * @access public 874 * @return boolean 875 */ 876 public function HasKoeFeature($feature) { 877 $capabilities = $this->device->GetKoeCapabilities(); 878 // in a settings request the capabilities might not yet be stored in the device 879 if (empty($capabilities)) { 880 $capabilities = Request::GetKoeCapabilities(); 881 } 882 return in_array($feature, $capabilities); 883 } 884 885 /** 886 * Indicates if the connected device is Outlook + KOE and supports the 887 * secondary contact folder synchronization. 888 * 889 * @access public 890 * @return boolean 891 */ 892 public function IsKoeSupportingSecondaryContacts() { 893 return defined('KOE_CAPABILITY_SECONDARYCONTACTS') && KOE_CAPABILITY_SECONDARYCONTACTS && $this->IsKoe() && $this->HasKoeFeature('secondarycontacts'); 894 } 895 896 /** 897 * Adds an Exceptions to the process tracking 898 * 899 * @param Exception $exception 900 * 901 * @access public 902 * @return boolean 903 */ 904 public function AnnounceProcessException($exception) { 905 return $this->loopdetection->ProcessLoopDetectionAddException($exception); 906 } 907 908 /** 909 * Adds a non-ok status for a folderid to the process tracking. 910 * On 'false' a hierarchy status is assumed 911 * 912 * @access public 913 * @return boolean 914 */ 915 public function AnnounceProcessStatus($folderid, $status) { 916 return $this->loopdetection->ProcessLoopDetectionAddStatus($folderid, $status); 917 } 918 919 /** 920 * Announces that the current process is a push connection to the process loop 921 * detection and to the Top collector 922 * 923 * @access public 924 * @return boolean 925 */ 926 public function AnnounceProcessAsPush() { 927 ZLog::Write(LOGLEVEL_DEBUG, "Announce process as PUSH connection"); 928 929 return $this->loopdetection->ProcessLoopDetectionSetAsPush() && ZPush::GetTopCollector()->SetAsPushConnection(); 930 } 931 932 /** 933 * Checks if the given counter for a certain uuid+folderid was already exported or modified. 934 * This is called when a heartbeat request found changes to make sure that the same 935 * changes are not exported twice, as during the heartbeat there could have been a normal 936 * sync request. 937 * 938 * @param string $folderid folder id 939 * @param string $uuid synkkey 940 * @param string $counter synckey counter 941 * 942 * @access public 943 * @return boolean indicating if an uuid+counter were exported (with changes) before 944 */ 945 public function CheckHearbeatStateIntegrity($folderid, $uuid, $counter) { 946 return $this->loopdetection->IsSyncStateObsolete($folderid, $uuid, $counter); 947 } 948 949 /** 950 * Marks a syncstate as obsolete for Heartbeat, as e.g. an import was started using it. 951 * 952 * @param string $folderid folder id 953 * @param string $uuid synkkey 954 * @param string $counter synckey counter 955 * 956 * @access public 957 * @return 958 */ 959 public function SetHeartbeatStateIntegrity($folderid, $uuid, $counter) { 960 return $this->loopdetection->SetSyncStateUsage($folderid, $uuid, $counter); 961 } 962 963 /** 964 * Checks the data integrity of the data in the hierarchy cache and the data of the content data (synchronized folders). 965 * If a folder is deleted, the sync states could still be on the server (and being loaded by PING) while 966 * the folder is not being synchronized anymore. See also https://jira.z-hub.io/browse/ZP-1077 967 * 968 * @access public 969 * @return boolean 970 */ 971 public function CheckFolderData() { 972 ZLog::Write(LOGLEVEL_DEBUG, "DeviceManager->CheckFolderData() checking integrity of hierarchy cache with synchronized folders"); 973 974 $hc = $this->device->GetHierarchyCache(); 975 $notInCache = array(); 976 foreach ($this->device->GetAllFolderIds() as $folderid) { 977 $uuid = $this->device->GetFolderUUID($folderid); 978 if ($uuid) { 979 // has a UUID but is not in the cache?! This is deleted, remove the states. 980 if (! $hc->GetFolder($folderid)) { 981 ZLog::Write(LOGLEVEL_WARN, sprintf("DeviceManager->CheckFolderData(): Folder '%s' has sync states but is not in the hierarchy cache. Removing states.", $folderid)); 982 StateManager::UnLinkState($this->device, $folderid); 983 } 984 } 985 } 986 return true; 987 } 988 989 /** 990 * Sets the current status of the folder 991 * 992 * @param string $folderid folder id 993 * @param int $statusflag current status: DeviceManager::FLD_SYNC_INITIALIZED, DeviceManager::FLD_SYNC_INPROGRESS, DeviceManager::FLD_SYNC_COMPLETED 994 * 995 * @access public 996 * @return 997 */ 998 public function SetFolderSyncStatus($folderid, $statusflag) { 999 $currentStatus = $this->device->GetFolderSyncStatus($folderid); 1000 1001 // status available or just initialized 1002 if (isset($currentStatus[ASDevice::FOLDERSYNCSTATUS]) || $statusflag == self::FLD_SYNC_INITIALIZED) { 1003 // only update if there is a change 1004 if ((!$currentStatus || $statusflag !== $currentStatus[ASDevice::FOLDERSYNCSTATUS]) && $statusflag != self::FLD_SYNC_COMPLETED) { 1005 $this->device->SetFolderSyncStatus($folderid, array(ASDevice::FOLDERSYNCSTATUS => $statusflag)); 1006 ZLog::Write(LOGLEVEL_DEBUG, sprintf("SetFolderSyncStatus(): set %s for %s", $statusflag, $folderid)); 1007 } 1008 // if completed, remove the status 1009 else if ($statusflag == self::FLD_SYNC_COMPLETED) { 1010 $this->device->SetFolderSyncStatus($folderid, false); 1011 ZLog::Write(LOGLEVEL_DEBUG, sprintf("SetFolderSyncStatus(): completed for %s", $folderid)); 1012 } 1013 } 1014 1015 return true; 1016 } 1017 1018 /** 1019 * Indicates if a folder is synchronizing by the saved status. 1020 * 1021 * @param string $folderid folder id 1022 * 1023 * @access public 1024 * @return boolean 1025 */ 1026 public function HasFolderSyncStatus($folderid) { 1027 $currentStatus = $this->device->GetFolderSyncStatus($folderid); 1028 1029 // status available ? 1030 $hasStatus = isset($currentStatus[ASDevice::FOLDERSYNCSTATUS]); 1031 if ($hasStatus) { 1032 ZLog::Write(LOGLEVEL_DEBUG, sprintf("HasFolderSyncStatus(): saved folder status for %s: %s", $folderid, $currentStatus[ASDevice::FOLDERSYNCSTATUS])); 1033 } 1034 1035 return $hasStatus; 1036 } 1037 1038 /** 1039 * Returns the indicator if the FolderSync was completed successfully (all folders synchronized) 1040 * 1041 * @access public 1042 * @return boolean 1043 */ 1044 public function GetFolderSyncComplete() { 1045 return $this->device->GetFolderSyncComplete(); 1046 } 1047 1048 /** 1049 * Sets if the FolderSync was completed successfully (all folders synchronized) 1050 * 1051 * @param boolean $complete indicating if all folders were sent 1052 * 1053 * @access public 1054 * @return boolean 1055 */ 1056 public function SetFolderSyncComplete($complete, $user = false, $devid = false) { 1057 $this->device->SetFolderSyncComplete($complete); 1058 } 1059 1060 /** 1061 * Removes the Loop detection data for a user & device 1062 * 1063 * @param string $user 1064 * @param string $devid 1065 * 1066 * @access public 1067 * @return boolean 1068 */ 1069 public function ClearLoopDetectionData($user, $devid) { 1070 if ($user == false || $devid == false) { 1071 return false; 1072 } 1073 ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->ClearLoopDetectionData(): clearing data for user '%s' and device '%s'", $user, $devid)); 1074 return $this->loopdetection->ClearData($user, $devid); 1075 } 1076 1077 /** 1078 * Indicates if the device needs an AS version update 1079 * 1080 * @access public 1081 * @return boolean 1082 */ 1083 public function AnnounceASVersion() { 1084 $latest = ZPush::GetSupportedASVersion(); 1085 $announced = $this->device->GetAnnouncedASversion(); 1086 $this->device->SetAnnouncedASversion($latest); 1087 1088 return ($announced != $latest); 1089 } 1090 1091 /** 1092 * Returns the User Agent. This data is consolidated with data from Request::GetUserAgent() 1093 * and the data saved in the ASDevice. 1094 * 1095 * @access public 1096 * @return string 1097 */ 1098 public function GetUserAgent() { 1099 return $this->device->GetDeviceUserAgent(); 1100 } 1101 1102 /** 1103 * Returns the backend folder id from the AS folderid known to the mobile. 1104 * If the id is not known, it's returned as is. 1105 * 1106 * @param mixed $folderid 1107 * 1108 * @access public 1109 * @return int/boolean returns false if the type is not set 1110 */ 1111 public function GetBackendIdForFolderId($folderid) { 1112 $backendId = $this->device->GetFolderBackendId($folderid); 1113 if (!$backendId) { 1114 ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetBackendIdForFolderId(): no backend-folderid available for '%s', returning as is.", $folderid)); 1115 return $folderid; 1116 } 1117 ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetBackendIdForFolderId(): folderid %s => %s", $folderid, $backendId)); 1118 return $backendId; 1119 } 1120 1121 /** 1122 * Gets the AS folderid for a backendFolderId. 1123 * If there is no known AS folderId a new one is being created. 1124 * 1125 * @param string $backendid Backend folder id 1126 * @param boolean $generateNewIdIfNew Generates a new AS folderid for the case the backend folder is not known yet, default: false. 1127 * @param string $folderOrigin Folder type is one of 'U' (user) 1128 * 'C' (configured) 1129 * 'S' (shared) 1130 * 'G' (global address book) 1131 * 'I' (impersonated) 1132 * @param string $folderName Folder name of the backend folder 1133 * 1134 * @access public 1135 * @return string/boolean returns false if there is folderid known for this backendid and $generateNewIdIfNew is not set or false. 1136 */ 1137 public function GetFolderIdForBackendId($backendid, $generateNewIdIfNew = false, $folderOrigin = self::FLD_ORIGIN_USER, $folderName = null) { 1138 if (!in_array($folderOrigin, array(DeviceManager::FLD_ORIGIN_CONFIG, DeviceManager::FLD_ORIGIN_GAB, DeviceManager::FLD_ORIGIN_SHARED, DeviceManager::FLD_ORIGIN_USER, DeviceManager::FLD_ORIGIN_IMPERSONATED))) { 1139 ZLog::Write(LOGLEVEL_WARN, sprintf("ASDevice->GetFolderIdForBackendId(): folder type '%' is unknown in DeviceManager", $folderOrigin)); 1140 } 1141 return $this->device->GetFolderIdForBackendId($backendid, $generateNewIdIfNew, $folderOrigin, $folderName); 1142 } 1143 1144 1145 /**---------------------------------------------------------------------------------------------------------- 1146 * private DeviceManager methods 1147 */ 1148 1149 /** 1150 * Loads devicedata from the StateMachine and loads it into the device 1151 * 1152 * @access public 1153 * @return boolean 1154 */ 1155 private function loadDeviceData() { 1156 if (!Request::IsValidDeviceID()) 1157 return false; 1158 try { 1159 $deviceHash = $this->statemachine->GetStateHash($this->devid, IStateMachine::DEVICEDATA); 1160 if ($deviceHash != $this->deviceHash) { 1161 if ($this->deviceHash) 1162 ZLog::Write(LOGLEVEL_DEBUG, "DeviceManager->loadDeviceData(): Device data was changed, reloading"); 1163 $this->device->SetData($this->statemachine->GetState($this->devid, IStateMachine::DEVICEDATA)); 1164 $this->deviceHash = $deviceHash; 1165 } 1166 } 1167 catch (StateNotFoundException $snfex) { 1168 $this->hierarchySyncRequired = true; 1169 } 1170 catch (UnavailableException $uaex) { 1171 // This is temporary and can be ignored e.g. in PING - see https://jira.z-hub.io/browse/ZP-1054 1172 // If the hash was not available before we treat it like a StateNotFoundException. 1173 if ($this->deviceHash === false) { 1174 $this->hierarchySyncRequired = true; 1175 } 1176 } 1177 return true; 1178 } 1179 1180 /** 1181 * Called when a SyncObject is not being streamed to the mobile. 1182 * The user can be informed so he knows about this issue 1183 * 1184 * @param string $folderid id of the parent folder (may be false if unknown) 1185 * @param string $id message id 1186 * @param SyncObject $message the broken message 1187 * @param string $reason (self::MSG_BROKEN_UNKNOWN, self::MSG_BROKEN_CAUSINGLOOP, self::MSG_BROKEN_SEMANTICERR) 1188 * 1189 * @access public 1190 * @return boolean 1191 */ 1192 public function AnnounceIgnoredMessage($folderid, $id, SyncObject $message, $reason = self::MSG_BROKEN_UNKNOWN) { 1193 if ($folderid === false) 1194 $folderid = $this->getLatestFolder(); 1195 1196 $class = get_class($message); 1197 1198 $brokenMessage = new StateObject(); 1199 $brokenMessage->id = $id; 1200 $brokenMessage->folderid = $folderid; 1201 $brokenMessage->ASClass = $class; 1202 $brokenMessage->folderid = $folderid; 1203 $brokenMessage->reasonCode = $reason; 1204 $brokenMessage->reasonString = 'unknown cause'; 1205 $brokenMessage->timestamp = time(); 1206 $brokenMessage->asobject = $message; 1207 $brokenMessage->reasonString = ZLog::GetLastMessage(LOGLEVEL_WARN); 1208 1209 $this->device->AddIgnoredMessage($brokenMessage); 1210 1211 ZLog::Write(LOGLEVEL_ERROR, sprintf("Ignored broken message (%s). Reason: '%s' Folderid: '%s' message id '%s'", $class, $reason, $folderid, $id)); 1212 return true; 1213 } 1214 1215 /** 1216 * Called when a SyncObject was streamed to the mobile. 1217 * If the message could not be sent before this data is obsolete 1218 * 1219 * @param string $folderid id of the parent folder 1220 * @param string $id message id 1221 * 1222 * @access public 1223 * @return boolean returns true if the message was ignored before 1224 */ 1225 private function announceAcceptedMessage($folderid, $id) { 1226 if ($this->device->RemoveIgnoredMessage($folderid, $id)) { 1227 ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->announceAcceptedMessage('%s', '%s'): cleared previously ignored message as message is sucessfully streamed",$folderid, $id)); 1228 return true; 1229 } 1230 return false; 1231 } 1232 1233 /** 1234 * Checks if there were broken messages streamed to the mobile. 1235 * If the sync completes/continues without further erros they are marked as accepted 1236 * 1237 * @param string $folderid folderid which is to be checked 1238 * 1239 * @access private 1240 * @return boolean 1241 */ 1242 private function checkBrokenMessages($folderid) { 1243 // check for correctly synchronized messages of the folder 1244 foreach($this->loopdetection->GetSyncedButBeforeIgnoredMessages($folderid) as $okID) { 1245 $this->announceAcceptedMessage($folderid, $okID); 1246 } 1247 return true; 1248 } 1249 1250 /** 1251 * Setter for the latest folder id 1252 * on multi-folder operations of AS 14 this is used to set the new current folder id 1253 * 1254 * @param string $folderid the current folder 1255 * 1256 * @access private 1257 * @return boolean 1258 */ 1259 private function setLatestFolder($folderid) { 1260 // this is a multi folder operation 1261 // check on ignoredmessages before discaring the folderid 1262 if ($this->latestFolder !== false) 1263 $this->checkBrokenMessages($this->latestFolder); 1264 1265 $this->latestFolder = $folderid; 1266 1267 return true; 1268 } 1269 1270 /** 1271 * Getter for the latest folder id 1272 * 1273 * @access private 1274 * @return string $folderid the current folder 1275 */ 1276 private function getLatestFolder() { 1277 return $this->latestFolder; 1278 } 1279 1280 /** 1281 * Loads Provisioning policies from the policies file. 1282 * 1283 * @param string $policyName The name of the policy 1284 * 1285 * @access private 1286 * @return array 1287 */ 1288 private function getProvisioningPolicies($policyName) { 1289 $policies = ZPush::GetPolicies(); 1290 1291 if (!isset($policies[$policyName]) && $policyName != ASDevice::DEFAULTPOLICYNAME) { 1292 ZLog::Write(LOGLEVEL_WARN, sprintf("The '%s' policy is configured, but it is not available in the policies' file. Please check %s file. Loading default policy.", $policyName, PROVISIONING_POLICYFILE)); 1293 return $policies[ASDevice::DEFAULTPOLICYNAME]; 1294 } 1295 ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->getProvisioningPolicies(): loaded '%s' policy.", $policyName)); 1296 1297 // Always load default policies, so that if a policy extends a default policy it doesn't have to copy all the values 1298 if ($policyName != ASDevice::DEFAULTPOLICYNAME) { 1299 $policies[$policyName] = array_replace_recursive($policies[ASDevice::DEFAULTPOLICYNAME], $policies[$policyName]); 1300 } 1301 return $policies[$policyName]; 1302 } 1303 1304 /** 1305 * Gets the policy name set in the backend or in device data. 1306 * 1307 * @access private 1308 * @return string 1309 */ 1310 private function getPolicyName() { 1311 $policyName = ZPush::GetBackend()->GetUserPolicyName(); 1312 $policyName = ((!empty($policyName) && $policyName !== false) ? $policyName : ASDevice::DEFAULTPOLICYNAME); 1313 ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->getPolicyName(): determined policy name: '%s'", $policyName)); 1314 return $policyName; 1315 } 1316 1317 /** 1318 * Generates and SyncFolder object and returns it. 1319 * 1320 * @param string $store 1321 * @param string $folderid 1322 * @param string $name 1323 * @param int $type 1324 * @param int $flags 1325 * @param string $folderOrigin 1326 * 1327 * @access public 1328 * @returns SyncFolder 1329 */ 1330 public function BuildSyncFolderObject($store, $folderid, $parentid, $name, $type, $flags, $folderOrigin) { 1331 $folder = new SyncFolder(); 1332 $folder->BackendId = $folderid; 1333 $folder->serverid = $this->GetFolderIdForBackendId($folder->BackendId, true, $folderOrigin, $name); 1334 $folder->parentid = $this->GetFolderIdForBackendId($parentid); 1335 $folder->displayname = $name; 1336 $folder->type = $type; 1337 // save store as custom property which is not streamed directly to the device 1338 $folder->NoBackendFolder = true; 1339 $folder->Store = $store; 1340 $folder->Flags = $flags; 1341 1342 // adjust additional folders so it matches not yet processed KOE type UNKNOWN folders 1343 $synctype = $this->device->GetFolderType($folder->serverid); 1344 if ($this->IsKoeSupportingSecondaryContacts() && $synctype !== $folder->type && $synctype == SYNC_FOLDER_TYPE_UNKNOWN) { 1345 ZLog::Write(LOGLEVEL_DEBUG, "DeviceManager->BuildSyncFolderObject(): Modifying additional folder so it matches an unprocessed KOE folder"); 1346 $folder = Utils::ChangeFolderToTypeUnknownForKoe($folder); 1347 } 1348 return $folder; 1349 } 1350 1351 /** 1352 * Returns the device id. 1353 * 1354 * @access public 1355 * @return string 1356 */ 1357 public function GetDevid() { 1358 return $this->devid; 1359 } 1360} 1361