@@ -19,907 +19,907 @@ |
||
19 | 19 | */ |
20 | 20 | |
21 | 21 | class SyncCollections implements Iterator { |
22 | - public const ERROR_NO_COLLECTIONS = 1; |
|
23 | - public const ERROR_WRONG_HIERARCHY = 2; |
|
24 | - public const OBSOLETE_CONNECTION = 3; |
|
25 | - public const HIERARCHY_CHANGED = 4; |
|
26 | - |
|
27 | - private $stateManager; |
|
28 | - |
|
29 | - private $collections = []; |
|
30 | - private $addparms = []; |
|
31 | - private $changes = []; |
|
32 | - private $saveData = true; |
|
33 | - |
|
34 | - private $refPolicyKey = false; |
|
35 | - private $refLifetime = false; |
|
36 | - |
|
37 | - private $globalWindowSize; |
|
38 | - private $lastSyncTime; |
|
39 | - |
|
40 | - private $waitingTime = 0; |
|
41 | - private $hierarchyExporterChecked = false; |
|
42 | - private $loggedGlobalWindowSizeOverwrite = false; |
|
43 | - |
|
44 | - /** |
|
45 | - * Invalidates all pingable flags for all folders. |
|
46 | - * |
|
47 | - * @return bool |
|
48 | - */ |
|
49 | - public static function InvalidatePingableFlags() { |
|
50 | - SLog::Write(LOGLEVEL_DEBUG, "SyncCollections::InvalidatePingableFlags(): Invalidating now"); |
|
51 | - |
|
52 | - try { |
|
53 | - $sc = new SyncCollections(); |
|
54 | - $sc->LoadAllCollections(); |
|
55 | - foreach ($sc as $folderid => $spa) { |
|
56 | - if ($spa->GetPingableFlag() == true) { |
|
57 | - $spa->DelPingableFlag(); |
|
58 | - $sc->SaveCollection($spa); |
|
59 | - } |
|
60 | - } |
|
61 | - |
|
62 | - return true; |
|
63 | - } |
|
64 | - catch (GSyncException $e) { |
|
65 | - } |
|
66 | - |
|
67 | - return false; |
|
68 | - } |
|
69 | - |
|
70 | - /** |
|
71 | - * Constructor. |
|
72 | - */ |
|
73 | - public function __construct() { |
|
74 | - } |
|
75 | - |
|
76 | - /** |
|
77 | - * Sets the StateManager for this object |
|
78 | - * If this is not done and a method needs it, the StateManager will be |
|
79 | - * requested from the DeviceManager. |
|
80 | - * |
|
81 | - * @param StateManager $statemanager |
|
82 | - * |
|
83 | - * @return |
|
84 | - */ |
|
85 | - public function SetStateManager($statemanager) { |
|
86 | - $this->stateManager = $statemanager; |
|
87 | - } |
|
88 | - |
|
89 | - /** |
|
90 | - * Loads all collections known for the current device. |
|
91 | - * |
|
92 | - * @param bool $overwriteLoaded (opt) overwrites Collection with saved state if set to true |
|
93 | - * @param bool $loadState (opt) indicates if the collection sync state should be loaded, default false |
|
94 | - * @param bool $checkPermissions (opt) if set to true each folder will pass |
|
95 | - * through a backend->Setup() to check permissions. |
|
96 | - * If this fails a StatusException will be thrown. |
|
97 | - * @param bool $loadHierarchy (opt) if the hierarchy sync states should be loaded, default false |
|
98 | - * @param bool $confirmedOnly (opt) indicates if only confirmed states should be loaded, default: false |
|
99 | - * |
|
100 | - * @throws StatusException with SyncCollections::ERROR_WRONG_HIERARCHY if permission check fails |
|
101 | - * @throws StateInvalidException if the sync state can not be found or relation between states is invalid ($loadState = true) |
|
102 | - * |
|
103 | - * @return bool |
|
104 | - */ |
|
105 | - public function LoadAllCollections($overwriteLoaded = false, $loadState = false, $checkPermissions = false, $loadHierarchy = false, $confirmedOnly = false) { |
|
106 | - $this->loadStateManager(); |
|
107 | - |
|
108 | - // this operation should not remove old state counters |
|
109 | - $this->stateManager->DoNotDeleteOldStates(); |
|
110 | - |
|
111 | - $invalidStates = false; |
|
112 | - foreach ($this->stateManager->GetSynchedFolders() as $folderid) { |
|
113 | - if ($overwriteLoaded === false && isset($this->collections[$folderid])) { |
|
114 | - continue; |
|
115 | - } |
|
116 | - |
|
117 | - // Load Collection! |
|
118 | - if (!$this->LoadCollection($folderid, $loadState, $checkPermissions, $confirmedOnly)) { |
|
119 | - $invalidStates = true; |
|
120 | - } |
|
121 | - } |
|
122 | - |
|
123 | - // load the hierarchy data - there are no permissions to verify so we just set it to false |
|
124 | - if ($loadHierarchy && !$this->LoadCollection(false, $loadState, false, false)) { |
|
125 | - throw new StatusException("Invalid states found while loading hierarchy data. Forcing hierarchy sync"); |
|
126 | - } |
|
127 | - |
|
128 | - if ($invalidStates) { |
|
129 | - throw new StateInvalidException("Invalid states found while loading collections. Forcing sync"); |
|
130 | - } |
|
131 | - |
|
132 | - return true; |
|
133 | - } |
|
134 | - |
|
135 | - /** |
|
136 | - * Loads all collections known for the current device. |
|
137 | - * |
|
138 | - * @param string $folderid folder id to be loaded |
|
139 | - * @param bool $loadState (opt) indicates if the collection sync state should be loaded, default true |
|
140 | - * @param bool $checkPermissions (opt) if set to true each folder will pass |
|
141 | - * through a backend->Setup() to check permissions. |
|
142 | - * If this fails a StatusException will be thrown. |
|
143 | - * @param bool $confirmedOnly (opt) indicates if only confirmed states should be loaded, default: false |
|
144 | - * |
|
145 | - * @throws StatusException with SyncCollections::ERROR_WRONG_HIERARCHY if permission check fails |
|
146 | - * @throws StateInvalidException if the sync state can not be found or relation between states is invalid ($loadState = true) |
|
147 | - * |
|
148 | - * @return bool |
|
149 | - */ |
|
150 | - public function LoadCollection($folderid, $loadState = false, $checkPermissions = false, $confirmedOnly = false) { |
|
151 | - $this->loadStateManager(); |
|
152 | - |
|
153 | - try { |
|
154 | - // Get SyncParameters for the folder from the state |
|
155 | - $spa = $this->stateManager->GetSynchedFolderState($folderid, !$loadState); |
|
156 | - |
|
157 | - // TODO remove resync of folders |
|
158 | - // this forces a resync of all states |
|
159 | - if (!$spa instanceof SyncParameters) { |
|
160 | - throw new StateInvalidException("Saved state are not of type SyncParameters"); |
|
161 | - } |
|
162 | - |
|
163 | - if ($spa->GetUuidCounter() == 0) { |
|
164 | - SLog::Write(LOGLEVEL_DEBUG, "SyncCollections->LoadCollection(): Found collection with move state only, ignoring."); |
|
165 | - |
|
166 | - return true; |
|
167 | - } |
|
168 | - } |
|
169 | - catch (StateInvalidException $sive) { |
|
170 | - // in case there is something wrong with the state, just stop here |
|
171 | - // later when trying to retrieve the SyncParameters nothing will be found |
|
172 | - |
|
173 | - if ($folderid === false) { |
|
174 | - throw new StatusException(sprintf("SyncCollections->LoadCollection(): could not get FOLDERDATA state of the hierarchy uuid: %s", $spa->GetUuid()), self::ERROR_WRONG_HIERARCHY); |
|
175 | - } |
|
176 | - |
|
177 | - // we also generate a fake change, so a sync on this folder is triggered |
|
178 | - $this->changes[$folderid] = 1; |
|
179 | - |
|
180 | - return false; |
|
181 | - } |
|
182 | - |
|
183 | - // if this is an additional folder the backend has to be setup correctly |
|
184 | - if ($checkPermissions === true && !GSync::GetBackend()->Setup(GSync::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()))) { |
|
185 | - throw new StatusException(sprintf("SyncCollections->LoadCollection(): could not Setup() the backend for folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), self::ERROR_WRONG_HIERARCHY); |
|
186 | - } |
|
187 | - |
|
188 | - // add collection to object |
|
189 | - $addStatus = $this->AddCollection($spa); |
|
190 | - |
|
191 | - // load the latest known syncstate if requested |
|
192 | - if ($addStatus && $loadState === true) { |
|
193 | - try { |
|
194 | - // make sure the hierarchy cache is loaded when we are loading hierarchy states |
|
195 | - $this->addparms[$folderid]["state"] = $this->stateManager->GetSyncState($spa->GetLatestSyncKey($confirmedOnly), ($folderid === false)); |
|
196 | - } |
|
197 | - catch (StateNotFoundException $snfe) { |
|
198 | - // if we can't find the state, first we should try a sync of that folder, so |
|
199 | - // we generate a fake change, so a sync on this folder is triggered |
|
200 | - $this->changes[$folderid] = 1; |
|
201 | - |
|
202 | - // make sure this folder is fully synched on next Sync request |
|
203 | - $this->invalidateFolderStat($spa); |
|
204 | - |
|
205 | - return false; |
|
206 | - } |
|
207 | - } |
|
208 | - |
|
209 | - return $addStatus; |
|
210 | - } |
|
211 | - |
|
212 | - /** |
|
213 | - * Saves a SyncParameters Object. |
|
214 | - * |
|
215 | - * @param SyncParamerts $spa |
|
216 | - * |
|
217 | - * @return bool |
|
218 | - */ |
|
219 | - public function SaveCollection($spa) { |
|
220 | - if (!$this->saveData || !$spa->HasFolderId()) { |
|
221 | - return false; |
|
222 | - } |
|
223 | - |
|
224 | - if ($spa->IsDataChanged()) { |
|
225 | - $this->loadStateManager(); |
|
226 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->SaveCollection(): Data of folder '%s' changed", $spa->GetFolderId())); |
|
227 | - |
|
228 | - // save new windowsize |
|
229 | - if (isset($this->globalWindowSize)) { |
|
230 | - $spa->SetWindowSize($this->globalWindowSize); |
|
231 | - } |
|
232 | - |
|
233 | - // update latest lifetime |
|
234 | - if (isset($this->refLifetime)) { |
|
235 | - $spa->SetReferenceLifetime($this->refLifetime); |
|
236 | - } |
|
237 | - |
|
238 | - return $this->stateManager->SetSynchedFolderState($spa); |
|
239 | - } |
|
240 | - |
|
241 | - return false; |
|
242 | - } |
|
243 | - |
|
244 | - /** |
|
245 | - * Adds a SyncParameters object to the current list of collections. |
|
246 | - * |
|
247 | - * @param SyncParameters $spa |
|
248 | - * |
|
249 | - * @return bool |
|
250 | - */ |
|
251 | - public function AddCollection($spa) { |
|
252 | - if (!$spa->HasFolderId()) { |
|
253 | - return false; |
|
254 | - } |
|
255 | - |
|
256 | - $this->collections[$spa->GetFolderId()] = $spa; |
|
257 | - |
|
258 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->AddCollection(): Folder id '%s' : ref. Lifetime '%s', last sync at '%s'", $spa->GetFolderId(), $spa->GetReferenceLifetime(), $spa->GetLastSyncTime())); |
|
259 | - if ($spa->HasLastSyncTime() && $spa->GetLastSyncTime() > $this->lastSyncTime) { |
|
260 | - $this->lastSyncTime = $spa->GetLastSyncTime(); |
|
261 | - |
|
262 | - // use SyncParameters PolicyKey as reference if available |
|
263 | - if ($spa->HasReferencePolicyKey()) { |
|
264 | - $this->refPolicyKey = $spa->GetReferencePolicyKey(); |
|
265 | - } |
|
266 | - |
|
267 | - // use SyncParameters LifeTime as reference if available |
|
268 | - if ($spa->HasReferenceLifetime()) { |
|
269 | - $this->refLifetime = $spa->GetReferenceLifetime(); |
|
270 | - } |
|
271 | - |
|
272 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->AddCollection(): Updated reference PolicyKey '%s', reference Lifetime '%s', Last sync at '%s'", $this->refPolicyKey, $this->refLifetime, $this->lastSyncTime)); |
|
273 | - } |
|
274 | - |
|
275 | - return true; |
|
276 | - } |
|
277 | - |
|
278 | - /** |
|
279 | - * Returns a previousily added or loaded SyncParameters object for a folderid. |
|
280 | - * |
|
281 | - * @param SyncParameters $spa |
|
282 | - * @param mixed $folderid |
|
283 | - * |
|
284 | - * @return SyncParameters / boolean false if no SyncParameters object is found for folderid |
|
285 | - */ |
|
286 | - public function GetCollection($folderid) { |
|
287 | - if (isset($this->collections[$folderid])) { |
|
288 | - return $this->collections[$folderid]; |
|
289 | - } |
|
290 | - |
|
291 | - return false; |
|
292 | - } |
|
293 | - |
|
294 | - /** |
|
295 | - * Indicates if there are any loaded CPOs. |
|
296 | - * |
|
297 | - * @return bool |
|
298 | - */ |
|
299 | - public function HasCollections() { |
|
300 | - return !empty($this->collections); |
|
301 | - } |
|
302 | - |
|
303 | - /** |
|
304 | - * Indicates the amount of collections loaded. |
|
305 | - * |
|
306 | - * @return int |
|
307 | - */ |
|
308 | - public function GetCollectionCount() { |
|
309 | - return count($this->collections); |
|
310 | - } |
|
311 | - |
|
312 | - /** |
|
313 | - * Add a non-permanent key/value pair for a SyncParameters object. |
|
314 | - * |
|
315 | - * @param SyncParameters $spa target SyncParameters |
|
316 | - * @param string $key |
|
317 | - * @param mixed $value |
|
318 | - * |
|
319 | - * @return bool |
|
320 | - */ |
|
321 | - public function AddParameter($spa, $key, $value) { |
|
322 | - if (!$spa->HasFolderId()) { |
|
323 | - return false; |
|
324 | - } |
|
325 | - |
|
326 | - $folderid = $spa->GetFolderId(); |
|
327 | - if (!isset($this->addparms[$folderid])) { |
|
328 | - $this->addparms[$folderid] = []; |
|
329 | - } |
|
330 | - |
|
331 | - $this->addparms[$folderid][$key] = $value; |
|
332 | - |
|
333 | - return true; |
|
334 | - } |
|
335 | - |
|
336 | - /** |
|
337 | - * Returns a previousily set non-permanent value for a SyncParameters object. |
|
338 | - * |
|
339 | - * @param SyncParameters $spa target SyncParameters |
|
340 | - * @param string $key |
|
341 | - * |
|
342 | - * @return mixed returns 'null' if nothing set |
|
343 | - */ |
|
344 | - public function GetParameter($spa, $key) { |
|
345 | - if (!$spa->HasFolderId()) { |
|
346 | - return null; |
|
347 | - } |
|
348 | - |
|
349 | - if (isset($this->addparms[$spa->GetFolderId()], $this->addparms[$spa->GetFolderId()][$key])) { |
|
350 | - return $this->addparms[$spa->GetFolderId()][$key]; |
|
351 | - } |
|
352 | - |
|
353 | - return null; |
|
354 | - } |
|
355 | - |
|
356 | - /** |
|
357 | - * Returns the latest known PolicyKey to be used as reference. |
|
358 | - * |
|
359 | - * @return int/boolean returns false if nothing found in collections |
|
360 | - */ |
|
361 | - public function GetReferencePolicyKey() { |
|
362 | - return $this->refPolicyKey; |
|
363 | - } |
|
364 | - |
|
365 | - /** |
|
366 | - * Sets a global window size which should be used for all collections |
|
367 | - * in a case of a heartbeat and/or partial sync. |
|
368 | - * |
|
369 | - * @param int $windowsize |
|
370 | - * |
|
371 | - * @return bool |
|
372 | - */ |
|
373 | - public function SetGlobalWindowSize($windowsize) { |
|
374 | - $this->globalWindowSize = $windowsize; |
|
375 | - |
|
376 | - return true; |
|
377 | - } |
|
378 | - |
|
379 | - /** |
|
380 | - * Returns the global window size of items to be exported in total over all |
|
381 | - * requested collections. |
|
382 | - * |
|
383 | - * @return int/boolean returns requested windows size, 512 (max) or the |
|
384 | - * value of config SYNC_MAX_ITEMS if it is lower |
|
385 | - */ |
|
386 | - public function GetGlobalWindowSize() { |
|
387 | - // take the requested global windowsize or the max 512 if not defined |
|
388 | - if (isset($this->globalWindowSize)) { |
|
389 | - $globalWindowSize = $this->globalWindowSize; |
|
390 | - } |
|
391 | - else { |
|
392 | - $globalWindowSize = WINDOW_SIZE_MAX; // 512 by default |
|
393 | - } |
|
394 | - |
|
395 | - if (defined("SYNC_MAX_ITEMS") && $globalWindowSize > SYNC_MAX_ITEMS) { |
|
396 | - if (!$this->loggedGlobalWindowSizeOverwrite) { |
|
397 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->GetGlobalWindowSize() overwriting requested global window size of %d by %d forced in configuration.", $globalWindowSize, SYNC_MAX_ITEMS)); |
|
398 | - $this->loggedGlobalWindowSizeOverwrite = true; |
|
399 | - } |
|
400 | - $globalWindowSize = SYNC_MAX_ITEMS; |
|
401 | - } |
|
402 | - |
|
403 | - return $globalWindowSize; |
|
404 | - } |
|
405 | - |
|
406 | - /** |
|
407 | - * Sets the lifetime for heartbeat or ping connections. |
|
408 | - * |
|
409 | - * @param int $lifetime time in seconds |
|
410 | - * |
|
411 | - * @return bool |
|
412 | - */ |
|
413 | - public function SetLifetime($lifetime) { |
|
414 | - $this->refLifetime = $lifetime; |
|
415 | - |
|
416 | - return true; |
|
417 | - } |
|
418 | - |
|
419 | - /** |
|
420 | - * Sets the lifetime for heartbeat or ping connections |
|
421 | - * previousily set or saved in a collection. |
|
422 | - * |
|
423 | - * @return int returns PING_HIGHER_BOUND_LIFETIME as default if nothing set or not available. |
|
424 | - * If PING_HIGHER_BOUND_LIFETIME is not set, returns 600. |
|
425 | - */ |
|
426 | - public function GetLifetime() { |
|
427 | - if (!isset($this->refLifetime) || $this->refLifetime === false) { |
|
428 | - if (PING_HIGHER_BOUND_LIFETIME !== false) { |
|
429 | - return PING_HIGHER_BOUND_LIFETIME; |
|
430 | - } |
|
431 | - |
|
432 | - return 600; |
|
433 | - } |
|
434 | - |
|
435 | - return $this->refLifetime; |
|
436 | - } |
|
437 | - |
|
438 | - /** |
|
439 | - * Returns the timestamp of the last synchronization for all |
|
440 | - * loaded collections. |
|
441 | - * |
|
442 | - * @return int timestamp |
|
443 | - */ |
|
444 | - public function GetLastSyncTime() { |
|
445 | - return $this->lastSyncTime; |
|
446 | - } |
|
447 | - |
|
448 | - /** |
|
449 | - * Checks if the currently known collections for changes for $lifetime seconds. |
|
450 | - * If the backend provides a ChangesSink the sink will be used. |
|
451 | - * If not every $interval seconds an exporter will be configured for each |
|
452 | - * folder to perform GetChangeCount(). |
|
453 | - * |
|
454 | - * @param int $lifetime (opt) total lifetime to wait for changes / default 600s |
|
455 | - * @param int $interval (opt) time between blocking operations of sink or polling / default 30s |
|
456 | - * @param bool $onlyPingable (opt) only check for folders which have the PingableFlag |
|
457 | - * |
|
458 | - * @throws StatusException with code SyncCollections::ERROR_NO_COLLECTIONS if no collections available |
|
459 | - * with code SyncCollections::ERROR_WRONG_HIERARCHY if there were errors getting changes |
|
460 | - * |
|
461 | - * @return bool indicating if changes were found |
|
462 | - */ |
|
463 | - public function CheckForChanges($lifetime = 600, $interval = 30, $onlyPingable = false) { |
|
464 | - $classes = []; |
|
465 | - foreach ($this->collections as $folderid => $spa) { |
|
466 | - if ($onlyPingable && $spa->GetPingableFlag() !== true || !$folderid) { |
|
467 | - continue; |
|
468 | - } |
|
469 | - |
|
470 | - $class = $this->getPingClass($spa); |
|
471 | - |
|
472 | - if (!isset($classes[$class])) { |
|
473 | - $classes[$class] = 0; |
|
474 | - } |
|
475 | - ++$classes[$class]; |
|
476 | - } |
|
477 | - if (empty($classes)) { |
|
478 | - $checkClasses = "policies only"; |
|
479 | - } |
|
480 | - elseif (array_sum($classes) > 4) { |
|
481 | - $checkClasses = ""; |
|
482 | - foreach ($classes as $class => $count) { |
|
483 | - if ($count == 1) { |
|
484 | - $checkClasses .= sprintf("%s ", $class); |
|
485 | - } |
|
486 | - else { |
|
487 | - $checkClasses .= sprintf("%s(%d) ", $class, $count); |
|
488 | - } |
|
489 | - } |
|
490 | - } |
|
491 | - else { |
|
492 | - $checkClasses = implode(" ", array_keys($classes)); |
|
493 | - } |
|
494 | - |
|
495 | - $pingTracking = new PingTracking(); |
|
496 | - $this->changes = []; |
|
497 | - |
|
498 | - GSync::GetDeviceManager()->AnnounceProcessAsPush(); |
|
499 | - GSync::GetTopCollector()->AnnounceInformation(sprintf("lifetime %ds", $lifetime), true); |
|
500 | - SLog::Write(LOGLEVEL_INFO, sprintf("SyncCollections->CheckForChanges(): Waiting for %s changes... (lifetime %d seconds)", (empty($classes)) ? 'policy' : 'store', $lifetime)); |
|
501 | - |
|
502 | - // use changes sink where available |
|
503 | - $changesSink = GSync::GetBackend()->HasChangesSink(); |
|
504 | - |
|
505 | - // create changessink and check folder stats if there are folders to Ping |
|
506 | - if (!empty($classes)) { |
|
507 | - // initialize all possible folders |
|
508 | - foreach ($this->collections as $folderid => $spa) { |
|
509 | - if (($onlyPingable && $spa->GetPingableFlag() !== true) || !$folderid) { |
|
510 | - continue; |
|
511 | - } |
|
512 | - |
|
513 | - $backendFolderId = $spa->GetBackendFolderId(); |
|
514 | - |
|
515 | - // get the user store if this is a additional folder |
|
516 | - $store = GSync::GetAdditionalSyncFolderStore($backendFolderId); |
|
517 | - |
|
518 | - // initialize sink if no immediate changes were found so far |
|
519 | - if ($changesSink && empty($this->changes)) { |
|
520 | - GSync::GetBackend()->Setup($store); |
|
521 | - if (!GSync::GetBackend()->ChangesSinkInitialize($backendFolderId)) { |
|
522 | - throw new StatusException(sprintf("Error initializing ChangesSink for folder id %s/%s", $folderid, $backendFolderId), self::ERROR_WRONG_HIERARCHY); |
|
523 | - } |
|
524 | - } |
|
525 | - |
|
526 | - // check if the folder stat changed since the last sync, if so generate a change for it (only on first run) |
|
527 | - $currentFolderStat = GSync::GetBackend()->GetFolderStat($store, $backendFolderId); |
|
528 | - if ($this->waitingTime == 0 && GSync::GetBackend()->HasFolderStats() && $currentFolderStat !== false && $spa->IsExporterRunRequired($currentFolderStat, true)) { |
|
529 | - $this->changes[$spa->GetFolderId()] = 1; |
|
530 | - } |
|
531 | - } |
|
532 | - } |
|
533 | - |
|
534 | - if (!empty($this->changes)) { |
|
535 | - SLog::Write(LOGLEVEL_DEBUG, "SyncCollections->CheckForChanges(): Using ChangesSink but found changes verifying the folder stats"); |
|
536 | - |
|
537 | - return true; |
|
538 | - } |
|
539 | - |
|
540 | - // wait for changes |
|
541 | - $started = time(); |
|
542 | - $endat = time() + $lifetime; |
|
543 | - |
|
544 | - // always use policy key from the request if it was sent |
|
545 | - $policyKey = $this->GetReferencePolicyKey(); |
|
546 | - if (Request::WasPolicyKeySent() && Request::GetPolicyKey() != 0) { |
|
547 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("refpolkey:'%s', sent polkey:'%s'", $policyKey, Request::GetPolicyKey())); |
|
548 | - $policyKey = Request::GetPolicyKey(); |
|
549 | - } |
|
550 | - while (($now = time()) < $endat) { |
|
551 | - // how long are we waiting for changes |
|
552 | - $this->waitingTime = $now - $started; |
|
553 | - |
|
554 | - $nextInterval = $interval; |
|
555 | - // we should not block longer than the lifetime |
|
556 | - if ($endat - $now < $nextInterval) { |
|
557 | - $nextInterval = $endat - $now; |
|
558 | - } |
|
559 | - |
|
560 | - // Check if provisioning is necessary |
|
561 | - // if a PolicyKey was sent use it. If not, compare with the ReferencePolicyKey |
|
562 | - if (PROVISIONING === true && $policyKey !== false && GSync::GetProvisioningManager()->ProvisioningRequired($policyKey, true, false)) { |
|
563 | - // the hierarchysync forces provisioning |
|
564 | - throw new StatusException("SyncCollections->CheckForChanges(): Policies or PolicyKey changed. Provisioning required.", self::ERROR_WRONG_HIERARCHY); |
|
565 | - } |
|
566 | - |
|
567 | - // Check if a hierarchy sync is necessary |
|
568 | - if ($this->countHierarchyChange()) { |
|
569 | - throw new StatusException("SyncCollections->CheckForChanges(): HierarchySync required.", self::HIERARCHY_CHANGED); |
|
570 | - } |
|
571 | - |
|
572 | - // Check if there are newer requests |
|
573 | - // If so, this process should be terminated if more than 60 secs to go |
|
574 | - if ($pingTracking->DoForcePingTimeout()) { |
|
575 | - // do not update CPOs because another process has already read them! |
|
576 | - $this->saveData = false; |
|
577 | - |
|
578 | - // more than 60 secs to go? |
|
579 | - if (($now + 60) < $endat) { |
|
580 | - GSync::GetTopCollector()->AnnounceInformation(sprintf("Forced timeout after %ds", ($now - $started)), true); |
|
581 | - |
|
582 | - throw new StatusException(sprintf("SyncCollections->CheckForChanges(): Timeout forced after %ss from %ss due to other process", ($now - $started), $lifetime), self::OBSOLETE_CONNECTION); |
|
583 | - } |
|
584 | - } |
|
585 | - |
|
586 | - // Use changes sink if available |
|
587 | - if ($changesSink) { |
|
588 | - GSync::GetTopCollector()->AnnounceInformation(sprintf("Sink %d/%ds on %s", ($now - $started), $lifetime, $checkClasses)); |
|
589 | - $notifications = GSync::GetBackend()->ChangesSink($nextInterval); |
|
590 | - |
|
591 | - // how long are we waiting for changes |
|
592 | - $this->waitingTime = time() - $started; |
|
593 | - |
|
594 | - $validNotifications = false; |
|
595 | - foreach ($notifications as $backendFolderId) { |
|
596 | - // Check hierarchy notifications |
|
597 | - if ($backendFolderId === IBackend::HIERARCHYNOTIFICATION) { |
|
598 | - // wait two seconds before validating this notification, because it could potentially be made by the mobile and we need some time to update the states. |
|
599 | - sleep(2); |
|
600 | - // check received hierarchy notifications by exporting |
|
601 | - if ($this->countHierarchyChange(true)) { |
|
602 | - throw new StatusException("SyncCollections->CheckForChanges(): HierarchySync required.", self::HIERARCHY_CHANGED); |
|
603 | - } |
|
604 | - } |
|
605 | - else { |
|
606 | - // the backend will notify on the backend folderid |
|
607 | - $folderid = GSync::GetDeviceManager()->GetFolderIdForBackendId($backendFolderId); |
|
608 | - |
|
609 | - // check if the notification on the folder is within our filter |
|
610 | - if ($this->CountChange($folderid)) { |
|
611 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s'", $folderid)); |
|
612 | - $validNotifications = true; |
|
613 | - $this->waitingTime = time() - $started; |
|
614 | - } |
|
615 | - else { |
|
616 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s', but it is not relevant", $folderid)); |
|
617 | - } |
|
618 | - } |
|
619 | - } |
|
620 | - if ($validNotifications) { |
|
621 | - return true; |
|
622 | - } |
|
623 | - } |
|
624 | - // use polling mechanism |
|
625 | - else { |
|
626 | - GSync::GetTopCollector()->AnnounceInformation(sprintf("Polling %d/%ds on %s", ($now - $started), $lifetime, $checkClasses)); |
|
627 | - if ($this->CountChanges($onlyPingable)) { |
|
628 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Found changes polling")); |
|
629 | - |
|
630 | - return true; |
|
631 | - } |
|
632 | - |
|
633 | - sleep($nextInterval); |
|
634 | - } // end polling |
|
635 | - } // end wait for changes |
|
636 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): no changes found after %ds", time() - $started)); |
|
637 | - |
|
638 | - return false; |
|
639 | - } |
|
640 | - |
|
641 | - /** |
|
642 | - * Checks if the currently known collections for |
|
643 | - * changes performing Exporter->GetChangeCount(). |
|
644 | - * |
|
645 | - * @param bool $onlyPingable (opt) only check for folders which have the PingableFlag |
|
646 | - * |
|
647 | - * @return bool indicating if changes were found or not |
|
648 | - */ |
|
649 | - public function CountChanges($onlyPingable = false) { |
|
650 | - $changesAvailable = false; |
|
651 | - foreach ($this->collections as $folderid => $spa) { |
|
652 | - if ($onlyPingable && $spa->GetPingableFlag() !== true) { |
|
653 | - continue; |
|
654 | - } |
|
655 | - |
|
656 | - if (isset($this->addparms[$spa->GetFolderId()]["status"]) && $this->addparms[$spa->GetFolderId()]["status"] != SYNC_STATUS_SUCCESS) { |
|
657 | - continue; |
|
658 | - } |
|
659 | - |
|
660 | - if ($this->CountChange($folderid)) { |
|
661 | - $changesAvailable = true; |
|
662 | - } |
|
663 | - } |
|
664 | - |
|
665 | - return $changesAvailable; |
|
666 | - } |
|
667 | - |
|
668 | - /** |
|
669 | - * Checks a folder for changes performing Exporter->GetChangeCount(). |
|
670 | - * |
|
671 | - * @param string $folderid counts changes for a folder |
|
672 | - * |
|
673 | - * @return bool indicating if changes were found or not |
|
674 | - */ |
|
675 | - private function CountChange($folderid) { |
|
676 | - $spa = $this->GetCollection($folderid); |
|
677 | - |
|
678 | - if (!$spa) { |
|
679 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CountChange(): Could not get SyncParameters object from cache for folderid '%s' to verify notification. Ignoring.", $folderid)); |
|
680 | - |
|
681 | - return false; |
|
682 | - } |
|
683 | - |
|
684 | - $backendFolderId = GSync::GetDeviceManager()->GetBackendIdForFolderId($folderid); |
|
685 | - // switch user store if this is a additional folder (additional true -> do not debug) |
|
686 | - GSync::GetBackend()->Setup(GSync::GetAdditionalSyncFolderStore($backendFolderId, true)); |
|
687 | - $changecount = false; |
|
688 | - |
|
689 | - try { |
|
690 | - $exporter = GSync::GetBackend()->GetExporter($backendFolderId); |
|
691 | - if ($exporter !== false && isset($this->addparms[$folderid]["state"])) { |
|
692 | - $importer = false; |
|
693 | - $exporter->Config($this->addparms[$folderid]["state"], BACKEND_DISCARD_DATA); |
|
694 | - $exporter->ConfigContentParameters($spa->GetCPO()); |
|
695 | - $ret = $exporter->InitializeExporter($importer); |
|
696 | - |
|
697 | - if ($ret !== false) { |
|
698 | - $changecount = $exporter->GetChangeCount(); |
|
699 | - } |
|
700 | - } |
|
701 | - } |
|
702 | - catch (StatusException $ste) { |
|
703 | - if ($ste->getCode() == SYNC_STATUS_FOLDERHIERARCHYCHANGED) { |
|
704 | - SLog::Write(LOGLEVEL_WARN, "SyncCollections->CountChange(): exporter can not be re-configured due to state error, emulating change in folder to force Sync."); |
|
705 | - $this->changes[$folderid] = 1; |
|
706 | - // make sure this folder is fully synched on next Sync request |
|
707 | - $this->invalidateFolderStat($spa); |
|
708 | - |
|
709 | - return true; |
|
710 | - } |
|
711 | - |
|
712 | - throw new StatusException("SyncCollections->CountChange(): exporter can not be re-configured.", self::ERROR_WRONG_HIERARCHY, null, LOGLEVEL_WARN); |
|
713 | - } |
|
714 | - |
|
715 | - // start over if exporter can not be configured atm |
|
716 | - if ($changecount === false) { |
|
717 | - SLog::Write(LOGLEVEL_WARN, "SyncCollections->CountChange(): no changes received from Exporter."); |
|
718 | - } |
|
719 | - |
|
720 | - $this->changes[$folderid] = $changecount; |
|
721 | - |
|
722 | - return $changecount > 0; |
|
723 | - } |
|
724 | - |
|
725 | - /** |
|
726 | - * Checks the hierarchy for changes. |
|
727 | - * |
|
728 | - * @param bool export changes, default: false |
|
729 | - * @param mixed $exportChanges |
|
730 | - * |
|
731 | - * @return bool indicating if changes were found or not |
|
732 | - */ |
|
733 | - private function countHierarchyChange($exportChanges = false) { |
|
734 | - $folderid = false; |
|
735 | - |
|
736 | - // Check with device manager if the hierarchy should be reloaded. |
|
737 | - // New additional folders are loaded here. |
|
738 | - if (GSync::GetDeviceManager()->IsHierarchySyncRequired()) { |
|
739 | - SLog::Write(LOGLEVEL_DEBUG, "SyncCollections->countHierarchyChange(): DeviceManager says HierarchySync is required."); |
|
740 | - |
|
741 | - return true; |
|
742 | - } |
|
743 | - |
|
744 | - $changecount = false; |
|
745 | - if ($exportChanges || $this->hierarchyExporterChecked === false) { |
|
746 | - try { |
|
747 | - // if this is a validation (not first run), make sure to load the hierarchy data again |
|
748 | - if ($this->hierarchyExporterChecked === true && !$this->LoadCollection(false, true, false)) { |
|
749 | - throw new StatusException("Invalid states found while re-loading hierarchy data."); |
|
750 | - } |
|
751 | - |
|
752 | - $changesMem = GSync::GetDeviceManager()->GetHierarchyChangesWrapper(); |
|
753 | - // the hierarchyCache should now fully be initialized - check for changes in the additional folders |
|
754 | - $changesMem->Config(GSync::GetAdditionalSyncFolders(false)); |
|
755 | - |
|
756 | - // reset backend to the main store |
|
757 | - GSync::GetBackend()->Setup(false); |
|
758 | - $exporter = GSync::GetBackend()->GetExporter(); |
|
759 | - if ($exporter !== false && isset($this->addparms[$folderid]["state"])) { |
|
760 | - $exporter->Config($this->addparms[$folderid]["state"]); |
|
761 | - $ret = $exporter->InitializeExporter($changesMem); |
|
762 | - while (is_array($exporter->Synchronize())); |
|
763 | - |
|
764 | - if ($ret !== false) { |
|
765 | - $changecount = $changesMem->GetChangeCount(); |
|
766 | - } |
|
767 | - |
|
768 | - $this->hierarchyExporterChecked = true; |
|
769 | - } |
|
770 | - } |
|
771 | - catch (StatusException $ste) { |
|
772 | - throw new StatusException("SyncCollections->countHierarchyChange(): exporter can not be re-configured.", self::ERROR_WRONG_HIERARCHY, null, LOGLEVEL_WARN); |
|
773 | - } |
|
774 | - |
|
775 | - // start over if exporter can not be configured atm |
|
776 | - if ($changecount === false) { |
|
777 | - SLog::Write(LOGLEVEL_WARN, "SyncCollections->countHierarchyChange(): no changes received from Exporter."); |
|
778 | - } |
|
779 | - } |
|
780 | - |
|
781 | - return $changecount > 0; |
|
782 | - } |
|
783 | - |
|
784 | - /** |
|
785 | - * Returns an array with all folderid and the amount of changes found. |
|
786 | - * |
|
787 | - * @return array |
|
788 | - */ |
|
789 | - public function GetChangedFolderIds() { |
|
790 | - return $this->changes; |
|
791 | - } |
|
792 | - |
|
793 | - /** |
|
794 | - * Indicates if there are folders which are pingable. |
|
795 | - * |
|
796 | - * @return bool |
|
797 | - */ |
|
798 | - public function PingableFolders() { |
|
799 | - foreach ($this->collections as $folderid => $spa) { |
|
800 | - if ($spa->GetPingableFlag() == true) { |
|
801 | - return true; |
|
802 | - } |
|
803 | - } |
|
804 | - |
|
805 | - return false; |
|
806 | - } |
|
807 | - |
|
808 | - /** |
|
809 | - * Indicates if the process did wait in a sink, polling or before running a |
|
810 | - * regular export to find changes. |
|
811 | - * |
|
812 | - * @return bool |
|
813 | - */ |
|
814 | - public function WaitedForChanges() { |
|
815 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->WaitedForChanges: waited for %d seconds", $this->waitingTime)); |
|
816 | - |
|
817 | - return $this->waitingTime > 0; |
|
818 | - } |
|
819 | - |
|
820 | - /** |
|
821 | - * Indicates how many seconds the process did wait in a sink, polling or before running a |
|
822 | - * regular export to find changes. |
|
823 | - * |
|
824 | - * @return int |
|
825 | - */ |
|
826 | - public function GetWaitedSeconds() { |
|
827 | - return $this->waitingTime; |
|
828 | - } |
|
829 | - |
|
830 | - /** |
|
831 | - * Returns how the current folder should be called in the PING comment. |
|
832 | - * |
|
833 | - * @param SyncParameters $spa |
|
834 | - * |
|
835 | - * @return string |
|
836 | - */ |
|
837 | - private function getPingClass($spa) { |
|
838 | - $class = $spa->GetContentClass(); |
|
839 | - if ($class == "Calendar" && strpos($spa->GetFolderId(), DeviceManager::FLD_ORIGIN_GAB) === 0) { |
|
840 | - $class = "GAB"; |
|
841 | - } |
|
842 | - |
|
843 | - return $class; |
|
844 | - } |
|
845 | - |
|
846 | - /** |
|
847 | - * Simple Iterator Interface implementation to traverse through collections. |
|
848 | - */ |
|
849 | - |
|
850 | - /** |
|
851 | - * Rewind the Iterator to the first element. |
|
852 | - * |
|
853 | - * @return |
|
854 | - */ |
|
855 | - public function rewind() { |
|
856 | - return reset($this->collections); |
|
857 | - } |
|
858 | - |
|
859 | - /** |
|
860 | - * Returns the current element. |
|
861 | - * |
|
862 | - * @return mixed |
|
863 | - */ |
|
864 | - public function current() { |
|
865 | - return current($this->collections); |
|
866 | - } |
|
867 | - |
|
868 | - /** |
|
869 | - * Return the key of the current element. |
|
870 | - * |
|
871 | - * @return scalar on success, or NULL on failure |
|
872 | - */ |
|
873 | - public function key() { |
|
874 | - return key($this->collections); |
|
875 | - } |
|
876 | - |
|
877 | - /** |
|
878 | - * Move forward to next element. |
|
879 | - * |
|
880 | - * @return |
|
881 | - */ |
|
882 | - public function next() { |
|
883 | - return next($this->collections); |
|
884 | - } |
|
885 | - |
|
886 | - /** |
|
887 | - * Checks if current position is valid. |
|
888 | - * |
|
889 | - * @return bool |
|
890 | - */ |
|
891 | - public function valid() { |
|
892 | - return key($this->collections) != null && key($this->collections) != false; |
|
893 | - } |
|
894 | - |
|
895 | - /** |
|
896 | - * Gets the StateManager from the DeviceManager |
|
897 | - * if it's not available. |
|
898 | - * |
|
899 | - * @return |
|
900 | - */ |
|
901 | - private function loadStateManager() { |
|
902 | - if (!isset($this->stateManager)) { |
|
903 | - $this->stateManager = GSync::GetDeviceManager()->GetStateManager(); |
|
904 | - } |
|
905 | - } |
|
906 | - |
|
907 | - /** |
|
908 | - * Remove folder statistics from a SyncParameter object. |
|
909 | - * |
|
910 | - * @param SyncParameters $spa |
|
911 | - * |
|
912 | - * @return |
|
913 | - */ |
|
914 | - private function invalidateFolderStat($spa) { |
|
915 | - if ($spa->HasFolderStat()) { |
|
916 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->invalidateFolderStat(): removing folder stat '%s' for folderid '%s'", $spa->GetFolderStat(), $spa->GetFolderId())); |
|
917 | - $spa->DelFolderStat(); |
|
918 | - $this->SaveCollection($spa); |
|
919 | - |
|
920 | - return true; |
|
921 | - } |
|
922 | - |
|
923 | - return false; |
|
924 | - } |
|
22 | + public const ERROR_NO_COLLECTIONS = 1; |
|
23 | + public const ERROR_WRONG_HIERARCHY = 2; |
|
24 | + public const OBSOLETE_CONNECTION = 3; |
|
25 | + public const HIERARCHY_CHANGED = 4; |
|
26 | + |
|
27 | + private $stateManager; |
|
28 | + |
|
29 | + private $collections = []; |
|
30 | + private $addparms = []; |
|
31 | + private $changes = []; |
|
32 | + private $saveData = true; |
|
33 | + |
|
34 | + private $refPolicyKey = false; |
|
35 | + private $refLifetime = false; |
|
36 | + |
|
37 | + private $globalWindowSize; |
|
38 | + private $lastSyncTime; |
|
39 | + |
|
40 | + private $waitingTime = 0; |
|
41 | + private $hierarchyExporterChecked = false; |
|
42 | + private $loggedGlobalWindowSizeOverwrite = false; |
|
43 | + |
|
44 | + /** |
|
45 | + * Invalidates all pingable flags for all folders. |
|
46 | + * |
|
47 | + * @return bool |
|
48 | + */ |
|
49 | + public static function InvalidatePingableFlags() { |
|
50 | + SLog::Write(LOGLEVEL_DEBUG, "SyncCollections::InvalidatePingableFlags(): Invalidating now"); |
|
51 | + |
|
52 | + try { |
|
53 | + $sc = new SyncCollections(); |
|
54 | + $sc->LoadAllCollections(); |
|
55 | + foreach ($sc as $folderid => $spa) { |
|
56 | + if ($spa->GetPingableFlag() == true) { |
|
57 | + $spa->DelPingableFlag(); |
|
58 | + $sc->SaveCollection($spa); |
|
59 | + } |
|
60 | + } |
|
61 | + |
|
62 | + return true; |
|
63 | + } |
|
64 | + catch (GSyncException $e) { |
|
65 | + } |
|
66 | + |
|
67 | + return false; |
|
68 | + } |
|
69 | + |
|
70 | + /** |
|
71 | + * Constructor. |
|
72 | + */ |
|
73 | + public function __construct() { |
|
74 | + } |
|
75 | + |
|
76 | + /** |
|
77 | + * Sets the StateManager for this object |
|
78 | + * If this is not done and a method needs it, the StateManager will be |
|
79 | + * requested from the DeviceManager. |
|
80 | + * |
|
81 | + * @param StateManager $statemanager |
|
82 | + * |
|
83 | + * @return |
|
84 | + */ |
|
85 | + public function SetStateManager($statemanager) { |
|
86 | + $this->stateManager = $statemanager; |
|
87 | + } |
|
88 | + |
|
89 | + /** |
|
90 | + * Loads all collections known for the current device. |
|
91 | + * |
|
92 | + * @param bool $overwriteLoaded (opt) overwrites Collection with saved state if set to true |
|
93 | + * @param bool $loadState (opt) indicates if the collection sync state should be loaded, default false |
|
94 | + * @param bool $checkPermissions (opt) if set to true each folder will pass |
|
95 | + * through a backend->Setup() to check permissions. |
|
96 | + * If this fails a StatusException will be thrown. |
|
97 | + * @param bool $loadHierarchy (opt) if the hierarchy sync states should be loaded, default false |
|
98 | + * @param bool $confirmedOnly (opt) indicates if only confirmed states should be loaded, default: false |
|
99 | + * |
|
100 | + * @throws StatusException with SyncCollections::ERROR_WRONG_HIERARCHY if permission check fails |
|
101 | + * @throws StateInvalidException if the sync state can not be found or relation between states is invalid ($loadState = true) |
|
102 | + * |
|
103 | + * @return bool |
|
104 | + */ |
|
105 | + public function LoadAllCollections($overwriteLoaded = false, $loadState = false, $checkPermissions = false, $loadHierarchy = false, $confirmedOnly = false) { |
|
106 | + $this->loadStateManager(); |
|
107 | + |
|
108 | + // this operation should not remove old state counters |
|
109 | + $this->stateManager->DoNotDeleteOldStates(); |
|
110 | + |
|
111 | + $invalidStates = false; |
|
112 | + foreach ($this->stateManager->GetSynchedFolders() as $folderid) { |
|
113 | + if ($overwriteLoaded === false && isset($this->collections[$folderid])) { |
|
114 | + continue; |
|
115 | + } |
|
116 | + |
|
117 | + // Load Collection! |
|
118 | + if (!$this->LoadCollection($folderid, $loadState, $checkPermissions, $confirmedOnly)) { |
|
119 | + $invalidStates = true; |
|
120 | + } |
|
121 | + } |
|
122 | + |
|
123 | + // load the hierarchy data - there are no permissions to verify so we just set it to false |
|
124 | + if ($loadHierarchy && !$this->LoadCollection(false, $loadState, false, false)) { |
|
125 | + throw new StatusException("Invalid states found while loading hierarchy data. Forcing hierarchy sync"); |
|
126 | + } |
|
127 | + |
|
128 | + if ($invalidStates) { |
|
129 | + throw new StateInvalidException("Invalid states found while loading collections. Forcing sync"); |
|
130 | + } |
|
131 | + |
|
132 | + return true; |
|
133 | + } |
|
134 | + |
|
135 | + /** |
|
136 | + * Loads all collections known for the current device. |
|
137 | + * |
|
138 | + * @param string $folderid folder id to be loaded |
|
139 | + * @param bool $loadState (opt) indicates if the collection sync state should be loaded, default true |
|
140 | + * @param bool $checkPermissions (opt) if set to true each folder will pass |
|
141 | + * through a backend->Setup() to check permissions. |
|
142 | + * If this fails a StatusException will be thrown. |
|
143 | + * @param bool $confirmedOnly (opt) indicates if only confirmed states should be loaded, default: false |
|
144 | + * |
|
145 | + * @throws StatusException with SyncCollections::ERROR_WRONG_HIERARCHY if permission check fails |
|
146 | + * @throws StateInvalidException if the sync state can not be found or relation between states is invalid ($loadState = true) |
|
147 | + * |
|
148 | + * @return bool |
|
149 | + */ |
|
150 | + public function LoadCollection($folderid, $loadState = false, $checkPermissions = false, $confirmedOnly = false) { |
|
151 | + $this->loadStateManager(); |
|
152 | + |
|
153 | + try { |
|
154 | + // Get SyncParameters for the folder from the state |
|
155 | + $spa = $this->stateManager->GetSynchedFolderState($folderid, !$loadState); |
|
156 | + |
|
157 | + // TODO remove resync of folders |
|
158 | + // this forces a resync of all states |
|
159 | + if (!$spa instanceof SyncParameters) { |
|
160 | + throw new StateInvalidException("Saved state are not of type SyncParameters"); |
|
161 | + } |
|
162 | + |
|
163 | + if ($spa->GetUuidCounter() == 0) { |
|
164 | + SLog::Write(LOGLEVEL_DEBUG, "SyncCollections->LoadCollection(): Found collection with move state only, ignoring."); |
|
165 | + |
|
166 | + return true; |
|
167 | + } |
|
168 | + } |
|
169 | + catch (StateInvalidException $sive) { |
|
170 | + // in case there is something wrong with the state, just stop here |
|
171 | + // later when trying to retrieve the SyncParameters nothing will be found |
|
172 | + |
|
173 | + if ($folderid === false) { |
|
174 | + throw new StatusException(sprintf("SyncCollections->LoadCollection(): could not get FOLDERDATA state of the hierarchy uuid: %s", $spa->GetUuid()), self::ERROR_WRONG_HIERARCHY); |
|
175 | + } |
|
176 | + |
|
177 | + // we also generate a fake change, so a sync on this folder is triggered |
|
178 | + $this->changes[$folderid] = 1; |
|
179 | + |
|
180 | + return false; |
|
181 | + } |
|
182 | + |
|
183 | + // if this is an additional folder the backend has to be setup correctly |
|
184 | + if ($checkPermissions === true && !GSync::GetBackend()->Setup(GSync::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()))) { |
|
185 | + throw new StatusException(sprintf("SyncCollections->LoadCollection(): could not Setup() the backend for folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), self::ERROR_WRONG_HIERARCHY); |
|
186 | + } |
|
187 | + |
|
188 | + // add collection to object |
|
189 | + $addStatus = $this->AddCollection($spa); |
|
190 | + |
|
191 | + // load the latest known syncstate if requested |
|
192 | + if ($addStatus && $loadState === true) { |
|
193 | + try { |
|
194 | + // make sure the hierarchy cache is loaded when we are loading hierarchy states |
|
195 | + $this->addparms[$folderid]["state"] = $this->stateManager->GetSyncState($spa->GetLatestSyncKey($confirmedOnly), ($folderid === false)); |
|
196 | + } |
|
197 | + catch (StateNotFoundException $snfe) { |
|
198 | + // if we can't find the state, first we should try a sync of that folder, so |
|
199 | + // we generate a fake change, so a sync on this folder is triggered |
|
200 | + $this->changes[$folderid] = 1; |
|
201 | + |
|
202 | + // make sure this folder is fully synched on next Sync request |
|
203 | + $this->invalidateFolderStat($spa); |
|
204 | + |
|
205 | + return false; |
|
206 | + } |
|
207 | + } |
|
208 | + |
|
209 | + return $addStatus; |
|
210 | + } |
|
211 | + |
|
212 | + /** |
|
213 | + * Saves a SyncParameters Object. |
|
214 | + * |
|
215 | + * @param SyncParamerts $spa |
|
216 | + * |
|
217 | + * @return bool |
|
218 | + */ |
|
219 | + public function SaveCollection($spa) { |
|
220 | + if (!$this->saveData || !$spa->HasFolderId()) { |
|
221 | + return false; |
|
222 | + } |
|
223 | + |
|
224 | + if ($spa->IsDataChanged()) { |
|
225 | + $this->loadStateManager(); |
|
226 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->SaveCollection(): Data of folder '%s' changed", $spa->GetFolderId())); |
|
227 | + |
|
228 | + // save new windowsize |
|
229 | + if (isset($this->globalWindowSize)) { |
|
230 | + $spa->SetWindowSize($this->globalWindowSize); |
|
231 | + } |
|
232 | + |
|
233 | + // update latest lifetime |
|
234 | + if (isset($this->refLifetime)) { |
|
235 | + $spa->SetReferenceLifetime($this->refLifetime); |
|
236 | + } |
|
237 | + |
|
238 | + return $this->stateManager->SetSynchedFolderState($spa); |
|
239 | + } |
|
240 | + |
|
241 | + return false; |
|
242 | + } |
|
243 | + |
|
244 | + /** |
|
245 | + * Adds a SyncParameters object to the current list of collections. |
|
246 | + * |
|
247 | + * @param SyncParameters $spa |
|
248 | + * |
|
249 | + * @return bool |
|
250 | + */ |
|
251 | + public function AddCollection($spa) { |
|
252 | + if (!$spa->HasFolderId()) { |
|
253 | + return false; |
|
254 | + } |
|
255 | + |
|
256 | + $this->collections[$spa->GetFolderId()] = $spa; |
|
257 | + |
|
258 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->AddCollection(): Folder id '%s' : ref. Lifetime '%s', last sync at '%s'", $spa->GetFolderId(), $spa->GetReferenceLifetime(), $spa->GetLastSyncTime())); |
|
259 | + if ($spa->HasLastSyncTime() && $spa->GetLastSyncTime() > $this->lastSyncTime) { |
|
260 | + $this->lastSyncTime = $spa->GetLastSyncTime(); |
|
261 | + |
|
262 | + // use SyncParameters PolicyKey as reference if available |
|
263 | + if ($spa->HasReferencePolicyKey()) { |
|
264 | + $this->refPolicyKey = $spa->GetReferencePolicyKey(); |
|
265 | + } |
|
266 | + |
|
267 | + // use SyncParameters LifeTime as reference if available |
|
268 | + if ($spa->HasReferenceLifetime()) { |
|
269 | + $this->refLifetime = $spa->GetReferenceLifetime(); |
|
270 | + } |
|
271 | + |
|
272 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->AddCollection(): Updated reference PolicyKey '%s', reference Lifetime '%s', Last sync at '%s'", $this->refPolicyKey, $this->refLifetime, $this->lastSyncTime)); |
|
273 | + } |
|
274 | + |
|
275 | + return true; |
|
276 | + } |
|
277 | + |
|
278 | + /** |
|
279 | + * Returns a previousily added or loaded SyncParameters object for a folderid. |
|
280 | + * |
|
281 | + * @param SyncParameters $spa |
|
282 | + * @param mixed $folderid |
|
283 | + * |
|
284 | + * @return SyncParameters / boolean false if no SyncParameters object is found for folderid |
|
285 | + */ |
|
286 | + public function GetCollection($folderid) { |
|
287 | + if (isset($this->collections[$folderid])) { |
|
288 | + return $this->collections[$folderid]; |
|
289 | + } |
|
290 | + |
|
291 | + return false; |
|
292 | + } |
|
293 | + |
|
294 | + /** |
|
295 | + * Indicates if there are any loaded CPOs. |
|
296 | + * |
|
297 | + * @return bool |
|
298 | + */ |
|
299 | + public function HasCollections() { |
|
300 | + return !empty($this->collections); |
|
301 | + } |
|
302 | + |
|
303 | + /** |
|
304 | + * Indicates the amount of collections loaded. |
|
305 | + * |
|
306 | + * @return int |
|
307 | + */ |
|
308 | + public function GetCollectionCount() { |
|
309 | + return count($this->collections); |
|
310 | + } |
|
311 | + |
|
312 | + /** |
|
313 | + * Add a non-permanent key/value pair for a SyncParameters object. |
|
314 | + * |
|
315 | + * @param SyncParameters $spa target SyncParameters |
|
316 | + * @param string $key |
|
317 | + * @param mixed $value |
|
318 | + * |
|
319 | + * @return bool |
|
320 | + */ |
|
321 | + public function AddParameter($spa, $key, $value) { |
|
322 | + if (!$spa->HasFolderId()) { |
|
323 | + return false; |
|
324 | + } |
|
325 | + |
|
326 | + $folderid = $spa->GetFolderId(); |
|
327 | + if (!isset($this->addparms[$folderid])) { |
|
328 | + $this->addparms[$folderid] = []; |
|
329 | + } |
|
330 | + |
|
331 | + $this->addparms[$folderid][$key] = $value; |
|
332 | + |
|
333 | + return true; |
|
334 | + } |
|
335 | + |
|
336 | + /** |
|
337 | + * Returns a previousily set non-permanent value for a SyncParameters object. |
|
338 | + * |
|
339 | + * @param SyncParameters $spa target SyncParameters |
|
340 | + * @param string $key |
|
341 | + * |
|
342 | + * @return mixed returns 'null' if nothing set |
|
343 | + */ |
|
344 | + public function GetParameter($spa, $key) { |
|
345 | + if (!$spa->HasFolderId()) { |
|
346 | + return null; |
|
347 | + } |
|
348 | + |
|
349 | + if (isset($this->addparms[$spa->GetFolderId()], $this->addparms[$spa->GetFolderId()][$key])) { |
|
350 | + return $this->addparms[$spa->GetFolderId()][$key]; |
|
351 | + } |
|
352 | + |
|
353 | + return null; |
|
354 | + } |
|
355 | + |
|
356 | + /** |
|
357 | + * Returns the latest known PolicyKey to be used as reference. |
|
358 | + * |
|
359 | + * @return int/boolean returns false if nothing found in collections |
|
360 | + */ |
|
361 | + public function GetReferencePolicyKey() { |
|
362 | + return $this->refPolicyKey; |
|
363 | + } |
|
364 | + |
|
365 | + /** |
|
366 | + * Sets a global window size which should be used for all collections |
|
367 | + * in a case of a heartbeat and/or partial sync. |
|
368 | + * |
|
369 | + * @param int $windowsize |
|
370 | + * |
|
371 | + * @return bool |
|
372 | + */ |
|
373 | + public function SetGlobalWindowSize($windowsize) { |
|
374 | + $this->globalWindowSize = $windowsize; |
|
375 | + |
|
376 | + return true; |
|
377 | + } |
|
378 | + |
|
379 | + /** |
|
380 | + * Returns the global window size of items to be exported in total over all |
|
381 | + * requested collections. |
|
382 | + * |
|
383 | + * @return int/boolean returns requested windows size, 512 (max) or the |
|
384 | + * value of config SYNC_MAX_ITEMS if it is lower |
|
385 | + */ |
|
386 | + public function GetGlobalWindowSize() { |
|
387 | + // take the requested global windowsize or the max 512 if not defined |
|
388 | + if (isset($this->globalWindowSize)) { |
|
389 | + $globalWindowSize = $this->globalWindowSize; |
|
390 | + } |
|
391 | + else { |
|
392 | + $globalWindowSize = WINDOW_SIZE_MAX; // 512 by default |
|
393 | + } |
|
394 | + |
|
395 | + if (defined("SYNC_MAX_ITEMS") && $globalWindowSize > SYNC_MAX_ITEMS) { |
|
396 | + if (!$this->loggedGlobalWindowSizeOverwrite) { |
|
397 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->GetGlobalWindowSize() overwriting requested global window size of %d by %d forced in configuration.", $globalWindowSize, SYNC_MAX_ITEMS)); |
|
398 | + $this->loggedGlobalWindowSizeOverwrite = true; |
|
399 | + } |
|
400 | + $globalWindowSize = SYNC_MAX_ITEMS; |
|
401 | + } |
|
402 | + |
|
403 | + return $globalWindowSize; |
|
404 | + } |
|
405 | + |
|
406 | + /** |
|
407 | + * Sets the lifetime for heartbeat or ping connections. |
|
408 | + * |
|
409 | + * @param int $lifetime time in seconds |
|
410 | + * |
|
411 | + * @return bool |
|
412 | + */ |
|
413 | + public function SetLifetime($lifetime) { |
|
414 | + $this->refLifetime = $lifetime; |
|
415 | + |
|
416 | + return true; |
|
417 | + } |
|
418 | + |
|
419 | + /** |
|
420 | + * Sets the lifetime for heartbeat or ping connections |
|
421 | + * previousily set or saved in a collection. |
|
422 | + * |
|
423 | + * @return int returns PING_HIGHER_BOUND_LIFETIME as default if nothing set or not available. |
|
424 | + * If PING_HIGHER_BOUND_LIFETIME is not set, returns 600. |
|
425 | + */ |
|
426 | + public function GetLifetime() { |
|
427 | + if (!isset($this->refLifetime) || $this->refLifetime === false) { |
|
428 | + if (PING_HIGHER_BOUND_LIFETIME !== false) { |
|
429 | + return PING_HIGHER_BOUND_LIFETIME; |
|
430 | + } |
|
431 | + |
|
432 | + return 600; |
|
433 | + } |
|
434 | + |
|
435 | + return $this->refLifetime; |
|
436 | + } |
|
437 | + |
|
438 | + /** |
|
439 | + * Returns the timestamp of the last synchronization for all |
|
440 | + * loaded collections. |
|
441 | + * |
|
442 | + * @return int timestamp |
|
443 | + */ |
|
444 | + public function GetLastSyncTime() { |
|
445 | + return $this->lastSyncTime; |
|
446 | + } |
|
447 | + |
|
448 | + /** |
|
449 | + * Checks if the currently known collections for changes for $lifetime seconds. |
|
450 | + * If the backend provides a ChangesSink the sink will be used. |
|
451 | + * If not every $interval seconds an exporter will be configured for each |
|
452 | + * folder to perform GetChangeCount(). |
|
453 | + * |
|
454 | + * @param int $lifetime (opt) total lifetime to wait for changes / default 600s |
|
455 | + * @param int $interval (opt) time between blocking operations of sink or polling / default 30s |
|
456 | + * @param bool $onlyPingable (opt) only check for folders which have the PingableFlag |
|
457 | + * |
|
458 | + * @throws StatusException with code SyncCollections::ERROR_NO_COLLECTIONS if no collections available |
|
459 | + * with code SyncCollections::ERROR_WRONG_HIERARCHY if there were errors getting changes |
|
460 | + * |
|
461 | + * @return bool indicating if changes were found |
|
462 | + */ |
|
463 | + public function CheckForChanges($lifetime = 600, $interval = 30, $onlyPingable = false) { |
|
464 | + $classes = []; |
|
465 | + foreach ($this->collections as $folderid => $spa) { |
|
466 | + if ($onlyPingable && $spa->GetPingableFlag() !== true || !$folderid) { |
|
467 | + continue; |
|
468 | + } |
|
469 | + |
|
470 | + $class = $this->getPingClass($spa); |
|
471 | + |
|
472 | + if (!isset($classes[$class])) { |
|
473 | + $classes[$class] = 0; |
|
474 | + } |
|
475 | + ++$classes[$class]; |
|
476 | + } |
|
477 | + if (empty($classes)) { |
|
478 | + $checkClasses = "policies only"; |
|
479 | + } |
|
480 | + elseif (array_sum($classes) > 4) { |
|
481 | + $checkClasses = ""; |
|
482 | + foreach ($classes as $class => $count) { |
|
483 | + if ($count == 1) { |
|
484 | + $checkClasses .= sprintf("%s ", $class); |
|
485 | + } |
|
486 | + else { |
|
487 | + $checkClasses .= sprintf("%s(%d) ", $class, $count); |
|
488 | + } |
|
489 | + } |
|
490 | + } |
|
491 | + else { |
|
492 | + $checkClasses = implode(" ", array_keys($classes)); |
|
493 | + } |
|
494 | + |
|
495 | + $pingTracking = new PingTracking(); |
|
496 | + $this->changes = []; |
|
497 | + |
|
498 | + GSync::GetDeviceManager()->AnnounceProcessAsPush(); |
|
499 | + GSync::GetTopCollector()->AnnounceInformation(sprintf("lifetime %ds", $lifetime), true); |
|
500 | + SLog::Write(LOGLEVEL_INFO, sprintf("SyncCollections->CheckForChanges(): Waiting for %s changes... (lifetime %d seconds)", (empty($classes)) ? 'policy' : 'store', $lifetime)); |
|
501 | + |
|
502 | + // use changes sink where available |
|
503 | + $changesSink = GSync::GetBackend()->HasChangesSink(); |
|
504 | + |
|
505 | + // create changessink and check folder stats if there are folders to Ping |
|
506 | + if (!empty($classes)) { |
|
507 | + // initialize all possible folders |
|
508 | + foreach ($this->collections as $folderid => $spa) { |
|
509 | + if (($onlyPingable && $spa->GetPingableFlag() !== true) || !$folderid) { |
|
510 | + continue; |
|
511 | + } |
|
512 | + |
|
513 | + $backendFolderId = $spa->GetBackendFolderId(); |
|
514 | + |
|
515 | + // get the user store if this is a additional folder |
|
516 | + $store = GSync::GetAdditionalSyncFolderStore($backendFolderId); |
|
517 | + |
|
518 | + // initialize sink if no immediate changes were found so far |
|
519 | + if ($changesSink && empty($this->changes)) { |
|
520 | + GSync::GetBackend()->Setup($store); |
|
521 | + if (!GSync::GetBackend()->ChangesSinkInitialize($backendFolderId)) { |
|
522 | + throw new StatusException(sprintf("Error initializing ChangesSink for folder id %s/%s", $folderid, $backendFolderId), self::ERROR_WRONG_HIERARCHY); |
|
523 | + } |
|
524 | + } |
|
525 | + |
|
526 | + // check if the folder stat changed since the last sync, if so generate a change for it (only on first run) |
|
527 | + $currentFolderStat = GSync::GetBackend()->GetFolderStat($store, $backendFolderId); |
|
528 | + if ($this->waitingTime == 0 && GSync::GetBackend()->HasFolderStats() && $currentFolderStat !== false && $spa->IsExporterRunRequired($currentFolderStat, true)) { |
|
529 | + $this->changes[$spa->GetFolderId()] = 1; |
|
530 | + } |
|
531 | + } |
|
532 | + } |
|
533 | + |
|
534 | + if (!empty($this->changes)) { |
|
535 | + SLog::Write(LOGLEVEL_DEBUG, "SyncCollections->CheckForChanges(): Using ChangesSink but found changes verifying the folder stats"); |
|
536 | + |
|
537 | + return true; |
|
538 | + } |
|
539 | + |
|
540 | + // wait for changes |
|
541 | + $started = time(); |
|
542 | + $endat = time() + $lifetime; |
|
543 | + |
|
544 | + // always use policy key from the request if it was sent |
|
545 | + $policyKey = $this->GetReferencePolicyKey(); |
|
546 | + if (Request::WasPolicyKeySent() && Request::GetPolicyKey() != 0) { |
|
547 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("refpolkey:'%s', sent polkey:'%s'", $policyKey, Request::GetPolicyKey())); |
|
548 | + $policyKey = Request::GetPolicyKey(); |
|
549 | + } |
|
550 | + while (($now = time()) < $endat) { |
|
551 | + // how long are we waiting for changes |
|
552 | + $this->waitingTime = $now - $started; |
|
553 | + |
|
554 | + $nextInterval = $interval; |
|
555 | + // we should not block longer than the lifetime |
|
556 | + if ($endat - $now < $nextInterval) { |
|
557 | + $nextInterval = $endat - $now; |
|
558 | + } |
|
559 | + |
|
560 | + // Check if provisioning is necessary |
|
561 | + // if a PolicyKey was sent use it. If not, compare with the ReferencePolicyKey |
|
562 | + if (PROVISIONING === true && $policyKey !== false && GSync::GetProvisioningManager()->ProvisioningRequired($policyKey, true, false)) { |
|
563 | + // the hierarchysync forces provisioning |
|
564 | + throw new StatusException("SyncCollections->CheckForChanges(): Policies or PolicyKey changed. Provisioning required.", self::ERROR_WRONG_HIERARCHY); |
|
565 | + } |
|
566 | + |
|
567 | + // Check if a hierarchy sync is necessary |
|
568 | + if ($this->countHierarchyChange()) { |
|
569 | + throw new StatusException("SyncCollections->CheckForChanges(): HierarchySync required.", self::HIERARCHY_CHANGED); |
|
570 | + } |
|
571 | + |
|
572 | + // Check if there are newer requests |
|
573 | + // If so, this process should be terminated if more than 60 secs to go |
|
574 | + if ($pingTracking->DoForcePingTimeout()) { |
|
575 | + // do not update CPOs because another process has already read them! |
|
576 | + $this->saveData = false; |
|
577 | + |
|
578 | + // more than 60 secs to go? |
|
579 | + if (($now + 60) < $endat) { |
|
580 | + GSync::GetTopCollector()->AnnounceInformation(sprintf("Forced timeout after %ds", ($now - $started)), true); |
|
581 | + |
|
582 | + throw new StatusException(sprintf("SyncCollections->CheckForChanges(): Timeout forced after %ss from %ss due to other process", ($now - $started), $lifetime), self::OBSOLETE_CONNECTION); |
|
583 | + } |
|
584 | + } |
|
585 | + |
|
586 | + // Use changes sink if available |
|
587 | + if ($changesSink) { |
|
588 | + GSync::GetTopCollector()->AnnounceInformation(sprintf("Sink %d/%ds on %s", ($now - $started), $lifetime, $checkClasses)); |
|
589 | + $notifications = GSync::GetBackend()->ChangesSink($nextInterval); |
|
590 | + |
|
591 | + // how long are we waiting for changes |
|
592 | + $this->waitingTime = time() - $started; |
|
593 | + |
|
594 | + $validNotifications = false; |
|
595 | + foreach ($notifications as $backendFolderId) { |
|
596 | + // Check hierarchy notifications |
|
597 | + if ($backendFolderId === IBackend::HIERARCHYNOTIFICATION) { |
|
598 | + // wait two seconds before validating this notification, because it could potentially be made by the mobile and we need some time to update the states. |
|
599 | + sleep(2); |
|
600 | + // check received hierarchy notifications by exporting |
|
601 | + if ($this->countHierarchyChange(true)) { |
|
602 | + throw new StatusException("SyncCollections->CheckForChanges(): HierarchySync required.", self::HIERARCHY_CHANGED); |
|
603 | + } |
|
604 | + } |
|
605 | + else { |
|
606 | + // the backend will notify on the backend folderid |
|
607 | + $folderid = GSync::GetDeviceManager()->GetFolderIdForBackendId($backendFolderId); |
|
608 | + |
|
609 | + // check if the notification on the folder is within our filter |
|
610 | + if ($this->CountChange($folderid)) { |
|
611 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s'", $folderid)); |
|
612 | + $validNotifications = true; |
|
613 | + $this->waitingTime = time() - $started; |
|
614 | + } |
|
615 | + else { |
|
616 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s', but it is not relevant", $folderid)); |
|
617 | + } |
|
618 | + } |
|
619 | + } |
|
620 | + if ($validNotifications) { |
|
621 | + return true; |
|
622 | + } |
|
623 | + } |
|
624 | + // use polling mechanism |
|
625 | + else { |
|
626 | + GSync::GetTopCollector()->AnnounceInformation(sprintf("Polling %d/%ds on %s", ($now - $started), $lifetime, $checkClasses)); |
|
627 | + if ($this->CountChanges($onlyPingable)) { |
|
628 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Found changes polling")); |
|
629 | + |
|
630 | + return true; |
|
631 | + } |
|
632 | + |
|
633 | + sleep($nextInterval); |
|
634 | + } // end polling |
|
635 | + } // end wait for changes |
|
636 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): no changes found after %ds", time() - $started)); |
|
637 | + |
|
638 | + return false; |
|
639 | + } |
|
640 | + |
|
641 | + /** |
|
642 | + * Checks if the currently known collections for |
|
643 | + * changes performing Exporter->GetChangeCount(). |
|
644 | + * |
|
645 | + * @param bool $onlyPingable (opt) only check for folders which have the PingableFlag |
|
646 | + * |
|
647 | + * @return bool indicating if changes were found or not |
|
648 | + */ |
|
649 | + public function CountChanges($onlyPingable = false) { |
|
650 | + $changesAvailable = false; |
|
651 | + foreach ($this->collections as $folderid => $spa) { |
|
652 | + if ($onlyPingable && $spa->GetPingableFlag() !== true) { |
|
653 | + continue; |
|
654 | + } |
|
655 | + |
|
656 | + if (isset($this->addparms[$spa->GetFolderId()]["status"]) && $this->addparms[$spa->GetFolderId()]["status"] != SYNC_STATUS_SUCCESS) { |
|
657 | + continue; |
|
658 | + } |
|
659 | + |
|
660 | + if ($this->CountChange($folderid)) { |
|
661 | + $changesAvailable = true; |
|
662 | + } |
|
663 | + } |
|
664 | + |
|
665 | + return $changesAvailable; |
|
666 | + } |
|
667 | + |
|
668 | + /** |
|
669 | + * Checks a folder for changes performing Exporter->GetChangeCount(). |
|
670 | + * |
|
671 | + * @param string $folderid counts changes for a folder |
|
672 | + * |
|
673 | + * @return bool indicating if changes were found or not |
|
674 | + */ |
|
675 | + private function CountChange($folderid) { |
|
676 | + $spa = $this->GetCollection($folderid); |
|
677 | + |
|
678 | + if (!$spa) { |
|
679 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CountChange(): Could not get SyncParameters object from cache for folderid '%s' to verify notification. Ignoring.", $folderid)); |
|
680 | + |
|
681 | + return false; |
|
682 | + } |
|
683 | + |
|
684 | + $backendFolderId = GSync::GetDeviceManager()->GetBackendIdForFolderId($folderid); |
|
685 | + // switch user store if this is a additional folder (additional true -> do not debug) |
|
686 | + GSync::GetBackend()->Setup(GSync::GetAdditionalSyncFolderStore($backendFolderId, true)); |
|
687 | + $changecount = false; |
|
688 | + |
|
689 | + try { |
|
690 | + $exporter = GSync::GetBackend()->GetExporter($backendFolderId); |
|
691 | + if ($exporter !== false && isset($this->addparms[$folderid]["state"])) { |
|
692 | + $importer = false; |
|
693 | + $exporter->Config($this->addparms[$folderid]["state"], BACKEND_DISCARD_DATA); |
|
694 | + $exporter->ConfigContentParameters($spa->GetCPO()); |
|
695 | + $ret = $exporter->InitializeExporter($importer); |
|
696 | + |
|
697 | + if ($ret !== false) { |
|
698 | + $changecount = $exporter->GetChangeCount(); |
|
699 | + } |
|
700 | + } |
|
701 | + } |
|
702 | + catch (StatusException $ste) { |
|
703 | + if ($ste->getCode() == SYNC_STATUS_FOLDERHIERARCHYCHANGED) { |
|
704 | + SLog::Write(LOGLEVEL_WARN, "SyncCollections->CountChange(): exporter can not be re-configured due to state error, emulating change in folder to force Sync."); |
|
705 | + $this->changes[$folderid] = 1; |
|
706 | + // make sure this folder is fully synched on next Sync request |
|
707 | + $this->invalidateFolderStat($spa); |
|
708 | + |
|
709 | + return true; |
|
710 | + } |
|
711 | + |
|
712 | + throw new StatusException("SyncCollections->CountChange(): exporter can not be re-configured.", self::ERROR_WRONG_HIERARCHY, null, LOGLEVEL_WARN); |
|
713 | + } |
|
714 | + |
|
715 | + // start over if exporter can not be configured atm |
|
716 | + if ($changecount === false) { |
|
717 | + SLog::Write(LOGLEVEL_WARN, "SyncCollections->CountChange(): no changes received from Exporter."); |
|
718 | + } |
|
719 | + |
|
720 | + $this->changes[$folderid] = $changecount; |
|
721 | + |
|
722 | + return $changecount > 0; |
|
723 | + } |
|
724 | + |
|
725 | + /** |
|
726 | + * Checks the hierarchy for changes. |
|
727 | + * |
|
728 | + * @param bool export changes, default: false |
|
729 | + * @param mixed $exportChanges |
|
730 | + * |
|
731 | + * @return bool indicating if changes were found or not |
|
732 | + */ |
|
733 | + private function countHierarchyChange($exportChanges = false) { |
|
734 | + $folderid = false; |
|
735 | + |
|
736 | + // Check with device manager if the hierarchy should be reloaded. |
|
737 | + // New additional folders are loaded here. |
|
738 | + if (GSync::GetDeviceManager()->IsHierarchySyncRequired()) { |
|
739 | + SLog::Write(LOGLEVEL_DEBUG, "SyncCollections->countHierarchyChange(): DeviceManager says HierarchySync is required."); |
|
740 | + |
|
741 | + return true; |
|
742 | + } |
|
743 | + |
|
744 | + $changecount = false; |
|
745 | + if ($exportChanges || $this->hierarchyExporterChecked === false) { |
|
746 | + try { |
|
747 | + // if this is a validation (not first run), make sure to load the hierarchy data again |
|
748 | + if ($this->hierarchyExporterChecked === true && !$this->LoadCollection(false, true, false)) { |
|
749 | + throw new StatusException("Invalid states found while re-loading hierarchy data."); |
|
750 | + } |
|
751 | + |
|
752 | + $changesMem = GSync::GetDeviceManager()->GetHierarchyChangesWrapper(); |
|
753 | + // the hierarchyCache should now fully be initialized - check for changes in the additional folders |
|
754 | + $changesMem->Config(GSync::GetAdditionalSyncFolders(false)); |
|
755 | + |
|
756 | + // reset backend to the main store |
|
757 | + GSync::GetBackend()->Setup(false); |
|
758 | + $exporter = GSync::GetBackend()->GetExporter(); |
|
759 | + if ($exporter !== false && isset($this->addparms[$folderid]["state"])) { |
|
760 | + $exporter->Config($this->addparms[$folderid]["state"]); |
|
761 | + $ret = $exporter->InitializeExporter($changesMem); |
|
762 | + while (is_array($exporter->Synchronize())); |
|
763 | + |
|
764 | + if ($ret !== false) { |
|
765 | + $changecount = $changesMem->GetChangeCount(); |
|
766 | + } |
|
767 | + |
|
768 | + $this->hierarchyExporterChecked = true; |
|
769 | + } |
|
770 | + } |
|
771 | + catch (StatusException $ste) { |
|
772 | + throw new StatusException("SyncCollections->countHierarchyChange(): exporter can not be re-configured.", self::ERROR_WRONG_HIERARCHY, null, LOGLEVEL_WARN); |
|
773 | + } |
|
774 | + |
|
775 | + // start over if exporter can not be configured atm |
|
776 | + if ($changecount === false) { |
|
777 | + SLog::Write(LOGLEVEL_WARN, "SyncCollections->countHierarchyChange(): no changes received from Exporter."); |
|
778 | + } |
|
779 | + } |
|
780 | + |
|
781 | + return $changecount > 0; |
|
782 | + } |
|
783 | + |
|
784 | + /** |
|
785 | + * Returns an array with all folderid and the amount of changes found. |
|
786 | + * |
|
787 | + * @return array |
|
788 | + */ |
|
789 | + public function GetChangedFolderIds() { |
|
790 | + return $this->changes; |
|
791 | + } |
|
792 | + |
|
793 | + /** |
|
794 | + * Indicates if there are folders which are pingable. |
|
795 | + * |
|
796 | + * @return bool |
|
797 | + */ |
|
798 | + public function PingableFolders() { |
|
799 | + foreach ($this->collections as $folderid => $spa) { |
|
800 | + if ($spa->GetPingableFlag() == true) { |
|
801 | + return true; |
|
802 | + } |
|
803 | + } |
|
804 | + |
|
805 | + return false; |
|
806 | + } |
|
807 | + |
|
808 | + /** |
|
809 | + * Indicates if the process did wait in a sink, polling or before running a |
|
810 | + * regular export to find changes. |
|
811 | + * |
|
812 | + * @return bool |
|
813 | + */ |
|
814 | + public function WaitedForChanges() { |
|
815 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->WaitedForChanges: waited for %d seconds", $this->waitingTime)); |
|
816 | + |
|
817 | + return $this->waitingTime > 0; |
|
818 | + } |
|
819 | + |
|
820 | + /** |
|
821 | + * Indicates how many seconds the process did wait in a sink, polling or before running a |
|
822 | + * regular export to find changes. |
|
823 | + * |
|
824 | + * @return int |
|
825 | + */ |
|
826 | + public function GetWaitedSeconds() { |
|
827 | + return $this->waitingTime; |
|
828 | + } |
|
829 | + |
|
830 | + /** |
|
831 | + * Returns how the current folder should be called in the PING comment. |
|
832 | + * |
|
833 | + * @param SyncParameters $spa |
|
834 | + * |
|
835 | + * @return string |
|
836 | + */ |
|
837 | + private function getPingClass($spa) { |
|
838 | + $class = $spa->GetContentClass(); |
|
839 | + if ($class == "Calendar" && strpos($spa->GetFolderId(), DeviceManager::FLD_ORIGIN_GAB) === 0) { |
|
840 | + $class = "GAB"; |
|
841 | + } |
|
842 | + |
|
843 | + return $class; |
|
844 | + } |
|
845 | + |
|
846 | + /** |
|
847 | + * Simple Iterator Interface implementation to traverse through collections. |
|
848 | + */ |
|
849 | + |
|
850 | + /** |
|
851 | + * Rewind the Iterator to the first element. |
|
852 | + * |
|
853 | + * @return |
|
854 | + */ |
|
855 | + public function rewind() { |
|
856 | + return reset($this->collections); |
|
857 | + } |
|
858 | + |
|
859 | + /** |
|
860 | + * Returns the current element. |
|
861 | + * |
|
862 | + * @return mixed |
|
863 | + */ |
|
864 | + public function current() { |
|
865 | + return current($this->collections); |
|
866 | + } |
|
867 | + |
|
868 | + /** |
|
869 | + * Return the key of the current element. |
|
870 | + * |
|
871 | + * @return scalar on success, or NULL on failure |
|
872 | + */ |
|
873 | + public function key() { |
|
874 | + return key($this->collections); |
|
875 | + } |
|
876 | + |
|
877 | + /** |
|
878 | + * Move forward to next element. |
|
879 | + * |
|
880 | + * @return |
|
881 | + */ |
|
882 | + public function next() { |
|
883 | + return next($this->collections); |
|
884 | + } |
|
885 | + |
|
886 | + /** |
|
887 | + * Checks if current position is valid. |
|
888 | + * |
|
889 | + * @return bool |
|
890 | + */ |
|
891 | + public function valid() { |
|
892 | + return key($this->collections) != null && key($this->collections) != false; |
|
893 | + } |
|
894 | + |
|
895 | + /** |
|
896 | + * Gets the StateManager from the DeviceManager |
|
897 | + * if it's not available. |
|
898 | + * |
|
899 | + * @return |
|
900 | + */ |
|
901 | + private function loadStateManager() { |
|
902 | + if (!isset($this->stateManager)) { |
|
903 | + $this->stateManager = GSync::GetDeviceManager()->GetStateManager(); |
|
904 | + } |
|
905 | + } |
|
906 | + |
|
907 | + /** |
|
908 | + * Remove folder statistics from a SyncParameter object. |
|
909 | + * |
|
910 | + * @param SyncParameters $spa |
|
911 | + * |
|
912 | + * @return |
|
913 | + */ |
|
914 | + private function invalidateFolderStat($spa) { |
|
915 | + if ($spa->HasFolderStat()) { |
|
916 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->invalidateFolderStat(): removing folder stat '%s' for folderid '%s'", $spa->GetFolderStat(), $spa->GetFolderId())); |
|
917 | + $spa->DelFolderStat(); |
|
918 | + $this->SaveCollection($spa); |
|
919 | + |
|
920 | + return true; |
|
921 | + } |
|
922 | + |
|
923 | + return false; |
|
924 | + } |
|
925 | 925 | } |
@@ -60,8 +60,7 @@ discard block |
||
60 | 60 | } |
61 | 61 | |
62 | 62 | return true; |
63 | - } |
|
64 | - catch (GSyncException $e) { |
|
63 | + } catch (GSyncException $e) { |
|
65 | 64 | } |
66 | 65 | |
67 | 66 | return false; |
@@ -165,8 +164,7 @@ discard block |
||
165 | 164 | |
166 | 165 | return true; |
167 | 166 | } |
168 | - } |
|
169 | - catch (StateInvalidException $sive) { |
|
167 | + } catch (StateInvalidException $sive) { |
|
170 | 168 | // in case there is something wrong with the state, just stop here |
171 | 169 | // later when trying to retrieve the SyncParameters nothing will be found |
172 | 170 | |
@@ -193,8 +191,7 @@ discard block |
||
193 | 191 | try { |
194 | 192 | // make sure the hierarchy cache is loaded when we are loading hierarchy states |
195 | 193 | $this->addparms[$folderid]["state"] = $this->stateManager->GetSyncState($spa->GetLatestSyncKey($confirmedOnly), ($folderid === false)); |
196 | - } |
|
197 | - catch (StateNotFoundException $snfe) { |
|
194 | + } catch (StateNotFoundException $snfe) { |
|
198 | 195 | // if we can't find the state, first we should try a sync of that folder, so |
199 | 196 | // we generate a fake change, so a sync on this folder is triggered |
200 | 197 | $this->changes[$folderid] = 1; |
@@ -387,8 +384,7 @@ discard block |
||
387 | 384 | // take the requested global windowsize or the max 512 if not defined |
388 | 385 | if (isset($this->globalWindowSize)) { |
389 | 386 | $globalWindowSize = $this->globalWindowSize; |
390 | - } |
|
391 | - else { |
|
387 | + } else { |
|
392 | 388 | $globalWindowSize = WINDOW_SIZE_MAX; // 512 by default |
393 | 389 | } |
394 | 390 | |
@@ -476,19 +472,16 @@ discard block |
||
476 | 472 | } |
477 | 473 | if (empty($classes)) { |
478 | 474 | $checkClasses = "policies only"; |
479 | - } |
|
480 | - elseif (array_sum($classes) > 4) { |
|
475 | + } elseif (array_sum($classes) > 4) { |
|
481 | 476 | $checkClasses = ""; |
482 | 477 | foreach ($classes as $class => $count) { |
483 | 478 | if ($count == 1) { |
484 | 479 | $checkClasses .= sprintf("%s ", $class); |
485 | - } |
|
486 | - else { |
|
480 | + } else { |
|
487 | 481 | $checkClasses .= sprintf("%s(%d) ", $class, $count); |
488 | 482 | } |
489 | 483 | } |
490 | - } |
|
491 | - else { |
|
484 | + } else { |
|
492 | 485 | $checkClasses = implode(" ", array_keys($classes)); |
493 | 486 | } |
494 | 487 | |
@@ -601,8 +594,7 @@ discard block |
||
601 | 594 | if ($this->countHierarchyChange(true)) { |
602 | 595 | throw new StatusException("SyncCollections->CheckForChanges(): HierarchySync required.", self::HIERARCHY_CHANGED); |
603 | 596 | } |
604 | - } |
|
605 | - else { |
|
597 | + } else { |
|
606 | 598 | // the backend will notify on the backend folderid |
607 | 599 | $folderid = GSync::GetDeviceManager()->GetFolderIdForBackendId($backendFolderId); |
608 | 600 | |
@@ -611,8 +603,7 @@ discard block |
||
611 | 603 | SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s'", $folderid)); |
612 | 604 | $validNotifications = true; |
613 | 605 | $this->waitingTime = time() - $started; |
614 | - } |
|
615 | - else { |
|
606 | + } else { |
|
616 | 607 | SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s', but it is not relevant", $folderid)); |
617 | 608 | } |
618 | 609 | } |
@@ -698,8 +689,7 @@ discard block |
||
698 | 689 | $changecount = $exporter->GetChangeCount(); |
699 | 690 | } |
700 | 691 | } |
701 | - } |
|
702 | - catch (StatusException $ste) { |
|
692 | + } catch (StatusException $ste) { |
|
703 | 693 | if ($ste->getCode() == SYNC_STATUS_FOLDERHIERARCHYCHANGED) { |
704 | 694 | SLog::Write(LOGLEVEL_WARN, "SyncCollections->CountChange(): exporter can not be re-configured due to state error, emulating change in folder to force Sync."); |
705 | 695 | $this->changes[$folderid] = 1; |
@@ -767,8 +757,7 @@ discard block |
||
767 | 757 | |
768 | 758 | $this->hierarchyExporterChecked = true; |
769 | 759 | } |
770 | - } |
|
771 | - catch (StatusException $ste) { |
|
760 | + } catch (StatusException $ste) { |
|
772 | 761 | throw new StatusException("SyncCollections->countHierarchyChange(): exporter can not be re-configured.", self::ERROR_WRONG_HIERARCHY, null, LOGLEVEL_WARN); |
773 | 762 | } |
774 | 763 |
@@ -8,259 +8,259 @@ |
||
8 | 8 | */ |
9 | 9 | |
10 | 10 | class ImportChangesStream implements IImportChanges { |
11 | - private $encoder; |
|
12 | - private $objclass; |
|
13 | - private $seenObjects; |
|
14 | - private $importedMsgs; |
|
15 | - private $checkForIgnoredMessages; |
|
16 | - private $classAsString; |
|
17 | - |
|
18 | - /** |
|
19 | - * Constructor of the StreamImporter. |
|
20 | - * |
|
21 | - * @param WBXMLEncoder $encoder Objects are streamed to this encoder |
|
22 | - * @param SyncObject $class SyncObject class (only these are accepted when streaming content messages) |
|
23 | - */ |
|
24 | - public function __construct(&$encoder, $class) { |
|
25 | - $this->encoder = &$encoder; |
|
26 | - $this->objclass = $class; |
|
27 | - $this->classAsString = (is_object($class)) ? get_class($class) : ''; |
|
28 | - $this->seenObjects = []; |
|
29 | - $this->importedMsgs = 0; |
|
30 | - $this->checkForIgnoredMessages = true; |
|
31 | - } |
|
32 | - |
|
33 | - /** |
|
34 | - * Implement interface - never used. |
|
35 | - * |
|
36 | - * @param mixed $state |
|
37 | - * @param mixed $flags |
|
38 | - */ |
|
39 | - public function Config($state, $flags = 0) { |
|
40 | - return true; |
|
41 | - } |
|
42 | - |
|
43 | - public function ConfigContentParameters($contentparameters) { |
|
44 | - return true; |
|
45 | - } |
|
46 | - |
|
47 | - public function GetState() { |
|
48 | - return false; |
|
49 | - } |
|
50 | - |
|
51 | - public function LoadConflicts($contentparameters, $state) { |
|
52 | - return true; |
|
53 | - } |
|
54 | - |
|
55 | - /** |
|
56 | - * Imports a single message. |
|
57 | - * |
|
58 | - * @param string $id |
|
59 | - * @param SyncObject $message |
|
60 | - * |
|
61 | - * @return bool |
|
62 | - */ |
|
63 | - public function ImportMessageChange($id, $message) { |
|
64 | - // ignore other SyncObjects |
|
65 | - if (!($message instanceof $this->classAsString)) { |
|
66 | - return false; |
|
67 | - } |
|
68 | - |
|
69 | - // prevent sending the same object twice in one request |
|
70 | - if (in_array($id, $this->seenObjects)) { |
|
71 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("Object '%s' discarded! Object already sent in this request.", $id)); |
|
72 | - |
|
73 | - return true; |
|
74 | - } |
|
75 | - |
|
76 | - ++$this->importedMsgs; |
|
77 | - $this->seenObjects[] = $id; |
|
78 | - |
|
79 | - // checks if the next message may cause a loop or is broken |
|
80 | - if (GSync::GetDeviceManager()->DoNotStreamMessage($id, $message)) { |
|
81 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesStream->ImportMessageChange('%s'): message ignored and requested to be removed from mobile", $id)); |
|
82 | - |
|
83 | - // this is an internal operation & should not trigger an update in the device manager |
|
84 | - $this->checkForIgnoredMessages = false; |
|
85 | - $stat = $this->ImportMessageDeletion($id); |
|
86 | - $this->checkForIgnoredMessages = true; |
|
87 | - |
|
88 | - return $stat; |
|
89 | - } |
|
90 | - |
|
91 | - if ($message->flags === false || $message->flags === SYNC_NEWMESSAGE) { |
|
92 | - $this->encoder->startTag(SYNC_ADD); |
|
93 | - } |
|
94 | - else { |
|
95 | - // on update of an SyncEmail we only export the flags and categories |
|
96 | - if ($message instanceof SyncMail && ((isset($message->flag) && $message->flag instanceof SyncMailFlags) || isset($message->categories))) { |
|
97 | - $newmessage = new SyncMail(); |
|
98 | - $newmessage->read = $message->read; |
|
99 | - if (isset($message->flag)) { |
|
100 | - $newmessage->flag = $message->flag; |
|
101 | - } |
|
102 | - if (isset($message->lastverbexectime)) { |
|
103 | - $newmessage->lastverbexectime = $message->lastverbexectime; |
|
104 | - } |
|
105 | - if (isset($message->lastverbexecuted)) { |
|
106 | - $newmessage->lastverbexecuted = $message->lastverbexecuted; |
|
107 | - } |
|
108 | - if (isset($message->categories)) { |
|
109 | - $newmessage->categories = $message->categories; |
|
110 | - } |
|
111 | - $message = $newmessage; |
|
112 | - unset($newmessage); |
|
113 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesStream->ImportMessageChange('%s'): SyncMail message updated. Message content is striped, only flags/categories are streamed.", $id)); |
|
114 | - } |
|
115 | - |
|
116 | - $this->encoder->startTag(SYNC_MODIFY); |
|
117 | - } |
|
118 | - |
|
119 | - // TAG: SYNC_ADD / SYNC_MODIFY |
|
120 | - $this->encoder->startTag(SYNC_SERVERENTRYID); |
|
121 | - $this->encoder->content($id); |
|
122 | - $this->encoder->endTag(); |
|
123 | - $this->encoder->startTag(SYNC_DATA); |
|
124 | - $message->Encode($this->encoder); |
|
125 | - $this->encoder->endTag(); |
|
126 | - $this->encoder->endTag(); |
|
127 | - |
|
128 | - return true; |
|
129 | - } |
|
130 | - |
|
131 | - /** |
|
132 | - * Imports a deletion. |
|
133 | - * |
|
134 | - * @param string $id |
|
135 | - * @param bool $asSoftDelete (opt) if true, the deletion is exported as "SoftDelete", else as "Remove" - default: false |
|
136 | - * |
|
137 | - * @return bool |
|
138 | - */ |
|
139 | - public function ImportMessageDeletion($id, $asSoftDelete = false) { |
|
140 | - if ($this->checkForIgnoredMessages) { |
|
141 | - GSync::GetDeviceManager()->RemoveBrokenMessage($id); |
|
142 | - } |
|
143 | - |
|
144 | - ++$this->importedMsgs; |
|
145 | - if ($asSoftDelete) { |
|
146 | - $this->encoder->startTag(SYNC_SOFTDELETE); |
|
147 | - } |
|
148 | - else { |
|
149 | - $this->encoder->startTag(SYNC_REMOVE); |
|
150 | - } |
|
151 | - $this->encoder->startTag(SYNC_SERVERENTRYID); |
|
152 | - $this->encoder->content($id); |
|
153 | - $this->encoder->endTag(); |
|
154 | - $this->encoder->endTag(); |
|
155 | - |
|
156 | - return true; |
|
157 | - } |
|
158 | - |
|
159 | - /** |
|
160 | - * Imports a change in 'read' flag |
|
161 | - * Can only be applied to SyncMail (Email) requests. |
|
162 | - * |
|
163 | - * @param string $id |
|
164 | - * @param int $flags - read/unread |
|
165 | - * @param array $categories |
|
166 | - * |
|
167 | - * @return bool |
|
168 | - */ |
|
169 | - public function ImportMessageReadFlag($id, $flags, $categories = []) { |
|
170 | - if (!($this->objclass instanceof SyncMail)) { |
|
171 | - return false; |
|
172 | - } |
|
173 | - |
|
174 | - ++$this->importedMsgs; |
|
175 | - |
|
176 | - $this->encoder->startTag(SYNC_MODIFY); |
|
177 | - $this->encoder->startTag(SYNC_SERVERENTRYID); |
|
178 | - $this->encoder->content($id); |
|
179 | - $this->encoder->endTag(); |
|
180 | - $this->encoder->startTag(SYNC_DATA); |
|
181 | - $this->encoder->startTag(SYNC_POOMMAIL_READ); |
|
182 | - $this->encoder->content($flags); |
|
183 | - $this->encoder->endTag(); |
|
184 | - if (!empty($categories) && is_array($categories)) { |
|
185 | - $this->encoder->startTag(SYNC_POOMMAIL_CATEGORIES); |
|
186 | - foreach ($categories as $category) { |
|
187 | - $this->encoder->startTag(SYNC_POOMMAIL_CATEGORY); |
|
188 | - $this->encoder->content($category); |
|
189 | - $this->encoder->endTag(); |
|
190 | - } |
|
191 | - $this->encoder->endTag(); |
|
192 | - } |
|
193 | - $this->encoder->endTag(); |
|
194 | - $this->encoder->endTag(); |
|
195 | - |
|
196 | - return true; |
|
197 | - } |
|
198 | - |
|
199 | - /** |
|
200 | - * ImportMessageMove is not implemented, as this operation can not be streamed to a WBXMLEncoder. |
|
201 | - * |
|
202 | - * @param string $id |
|
203 | - * @param int $flags read/unread |
|
204 | - * @param mixed $newfolder |
|
205 | - * |
|
206 | - * @return bool |
|
207 | - */ |
|
208 | - public function ImportMessageMove($id, $newfolder) { |
|
209 | - return true; |
|
210 | - } |
|
211 | - |
|
212 | - /** |
|
213 | - * Imports a change on a folder. |
|
214 | - * |
|
215 | - * @param object $folder SyncFolder |
|
216 | - * |
|
217 | - * @return boolean/SyncObject status/object with the ath least the serverid of the folder set |
|
218 | - */ |
|
219 | - public function ImportFolderChange($folder) { |
|
220 | - // checks if the next message may cause a loop or is broken |
|
221 | - if (GSync::GetDeviceManager(false) && GSync::GetDeviceManager()->DoNotStreamMessage($folder->serverid, $folder)) { |
|
222 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesStream->ImportFolderChange('%s'): folder ignored as requested by DeviceManager.", $folder->serverid)); |
|
223 | - |
|
224 | - return true; |
|
225 | - } |
|
226 | - |
|
227 | - // send a modify flag if the folder is already known on the device |
|
228 | - if (isset($folder->flags) && $folder->flags === SYNC_NEWMESSAGE) { |
|
229 | - $this->encoder->startTag(SYNC_FOLDERHIERARCHY_ADD); |
|
230 | - } |
|
231 | - else { |
|
232 | - $this->encoder->startTag(SYNC_FOLDERHIERARCHY_UPDATE); |
|
233 | - } |
|
234 | - |
|
235 | - $folder->Encode($this->encoder); |
|
236 | - $this->encoder->endTag(); |
|
237 | - |
|
238 | - return true; |
|
239 | - } |
|
240 | - |
|
241 | - /** |
|
242 | - * Imports a folder deletion. |
|
243 | - * |
|
244 | - * @param SyncFolder $folder at least "serverid" needs to be set |
|
245 | - * |
|
246 | - * @return bool |
|
247 | - */ |
|
248 | - public function ImportFolderDeletion($folder) { |
|
249 | - $this->encoder->startTag(SYNC_FOLDERHIERARCHY_REMOVE); |
|
250 | - $this->encoder->startTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID); |
|
251 | - $this->encoder->content($folder->serverid); |
|
252 | - $this->encoder->endTag(); |
|
253 | - $this->encoder->endTag(); |
|
254 | - |
|
255 | - return true; |
|
256 | - } |
|
257 | - |
|
258 | - /** |
|
259 | - * Returns the number of messages which were changed, deleted and had changed read status. |
|
260 | - * |
|
261 | - * @return int |
|
262 | - */ |
|
263 | - public function GetImportedMessages() { |
|
264 | - return $this->importedMsgs; |
|
265 | - } |
|
11 | + private $encoder; |
|
12 | + private $objclass; |
|
13 | + private $seenObjects; |
|
14 | + private $importedMsgs; |
|
15 | + private $checkForIgnoredMessages; |
|
16 | + private $classAsString; |
|
17 | + |
|
18 | + /** |
|
19 | + * Constructor of the StreamImporter. |
|
20 | + * |
|
21 | + * @param WBXMLEncoder $encoder Objects are streamed to this encoder |
|
22 | + * @param SyncObject $class SyncObject class (only these are accepted when streaming content messages) |
|
23 | + */ |
|
24 | + public function __construct(&$encoder, $class) { |
|
25 | + $this->encoder = &$encoder; |
|
26 | + $this->objclass = $class; |
|
27 | + $this->classAsString = (is_object($class)) ? get_class($class) : ''; |
|
28 | + $this->seenObjects = []; |
|
29 | + $this->importedMsgs = 0; |
|
30 | + $this->checkForIgnoredMessages = true; |
|
31 | + } |
|
32 | + |
|
33 | + /** |
|
34 | + * Implement interface - never used. |
|
35 | + * |
|
36 | + * @param mixed $state |
|
37 | + * @param mixed $flags |
|
38 | + */ |
|
39 | + public function Config($state, $flags = 0) { |
|
40 | + return true; |
|
41 | + } |
|
42 | + |
|
43 | + public function ConfigContentParameters($contentparameters) { |
|
44 | + return true; |
|
45 | + } |
|
46 | + |
|
47 | + public function GetState() { |
|
48 | + return false; |
|
49 | + } |
|
50 | + |
|
51 | + public function LoadConflicts($contentparameters, $state) { |
|
52 | + return true; |
|
53 | + } |
|
54 | + |
|
55 | + /** |
|
56 | + * Imports a single message. |
|
57 | + * |
|
58 | + * @param string $id |
|
59 | + * @param SyncObject $message |
|
60 | + * |
|
61 | + * @return bool |
|
62 | + */ |
|
63 | + public function ImportMessageChange($id, $message) { |
|
64 | + // ignore other SyncObjects |
|
65 | + if (!($message instanceof $this->classAsString)) { |
|
66 | + return false; |
|
67 | + } |
|
68 | + |
|
69 | + // prevent sending the same object twice in one request |
|
70 | + if (in_array($id, $this->seenObjects)) { |
|
71 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("Object '%s' discarded! Object already sent in this request.", $id)); |
|
72 | + |
|
73 | + return true; |
|
74 | + } |
|
75 | + |
|
76 | + ++$this->importedMsgs; |
|
77 | + $this->seenObjects[] = $id; |
|
78 | + |
|
79 | + // checks if the next message may cause a loop or is broken |
|
80 | + if (GSync::GetDeviceManager()->DoNotStreamMessage($id, $message)) { |
|
81 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesStream->ImportMessageChange('%s'): message ignored and requested to be removed from mobile", $id)); |
|
82 | + |
|
83 | + // this is an internal operation & should not trigger an update in the device manager |
|
84 | + $this->checkForIgnoredMessages = false; |
|
85 | + $stat = $this->ImportMessageDeletion($id); |
|
86 | + $this->checkForIgnoredMessages = true; |
|
87 | + |
|
88 | + return $stat; |
|
89 | + } |
|
90 | + |
|
91 | + if ($message->flags === false || $message->flags === SYNC_NEWMESSAGE) { |
|
92 | + $this->encoder->startTag(SYNC_ADD); |
|
93 | + } |
|
94 | + else { |
|
95 | + // on update of an SyncEmail we only export the flags and categories |
|
96 | + if ($message instanceof SyncMail && ((isset($message->flag) && $message->flag instanceof SyncMailFlags) || isset($message->categories))) { |
|
97 | + $newmessage = new SyncMail(); |
|
98 | + $newmessage->read = $message->read; |
|
99 | + if (isset($message->flag)) { |
|
100 | + $newmessage->flag = $message->flag; |
|
101 | + } |
|
102 | + if (isset($message->lastverbexectime)) { |
|
103 | + $newmessage->lastverbexectime = $message->lastverbexectime; |
|
104 | + } |
|
105 | + if (isset($message->lastverbexecuted)) { |
|
106 | + $newmessage->lastverbexecuted = $message->lastverbexecuted; |
|
107 | + } |
|
108 | + if (isset($message->categories)) { |
|
109 | + $newmessage->categories = $message->categories; |
|
110 | + } |
|
111 | + $message = $newmessage; |
|
112 | + unset($newmessage); |
|
113 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesStream->ImportMessageChange('%s'): SyncMail message updated. Message content is striped, only flags/categories are streamed.", $id)); |
|
114 | + } |
|
115 | + |
|
116 | + $this->encoder->startTag(SYNC_MODIFY); |
|
117 | + } |
|
118 | + |
|
119 | + // TAG: SYNC_ADD / SYNC_MODIFY |
|
120 | + $this->encoder->startTag(SYNC_SERVERENTRYID); |
|
121 | + $this->encoder->content($id); |
|
122 | + $this->encoder->endTag(); |
|
123 | + $this->encoder->startTag(SYNC_DATA); |
|
124 | + $message->Encode($this->encoder); |
|
125 | + $this->encoder->endTag(); |
|
126 | + $this->encoder->endTag(); |
|
127 | + |
|
128 | + return true; |
|
129 | + } |
|
130 | + |
|
131 | + /** |
|
132 | + * Imports a deletion. |
|
133 | + * |
|
134 | + * @param string $id |
|
135 | + * @param bool $asSoftDelete (opt) if true, the deletion is exported as "SoftDelete", else as "Remove" - default: false |
|
136 | + * |
|
137 | + * @return bool |
|
138 | + */ |
|
139 | + public function ImportMessageDeletion($id, $asSoftDelete = false) { |
|
140 | + if ($this->checkForIgnoredMessages) { |
|
141 | + GSync::GetDeviceManager()->RemoveBrokenMessage($id); |
|
142 | + } |
|
143 | + |
|
144 | + ++$this->importedMsgs; |
|
145 | + if ($asSoftDelete) { |
|
146 | + $this->encoder->startTag(SYNC_SOFTDELETE); |
|
147 | + } |
|
148 | + else { |
|
149 | + $this->encoder->startTag(SYNC_REMOVE); |
|
150 | + } |
|
151 | + $this->encoder->startTag(SYNC_SERVERENTRYID); |
|
152 | + $this->encoder->content($id); |
|
153 | + $this->encoder->endTag(); |
|
154 | + $this->encoder->endTag(); |
|
155 | + |
|
156 | + return true; |
|
157 | + } |
|
158 | + |
|
159 | + /** |
|
160 | + * Imports a change in 'read' flag |
|
161 | + * Can only be applied to SyncMail (Email) requests. |
|
162 | + * |
|
163 | + * @param string $id |
|
164 | + * @param int $flags - read/unread |
|
165 | + * @param array $categories |
|
166 | + * |
|
167 | + * @return bool |
|
168 | + */ |
|
169 | + public function ImportMessageReadFlag($id, $flags, $categories = []) { |
|
170 | + if (!($this->objclass instanceof SyncMail)) { |
|
171 | + return false; |
|
172 | + } |
|
173 | + |
|
174 | + ++$this->importedMsgs; |
|
175 | + |
|
176 | + $this->encoder->startTag(SYNC_MODIFY); |
|
177 | + $this->encoder->startTag(SYNC_SERVERENTRYID); |
|
178 | + $this->encoder->content($id); |
|
179 | + $this->encoder->endTag(); |
|
180 | + $this->encoder->startTag(SYNC_DATA); |
|
181 | + $this->encoder->startTag(SYNC_POOMMAIL_READ); |
|
182 | + $this->encoder->content($flags); |
|
183 | + $this->encoder->endTag(); |
|
184 | + if (!empty($categories) && is_array($categories)) { |
|
185 | + $this->encoder->startTag(SYNC_POOMMAIL_CATEGORIES); |
|
186 | + foreach ($categories as $category) { |
|
187 | + $this->encoder->startTag(SYNC_POOMMAIL_CATEGORY); |
|
188 | + $this->encoder->content($category); |
|
189 | + $this->encoder->endTag(); |
|
190 | + } |
|
191 | + $this->encoder->endTag(); |
|
192 | + } |
|
193 | + $this->encoder->endTag(); |
|
194 | + $this->encoder->endTag(); |
|
195 | + |
|
196 | + return true; |
|
197 | + } |
|
198 | + |
|
199 | + /** |
|
200 | + * ImportMessageMove is not implemented, as this operation can not be streamed to a WBXMLEncoder. |
|
201 | + * |
|
202 | + * @param string $id |
|
203 | + * @param int $flags read/unread |
|
204 | + * @param mixed $newfolder |
|
205 | + * |
|
206 | + * @return bool |
|
207 | + */ |
|
208 | + public function ImportMessageMove($id, $newfolder) { |
|
209 | + return true; |
|
210 | + } |
|
211 | + |
|
212 | + /** |
|
213 | + * Imports a change on a folder. |
|
214 | + * |
|
215 | + * @param object $folder SyncFolder |
|
216 | + * |
|
217 | + * @return boolean/SyncObject status/object with the ath least the serverid of the folder set |
|
218 | + */ |
|
219 | + public function ImportFolderChange($folder) { |
|
220 | + // checks if the next message may cause a loop or is broken |
|
221 | + if (GSync::GetDeviceManager(false) && GSync::GetDeviceManager()->DoNotStreamMessage($folder->serverid, $folder)) { |
|
222 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesStream->ImportFolderChange('%s'): folder ignored as requested by DeviceManager.", $folder->serverid)); |
|
223 | + |
|
224 | + return true; |
|
225 | + } |
|
226 | + |
|
227 | + // send a modify flag if the folder is already known on the device |
|
228 | + if (isset($folder->flags) && $folder->flags === SYNC_NEWMESSAGE) { |
|
229 | + $this->encoder->startTag(SYNC_FOLDERHIERARCHY_ADD); |
|
230 | + } |
|
231 | + else { |
|
232 | + $this->encoder->startTag(SYNC_FOLDERHIERARCHY_UPDATE); |
|
233 | + } |
|
234 | + |
|
235 | + $folder->Encode($this->encoder); |
|
236 | + $this->encoder->endTag(); |
|
237 | + |
|
238 | + return true; |
|
239 | + } |
|
240 | + |
|
241 | + /** |
|
242 | + * Imports a folder deletion. |
|
243 | + * |
|
244 | + * @param SyncFolder $folder at least "serverid" needs to be set |
|
245 | + * |
|
246 | + * @return bool |
|
247 | + */ |
|
248 | + public function ImportFolderDeletion($folder) { |
|
249 | + $this->encoder->startTag(SYNC_FOLDERHIERARCHY_REMOVE); |
|
250 | + $this->encoder->startTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID); |
|
251 | + $this->encoder->content($folder->serverid); |
|
252 | + $this->encoder->endTag(); |
|
253 | + $this->encoder->endTag(); |
|
254 | + |
|
255 | + return true; |
|
256 | + } |
|
257 | + |
|
258 | + /** |
|
259 | + * Returns the number of messages which were changed, deleted and had changed read status. |
|
260 | + * |
|
261 | + * @return int |
|
262 | + */ |
|
263 | + public function GetImportedMessages() { |
|
264 | + return $this->importedMsgs; |
|
265 | + } |
|
266 | 266 | } |
@@ -90,8 +90,7 @@ discard block |
||
90 | 90 | |
91 | 91 | if ($message->flags === false || $message->flags === SYNC_NEWMESSAGE) { |
92 | 92 | $this->encoder->startTag(SYNC_ADD); |
93 | - } |
|
94 | - else { |
|
93 | + } else { |
|
95 | 94 | // on update of an SyncEmail we only export the flags and categories |
96 | 95 | if ($message instanceof SyncMail && ((isset($message->flag) && $message->flag instanceof SyncMailFlags) || isset($message->categories))) { |
97 | 96 | $newmessage = new SyncMail(); |
@@ -144,8 +143,7 @@ discard block |
||
144 | 143 | ++$this->importedMsgs; |
145 | 144 | if ($asSoftDelete) { |
146 | 145 | $this->encoder->startTag(SYNC_SOFTDELETE); |
147 | - } |
|
148 | - else { |
|
146 | + } else { |
|
149 | 147 | $this->encoder->startTag(SYNC_REMOVE); |
150 | 148 | } |
151 | 149 | $this->encoder->startTag(SYNC_SERVERENTRYID); |
@@ -227,8 +225,7 @@ discard block |
||
227 | 225 | // send a modify flag if the folder is already known on the device |
228 | 226 | if (isset($folder->flags) && $folder->flags === SYNC_NEWMESSAGE) { |
229 | 227 | $this->encoder->startTag(SYNC_FOLDERHIERARCHY_ADD); |
230 | - } |
|
231 | - else { |
|
228 | + } else { |
|
232 | 229 | $this->encoder->startTag(SYNC_FOLDERHIERARCHY_UPDATE); |
233 | 230 | } |
234 | 231 |
@@ -10,495 +10,495 @@ |
||
10 | 10 | */ |
11 | 11 | |
12 | 12 | class Streamer implements JsonSerializable { |
13 | - public const STREAMER_VAR = 1; |
|
14 | - public const STREAMER_ARRAY = 2; |
|
15 | - public const STREAMER_TYPE = 3; |
|
16 | - public const STREAMER_PROP = 4; |
|
17 | - public const STREAMER_RONOTIFY = 5; |
|
18 | - public const STREAMER_VALUEMAP = 20; |
|
19 | - public const STREAMER_TYPE_DATE = 1; |
|
20 | - public const STREAMER_TYPE_HEX = 2; |
|
21 | - public const STREAMER_TYPE_DATE_DASHES = 3; |
|
22 | - public const STREAMER_TYPE_STREAM = 4; // deprecated |
|
23 | - public const STREAMER_TYPE_IGNORE = 5; |
|
24 | - public const STREAMER_TYPE_SEND_EMPTY = 6; |
|
25 | - public const STREAMER_TYPE_NO_CONTAINER = 7; |
|
26 | - public const STREAMER_TYPE_COMMA_SEPARATED = 8; |
|
27 | - public const STREAMER_TYPE_SEMICOLON_SEPARATED = 9; |
|
28 | - public const STREAMER_TYPE_MULTIPART = 10; |
|
29 | - public const STREAMER_TYPE_STREAM_ASBASE64 = 11; |
|
30 | - public const STREAMER_TYPE_STREAM_ASPLAIN = 12; |
|
31 | - public const STREAMER_PRIVATE = 13; |
|
32 | - public const STRIP_PRIVATE_DATA = 1; |
|
33 | - public const STRIP_PRIVATE_SUBSTITUTE = 'Private'; |
|
34 | - |
|
35 | - protected $mapping; |
|
36 | - public $flags; |
|
37 | - public $content; |
|
38 | - |
|
39 | - /** |
|
40 | - * Constructor. |
|
41 | - * |
|
42 | - * @param array $mapping internal mapping of variables |
|
43 | - */ |
|
44 | - public function __construct($mapping) { |
|
45 | - $this->mapping = $mapping; |
|
46 | - $this->flags = false; |
|
47 | - } |
|
48 | - |
|
49 | - /** |
|
50 | - * Return the streamer mapping for this object. |
|
51 | - */ |
|
52 | - public function GetMapping() { |
|
53 | - return $this->mapping; |
|
54 | - } |
|
55 | - |
|
56 | - /** |
|
57 | - * Decodes the WBXML from a WBXMLdecoder until we reach the same depth level of WBXML. |
|
58 | - * This means that if there are multiple objects at this level, then only the first is |
|
59 | - * decoded SubOjects are auto-instantiated and decoded using the same functionality. |
|
60 | - * |
|
61 | - * @param WBXMLDecoder $decoder |
|
62 | - */ |
|
63 | - public function Decode(&$decoder) { |
|
64 | - WBXMLDecoder::ResetInWhile("decodeMain"); |
|
65 | - while (WBXMLDecoder::InWhile("decodeMain")) { |
|
66 | - $entity = $decoder->getElement(); |
|
67 | - |
|
68 | - if ($entity[EN_TYPE] == EN_TYPE_STARTTAG) { |
|
69 | - if (!($entity[EN_FLAGS] & EN_FLAGS_CONTENT)) { |
|
70 | - $map = $this->mapping[$entity[EN_TAG]]; |
|
71 | - if (isset($map[self::STREAMER_ARRAY])) { |
|
72 | - $this->{$map[self::STREAMER_VAR]} = []; |
|
73 | - } |
|
74 | - elseif (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) { |
|
75 | - $this->{$map[self::STREAMER_VAR]} = "1"; |
|
76 | - } |
|
77 | - elseif (!isset($map[self::STREAMER_TYPE])) { |
|
78 | - $this->{$map[self::STREAMER_VAR]} = ""; |
|
79 | - } |
|
80 | - elseif ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES) { |
|
81 | - $this->{$map[self::STREAMER_VAR]} = ""; |
|
82 | - } |
|
83 | - |
|
84 | - continue; |
|
85 | - } |
|
86 | - // Found a start tag |
|
87 | - if (!isset($this->mapping[$entity[EN_TAG]])) { |
|
88 | - // This tag shouldn't be here, abort |
|
89 | - SLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("Tag '%s' unexpected in type XML type '%s'", $entity[EN_TAG], get_class($this))); |
|
90 | - |
|
91 | - return false; |
|
92 | - } |
|
93 | - |
|
94 | - $map = $this->mapping[$entity[EN_TAG]]; |
|
95 | - |
|
96 | - // Handle an array |
|
97 | - if (isset($map[self::STREAMER_ARRAY])) { |
|
98 | - WBXMLDecoder::ResetInWhile("decodeArray"); |
|
99 | - while (WBXMLDecoder::InWhile("decodeArray")) { |
|
100 | - // do not get start tag for an array without a container |
|
101 | - if (!(isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER)) { |
|
102 | - if (!$decoder->getElementStartTag($map[self::STREAMER_ARRAY])) { |
|
103 | - break; |
|
104 | - } |
|
105 | - } |
|
106 | - if (isset($map[self::STREAMER_TYPE])) { |
|
107 | - $decoded = new $map[self::STREAMER_TYPE](); |
|
108 | - |
|
109 | - $decoded->Decode($decoder); |
|
110 | - } |
|
111 | - else { |
|
112 | - $decoded = $decoder->getElementContent(); |
|
113 | - } |
|
114 | - |
|
115 | - if (!isset($this->{$map[self::STREAMER_VAR]})) { |
|
116 | - $this->{$map[self::STREAMER_VAR]} = [$decoded]; |
|
117 | - } |
|
118 | - else { |
|
119 | - array_push($this->{$map[self::STREAMER_VAR]}, $decoded); |
|
120 | - } |
|
121 | - |
|
122 | - if (!$decoder->getElementEndTag()) { // end tag of a container element |
|
123 | - return false; |
|
124 | - } |
|
125 | - |
|
126 | - if (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER) { |
|
127 | - $e = $decoder->peek(); |
|
128 | - // go back to the initial while if another block of no container elements is found |
|
129 | - if ($e[EN_TYPE] == EN_TYPE_STARTTAG) { |
|
130 | - continue 2; |
|
131 | - } |
|
132 | - // break on end tag because no container elements block end is reached |
|
133 | - if ($e[EN_TYPE] == EN_TYPE_ENDTAG) { |
|
134 | - break; |
|
135 | - } |
|
136 | - if (empty($e)) { |
|
137 | - break; |
|
138 | - } |
|
139 | - } |
|
140 | - } |
|
141 | - // do not get end tag for an array without a container |
|
142 | - if (!(isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER)) { |
|
143 | - if (!$decoder->getElementEndTag()) { // end tag of container |
|
144 | - return false; |
|
145 | - } |
|
146 | - } |
|
147 | - } |
|
148 | - else { // Handle single value |
|
149 | - if (isset($map[self::STREAMER_TYPE])) { |
|
150 | - // Complex type, decode recursively |
|
151 | - if ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES) { |
|
152 | - $decoded = $this->parseDate($decoder->getElementContent()); |
|
153 | - if (!$decoder->getElementEndTag()) { |
|
154 | - return false; |
|
155 | - } |
|
156 | - } |
|
157 | - elseif ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) { |
|
158 | - $decoded = hex2bin($decoder->getElementContent()); |
|
159 | - if (!$decoder->getElementEndTag()) { |
|
160 | - return false; |
|
161 | - } |
|
162 | - } |
|
163 | - // explode comma or semicolon strings into arrays |
|
164 | - elseif ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_SEMICOLON_SEPARATED) { |
|
165 | - $glue = ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED) ? ", " : "; "; |
|
166 | - $decoded = explode($glue, $decoder->getElementContent()); |
|
167 | - if (!$decoder->getElementEndTag()) { |
|
168 | - return false; |
|
169 | - } |
|
170 | - } |
|
171 | - elseif ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM_ASPLAIN) { |
|
172 | - $decoded = StringStreamWrapper::Open($decoder->getElementContent()); |
|
173 | - if (!$decoder->getElementEndTag()) { |
|
174 | - return false; |
|
175 | - } |
|
176 | - } |
|
177 | - else { |
|
178 | - $subdecoder = new $map[self::STREAMER_TYPE](); |
|
179 | - if ($subdecoder->Decode($decoder) === false) { |
|
180 | - return false; |
|
181 | - } |
|
182 | - |
|
183 | - $decoded = $subdecoder; |
|
184 | - |
|
185 | - if (!$decoder->getElementEndTag()) { |
|
186 | - SLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("No end tag for '%s'", $entity[EN_TAG])); |
|
187 | - |
|
188 | - return false; |
|
189 | - } |
|
190 | - } |
|
191 | - } |
|
192 | - else { |
|
193 | - // Simple type, just get content |
|
194 | - $decoded = $decoder->getElementContent(); |
|
195 | - |
|
196 | - if ($decoded === false) { |
|
197 | - // the tag is declared to have content, but no content is available. |
|
198 | - // set an empty content |
|
199 | - $decoded = ""; |
|
200 | - } |
|
201 | - |
|
202 | - if (!$decoder->getElementEndTag()) { |
|
203 | - SLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("Unable to get end tag for '%s'", $entity[EN_TAG])); |
|
204 | - |
|
205 | - return false; |
|
206 | - } |
|
207 | - } |
|
208 | - // $decoded now contains data object (or string) |
|
209 | - $this->{$map[self::STREAMER_VAR]} = $decoded; |
|
210 | - } |
|
211 | - } |
|
212 | - elseif ($entity[EN_TYPE] == EN_TYPE_ENDTAG) { |
|
213 | - $decoder->ungetElement($entity); |
|
214 | - |
|
215 | - break; |
|
216 | - } |
|
217 | - else { |
|
218 | - SLog::Write(LOGLEVEL_WBXMLSTACK, "Unexpected content in type"); |
|
219 | - |
|
220 | - break; |
|
221 | - } |
|
222 | - } |
|
223 | - } |
|
224 | - |
|
225 | - /** |
|
226 | - * Encodes this object and any subobjects - output is ordered according to mapping. |
|
227 | - * |
|
228 | - * @param WBXMLEncoder $encoder |
|
229 | - */ |
|
230 | - public function Encode(&$encoder) { |
|
231 | - // A return value if anything was streamed. We need for empty tags. |
|
232 | - $streamed = false; |
|
233 | - foreach ($this->mapping as $tag => $map) { |
|
234 | - if (isset($this->{$map[self::STREAMER_VAR]})) { |
|
235 | - // Variable is available |
|
236 | - if (is_object($this->{$map[self::STREAMER_VAR]})) { |
|
237 | - // Subobjects can do their own encoding |
|
238 | - if ($this->{$map[self::STREAMER_VAR]} instanceof Streamer) { |
|
239 | - $encoder->startTag($tag); |
|
240 | - $res = $this->{$map[self::STREAMER_VAR]}->Encode($encoder); |
|
241 | - $encoder->endTag(); |
|
242 | - // nothing was streamed in previous encode but it should be streamed empty anyway |
|
243 | - if (!$res && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) { |
|
244 | - $encoder->startTag($tag, false, true); |
|
245 | - } |
|
246 | - } |
|
247 | - else { |
|
248 | - SLog::Write(LOGLEVEL_ERROR, sprintf("Streamer->Encode(): parameter '%s' of object %s is not of type Streamer", $map[self::STREAMER_VAR], get_class($this))); |
|
249 | - } |
|
250 | - } |
|
251 | - // Array of objects |
|
252 | - elseif (isset($map[self::STREAMER_ARRAY])) { |
|
253 | - if (empty($this->{$map[self::STREAMER_VAR]}) && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) { |
|
254 | - $encoder->startTag($tag, false, true); |
|
255 | - } |
|
256 | - else { |
|
257 | - // Outputs array container (eg Attachments) |
|
258 | - // Do not output start and end tag when type is STREAMER_TYPE_NO_CONTAINER |
|
259 | - if (!isset($map[self::STREAMER_PROP]) || $map[self::STREAMER_PROP] != self::STREAMER_TYPE_NO_CONTAINER) { |
|
260 | - $encoder->startTag($tag); |
|
261 | - } |
|
262 | - |
|
263 | - foreach ($this->{$map[self::STREAMER_VAR]} as $element) { |
|
264 | - if (is_object($element)) { |
|
265 | - $encoder->startTag($map[self::STREAMER_ARRAY]); // Outputs object container (eg Attachment) |
|
266 | - $element->Encode($encoder); |
|
267 | - $encoder->endTag(); |
|
268 | - } |
|
269 | - else { |
|
270 | - if (strlen($element) == 0) |
|
271 | - // Do not output empty items. Not sure if we should output an empty tag with $encoder->startTag($map[self::STREAMER_ARRAY], false, true); |
|
272 | - ; else { |
|
273 | - $encoder->startTag($map[self::STREAMER_ARRAY]); |
|
274 | - $encoder->content($element); |
|
275 | - $encoder->endTag(); |
|
276 | - $streamed = true; |
|
277 | - } |
|
278 | - } |
|
279 | - } |
|
280 | - |
|
281 | - if (!isset($map[self::STREAMER_PROP]) || $map[self::STREAMER_PROP] != self::STREAMER_TYPE_NO_CONTAINER) { |
|
282 | - $encoder->endTag(); |
|
283 | - } |
|
284 | - } |
|
285 | - } |
|
286 | - else { |
|
287 | - if (isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_IGNORE) { |
|
288 | - continue; |
|
289 | - } |
|
290 | - |
|
291 | - if ($encoder->getMultipart() && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_MULTIPART) { |
|
292 | - $encoder->addBodypartStream($this->{$map[self::STREAMER_VAR]}); |
|
293 | - $encoder->startTag(SYNC_ITEMOPERATIONS_PART); |
|
294 | - $encoder->content($encoder->getBodypartsCount()); |
|
295 | - $encoder->endTag(); |
|
296 | - |
|
297 | - continue; |
|
298 | - } |
|
299 | - |
|
300 | - // Simple type |
|
301 | - if (!isset($map[self::STREAMER_TYPE]) && strlen($this->{$map[self::STREAMER_VAR]}) == 0) { |
|
302 | - // send empty tags |
|
303 | - if (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) { |
|
304 | - $encoder->startTag($tag, false, true); |
|
305 | - } |
|
306 | - |
|
307 | - // Do not output empty items. See above: $encoder->startTag($tag, false, true); |
|
308 | - continue; |
|
309 | - } |
|
310 | - $encoder->startTag($tag); |
|
311 | - |
|
312 | - if (isset($map[self::STREAMER_TYPE]) && ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES)) { |
|
313 | - if ($this->{$map[self::STREAMER_VAR]} != 0) { // don't output 1-1-1970 |
|
314 | - $encoder->content($this->formatDate($this->{$map[self::STREAMER_VAR]}, $map[self::STREAMER_TYPE])); |
|
315 | - } |
|
316 | - } |
|
317 | - elseif (isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) { |
|
318 | - $encoder->content(strtoupper(bin2hex($this->{$map[self::STREAMER_VAR]}))); |
|
319 | - } |
|
320 | - elseif (isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM_ASPLAIN) { |
|
321 | - $encoder->contentStream($this->{$map[self::STREAMER_VAR]}, false); |
|
322 | - } |
|
323 | - elseif (isset($map[self::STREAMER_TYPE]) && ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM_ASBASE64 || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM)) { |
|
324 | - $encoder->contentStream($this->{$map[self::STREAMER_VAR]}, true); |
|
325 | - } |
|
326 | - // implode comma or semicolon arrays into a string |
|
327 | - elseif (isset($map[self::STREAMER_TYPE]) && is_array($this->{$map[self::STREAMER_VAR]}) && |
|
328 | - ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_SEMICOLON_SEPARATED)) { |
|
329 | - $glue = ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED) ? ", " : "; "; |
|
330 | - $encoder->content(implode($glue, $this->{$map[self::STREAMER_VAR]})); |
|
331 | - } |
|
332 | - else { |
|
333 | - $encoder->content($this->{$map[self::STREAMER_VAR]}); |
|
334 | - } |
|
335 | - $encoder->endTag(); |
|
336 | - $streamed = true; |
|
337 | - } |
|
338 | - } |
|
339 | - } |
|
340 | - // Output our own content |
|
341 | - if (isset($this->content)) { |
|
342 | - $encoder->content($this->content); |
|
343 | - } |
|
344 | - |
|
345 | - return $streamed; |
|
346 | - } |
|
347 | - |
|
348 | - /** |
|
349 | - * Removes not necessary data from the object. |
|
350 | - * |
|
351 | - * @param mixed $flags |
|
352 | - * |
|
353 | - * @return bool |
|
354 | - */ |
|
355 | - public function StripData($flags = 0) { |
|
356 | - foreach ($this->mapping as $k => $v) { |
|
357 | - if (isset($this->{$v[self::STREAMER_VAR]})) { |
|
358 | - if (is_object($this->{$v[self::STREAMER_VAR]}) && method_exists($this->{$v[self::STREAMER_VAR]}, "StripData")) { |
|
359 | - $this->{$v[self::STREAMER_VAR]}->StripData($flags); |
|
360 | - } |
|
361 | - elseif (isset($v[self::STREAMER_ARRAY]) && !empty($this->{$v[self::STREAMER_VAR]})) { |
|
362 | - foreach ($this->{$v[self::STREAMER_VAR]} as $element) { |
|
363 | - if (is_object($element) && method_exists($element, "StripData")) { |
|
364 | - $element->StripData($flags); |
|
365 | - } |
|
366 | - elseif ($flags === Streamer::STRIP_PRIVATE_DATA && isset($v[self::STREAMER_PRIVATE])) { |
|
367 | - if ($v[self::STREAMER_PRIVATE] !== true) { |
|
368 | - $this->{$v[self::STREAMER_VAR]} = $v[self::STREAMER_PRIVATE]; |
|
369 | - } |
|
370 | - else { |
|
371 | - unset($this->{$v[self::STREAMER_VAR]}); |
|
372 | - } |
|
373 | - } |
|
374 | - } |
|
375 | - } |
|
376 | - elseif ($flags === Streamer::STRIP_PRIVATE_DATA && isset($v[self::STREAMER_PRIVATE])) { |
|
377 | - if ($v[self::STREAMER_PRIVATE] !== true) { |
|
378 | - $this->{$v[self::STREAMER_VAR]} = $v[self::STREAMER_PRIVATE]; |
|
379 | - } |
|
380 | - else { |
|
381 | - unset($this->{$v[self::STREAMER_VAR]}); |
|
382 | - } |
|
383 | - } |
|
384 | - } |
|
385 | - } |
|
386 | - if ($flags === 0) { |
|
387 | - unset($this->mapping); |
|
388 | - } |
|
389 | - |
|
390 | - return true; |
|
391 | - } |
|
392 | - |
|
393 | - /** |
|
394 | - * Returns SyncObject's streamer variable names. |
|
395 | - * |
|
396 | - * @return array |
|
397 | - */ |
|
398 | - public function GetStreamerVars() { |
|
399 | - $streamerVars = []; |
|
400 | - foreach ($this->mapping as $v) { |
|
401 | - $streamerVars[] = $v[self::STREAMER_VAR]; |
|
402 | - } |
|
403 | - |
|
404 | - return $streamerVars; |
|
405 | - } |
|
406 | - |
|
407 | - /** |
|
408 | - * JsonSerializable interface method. |
|
409 | - * |
|
410 | - * Serializes the object to a value that can be serialized natively by json_encode() |
|
411 | - * |
|
412 | - * @return array |
|
413 | - */ |
|
414 | - public function jsonSerialize() { |
|
415 | - $data = []; |
|
416 | - foreach ($this->mapping as $k => $v) { |
|
417 | - if (isset($this->{$v[self::STREAMER_VAR]})) { |
|
418 | - $data[$v[self::STREAMER_VAR]] = $this->{$v[self::STREAMER_VAR]}; |
|
419 | - } |
|
420 | - } |
|
421 | - |
|
422 | - return [ |
|
423 | - 'gsSyncStateClass' => get_class($this), |
|
424 | - 'data' => $data, |
|
425 | - ]; |
|
426 | - } |
|
427 | - |
|
428 | - /** |
|
429 | - * Restores the object from a value provided by json_decode. |
|
430 | - * |
|
431 | - * @param $stdObj stdClass Object |
|
432 | - */ |
|
433 | - public function jsonDeserialize($stdObj) { |
|
434 | - foreach ($stdObj->data as $k => $v) { |
|
435 | - if (is_object($v) && isset($v->gsSyncStateClass)) { |
|
436 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("Streamer->jsonDeserialize(): top class '%s'", $v->gsSyncStateClass)); |
|
437 | - $this->{$k} = new $v->gsSyncStateClass(); |
|
438 | - $this->{$k}->jsonDeserialize($v); |
|
439 | - } |
|
440 | - else { |
|
441 | - $this->{$k} = $v; |
|
442 | - } |
|
443 | - } |
|
444 | - } |
|
445 | - |
|
446 | - /*---------------------------------------------------------------------------------------------------------- |
|
13 | + public const STREAMER_VAR = 1; |
|
14 | + public const STREAMER_ARRAY = 2; |
|
15 | + public const STREAMER_TYPE = 3; |
|
16 | + public const STREAMER_PROP = 4; |
|
17 | + public const STREAMER_RONOTIFY = 5; |
|
18 | + public const STREAMER_VALUEMAP = 20; |
|
19 | + public const STREAMER_TYPE_DATE = 1; |
|
20 | + public const STREAMER_TYPE_HEX = 2; |
|
21 | + public const STREAMER_TYPE_DATE_DASHES = 3; |
|
22 | + public const STREAMER_TYPE_STREAM = 4; // deprecated |
|
23 | + public const STREAMER_TYPE_IGNORE = 5; |
|
24 | + public const STREAMER_TYPE_SEND_EMPTY = 6; |
|
25 | + public const STREAMER_TYPE_NO_CONTAINER = 7; |
|
26 | + public const STREAMER_TYPE_COMMA_SEPARATED = 8; |
|
27 | + public const STREAMER_TYPE_SEMICOLON_SEPARATED = 9; |
|
28 | + public const STREAMER_TYPE_MULTIPART = 10; |
|
29 | + public const STREAMER_TYPE_STREAM_ASBASE64 = 11; |
|
30 | + public const STREAMER_TYPE_STREAM_ASPLAIN = 12; |
|
31 | + public const STREAMER_PRIVATE = 13; |
|
32 | + public const STRIP_PRIVATE_DATA = 1; |
|
33 | + public const STRIP_PRIVATE_SUBSTITUTE = 'Private'; |
|
34 | + |
|
35 | + protected $mapping; |
|
36 | + public $flags; |
|
37 | + public $content; |
|
38 | + |
|
39 | + /** |
|
40 | + * Constructor. |
|
41 | + * |
|
42 | + * @param array $mapping internal mapping of variables |
|
43 | + */ |
|
44 | + public function __construct($mapping) { |
|
45 | + $this->mapping = $mapping; |
|
46 | + $this->flags = false; |
|
47 | + } |
|
48 | + |
|
49 | + /** |
|
50 | + * Return the streamer mapping for this object. |
|
51 | + */ |
|
52 | + public function GetMapping() { |
|
53 | + return $this->mapping; |
|
54 | + } |
|
55 | + |
|
56 | + /** |
|
57 | + * Decodes the WBXML from a WBXMLdecoder until we reach the same depth level of WBXML. |
|
58 | + * This means that if there are multiple objects at this level, then only the first is |
|
59 | + * decoded SubOjects are auto-instantiated and decoded using the same functionality. |
|
60 | + * |
|
61 | + * @param WBXMLDecoder $decoder |
|
62 | + */ |
|
63 | + public function Decode(&$decoder) { |
|
64 | + WBXMLDecoder::ResetInWhile("decodeMain"); |
|
65 | + while (WBXMLDecoder::InWhile("decodeMain")) { |
|
66 | + $entity = $decoder->getElement(); |
|
67 | + |
|
68 | + if ($entity[EN_TYPE] == EN_TYPE_STARTTAG) { |
|
69 | + if (!($entity[EN_FLAGS] & EN_FLAGS_CONTENT)) { |
|
70 | + $map = $this->mapping[$entity[EN_TAG]]; |
|
71 | + if (isset($map[self::STREAMER_ARRAY])) { |
|
72 | + $this->{$map[self::STREAMER_VAR]} = []; |
|
73 | + } |
|
74 | + elseif (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) { |
|
75 | + $this->{$map[self::STREAMER_VAR]} = "1"; |
|
76 | + } |
|
77 | + elseif (!isset($map[self::STREAMER_TYPE])) { |
|
78 | + $this->{$map[self::STREAMER_VAR]} = ""; |
|
79 | + } |
|
80 | + elseif ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES) { |
|
81 | + $this->{$map[self::STREAMER_VAR]} = ""; |
|
82 | + } |
|
83 | + |
|
84 | + continue; |
|
85 | + } |
|
86 | + // Found a start tag |
|
87 | + if (!isset($this->mapping[$entity[EN_TAG]])) { |
|
88 | + // This tag shouldn't be here, abort |
|
89 | + SLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("Tag '%s' unexpected in type XML type '%s'", $entity[EN_TAG], get_class($this))); |
|
90 | + |
|
91 | + return false; |
|
92 | + } |
|
93 | + |
|
94 | + $map = $this->mapping[$entity[EN_TAG]]; |
|
95 | + |
|
96 | + // Handle an array |
|
97 | + if (isset($map[self::STREAMER_ARRAY])) { |
|
98 | + WBXMLDecoder::ResetInWhile("decodeArray"); |
|
99 | + while (WBXMLDecoder::InWhile("decodeArray")) { |
|
100 | + // do not get start tag for an array without a container |
|
101 | + if (!(isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER)) { |
|
102 | + if (!$decoder->getElementStartTag($map[self::STREAMER_ARRAY])) { |
|
103 | + break; |
|
104 | + } |
|
105 | + } |
|
106 | + if (isset($map[self::STREAMER_TYPE])) { |
|
107 | + $decoded = new $map[self::STREAMER_TYPE](); |
|
108 | + |
|
109 | + $decoded->Decode($decoder); |
|
110 | + } |
|
111 | + else { |
|
112 | + $decoded = $decoder->getElementContent(); |
|
113 | + } |
|
114 | + |
|
115 | + if (!isset($this->{$map[self::STREAMER_VAR]})) { |
|
116 | + $this->{$map[self::STREAMER_VAR]} = [$decoded]; |
|
117 | + } |
|
118 | + else { |
|
119 | + array_push($this->{$map[self::STREAMER_VAR]}, $decoded); |
|
120 | + } |
|
121 | + |
|
122 | + if (!$decoder->getElementEndTag()) { // end tag of a container element |
|
123 | + return false; |
|
124 | + } |
|
125 | + |
|
126 | + if (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER) { |
|
127 | + $e = $decoder->peek(); |
|
128 | + // go back to the initial while if another block of no container elements is found |
|
129 | + if ($e[EN_TYPE] == EN_TYPE_STARTTAG) { |
|
130 | + continue 2; |
|
131 | + } |
|
132 | + // break on end tag because no container elements block end is reached |
|
133 | + if ($e[EN_TYPE] == EN_TYPE_ENDTAG) { |
|
134 | + break; |
|
135 | + } |
|
136 | + if (empty($e)) { |
|
137 | + break; |
|
138 | + } |
|
139 | + } |
|
140 | + } |
|
141 | + // do not get end tag for an array without a container |
|
142 | + if (!(isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER)) { |
|
143 | + if (!$decoder->getElementEndTag()) { // end tag of container |
|
144 | + return false; |
|
145 | + } |
|
146 | + } |
|
147 | + } |
|
148 | + else { // Handle single value |
|
149 | + if (isset($map[self::STREAMER_TYPE])) { |
|
150 | + // Complex type, decode recursively |
|
151 | + if ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES) { |
|
152 | + $decoded = $this->parseDate($decoder->getElementContent()); |
|
153 | + if (!$decoder->getElementEndTag()) { |
|
154 | + return false; |
|
155 | + } |
|
156 | + } |
|
157 | + elseif ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) { |
|
158 | + $decoded = hex2bin($decoder->getElementContent()); |
|
159 | + if (!$decoder->getElementEndTag()) { |
|
160 | + return false; |
|
161 | + } |
|
162 | + } |
|
163 | + // explode comma or semicolon strings into arrays |
|
164 | + elseif ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_SEMICOLON_SEPARATED) { |
|
165 | + $glue = ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED) ? ", " : "; "; |
|
166 | + $decoded = explode($glue, $decoder->getElementContent()); |
|
167 | + if (!$decoder->getElementEndTag()) { |
|
168 | + return false; |
|
169 | + } |
|
170 | + } |
|
171 | + elseif ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM_ASPLAIN) { |
|
172 | + $decoded = StringStreamWrapper::Open($decoder->getElementContent()); |
|
173 | + if (!$decoder->getElementEndTag()) { |
|
174 | + return false; |
|
175 | + } |
|
176 | + } |
|
177 | + else { |
|
178 | + $subdecoder = new $map[self::STREAMER_TYPE](); |
|
179 | + if ($subdecoder->Decode($decoder) === false) { |
|
180 | + return false; |
|
181 | + } |
|
182 | + |
|
183 | + $decoded = $subdecoder; |
|
184 | + |
|
185 | + if (!$decoder->getElementEndTag()) { |
|
186 | + SLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("No end tag for '%s'", $entity[EN_TAG])); |
|
187 | + |
|
188 | + return false; |
|
189 | + } |
|
190 | + } |
|
191 | + } |
|
192 | + else { |
|
193 | + // Simple type, just get content |
|
194 | + $decoded = $decoder->getElementContent(); |
|
195 | + |
|
196 | + if ($decoded === false) { |
|
197 | + // the tag is declared to have content, but no content is available. |
|
198 | + // set an empty content |
|
199 | + $decoded = ""; |
|
200 | + } |
|
201 | + |
|
202 | + if (!$decoder->getElementEndTag()) { |
|
203 | + SLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("Unable to get end tag for '%s'", $entity[EN_TAG])); |
|
204 | + |
|
205 | + return false; |
|
206 | + } |
|
207 | + } |
|
208 | + // $decoded now contains data object (or string) |
|
209 | + $this->{$map[self::STREAMER_VAR]} = $decoded; |
|
210 | + } |
|
211 | + } |
|
212 | + elseif ($entity[EN_TYPE] == EN_TYPE_ENDTAG) { |
|
213 | + $decoder->ungetElement($entity); |
|
214 | + |
|
215 | + break; |
|
216 | + } |
|
217 | + else { |
|
218 | + SLog::Write(LOGLEVEL_WBXMLSTACK, "Unexpected content in type"); |
|
219 | + |
|
220 | + break; |
|
221 | + } |
|
222 | + } |
|
223 | + } |
|
224 | + |
|
225 | + /** |
|
226 | + * Encodes this object and any subobjects - output is ordered according to mapping. |
|
227 | + * |
|
228 | + * @param WBXMLEncoder $encoder |
|
229 | + */ |
|
230 | + public function Encode(&$encoder) { |
|
231 | + // A return value if anything was streamed. We need for empty tags. |
|
232 | + $streamed = false; |
|
233 | + foreach ($this->mapping as $tag => $map) { |
|
234 | + if (isset($this->{$map[self::STREAMER_VAR]})) { |
|
235 | + // Variable is available |
|
236 | + if (is_object($this->{$map[self::STREAMER_VAR]})) { |
|
237 | + // Subobjects can do their own encoding |
|
238 | + if ($this->{$map[self::STREAMER_VAR]} instanceof Streamer) { |
|
239 | + $encoder->startTag($tag); |
|
240 | + $res = $this->{$map[self::STREAMER_VAR]}->Encode($encoder); |
|
241 | + $encoder->endTag(); |
|
242 | + // nothing was streamed in previous encode but it should be streamed empty anyway |
|
243 | + if (!$res && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) { |
|
244 | + $encoder->startTag($tag, false, true); |
|
245 | + } |
|
246 | + } |
|
247 | + else { |
|
248 | + SLog::Write(LOGLEVEL_ERROR, sprintf("Streamer->Encode(): parameter '%s' of object %s is not of type Streamer", $map[self::STREAMER_VAR], get_class($this))); |
|
249 | + } |
|
250 | + } |
|
251 | + // Array of objects |
|
252 | + elseif (isset($map[self::STREAMER_ARRAY])) { |
|
253 | + if (empty($this->{$map[self::STREAMER_VAR]}) && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) { |
|
254 | + $encoder->startTag($tag, false, true); |
|
255 | + } |
|
256 | + else { |
|
257 | + // Outputs array container (eg Attachments) |
|
258 | + // Do not output start and end tag when type is STREAMER_TYPE_NO_CONTAINER |
|
259 | + if (!isset($map[self::STREAMER_PROP]) || $map[self::STREAMER_PROP] != self::STREAMER_TYPE_NO_CONTAINER) { |
|
260 | + $encoder->startTag($tag); |
|
261 | + } |
|
262 | + |
|
263 | + foreach ($this->{$map[self::STREAMER_VAR]} as $element) { |
|
264 | + if (is_object($element)) { |
|
265 | + $encoder->startTag($map[self::STREAMER_ARRAY]); // Outputs object container (eg Attachment) |
|
266 | + $element->Encode($encoder); |
|
267 | + $encoder->endTag(); |
|
268 | + } |
|
269 | + else { |
|
270 | + if (strlen($element) == 0) |
|
271 | + // Do not output empty items. Not sure if we should output an empty tag with $encoder->startTag($map[self::STREAMER_ARRAY], false, true); |
|
272 | + ; else { |
|
273 | + $encoder->startTag($map[self::STREAMER_ARRAY]); |
|
274 | + $encoder->content($element); |
|
275 | + $encoder->endTag(); |
|
276 | + $streamed = true; |
|
277 | + } |
|
278 | + } |
|
279 | + } |
|
280 | + |
|
281 | + if (!isset($map[self::STREAMER_PROP]) || $map[self::STREAMER_PROP] != self::STREAMER_TYPE_NO_CONTAINER) { |
|
282 | + $encoder->endTag(); |
|
283 | + } |
|
284 | + } |
|
285 | + } |
|
286 | + else { |
|
287 | + if (isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_IGNORE) { |
|
288 | + continue; |
|
289 | + } |
|
290 | + |
|
291 | + if ($encoder->getMultipart() && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_MULTIPART) { |
|
292 | + $encoder->addBodypartStream($this->{$map[self::STREAMER_VAR]}); |
|
293 | + $encoder->startTag(SYNC_ITEMOPERATIONS_PART); |
|
294 | + $encoder->content($encoder->getBodypartsCount()); |
|
295 | + $encoder->endTag(); |
|
296 | + |
|
297 | + continue; |
|
298 | + } |
|
299 | + |
|
300 | + // Simple type |
|
301 | + if (!isset($map[self::STREAMER_TYPE]) && strlen($this->{$map[self::STREAMER_VAR]}) == 0) { |
|
302 | + // send empty tags |
|
303 | + if (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) { |
|
304 | + $encoder->startTag($tag, false, true); |
|
305 | + } |
|
306 | + |
|
307 | + // Do not output empty items. See above: $encoder->startTag($tag, false, true); |
|
308 | + continue; |
|
309 | + } |
|
310 | + $encoder->startTag($tag); |
|
311 | + |
|
312 | + if (isset($map[self::STREAMER_TYPE]) && ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES)) { |
|
313 | + if ($this->{$map[self::STREAMER_VAR]} != 0) { // don't output 1-1-1970 |
|
314 | + $encoder->content($this->formatDate($this->{$map[self::STREAMER_VAR]}, $map[self::STREAMER_TYPE])); |
|
315 | + } |
|
316 | + } |
|
317 | + elseif (isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) { |
|
318 | + $encoder->content(strtoupper(bin2hex($this->{$map[self::STREAMER_VAR]}))); |
|
319 | + } |
|
320 | + elseif (isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM_ASPLAIN) { |
|
321 | + $encoder->contentStream($this->{$map[self::STREAMER_VAR]}, false); |
|
322 | + } |
|
323 | + elseif (isset($map[self::STREAMER_TYPE]) && ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM_ASBASE64 || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM)) { |
|
324 | + $encoder->contentStream($this->{$map[self::STREAMER_VAR]}, true); |
|
325 | + } |
|
326 | + // implode comma or semicolon arrays into a string |
|
327 | + elseif (isset($map[self::STREAMER_TYPE]) && is_array($this->{$map[self::STREAMER_VAR]}) && |
|
328 | + ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_SEMICOLON_SEPARATED)) { |
|
329 | + $glue = ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED) ? ", " : "; "; |
|
330 | + $encoder->content(implode($glue, $this->{$map[self::STREAMER_VAR]})); |
|
331 | + } |
|
332 | + else { |
|
333 | + $encoder->content($this->{$map[self::STREAMER_VAR]}); |
|
334 | + } |
|
335 | + $encoder->endTag(); |
|
336 | + $streamed = true; |
|
337 | + } |
|
338 | + } |
|
339 | + } |
|
340 | + // Output our own content |
|
341 | + if (isset($this->content)) { |
|
342 | + $encoder->content($this->content); |
|
343 | + } |
|
344 | + |
|
345 | + return $streamed; |
|
346 | + } |
|
347 | + |
|
348 | + /** |
|
349 | + * Removes not necessary data from the object. |
|
350 | + * |
|
351 | + * @param mixed $flags |
|
352 | + * |
|
353 | + * @return bool |
|
354 | + */ |
|
355 | + public function StripData($flags = 0) { |
|
356 | + foreach ($this->mapping as $k => $v) { |
|
357 | + if (isset($this->{$v[self::STREAMER_VAR]})) { |
|
358 | + if (is_object($this->{$v[self::STREAMER_VAR]}) && method_exists($this->{$v[self::STREAMER_VAR]}, "StripData")) { |
|
359 | + $this->{$v[self::STREAMER_VAR]}->StripData($flags); |
|
360 | + } |
|
361 | + elseif (isset($v[self::STREAMER_ARRAY]) && !empty($this->{$v[self::STREAMER_VAR]})) { |
|
362 | + foreach ($this->{$v[self::STREAMER_VAR]} as $element) { |
|
363 | + if (is_object($element) && method_exists($element, "StripData")) { |
|
364 | + $element->StripData($flags); |
|
365 | + } |
|
366 | + elseif ($flags === Streamer::STRIP_PRIVATE_DATA && isset($v[self::STREAMER_PRIVATE])) { |
|
367 | + if ($v[self::STREAMER_PRIVATE] !== true) { |
|
368 | + $this->{$v[self::STREAMER_VAR]} = $v[self::STREAMER_PRIVATE]; |
|
369 | + } |
|
370 | + else { |
|
371 | + unset($this->{$v[self::STREAMER_VAR]}); |
|
372 | + } |
|
373 | + } |
|
374 | + } |
|
375 | + } |
|
376 | + elseif ($flags === Streamer::STRIP_PRIVATE_DATA && isset($v[self::STREAMER_PRIVATE])) { |
|
377 | + if ($v[self::STREAMER_PRIVATE] !== true) { |
|
378 | + $this->{$v[self::STREAMER_VAR]} = $v[self::STREAMER_PRIVATE]; |
|
379 | + } |
|
380 | + else { |
|
381 | + unset($this->{$v[self::STREAMER_VAR]}); |
|
382 | + } |
|
383 | + } |
|
384 | + } |
|
385 | + } |
|
386 | + if ($flags === 0) { |
|
387 | + unset($this->mapping); |
|
388 | + } |
|
389 | + |
|
390 | + return true; |
|
391 | + } |
|
392 | + |
|
393 | + /** |
|
394 | + * Returns SyncObject's streamer variable names. |
|
395 | + * |
|
396 | + * @return array |
|
397 | + */ |
|
398 | + public function GetStreamerVars() { |
|
399 | + $streamerVars = []; |
|
400 | + foreach ($this->mapping as $v) { |
|
401 | + $streamerVars[] = $v[self::STREAMER_VAR]; |
|
402 | + } |
|
403 | + |
|
404 | + return $streamerVars; |
|
405 | + } |
|
406 | + |
|
407 | + /** |
|
408 | + * JsonSerializable interface method. |
|
409 | + * |
|
410 | + * Serializes the object to a value that can be serialized natively by json_encode() |
|
411 | + * |
|
412 | + * @return array |
|
413 | + */ |
|
414 | + public function jsonSerialize() { |
|
415 | + $data = []; |
|
416 | + foreach ($this->mapping as $k => $v) { |
|
417 | + if (isset($this->{$v[self::STREAMER_VAR]})) { |
|
418 | + $data[$v[self::STREAMER_VAR]] = $this->{$v[self::STREAMER_VAR]}; |
|
419 | + } |
|
420 | + } |
|
421 | + |
|
422 | + return [ |
|
423 | + 'gsSyncStateClass' => get_class($this), |
|
424 | + 'data' => $data, |
|
425 | + ]; |
|
426 | + } |
|
427 | + |
|
428 | + /** |
|
429 | + * Restores the object from a value provided by json_decode. |
|
430 | + * |
|
431 | + * @param $stdObj stdClass Object |
|
432 | + */ |
|
433 | + public function jsonDeserialize($stdObj) { |
|
434 | + foreach ($stdObj->data as $k => $v) { |
|
435 | + if (is_object($v) && isset($v->gsSyncStateClass)) { |
|
436 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("Streamer->jsonDeserialize(): top class '%s'", $v->gsSyncStateClass)); |
|
437 | + $this->{$k} = new $v->gsSyncStateClass(); |
|
438 | + $this->{$k}->jsonDeserialize($v); |
|
439 | + } |
|
440 | + else { |
|
441 | + $this->{$k} = $v; |
|
442 | + } |
|
443 | + } |
|
444 | + } |
|
445 | + |
|
446 | + /*---------------------------------------------------------------------------------------------------------- |
|
447 | 447 | * Private methods for conversion |
448 | 448 | */ |
449 | 449 | |
450 | - /** |
|
451 | - * Formats a timestamp |
|
452 | - * Oh yeah, this is beautiful. Exchange outputs date fields differently in calendar items |
|
453 | - * and emails. We could just always send one or the other, but unfortunately nokia's 'Mail for |
|
454 | - * exchange' depends on this quirk. So we have to send a different date type depending on where |
|
455 | - * it's used. Sigh. |
|
456 | - * |
|
457 | - * @param int $ts |
|
458 | - * @param int $type |
|
459 | - * |
|
460 | - * @return string |
|
461 | - */ |
|
462 | - private function formatDate($ts, $type) { |
|
463 | - if ($type == self::STREAMER_TYPE_DATE) { |
|
464 | - return gmstrftime("%Y%m%dT%H%M%SZ", $ts); |
|
465 | - } |
|
466 | - if ($type == self::STREAMER_TYPE_DATE_DASHES) { |
|
467 | - return gmstrftime("%Y-%m-%dT%H:%M:%S.000Z", $ts); |
|
468 | - } |
|
469 | - // fallback to dashes (should never be reached) |
|
470 | - return gmstrftime("%Y-%m-%dT%H:%M:%S.000Z", $ts); |
|
471 | - } |
|
472 | - |
|
473 | - /** |
|
474 | - * Transforms an AS timestamp into a unix timestamp. |
|
475 | - * |
|
476 | - * @param string $ts |
|
477 | - * |
|
478 | - * @return long |
|
479 | - */ |
|
480 | - public function parseDate($ts) { |
|
481 | - if (preg_match("/(\\d{4})[^0-9]*(\\d{2})[^0-9]*(\\d{2})(T(\\d{2})[^0-9]*(\\d{2})[^0-9]*(\\d{2})(.\\d+)?Z){0,1}$/", $ts, $matches)) { |
|
482 | - if ($matches[1] >= 2038) { |
|
483 | - $matches[1] = 2038; |
|
484 | - $matches[2] = 1; |
|
485 | - $matches[3] = 18; |
|
486 | - $matches[5] = $matches[6] = $matches[7] = 0; |
|
487 | - } |
|
488 | - |
|
489 | - if (!isset($matches[5])) { |
|
490 | - $matches[5] = 0; |
|
491 | - } |
|
492 | - if (!isset($matches[6])) { |
|
493 | - $matches[6] = 0; |
|
494 | - } |
|
495 | - if (!isset($matches[7])) { |
|
496 | - $matches[7] = 0; |
|
497 | - } |
|
498 | - |
|
499 | - return gmmktime($matches[5], $matches[6], $matches[7], $matches[2], $matches[3], $matches[1]); |
|
500 | - } |
|
501 | - |
|
502 | - return 0; |
|
503 | - } |
|
450 | + /** |
|
451 | + * Formats a timestamp |
|
452 | + * Oh yeah, this is beautiful. Exchange outputs date fields differently in calendar items |
|
453 | + * and emails. We could just always send one or the other, but unfortunately nokia's 'Mail for |
|
454 | + * exchange' depends on this quirk. So we have to send a different date type depending on where |
|
455 | + * it's used. Sigh. |
|
456 | + * |
|
457 | + * @param int $ts |
|
458 | + * @param int $type |
|
459 | + * |
|
460 | + * @return string |
|
461 | + */ |
|
462 | + private function formatDate($ts, $type) { |
|
463 | + if ($type == self::STREAMER_TYPE_DATE) { |
|
464 | + return gmstrftime("%Y%m%dT%H%M%SZ", $ts); |
|
465 | + } |
|
466 | + if ($type == self::STREAMER_TYPE_DATE_DASHES) { |
|
467 | + return gmstrftime("%Y-%m-%dT%H:%M:%S.000Z", $ts); |
|
468 | + } |
|
469 | + // fallback to dashes (should never be reached) |
|
470 | + return gmstrftime("%Y-%m-%dT%H:%M:%S.000Z", $ts); |
|
471 | + } |
|
472 | + |
|
473 | + /** |
|
474 | + * Transforms an AS timestamp into a unix timestamp. |
|
475 | + * |
|
476 | + * @param string $ts |
|
477 | + * |
|
478 | + * @return long |
|
479 | + */ |
|
480 | + public function parseDate($ts) { |
|
481 | + if (preg_match("/(\\d{4})[^0-9]*(\\d{2})[^0-9]*(\\d{2})(T(\\d{2})[^0-9]*(\\d{2})[^0-9]*(\\d{2})(.\\d+)?Z){0,1}$/", $ts, $matches)) { |
|
482 | + if ($matches[1] >= 2038) { |
|
483 | + $matches[1] = 2038; |
|
484 | + $matches[2] = 1; |
|
485 | + $matches[3] = 18; |
|
486 | + $matches[5] = $matches[6] = $matches[7] = 0; |
|
487 | + } |
|
488 | + |
|
489 | + if (!isset($matches[5])) { |
|
490 | + $matches[5] = 0; |
|
491 | + } |
|
492 | + if (!isset($matches[6])) { |
|
493 | + $matches[6] = 0; |
|
494 | + } |
|
495 | + if (!isset($matches[7])) { |
|
496 | + $matches[7] = 0; |
|
497 | + } |
|
498 | + |
|
499 | + return gmmktime($matches[5], $matches[6], $matches[7], $matches[2], $matches[3], $matches[1]); |
|
500 | + } |
|
501 | + |
|
502 | + return 0; |
|
503 | + } |
|
504 | 504 | } |
@@ -70,14 +70,11 @@ discard block |
||
70 | 70 | $map = $this->mapping[$entity[EN_TAG]]; |
71 | 71 | if (isset($map[self::STREAMER_ARRAY])) { |
72 | 72 | $this->{$map[self::STREAMER_VAR]} = []; |
73 | - } |
|
74 | - elseif (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) { |
|
73 | + } elseif (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) { |
|
75 | 74 | $this->{$map[self::STREAMER_VAR]} = "1"; |
76 | - } |
|
77 | - elseif (!isset($map[self::STREAMER_TYPE])) { |
|
75 | + } elseif (!isset($map[self::STREAMER_TYPE])) { |
|
78 | 76 | $this->{$map[self::STREAMER_VAR]} = ""; |
79 | - } |
|
80 | - elseif ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES) { |
|
77 | + } elseif ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES) { |
|
81 | 78 | $this->{$map[self::STREAMER_VAR]} = ""; |
82 | 79 | } |
83 | 80 | |
@@ -107,15 +104,13 @@ discard block |
||
107 | 104 | $decoded = new $map[self::STREAMER_TYPE](); |
108 | 105 | |
109 | 106 | $decoded->Decode($decoder); |
110 | - } |
|
111 | - else { |
|
107 | + } else { |
|
112 | 108 | $decoded = $decoder->getElementContent(); |
113 | 109 | } |
114 | 110 | |
115 | 111 | if (!isset($this->{$map[self::STREAMER_VAR]})) { |
116 | 112 | $this->{$map[self::STREAMER_VAR]} = [$decoded]; |
117 | - } |
|
118 | - else { |
|
113 | + } else { |
|
119 | 114 | array_push($this->{$map[self::STREAMER_VAR]}, $decoded); |
120 | 115 | } |
121 | 116 | |
@@ -144,8 +139,7 @@ discard block |
||
144 | 139 | return false; |
145 | 140 | } |
146 | 141 | } |
147 | - } |
|
148 | - else { // Handle single value |
|
142 | + } else { // Handle single value |
|
149 | 143 | if (isset($map[self::STREAMER_TYPE])) { |
150 | 144 | // Complex type, decode recursively |
151 | 145 | if ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES) { |
@@ -153,8 +147,7 @@ discard block |
||
153 | 147 | if (!$decoder->getElementEndTag()) { |
154 | 148 | return false; |
155 | 149 | } |
156 | - } |
|
157 | - elseif ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) { |
|
150 | + } elseif ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) { |
|
158 | 151 | $decoded = hex2bin($decoder->getElementContent()); |
159 | 152 | if (!$decoder->getElementEndTag()) { |
160 | 153 | return false; |
@@ -167,14 +160,12 @@ discard block |
||
167 | 160 | if (!$decoder->getElementEndTag()) { |
168 | 161 | return false; |
169 | 162 | } |
170 | - } |
|
171 | - elseif ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM_ASPLAIN) { |
|
163 | + } elseif ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM_ASPLAIN) { |
|
172 | 164 | $decoded = StringStreamWrapper::Open($decoder->getElementContent()); |
173 | 165 | if (!$decoder->getElementEndTag()) { |
174 | 166 | return false; |
175 | 167 | } |
176 | - } |
|
177 | - else { |
|
168 | + } else { |
|
178 | 169 | $subdecoder = new $map[self::STREAMER_TYPE](); |
179 | 170 | if ($subdecoder->Decode($decoder) === false) { |
180 | 171 | return false; |
@@ -188,8 +179,7 @@ discard block |
||
188 | 179 | return false; |
189 | 180 | } |
190 | 181 | } |
191 | - } |
|
192 | - else { |
|
182 | + } else { |
|
193 | 183 | // Simple type, just get content |
194 | 184 | $decoded = $decoder->getElementContent(); |
195 | 185 | |
@@ -208,13 +198,11 @@ discard block |
||
208 | 198 | // $decoded now contains data object (or string) |
209 | 199 | $this->{$map[self::STREAMER_VAR]} = $decoded; |
210 | 200 | } |
211 | - } |
|
212 | - elseif ($entity[EN_TYPE] == EN_TYPE_ENDTAG) { |
|
201 | + } elseif ($entity[EN_TYPE] == EN_TYPE_ENDTAG) { |
|
213 | 202 | $decoder->ungetElement($entity); |
214 | 203 | |
215 | 204 | break; |
216 | - } |
|
217 | - else { |
|
205 | + } else { |
|
218 | 206 | SLog::Write(LOGLEVEL_WBXMLSTACK, "Unexpected content in type"); |
219 | 207 | |
220 | 208 | break; |
@@ -243,8 +231,7 @@ discard block |
||
243 | 231 | if (!$res && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) { |
244 | 232 | $encoder->startTag($tag, false, true); |
245 | 233 | } |
246 | - } |
|
247 | - else { |
|
234 | + } else { |
|
248 | 235 | SLog::Write(LOGLEVEL_ERROR, sprintf("Streamer->Encode(): parameter '%s' of object %s is not of type Streamer", $map[self::STREAMER_VAR], get_class($this))); |
249 | 236 | } |
250 | 237 | } |
@@ -252,8 +239,7 @@ discard block |
||
252 | 239 | elseif (isset($map[self::STREAMER_ARRAY])) { |
253 | 240 | if (empty($this->{$map[self::STREAMER_VAR]}) && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) { |
254 | 241 | $encoder->startTag($tag, false, true); |
255 | - } |
|
256 | - else { |
|
242 | + } else { |
|
257 | 243 | // Outputs array container (eg Attachments) |
258 | 244 | // Do not output start and end tag when type is STREAMER_TYPE_NO_CONTAINER |
259 | 245 | if (!isset($map[self::STREAMER_PROP]) || $map[self::STREAMER_PROP] != self::STREAMER_TYPE_NO_CONTAINER) { |
@@ -265,8 +251,7 @@ discard block |
||
265 | 251 | $encoder->startTag($map[self::STREAMER_ARRAY]); // Outputs object container (eg Attachment) |
266 | 252 | $element->Encode($encoder); |
267 | 253 | $encoder->endTag(); |
268 | - } |
|
269 | - else { |
|
254 | + } else { |
|
270 | 255 | if (strlen($element) == 0) |
271 | 256 | // Do not output empty items. Not sure if we should output an empty tag with $encoder->startTag($map[self::STREAMER_ARRAY], false, true); |
272 | 257 | ; else { |
@@ -282,8 +267,7 @@ discard block |
||
282 | 267 | $encoder->endTag(); |
283 | 268 | } |
284 | 269 | } |
285 | - } |
|
286 | - else { |
|
270 | + } else { |
|
287 | 271 | if (isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_IGNORE) { |
288 | 272 | continue; |
289 | 273 | } |
@@ -313,14 +297,11 @@ discard block |
||
313 | 297 | if ($this->{$map[self::STREAMER_VAR]} != 0) { // don't output 1-1-1970 |
314 | 298 | $encoder->content($this->formatDate($this->{$map[self::STREAMER_VAR]}, $map[self::STREAMER_TYPE])); |
315 | 299 | } |
316 | - } |
|
317 | - elseif (isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) { |
|
300 | + } elseif (isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) { |
|
318 | 301 | $encoder->content(strtoupper(bin2hex($this->{$map[self::STREAMER_VAR]}))); |
319 | - } |
|
320 | - elseif (isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM_ASPLAIN) { |
|
302 | + } elseif (isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM_ASPLAIN) { |
|
321 | 303 | $encoder->contentStream($this->{$map[self::STREAMER_VAR]}, false); |
322 | - } |
|
323 | - elseif (isset($map[self::STREAMER_TYPE]) && ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM_ASBASE64 || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM)) { |
|
304 | + } elseif (isset($map[self::STREAMER_TYPE]) && ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM_ASBASE64 || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM)) { |
|
324 | 305 | $encoder->contentStream($this->{$map[self::STREAMER_VAR]}, true); |
325 | 306 | } |
326 | 307 | // implode comma or semicolon arrays into a string |
@@ -328,8 +309,7 @@ discard block |
||
328 | 309 | ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_SEMICOLON_SEPARATED)) { |
329 | 310 | $glue = ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED) ? ", " : "; "; |
330 | 311 | $encoder->content(implode($glue, $this->{$map[self::STREAMER_VAR]})); |
331 | - } |
|
332 | - else { |
|
312 | + } else { |
|
333 | 313 | $encoder->content($this->{$map[self::STREAMER_VAR]}); |
334 | 314 | } |
335 | 315 | $encoder->endTag(); |
@@ -357,27 +337,22 @@ discard block |
||
357 | 337 | if (isset($this->{$v[self::STREAMER_VAR]})) { |
358 | 338 | if (is_object($this->{$v[self::STREAMER_VAR]}) && method_exists($this->{$v[self::STREAMER_VAR]}, "StripData")) { |
359 | 339 | $this->{$v[self::STREAMER_VAR]}->StripData($flags); |
360 | - } |
|
361 | - elseif (isset($v[self::STREAMER_ARRAY]) && !empty($this->{$v[self::STREAMER_VAR]})) { |
|
340 | + } elseif (isset($v[self::STREAMER_ARRAY]) && !empty($this->{$v[self::STREAMER_VAR]})) { |
|
362 | 341 | foreach ($this->{$v[self::STREAMER_VAR]} as $element) { |
363 | 342 | if (is_object($element) && method_exists($element, "StripData")) { |
364 | 343 | $element->StripData($flags); |
365 | - } |
|
366 | - elseif ($flags === Streamer::STRIP_PRIVATE_DATA && isset($v[self::STREAMER_PRIVATE])) { |
|
344 | + } elseif ($flags === Streamer::STRIP_PRIVATE_DATA && isset($v[self::STREAMER_PRIVATE])) { |
|
367 | 345 | if ($v[self::STREAMER_PRIVATE] !== true) { |
368 | 346 | $this->{$v[self::STREAMER_VAR]} = $v[self::STREAMER_PRIVATE]; |
369 | - } |
|
370 | - else { |
|
347 | + } else { |
|
371 | 348 | unset($this->{$v[self::STREAMER_VAR]}); |
372 | 349 | } |
373 | 350 | } |
374 | 351 | } |
375 | - } |
|
376 | - elseif ($flags === Streamer::STRIP_PRIVATE_DATA && isset($v[self::STREAMER_PRIVATE])) { |
|
352 | + } elseif ($flags === Streamer::STRIP_PRIVATE_DATA && isset($v[self::STREAMER_PRIVATE])) { |
|
377 | 353 | if ($v[self::STREAMER_PRIVATE] !== true) { |
378 | 354 | $this->{$v[self::STREAMER_VAR]} = $v[self::STREAMER_PRIVATE]; |
379 | - } |
|
380 | - else { |
|
355 | + } else { |
|
381 | 356 | unset($this->{$v[self::STREAMER_VAR]}); |
382 | 357 | } |
383 | 358 | } |
@@ -436,8 +411,7 @@ discard block |
||
436 | 411 | SLog::Write(LOGLEVEL_DEBUG, sprintf("Streamer->jsonDeserialize(): top class '%s'", $v->gsSyncStateClass)); |
437 | 412 | $this->{$k} = new $v->gsSyncStateClass(); |
438 | 413 | $this->{$k}->jsonDeserialize($v); |
439 | - } |
|
440 | - else { |
|
414 | + } else { |
|
441 | 415 | $this->{$k} = $v; |
442 | 416 | } |
443 | 417 | } |
@@ -8,142 +8,142 @@ discard block |
||
8 | 8 | */ |
9 | 9 | |
10 | 10 | class SLog { |
11 | - private static $wbxmlDebug = ''; |
|
12 | - private static $lastLogs = []; |
|
13 | - |
|
14 | - /** |
|
15 | - * @var Log |
|
16 | - */ |
|
17 | - private static $logger; |
|
18 | - |
|
19 | - /** |
|
20 | - * Initializes the logging. |
|
21 | - * |
|
22 | - * @return bool |
|
23 | - */ |
|
24 | - public static function Initialize() { |
|
25 | - // define some constants for the logging |
|
26 | - if (!defined('LOGUSERLEVEL')) { |
|
27 | - define('LOGUSERLEVEL', LOGLEVEL_OFF); |
|
28 | - } |
|
29 | - |
|
30 | - if (!defined('LOGLEVEL')) { |
|
31 | - define('LOGLEVEL', LOGLEVEL_OFF); |
|
32 | - } |
|
33 | - |
|
34 | - $logger = self::getLogger(); |
|
35 | - |
|
36 | - return true; |
|
37 | - } |
|
38 | - |
|
39 | - /** |
|
40 | - * Check if WBXML logging is enabled in current LOG(USER)LEVEL. |
|
41 | - * |
|
42 | - * @return bool |
|
43 | - */ |
|
44 | - public static function IsWbxmlDebugEnabled() { |
|
45 | - return LOGLEVEL >= LOGLEVEL_WBXML || (LOGUSERLEVEL >= LOGLEVEL_WBXML && self::getLogger()->HasSpecialLogUsers()); |
|
46 | - } |
|
47 | - |
|
48 | - /** |
|
49 | - * Writes a log line. |
|
50 | - * |
|
51 | - * @param int $loglevel one of the defined LOGLEVELS |
|
52 | - * @param string $message |
|
53 | - * @param bool $truncate indicate if the message should be truncated, default true |
|
54 | - */ |
|
55 | - public static function Write($loglevel, $message, $truncate = true) { |
|
56 | - // truncate messages longer than 10 KB |
|
57 | - $messagesize = strlen($message); |
|
58 | - if ($truncate && $messagesize > 10240) { |
|
59 | - $message = substr($message, 0, 10240) . sprintf(" <log message with %d bytes truncated>", $messagesize); |
|
60 | - } |
|
61 | - |
|
62 | - self::$lastLogs[$loglevel] = $message; |
|
63 | - |
|
64 | - try { |
|
65 | - self::getLogger()->Log($loglevel, $message); |
|
66 | - } |
|
67 | - catch (\Exception $e) { |
|
68 | - // @TODO How should we handle logging error ? |
|
69 | - // Ignore any error. |
|
70 | - } |
|
71 | - |
|
72 | - if ($loglevel & LOGLEVEL_WBXMLSTACK) { |
|
73 | - self::$wbxmlDebug .= $message . PHP_EOL; |
|
74 | - } |
|
75 | - } |
|
76 | - |
|
77 | - /** |
|
78 | - * Returns logged information about the WBXML stack. |
|
79 | - * |
|
80 | - * @return string |
|
81 | - */ |
|
82 | - public static function GetWBXMLDebugInfo() { |
|
83 | - return trim(self::$wbxmlDebug); |
|
84 | - } |
|
85 | - |
|
86 | - /** |
|
87 | - * Returns the last message logged for a log level. |
|
88 | - * |
|
89 | - * @param int $loglevel one of the defined LOGLEVELS |
|
90 | - * |
|
91 | - * @return string/false returns false if there was no message logged in that level |
|
92 | - */ |
|
93 | - public static function GetLastMessage($loglevel) { |
|
94 | - return (isset(self::$lastLogs[$loglevel])) ? self::$lastLogs[$loglevel] : false; |
|
95 | - } |
|
96 | - |
|
97 | - /** |
|
98 | - * If called, the authenticated current user gets an extra log-file. |
|
99 | - * |
|
100 | - * If called until the user is authenticated (e.g. at the end of IBackend->Logon()) all log |
|
101 | - * messages that happened until this point will also be logged. |
|
102 | - */ |
|
103 | - public static function SpecialLogUser() { |
|
104 | - self::getLogger()->SpecialLogUser(); |
|
105 | - } |
|
106 | - |
|
107 | - /** |
|
108 | - * Returns the logger object. If no logger has been initialized, FileLog will be initialized and returned. |
|
109 | - * |
|
110 | - * @throws Exception thrown if the logger class cannot be instantiated |
|
111 | - * |
|
112 | - * @return Log |
|
113 | - */ |
|
114 | - private static function getLogger() { |
|
115 | - if (!self::$logger) { |
|
116 | - global $specialLogUsers; // This variable comes from the configuration file (config.php) |
|
117 | - |
|
118 | - $logger = LOGBACKEND_CLASS; |
|
119 | - if (!class_exists($logger)) { |
|
120 | - $errmsg = 'The configured logging class `' . $logger . '` does not exist. Check your configuration.'; |
|
121 | - error_log($errmsg); |
|
122 | - |
|
123 | - throw new \Exception($errmsg); |
|
124 | - } |
|
125 | - |
|
126 | - // if there is an impersonated user it's used instead of the GET user |
|
127 | - if (Request::GetImpersonatedUser()) { |
|
128 | - $user = Request::GetImpersonatedUser(); |
|
129 | - } |
|
130 | - else { |
|
131 | - list($user) = Utils::SplitDomainUser(strtolower(Request::GetGETUser())); |
|
132 | - } |
|
133 | - |
|
134 | - self::$logger = new $logger(); |
|
135 | - self::$logger->SetUser($user); |
|
136 | - self::$logger->SetAuthUser(Request::GetAuthUser()); |
|
137 | - self::$logger->SetSpecialLogUsers($specialLogUsers); |
|
138 | - self::$logger->SetDevid(Request::GetDeviceID()); |
|
139 | - self::$logger->SetPid(@getmypid()); |
|
140 | - self::$logger->AfterInitialize(); |
|
141 | - } |
|
142 | - |
|
143 | - return self::$logger; |
|
144 | - } |
|
145 | - |
|
146 | - /*---------------------------------------------------------------------------------------------------------- |
|
11 | + private static $wbxmlDebug = ''; |
|
12 | + private static $lastLogs = []; |
|
13 | + |
|
14 | + /** |
|
15 | + * @var Log |
|
16 | + */ |
|
17 | + private static $logger; |
|
18 | + |
|
19 | + /** |
|
20 | + * Initializes the logging. |
|
21 | + * |
|
22 | + * @return bool |
|
23 | + */ |
|
24 | + public static function Initialize() { |
|
25 | + // define some constants for the logging |
|
26 | + if (!defined('LOGUSERLEVEL')) { |
|
27 | + define('LOGUSERLEVEL', LOGLEVEL_OFF); |
|
28 | + } |
|
29 | + |
|
30 | + if (!defined('LOGLEVEL')) { |
|
31 | + define('LOGLEVEL', LOGLEVEL_OFF); |
|
32 | + } |
|
33 | + |
|
34 | + $logger = self::getLogger(); |
|
35 | + |
|
36 | + return true; |
|
37 | + } |
|
38 | + |
|
39 | + /** |
|
40 | + * Check if WBXML logging is enabled in current LOG(USER)LEVEL. |
|
41 | + * |
|
42 | + * @return bool |
|
43 | + */ |
|
44 | + public static function IsWbxmlDebugEnabled() { |
|
45 | + return LOGLEVEL >= LOGLEVEL_WBXML || (LOGUSERLEVEL >= LOGLEVEL_WBXML && self::getLogger()->HasSpecialLogUsers()); |
|
46 | + } |
|
47 | + |
|
48 | + /** |
|
49 | + * Writes a log line. |
|
50 | + * |
|
51 | + * @param int $loglevel one of the defined LOGLEVELS |
|
52 | + * @param string $message |
|
53 | + * @param bool $truncate indicate if the message should be truncated, default true |
|
54 | + */ |
|
55 | + public static function Write($loglevel, $message, $truncate = true) { |
|
56 | + // truncate messages longer than 10 KB |
|
57 | + $messagesize = strlen($message); |
|
58 | + if ($truncate && $messagesize > 10240) { |
|
59 | + $message = substr($message, 0, 10240) . sprintf(" <log message with %d bytes truncated>", $messagesize); |
|
60 | + } |
|
61 | + |
|
62 | + self::$lastLogs[$loglevel] = $message; |
|
63 | + |
|
64 | + try { |
|
65 | + self::getLogger()->Log($loglevel, $message); |
|
66 | + } |
|
67 | + catch (\Exception $e) { |
|
68 | + // @TODO How should we handle logging error ? |
|
69 | + // Ignore any error. |
|
70 | + } |
|
71 | + |
|
72 | + if ($loglevel & LOGLEVEL_WBXMLSTACK) { |
|
73 | + self::$wbxmlDebug .= $message . PHP_EOL; |
|
74 | + } |
|
75 | + } |
|
76 | + |
|
77 | + /** |
|
78 | + * Returns logged information about the WBXML stack. |
|
79 | + * |
|
80 | + * @return string |
|
81 | + */ |
|
82 | + public static function GetWBXMLDebugInfo() { |
|
83 | + return trim(self::$wbxmlDebug); |
|
84 | + } |
|
85 | + |
|
86 | + /** |
|
87 | + * Returns the last message logged for a log level. |
|
88 | + * |
|
89 | + * @param int $loglevel one of the defined LOGLEVELS |
|
90 | + * |
|
91 | + * @return string/false returns false if there was no message logged in that level |
|
92 | + */ |
|
93 | + public static function GetLastMessage($loglevel) { |
|
94 | + return (isset(self::$lastLogs[$loglevel])) ? self::$lastLogs[$loglevel] : false; |
|
95 | + } |
|
96 | + |
|
97 | + /** |
|
98 | + * If called, the authenticated current user gets an extra log-file. |
|
99 | + * |
|
100 | + * If called until the user is authenticated (e.g. at the end of IBackend->Logon()) all log |
|
101 | + * messages that happened until this point will also be logged. |
|
102 | + */ |
|
103 | + public static function SpecialLogUser() { |
|
104 | + self::getLogger()->SpecialLogUser(); |
|
105 | + } |
|
106 | + |
|
107 | + /** |
|
108 | + * Returns the logger object. If no logger has been initialized, FileLog will be initialized and returned. |
|
109 | + * |
|
110 | + * @throws Exception thrown if the logger class cannot be instantiated |
|
111 | + * |
|
112 | + * @return Log |
|
113 | + */ |
|
114 | + private static function getLogger() { |
|
115 | + if (!self::$logger) { |
|
116 | + global $specialLogUsers; // This variable comes from the configuration file (config.php) |
|
117 | + |
|
118 | + $logger = LOGBACKEND_CLASS; |
|
119 | + if (!class_exists($logger)) { |
|
120 | + $errmsg = 'The configured logging class `' . $logger . '` does not exist. Check your configuration.'; |
|
121 | + error_log($errmsg); |
|
122 | + |
|
123 | + throw new \Exception($errmsg); |
|
124 | + } |
|
125 | + |
|
126 | + // if there is an impersonated user it's used instead of the GET user |
|
127 | + if (Request::GetImpersonatedUser()) { |
|
128 | + $user = Request::GetImpersonatedUser(); |
|
129 | + } |
|
130 | + else { |
|
131 | + list($user) = Utils::SplitDomainUser(strtolower(Request::GetGETUser())); |
|
132 | + } |
|
133 | + |
|
134 | + self::$logger = new $logger(); |
|
135 | + self::$logger->SetUser($user); |
|
136 | + self::$logger->SetAuthUser(Request::GetAuthUser()); |
|
137 | + self::$logger->SetSpecialLogUsers($specialLogUsers); |
|
138 | + self::$logger->SetDevid(Request::GetDeviceID()); |
|
139 | + self::$logger->SetPid(@getmypid()); |
|
140 | + self::$logger->AfterInitialize(); |
|
141 | + } |
|
142 | + |
|
143 | + return self::$logger; |
|
144 | + } |
|
145 | + |
|
146 | + /*---------------------------------------------------------------------------------------------------------- |
|
147 | 147 | * private log stuff |
148 | 148 | */ |
149 | 149 | } |
@@ -154,67 +154,67 @@ discard block |
||
154 | 154 | |
155 | 155 | // TODO review error handler |
156 | 156 | function gsync_error_handler($errno, $errstr, $errfile, $errline) { |
157 | - if (defined('LOG_ERROR_MASK')) { |
|
158 | - $errno &= LOG_ERROR_MASK; |
|
159 | - } |
|
160 | - |
|
161 | - switch ($errno) { |
|
162 | - case 0: |
|
163 | - // logging disabled by LOG_ERROR_MASK |
|
164 | - break; |
|
165 | - |
|
166 | - case E_DEPRECATED: |
|
167 | - // do not handle this message |
|
168 | - break; |
|
169 | - |
|
170 | - case E_NOTICE: |
|
171 | - case E_WARNING: |
|
172 | - // TODO check if there is a better way to avoid these messages |
|
173 | - if (stripos($errfile, 'interprocessdata') !== false && stripos($errstr, 'shm_get_var()') !== false) { |
|
174 | - break; |
|
175 | - } |
|
176 | - SLog::Write(LOGLEVEL_WARN, "{$errfile}:{$errline} {$errstr} ({$errno})"); |
|
177 | - break; |
|
178 | - |
|
179 | - default: |
|
180 | - $bt = debug_backtrace(); |
|
181 | - SLog::Write(LOGLEVEL_ERROR, "trace error: {$errfile}:{$errline} {$errstr} ({$errno}) - backtrace: " . (count($bt) - 1) . " steps"); |
|
182 | - for ($i = 1, $bt_length = count($bt); $i < $bt_length; ++$i) { |
|
183 | - $file = $line = "unknown"; |
|
184 | - if (isset($bt[$i]['file'])) { |
|
185 | - $file = $bt[$i]['file']; |
|
186 | - } |
|
187 | - if (isset($bt[$i]['line'])) { |
|
188 | - $line = $bt[$i]['line']; |
|
189 | - } |
|
190 | - SLog::Write(LOGLEVEL_ERROR, "trace: {$i}:" . $file . ":" . $line . " - " . ((isset($bt[$i]['class'])) ? $bt[$i]['class'] . $bt[$i]['type'] : "") . $bt[$i]['function'] . "()"); |
|
191 | - } |
|
192 | - // throw new Exception("An error occurred."); |
|
193 | - break; |
|
194 | - } |
|
157 | + if (defined('LOG_ERROR_MASK')) { |
|
158 | + $errno &= LOG_ERROR_MASK; |
|
159 | + } |
|
160 | + |
|
161 | + switch ($errno) { |
|
162 | + case 0: |
|
163 | + // logging disabled by LOG_ERROR_MASK |
|
164 | + break; |
|
165 | + |
|
166 | + case E_DEPRECATED: |
|
167 | + // do not handle this message |
|
168 | + break; |
|
169 | + |
|
170 | + case E_NOTICE: |
|
171 | + case E_WARNING: |
|
172 | + // TODO check if there is a better way to avoid these messages |
|
173 | + if (stripos($errfile, 'interprocessdata') !== false && stripos($errstr, 'shm_get_var()') !== false) { |
|
174 | + break; |
|
175 | + } |
|
176 | + SLog::Write(LOGLEVEL_WARN, "{$errfile}:{$errline} {$errstr} ({$errno})"); |
|
177 | + break; |
|
178 | + |
|
179 | + default: |
|
180 | + $bt = debug_backtrace(); |
|
181 | + SLog::Write(LOGLEVEL_ERROR, "trace error: {$errfile}:{$errline} {$errstr} ({$errno}) - backtrace: " . (count($bt) - 1) . " steps"); |
|
182 | + for ($i = 1, $bt_length = count($bt); $i < $bt_length; ++$i) { |
|
183 | + $file = $line = "unknown"; |
|
184 | + if (isset($bt[$i]['file'])) { |
|
185 | + $file = $bt[$i]['file']; |
|
186 | + } |
|
187 | + if (isset($bt[$i]['line'])) { |
|
188 | + $line = $bt[$i]['line']; |
|
189 | + } |
|
190 | + SLog::Write(LOGLEVEL_ERROR, "trace: {$i}:" . $file . ":" . $line . " - " . ((isset($bt[$i]['class'])) ? $bt[$i]['class'] . $bt[$i]['type'] : "") . $bt[$i]['function'] . "()"); |
|
191 | + } |
|
192 | + // throw new Exception("An error occurred."); |
|
193 | + break; |
|
194 | + } |
|
195 | 195 | } |
196 | 196 | |
197 | 197 | error_reporting(E_ALL); |
198 | 198 | set_error_handler("gsync_error_handler"); |
199 | 199 | |
200 | 200 | function gsync_fatal_handler() { |
201 | - $errfile = "unknown file"; |
|
202 | - $errstr = "shutdown"; |
|
203 | - $errno = E_CORE_ERROR; |
|
204 | - $errline = 0; |
|
205 | - |
|
206 | - $error = error_get_last(); |
|
207 | - |
|
208 | - if ($error !== null) { |
|
209 | - $errno = $error["type"]; |
|
210 | - $errfile = $error["file"]; |
|
211 | - $errline = $error["line"]; |
|
212 | - $errstr = $error["message"]; |
|
213 | - |
|
214 | - // do NOT log PHP Notice, Warning, Deprecated or Strict as FATAL |
|
215 | - if ($errno & ~(E_NOTICE | E_WARNING | E_DEPRECATED | E_STRICT)) { |
|
216 | - SLog::Write(LOGLEVEL_FATAL, sprintf("Fatal error: %s:%d - %s (%s)", $errfile, $errline, $errstr, $errno)); |
|
217 | - } |
|
218 | - } |
|
201 | + $errfile = "unknown file"; |
|
202 | + $errstr = "shutdown"; |
|
203 | + $errno = E_CORE_ERROR; |
|
204 | + $errline = 0; |
|
205 | + |
|
206 | + $error = error_get_last(); |
|
207 | + |
|
208 | + if ($error !== null) { |
|
209 | + $errno = $error["type"]; |
|
210 | + $errfile = $error["file"]; |
|
211 | + $errline = $error["line"]; |
|
212 | + $errstr = $error["message"]; |
|
213 | + |
|
214 | + // do NOT log PHP Notice, Warning, Deprecated or Strict as FATAL |
|
215 | + if ($errno & ~(E_NOTICE | E_WARNING | E_DEPRECATED | E_STRICT)) { |
|
216 | + SLog::Write(LOGLEVEL_FATAL, sprintf("Fatal error: %s:%d - %s (%s)", $errfile, $errline, $errstr, $errno)); |
|
217 | + } |
|
218 | + } |
|
219 | 219 | } |
220 | 220 | register_shutdown_function("gsync_fatal_handler"); |
@@ -56,7 +56,7 @@ discard block |
||
56 | 56 | // truncate messages longer than 10 KB |
57 | 57 | $messagesize = strlen($message); |
58 | 58 | if ($truncate && $messagesize > 10240) { |
59 | - $message = substr($message, 0, 10240) . sprintf(" <log message with %d bytes truncated>", $messagesize); |
|
59 | + $message = substr($message, 0, 10240).sprintf(" <log message with %d bytes truncated>", $messagesize); |
|
60 | 60 | } |
61 | 61 | |
62 | 62 | self::$lastLogs[$loglevel] = $message; |
@@ -70,7 +70,7 @@ discard block |
||
70 | 70 | } |
71 | 71 | |
72 | 72 | if ($loglevel & LOGLEVEL_WBXMLSTACK) { |
73 | - self::$wbxmlDebug .= $message . PHP_EOL; |
|
73 | + self::$wbxmlDebug .= $message.PHP_EOL; |
|
74 | 74 | } |
75 | 75 | } |
76 | 76 | |
@@ -117,7 +117,7 @@ discard block |
||
117 | 117 | |
118 | 118 | $logger = LOGBACKEND_CLASS; |
119 | 119 | if (!class_exists($logger)) { |
120 | - $errmsg = 'The configured logging class `' . $logger . '` does not exist. Check your configuration.'; |
|
120 | + $errmsg = 'The configured logging class `'.$logger.'` does not exist. Check your configuration.'; |
|
121 | 121 | error_log($errmsg); |
122 | 122 | |
123 | 123 | throw new \Exception($errmsg); |
@@ -178,7 +178,7 @@ discard block |
||
178 | 178 | |
179 | 179 | default: |
180 | 180 | $bt = debug_backtrace(); |
181 | - SLog::Write(LOGLEVEL_ERROR, "trace error: {$errfile}:{$errline} {$errstr} ({$errno}) - backtrace: " . (count($bt) - 1) . " steps"); |
|
181 | + SLog::Write(LOGLEVEL_ERROR, "trace error: {$errfile}:{$errline} {$errstr} ({$errno}) - backtrace: ".(count($bt) - 1)." steps"); |
|
182 | 182 | for ($i = 1, $bt_length = count($bt); $i < $bt_length; ++$i) { |
183 | 183 | $file = $line = "unknown"; |
184 | 184 | if (isset($bt[$i]['file'])) { |
@@ -187,7 +187,7 @@ discard block |
||
187 | 187 | if (isset($bt[$i]['line'])) { |
188 | 188 | $line = $bt[$i]['line']; |
189 | 189 | } |
190 | - SLog::Write(LOGLEVEL_ERROR, "trace: {$i}:" . $file . ":" . $line . " - " . ((isset($bt[$i]['class'])) ? $bt[$i]['class'] . $bt[$i]['type'] : "") . $bt[$i]['function'] . "()"); |
|
190 | + SLog::Write(LOGLEVEL_ERROR, "trace: {$i}:".$file.":".$line." - ".((isset($bt[$i]['class'])) ? $bt[$i]['class'].$bt[$i]['type'] : "").$bt[$i]['function']."()"); |
|
191 | 191 | } |
192 | 192 | // throw new Exception("An error occurred."); |
193 | 193 | break; |
@@ -212,7 +212,7 @@ discard block |
||
212 | 212 | $errstr = $error["message"]; |
213 | 213 | |
214 | 214 | // do NOT log PHP Notice, Warning, Deprecated or Strict as FATAL |
215 | - if ($errno & ~(E_NOTICE | E_WARNING | E_DEPRECATED | E_STRICT)) { |
|
215 | + if ($errno & ~(E_NOTICE|E_WARNING|E_DEPRECATED|E_STRICT)) { |
|
216 | 216 | SLog::Write(LOGLEVEL_FATAL, sprintf("Fatal error: %s:%d - %s (%s)", $errfile, $errline, $errstr, $errno)); |
217 | 217 | } |
218 | 218 | } |
@@ -63,8 +63,7 @@ discard block |
||
63 | 63 | |
64 | 64 | try { |
65 | 65 | self::getLogger()->Log($loglevel, $message); |
66 | - } |
|
67 | - catch (\Exception $e) { |
|
66 | + } catch (\Exception $e) { |
|
68 | 67 | // @TODO How should we handle logging error ? |
69 | 68 | // Ignore any error. |
70 | 69 | } |
@@ -126,8 +125,7 @@ discard block |
||
126 | 125 | // if there is an impersonated user it's used instead of the GET user |
127 | 126 | if (Request::GetImpersonatedUser()) { |
128 | 127 | $user = Request::GetImpersonatedUser(); |
129 | - } |
|
130 | - else { |
|
128 | + } else { |
|
131 | 129 | list($user) = Utils::SplitDomainUser(strtolower(Request::GetGETUser())); |
132 | 130 | } |
133 | 131 |
@@ -8,491 +8,491 @@ |
||
8 | 8 | */ |
9 | 9 | |
10 | 10 | class WBXMLDecoder extends WBXMLDefs { |
11 | - private $in; |
|
12 | - private $inLog; |
|
13 | - private $tagcp = 0; |
|
14 | - private $ungetbuffer; |
|
15 | - private $log = false; |
|
16 | - private $logStack = []; |
|
17 | - private $inputBuffer = ""; |
|
18 | - private $isWBXML = true; |
|
19 | - private static $loopCounter = []; |
|
20 | - public const MAXLOOP = 5000; |
|
21 | - public const VERSION = 0x03; |
|
22 | - |
|
23 | - /** |
|
24 | - * Counts the amount of times a code part has been executed. |
|
25 | - * When being executed too often, the code throws a WBMXLException. |
|
26 | - * |
|
27 | - * @param string $name |
|
28 | - * |
|
29 | - * @throws WBXMLException |
|
30 | - * |
|
31 | - * @return bool |
|
32 | - */ |
|
33 | - public static function InWhile($name) { |
|
34 | - if (!isset(self::$loopCounter[$name])) { |
|
35 | - self::$loopCounter[$name] = 0; |
|
36 | - } |
|
37 | - else { |
|
38 | - ++self::$loopCounter[$name]; |
|
39 | - } |
|
40 | - |
|
41 | - if (self::$loopCounter[$name] > self::MAXLOOP) { |
|
42 | - throw new WBXMLException(sprintf("Loop count in while too high, code '%s' exceeded max. amount of permitted loops", $name)); |
|
43 | - } |
|
44 | - |
|
45 | - return true; |
|
46 | - } |
|
47 | - |
|
48 | - /** |
|
49 | - * Resets the inWhile counter. |
|
50 | - * |
|
51 | - * @param string $name |
|
52 | - * |
|
53 | - * @return bool |
|
54 | - */ |
|
55 | - public static function ResetInWhile($name) { |
|
56 | - if (isset(self::$loopCounter[$name])) { |
|
57 | - unset(self::$loopCounter[$name]); |
|
58 | - } |
|
59 | - |
|
60 | - return true; |
|
61 | - } |
|
62 | - |
|
63 | - /** |
|
64 | - * WBXML Decode Constructor |
|
65 | - * We only handle ActiveSync WBXML, which is a subset of WBXML. |
|
66 | - * |
|
67 | - * @param stream $input the incoming data stream |
|
68 | - */ |
|
69 | - public function __construct($input) { |
|
70 | - $this->log = SLog::IsWbxmlDebugEnabled(); |
|
71 | - |
|
72 | - $this->in = $input; |
|
73 | - |
|
74 | - $version = $this->getByte(); |
|
75 | - if ($version != self::VERSION) { |
|
76 | - $this->inputBuffer .= chr($version); |
|
77 | - $this->isWBXML = false; |
|
78 | - |
|
79 | - return; |
|
80 | - } |
|
81 | - |
|
82 | - $publicid = $this->getMBUInt(); |
|
83 | - if ($publicid !== 1) { |
|
84 | - throw new WBXMLException("Wrong publicid : " . $publicid); |
|
85 | - } |
|
86 | - |
|
87 | - $charsetid = $this->getMBUInt(); |
|
88 | - if ($charsetid !== 106) { |
|
89 | - throw new WBXMLException("Wrong charset : " . $charsetid); |
|
90 | - } |
|
91 | - |
|
92 | - $stringtablesize = $this->getMBUInt(); |
|
93 | - if ($stringtablesize !== 0) { |
|
94 | - throw new WBXMLException("Wrong string table size : " . $stringtablesize); |
|
95 | - } |
|
96 | - } |
|
97 | - |
|
98 | - /** |
|
99 | - * Returns either start, content or end, and auto-concatenates successive content. |
|
100 | - * |
|
101 | - * @return element/value |
|
102 | - */ |
|
103 | - public function getElement() { |
|
104 | - $element = $this->getToken(); |
|
105 | - |
|
106 | - switch ($element[EN_TYPE]) { |
|
107 | - case EN_TYPE_STARTTAG: |
|
108 | - return $element; |
|
109 | - |
|
110 | - case EN_TYPE_ENDTAG: |
|
111 | - return $element; |
|
112 | - |
|
113 | - case EN_TYPE_CONTENT: |
|
114 | - WBXMLDecoder::ResetInWhile("decoderGetElement"); |
|
115 | - while (WBXMLDecoder::InWhile("decoderGetElement")) { |
|
116 | - $next = $this->getToken(); |
|
117 | - if ($next == false) { |
|
118 | - return false; |
|
119 | - } |
|
120 | - if ($next[EN_TYPE] == EN_CONTENT) { |
|
121 | - $element[EN_CONTENT] .= $next[EN_CONTENT]; |
|
122 | - } |
|
123 | - else { |
|
124 | - $this->ungetElement($next); |
|
125 | - |
|
126 | - break; |
|
127 | - } |
|
128 | - } |
|
129 | - |
|
130 | - return $element; |
|
131 | - } |
|
132 | - |
|
133 | - return false; |
|
134 | - } |
|
135 | - |
|
136 | - /** |
|
137 | - * Get a peek at the next element. |
|
138 | - * |
|
139 | - * @return element |
|
140 | - */ |
|
141 | - public function peek() { |
|
142 | - $element = $this->getElement(); |
|
143 | - $this->ungetElement($element); |
|
144 | - |
|
145 | - return $element; |
|
146 | - } |
|
147 | - |
|
148 | - /** |
|
149 | - * Get the element of a StartTag. |
|
150 | - * |
|
151 | - * @param $tag |
|
152 | - * |
|
153 | - * @return element/boolean returns false if not available |
|
154 | - */ |
|
155 | - public function getElementStartTag($tag) { |
|
156 | - $element = $this->getToken(); |
|
157 | - |
|
158 | - if (!$element) { |
|
159 | - return false; |
|
160 | - } |
|
161 | - |
|
162 | - if ($element[EN_TYPE] == EN_TYPE_STARTTAG && $element[EN_TAG] == $tag) { |
|
163 | - return $element; |
|
164 | - } |
|
165 | - |
|
166 | - SLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("WBXMLDecoder->getElementStartTag(): unmatched WBXML tag: '%s' matching '%s' type '%s' flags '%s'", $tag, ((isset($element[EN_TAG])) ? $element[EN_TAG] : ""), ((isset($element[EN_TYPE])) ? $element[EN_TYPE] : ""), ((isset($element[EN_FLAGS])) ? $element[EN_FLAGS] : ""))); |
|
167 | - $this->ungetElement($element); |
|
168 | - |
|
169 | - return false; |
|
170 | - } |
|
171 | - |
|
172 | - /** |
|
173 | - * Get the element of a EndTag. |
|
174 | - * |
|
175 | - * @return element/boolean returns false if not available |
|
176 | - */ |
|
177 | - public function getElementEndTag() { |
|
178 | - $element = $this->getToken(); |
|
179 | - |
|
180 | - if ($element[EN_TYPE] == EN_TYPE_ENDTAG) { |
|
181 | - return $element; |
|
182 | - } |
|
183 | - |
|
184 | - SLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("WBXMLDecoder->getElementEndTag(): unmatched WBXML tag: '%s' type '%s' flags '%s'", ((isset($element[EN_TAG])) ? $element[EN_TAG] : ""), ((isset($element[EN_TYPE])) ? $element[EN_TYPE] : ""), ((isset($element[EN_FLAGS])) ? $element[EN_FLAGS] : ""))); |
|
185 | - |
|
186 | - $bt = debug_backtrace(); |
|
187 | - SLog::Write(LOGLEVEL_ERROR, sprintf("WBXMLDecoder->getElementEndTag(): could not read end tag in '%s'. Please enable the LOGLEVEL_WBXML and send the log to the grommunio-sync dev team.", $bt[0]["file"] . ":" . $bt[0]["line"])); |
|
188 | - |
|
189 | - // log the remaining wbxml content |
|
190 | - $this->ungetElement($element); |
|
191 | - while ($el = $this->getElement()); |
|
192 | - |
|
193 | - return false; |
|
194 | - } |
|
195 | - |
|
196 | - /** |
|
197 | - * Get the content of an element. |
|
198 | - * |
|
199 | - * @return string/boolean returns false if not available |
|
200 | - */ |
|
201 | - public function getElementContent() { |
|
202 | - $element = $this->getToken(); |
|
203 | - |
|
204 | - if ($element[EN_TYPE] == EN_TYPE_CONTENT) { |
|
205 | - return $element[EN_CONTENT]; |
|
206 | - } |
|
207 | - |
|
208 | - SLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("WBXMLDecoder->getElementContent(): unmatched WBXML content: '%s' type '%s' flags '%s'", ((isset($element[EN_TAG])) ? $element[EN_TAG] : ""), ((isset($element[EN_TYPE])) ? $element[EN_TYPE] : ""), ((isset($element[EN_FLAGS])) ? $element[EN_FLAGS] : ""))); |
|
209 | - $this->ungetElement($element); |
|
210 | - |
|
211 | - return false; |
|
212 | - } |
|
213 | - |
|
214 | - /** |
|
215 | - * 'Ungets' an element writing it into a buffer to be 'get' again. |
|
216 | - * |
|
217 | - * @param element $element the element to get ungetten |
|
218 | - * |
|
219 | - * @return |
|
220 | - */ |
|
221 | - public function ungetElement($element) { |
|
222 | - if ($this->ungetbuffer) { |
|
223 | - SLog::Write(LOGLEVEL_ERROR, sprintf("WBXMLDecoder->ungetElement(): WBXML double unget on tag: '%s' type '%s' flags '%s'", ((isset($element[EN_TAG])) ? $element[EN_TAG] : ""), ((isset($element[EN_TYPE])) ? $element[EN_TYPE] : ""), ((isset($element[EN_FLAGS])) ? $element[EN_FLAGS] : ""))); |
|
224 | - } |
|
225 | - |
|
226 | - $this->ungetbuffer = $element; |
|
227 | - } |
|
228 | - |
|
229 | - /** |
|
230 | - * Returns the plain input stream. |
|
231 | - * |
|
232 | - * @return string |
|
233 | - */ |
|
234 | - public function GetPlainInputStream() { |
|
235 | - return $this->inputBuffer . stream_get_contents($this->in); |
|
236 | - } |
|
237 | - |
|
238 | - /** |
|
239 | - * Returns if the input is WBXML. |
|
240 | - * |
|
241 | - * @return bool |
|
242 | - */ |
|
243 | - public function IsWBXML() { |
|
244 | - return $this->isWBXML; |
|
245 | - } |
|
246 | - |
|
247 | - /** |
|
248 | - * Reads the remaining data from the input stream. |
|
249 | - */ |
|
250 | - public function readRemainingData() { |
|
251 | - SLog::Write(LOGLEVEL_DEBUG, "WBXMLDecoder->readRemainingData() reading remaining data from input stream"); |
|
252 | - while ($this->getElement()); |
|
253 | - } |
|
254 | - |
|
255 | - /*---------------------------------------------------------------------------------------------------------- |
|
11 | + private $in; |
|
12 | + private $inLog; |
|
13 | + private $tagcp = 0; |
|
14 | + private $ungetbuffer; |
|
15 | + private $log = false; |
|
16 | + private $logStack = []; |
|
17 | + private $inputBuffer = ""; |
|
18 | + private $isWBXML = true; |
|
19 | + private static $loopCounter = []; |
|
20 | + public const MAXLOOP = 5000; |
|
21 | + public const VERSION = 0x03; |
|
22 | + |
|
23 | + /** |
|
24 | + * Counts the amount of times a code part has been executed. |
|
25 | + * When being executed too often, the code throws a WBMXLException. |
|
26 | + * |
|
27 | + * @param string $name |
|
28 | + * |
|
29 | + * @throws WBXMLException |
|
30 | + * |
|
31 | + * @return bool |
|
32 | + */ |
|
33 | + public static function InWhile($name) { |
|
34 | + if (!isset(self::$loopCounter[$name])) { |
|
35 | + self::$loopCounter[$name] = 0; |
|
36 | + } |
|
37 | + else { |
|
38 | + ++self::$loopCounter[$name]; |
|
39 | + } |
|
40 | + |
|
41 | + if (self::$loopCounter[$name] > self::MAXLOOP) { |
|
42 | + throw new WBXMLException(sprintf("Loop count in while too high, code '%s' exceeded max. amount of permitted loops", $name)); |
|
43 | + } |
|
44 | + |
|
45 | + return true; |
|
46 | + } |
|
47 | + |
|
48 | + /** |
|
49 | + * Resets the inWhile counter. |
|
50 | + * |
|
51 | + * @param string $name |
|
52 | + * |
|
53 | + * @return bool |
|
54 | + */ |
|
55 | + public static function ResetInWhile($name) { |
|
56 | + if (isset(self::$loopCounter[$name])) { |
|
57 | + unset(self::$loopCounter[$name]); |
|
58 | + } |
|
59 | + |
|
60 | + return true; |
|
61 | + } |
|
62 | + |
|
63 | + /** |
|
64 | + * WBXML Decode Constructor |
|
65 | + * We only handle ActiveSync WBXML, which is a subset of WBXML. |
|
66 | + * |
|
67 | + * @param stream $input the incoming data stream |
|
68 | + */ |
|
69 | + public function __construct($input) { |
|
70 | + $this->log = SLog::IsWbxmlDebugEnabled(); |
|
71 | + |
|
72 | + $this->in = $input; |
|
73 | + |
|
74 | + $version = $this->getByte(); |
|
75 | + if ($version != self::VERSION) { |
|
76 | + $this->inputBuffer .= chr($version); |
|
77 | + $this->isWBXML = false; |
|
78 | + |
|
79 | + return; |
|
80 | + } |
|
81 | + |
|
82 | + $publicid = $this->getMBUInt(); |
|
83 | + if ($publicid !== 1) { |
|
84 | + throw new WBXMLException("Wrong publicid : " . $publicid); |
|
85 | + } |
|
86 | + |
|
87 | + $charsetid = $this->getMBUInt(); |
|
88 | + if ($charsetid !== 106) { |
|
89 | + throw new WBXMLException("Wrong charset : " . $charsetid); |
|
90 | + } |
|
91 | + |
|
92 | + $stringtablesize = $this->getMBUInt(); |
|
93 | + if ($stringtablesize !== 0) { |
|
94 | + throw new WBXMLException("Wrong string table size : " . $stringtablesize); |
|
95 | + } |
|
96 | + } |
|
97 | + |
|
98 | + /** |
|
99 | + * Returns either start, content or end, and auto-concatenates successive content. |
|
100 | + * |
|
101 | + * @return element/value |
|
102 | + */ |
|
103 | + public function getElement() { |
|
104 | + $element = $this->getToken(); |
|
105 | + |
|
106 | + switch ($element[EN_TYPE]) { |
|
107 | + case EN_TYPE_STARTTAG: |
|
108 | + return $element; |
|
109 | + |
|
110 | + case EN_TYPE_ENDTAG: |
|
111 | + return $element; |
|
112 | + |
|
113 | + case EN_TYPE_CONTENT: |
|
114 | + WBXMLDecoder::ResetInWhile("decoderGetElement"); |
|
115 | + while (WBXMLDecoder::InWhile("decoderGetElement")) { |
|
116 | + $next = $this->getToken(); |
|
117 | + if ($next == false) { |
|
118 | + return false; |
|
119 | + } |
|
120 | + if ($next[EN_TYPE] == EN_CONTENT) { |
|
121 | + $element[EN_CONTENT] .= $next[EN_CONTENT]; |
|
122 | + } |
|
123 | + else { |
|
124 | + $this->ungetElement($next); |
|
125 | + |
|
126 | + break; |
|
127 | + } |
|
128 | + } |
|
129 | + |
|
130 | + return $element; |
|
131 | + } |
|
132 | + |
|
133 | + return false; |
|
134 | + } |
|
135 | + |
|
136 | + /** |
|
137 | + * Get a peek at the next element. |
|
138 | + * |
|
139 | + * @return element |
|
140 | + */ |
|
141 | + public function peek() { |
|
142 | + $element = $this->getElement(); |
|
143 | + $this->ungetElement($element); |
|
144 | + |
|
145 | + return $element; |
|
146 | + } |
|
147 | + |
|
148 | + /** |
|
149 | + * Get the element of a StartTag. |
|
150 | + * |
|
151 | + * @param $tag |
|
152 | + * |
|
153 | + * @return element/boolean returns false if not available |
|
154 | + */ |
|
155 | + public function getElementStartTag($tag) { |
|
156 | + $element = $this->getToken(); |
|
157 | + |
|
158 | + if (!$element) { |
|
159 | + return false; |
|
160 | + } |
|
161 | + |
|
162 | + if ($element[EN_TYPE] == EN_TYPE_STARTTAG && $element[EN_TAG] == $tag) { |
|
163 | + return $element; |
|
164 | + } |
|
165 | + |
|
166 | + SLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("WBXMLDecoder->getElementStartTag(): unmatched WBXML tag: '%s' matching '%s' type '%s' flags '%s'", $tag, ((isset($element[EN_TAG])) ? $element[EN_TAG] : ""), ((isset($element[EN_TYPE])) ? $element[EN_TYPE] : ""), ((isset($element[EN_FLAGS])) ? $element[EN_FLAGS] : ""))); |
|
167 | + $this->ungetElement($element); |
|
168 | + |
|
169 | + return false; |
|
170 | + } |
|
171 | + |
|
172 | + /** |
|
173 | + * Get the element of a EndTag. |
|
174 | + * |
|
175 | + * @return element/boolean returns false if not available |
|
176 | + */ |
|
177 | + public function getElementEndTag() { |
|
178 | + $element = $this->getToken(); |
|
179 | + |
|
180 | + if ($element[EN_TYPE] == EN_TYPE_ENDTAG) { |
|
181 | + return $element; |
|
182 | + } |
|
183 | + |
|
184 | + SLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("WBXMLDecoder->getElementEndTag(): unmatched WBXML tag: '%s' type '%s' flags '%s'", ((isset($element[EN_TAG])) ? $element[EN_TAG] : ""), ((isset($element[EN_TYPE])) ? $element[EN_TYPE] : ""), ((isset($element[EN_FLAGS])) ? $element[EN_FLAGS] : ""))); |
|
185 | + |
|
186 | + $bt = debug_backtrace(); |
|
187 | + SLog::Write(LOGLEVEL_ERROR, sprintf("WBXMLDecoder->getElementEndTag(): could not read end tag in '%s'. Please enable the LOGLEVEL_WBXML and send the log to the grommunio-sync dev team.", $bt[0]["file"] . ":" . $bt[0]["line"])); |
|
188 | + |
|
189 | + // log the remaining wbxml content |
|
190 | + $this->ungetElement($element); |
|
191 | + while ($el = $this->getElement()); |
|
192 | + |
|
193 | + return false; |
|
194 | + } |
|
195 | + |
|
196 | + /** |
|
197 | + * Get the content of an element. |
|
198 | + * |
|
199 | + * @return string/boolean returns false if not available |
|
200 | + */ |
|
201 | + public function getElementContent() { |
|
202 | + $element = $this->getToken(); |
|
203 | + |
|
204 | + if ($element[EN_TYPE] == EN_TYPE_CONTENT) { |
|
205 | + return $element[EN_CONTENT]; |
|
206 | + } |
|
207 | + |
|
208 | + SLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("WBXMLDecoder->getElementContent(): unmatched WBXML content: '%s' type '%s' flags '%s'", ((isset($element[EN_TAG])) ? $element[EN_TAG] : ""), ((isset($element[EN_TYPE])) ? $element[EN_TYPE] : ""), ((isset($element[EN_FLAGS])) ? $element[EN_FLAGS] : ""))); |
|
209 | + $this->ungetElement($element); |
|
210 | + |
|
211 | + return false; |
|
212 | + } |
|
213 | + |
|
214 | + /** |
|
215 | + * 'Ungets' an element writing it into a buffer to be 'get' again. |
|
216 | + * |
|
217 | + * @param element $element the element to get ungetten |
|
218 | + * |
|
219 | + * @return |
|
220 | + */ |
|
221 | + public function ungetElement($element) { |
|
222 | + if ($this->ungetbuffer) { |
|
223 | + SLog::Write(LOGLEVEL_ERROR, sprintf("WBXMLDecoder->ungetElement(): WBXML double unget on tag: '%s' type '%s' flags '%s'", ((isset($element[EN_TAG])) ? $element[EN_TAG] : ""), ((isset($element[EN_TYPE])) ? $element[EN_TYPE] : ""), ((isset($element[EN_FLAGS])) ? $element[EN_FLAGS] : ""))); |
|
224 | + } |
|
225 | + |
|
226 | + $this->ungetbuffer = $element; |
|
227 | + } |
|
228 | + |
|
229 | + /** |
|
230 | + * Returns the plain input stream. |
|
231 | + * |
|
232 | + * @return string |
|
233 | + */ |
|
234 | + public function GetPlainInputStream() { |
|
235 | + return $this->inputBuffer . stream_get_contents($this->in); |
|
236 | + } |
|
237 | + |
|
238 | + /** |
|
239 | + * Returns if the input is WBXML. |
|
240 | + * |
|
241 | + * @return bool |
|
242 | + */ |
|
243 | + public function IsWBXML() { |
|
244 | + return $this->isWBXML; |
|
245 | + } |
|
246 | + |
|
247 | + /** |
|
248 | + * Reads the remaining data from the input stream. |
|
249 | + */ |
|
250 | + public function readRemainingData() { |
|
251 | + SLog::Write(LOGLEVEL_DEBUG, "WBXMLDecoder->readRemainingData() reading remaining data from input stream"); |
|
252 | + while ($this->getElement()); |
|
253 | + } |
|
254 | + |
|
255 | + /*---------------------------------------------------------------------------------------------------------- |
|
256 | 256 | * Private WBXMLDecoder stuff |
257 | 257 | */ |
258 | 258 | |
259 | - /** |
|
260 | - * Returns the next token. |
|
261 | - * |
|
262 | - * @return token |
|
263 | - */ |
|
264 | - private function getToken() { |
|
265 | - // See if there's something in the ungetBuffer |
|
266 | - if ($this->ungetbuffer) { |
|
267 | - $element = $this->ungetbuffer; |
|
268 | - $this->ungetbuffer = false; |
|
269 | - |
|
270 | - return $element; |
|
271 | - } |
|
272 | - |
|
273 | - $el = $this->_getToken(); |
|
274 | - if ($this->log && $el) { |
|
275 | - $this->logToken($el); |
|
276 | - } |
|
277 | - |
|
278 | - return $el; |
|
279 | - } |
|
280 | - |
|
281 | - /** |
|
282 | - * Log the a token to SLog. |
|
283 | - * |
|
284 | - * @param string $el token |
|
285 | - * |
|
286 | - * @return |
|
287 | - */ |
|
288 | - private function logToken($el) { |
|
289 | - $spaces = str_repeat(" ", count($this->logStack)); |
|
290 | - |
|
291 | - switch ($el[EN_TYPE]) { |
|
292 | - case EN_TYPE_STARTTAG: |
|
293 | - if ($el[EN_FLAGS] & EN_FLAGS_CONTENT) { |
|
294 | - SLog::Write(LOGLEVEL_WBXML, "I " . $spaces . " <" . $el[EN_TAG] . ">"); |
|
295 | - array_push($this->logStack, $el[EN_TAG]); |
|
296 | - } |
|
297 | - else { |
|
298 | - SLog::Write(LOGLEVEL_WBXML, "I " . $spaces . " <" . $el[EN_TAG] . "/>"); |
|
299 | - } |
|
300 | - break; |
|
301 | - |
|
302 | - case EN_TYPE_ENDTAG: |
|
303 | - $tag = array_pop($this->logStack); |
|
304 | - SLog::Write(LOGLEVEL_WBXML, "I " . $spaces . "</" . $tag . ">"); |
|
305 | - break; |
|
306 | - |
|
307 | - case EN_TYPE_CONTENT: |
|
308 | - // as we concatenate the string here, the entire content is copied. |
|
309 | - // when sending an email with an attachment this single log line (which is never logged in INFO) |
|
310 | - // requires easily additional 20 MB of RAM. See https://jira.z-hub.io/browse/ZP-1159 |
|
311 | - $messagesize = strlen($el[EN_CONTENT]); |
|
312 | - if ($messagesize > 10240 && !defined('WBXML_DEBUGGING')) { |
|
313 | - $content = substr($el[EN_CONTENT], 0, 10240) . sprintf(" <log message with %d bytes truncated>", $messagesize); |
|
314 | - } |
|
315 | - else { |
|
316 | - $content = $el[EN_CONTENT]; |
|
317 | - } |
|
318 | - // Log but make sure it's not truncated again (will be slightly bigger than 10KB) |
|
319 | - SLog::Write(LOGLEVEL_WBXML, "I " . $spaces . " " . $content, false); |
|
320 | - break; |
|
321 | - } |
|
322 | - } |
|
323 | - |
|
324 | - /** |
|
325 | - * Returns either a start tag, content or end tag. |
|
326 | - * |
|
327 | - * @return |
|
328 | - */ |
|
329 | - private function _getToken() { |
|
330 | - // Get the data from the input stream |
|
331 | - $element = []; |
|
332 | - |
|
333 | - WBXMLDecoder::ResetInWhile("decoderGetToken"); |
|
334 | - while (WBXMLDecoder::InWhile("decoderGetToken")) { |
|
335 | - $byte = fread($this->in, 1); |
|
336 | - if ($byte === "" || $byte === false) { |
|
337 | - break; |
|
338 | - } |
|
339 | - $byte = ord($byte); |
|
340 | - |
|
341 | - switch ($byte) { |
|
342 | - case self::WBXML_SWITCH_PAGE: |
|
343 | - $this->tagcp = $this->getByte(); |
|
344 | - break; |
|
345 | - |
|
346 | - case self::WBXML_END: |
|
347 | - $element[EN_TYPE] = EN_TYPE_ENDTAG; |
|
348 | - |
|
349 | - return $element; |
|
350 | - |
|
351 | - case self::WBXML_STR_I: |
|
352 | - $element[EN_TYPE] = EN_TYPE_CONTENT; |
|
353 | - $element[EN_CONTENT] = $this->getTermStr(); |
|
354 | - |
|
355 | - return $element; |
|
356 | - |
|
357 | - case self::WBXML_OPAQUE: |
|
358 | - $length = $this->getMBUInt(); |
|
359 | - $element[EN_TYPE] = EN_TYPE_CONTENT; |
|
360 | - $element[EN_CONTENT] = $this->getOpaque($length); |
|
361 | - |
|
362 | - return $element; |
|
363 | - |
|
364 | - case self::WBXML_ENTITY: |
|
365 | - case self::WBXML_LITERAL: |
|
366 | - case self::WBXML_EXT_I_0: |
|
367 | - case self::WBXML_EXT_I_1: |
|
368 | - case self::WBXML_EXT_I_2: |
|
369 | - case self::WBXML_PI: |
|
370 | - case self::WBXML_LITERAL_C: |
|
371 | - case self::WBXML_EXT_T_0: |
|
372 | - case self::WBXML_EXT_T_1: |
|
373 | - case self::WBXML_EXT_T_2: |
|
374 | - case self::WBXML_STR_T: |
|
375 | - case self::WBXML_LITERAL_A: |
|
376 | - case self::WBXML_EXT_0: |
|
377 | - case self::WBXML_EXT_1: |
|
378 | - case self::WBXML_EXT_2: |
|
379 | - case self::WBXML_LITERAL_AC: |
|
380 | - throw new WBXMLException("Invalid token :" . $byte); |
|
381 | - |
|
382 | - default: |
|
383 | - if ($byte & self::WBXML_WITH_ATTRIBUTES) { |
|
384 | - throw new WBXMLException("Attributes are not allowed :" . $byte); |
|
385 | - } |
|
386 | - $element[EN_TYPE] = EN_TYPE_STARTTAG; |
|
387 | - $element[EN_TAG] = $this->getMapping($this->tagcp, $byte & 0x3F); |
|
388 | - $element[EN_FLAGS] = ($byte & self::WBXML_WITH_CONTENT ? EN_FLAGS_CONTENT : 0); |
|
389 | - |
|
390 | - return $element; |
|
391 | - } |
|
392 | - } |
|
393 | - } |
|
394 | - |
|
395 | - /** |
|
396 | - * Reads from the stream until getting a string terminator. |
|
397 | - * |
|
398 | - * @return string |
|
399 | - */ |
|
400 | - private function getTermStr() { |
|
401 | - if (defined('WBXML_DEBUGGING') && WBXML_DEBUGGING === true) { |
|
402 | - $str = ""; |
|
403 | - while (1) { |
|
404 | - $in = $this->getByte(); |
|
405 | - if ($in == 0) { |
|
406 | - break; |
|
407 | - } |
|
408 | - |
|
409 | - $str .= chr($in); |
|
410 | - } |
|
411 | - |
|
412 | - return $str; |
|
413 | - } |
|
414 | - |
|
415 | - // there is no unlimited "length" for stream_get_line, |
|
416 | - // so we use a huge value for "length" param (1Gb) |
|
417 | - // (0 == PHP_SOCK_CHUNK_SIZE (8192)) |
|
418 | - // internally php read at most PHP_SOCK_CHUNK_SIZE at a time, |
|
419 | - // so we can use a huge value for "length" without problem |
|
420 | - return stream_get_line($this->in, 1073741824, "\0"); |
|
421 | - } |
|
422 | - |
|
423 | - /** |
|
424 | - * Reads $len from the input stream. |
|
425 | - * |
|
426 | - * @param int $len |
|
427 | - * |
|
428 | - * @return string |
|
429 | - */ |
|
430 | - private function getOpaque($len) { |
|
431 | - $d = stream_get_contents($this->in, $len); |
|
432 | - if ($d === false) { |
|
433 | - throw new HTTPReturnCodeException("WBXMLDecoder->getOpaque(): stream_get_contents === false", HTTP_CODE_500, null, LOGLEVEL_WARN); |
|
434 | - } |
|
435 | - $l = strlen($d); |
|
436 | - if ($l !== $len) { |
|
437 | - throw new HTTPReturnCodeException("WBXMLDecoder->getOpaque(): only {$l} byte read instead of {$len}", HTTP_CODE_500, null, LOGLEVEL_WARN); |
|
438 | - } |
|
439 | - |
|
440 | - return $d; |
|
441 | - } |
|
442 | - |
|
443 | - /** |
|
444 | - * Reads one byte from the input stream. |
|
445 | - * |
|
446 | - * @return int |
|
447 | - */ |
|
448 | - private function getByte() { |
|
449 | - $ch = fread($this->in, 1); |
|
450 | - if (strlen($ch) > 0) { |
|
451 | - return ord($ch); |
|
452 | - } |
|
453 | - } |
|
454 | - |
|
455 | - /** |
|
456 | - * Reads string length from the input stream. |
|
457 | - * |
|
458 | - * @return |
|
459 | - */ |
|
460 | - private function getMBUInt() { |
|
461 | - $uint = 0; |
|
462 | - |
|
463 | - while (1) { |
|
464 | - $byte = $this->getByte(); |
|
465 | - |
|
466 | - $uint |= $byte & 0x7F; |
|
467 | - |
|
468 | - if ($byte & 0x80) { |
|
469 | - $uint = $uint << 7; |
|
470 | - } |
|
471 | - else { |
|
472 | - break; |
|
473 | - } |
|
474 | - } |
|
475 | - |
|
476 | - return $uint; |
|
477 | - } |
|
478 | - |
|
479 | - /** |
|
480 | - * Returns the mapping for a specified codepage and id. |
|
481 | - * |
|
482 | - * @param $cp codepage |
|
483 | - * @param $id |
|
484 | - * |
|
485 | - * @return string |
|
486 | - */ |
|
487 | - private function getMapping($cp, $id) { |
|
488 | - if (!isset($this->dtd["codes"][$cp]) || !isset($this->dtd["codes"][$cp][$id])) { |
|
489 | - return false; |
|
490 | - } |
|
491 | - |
|
492 | - if (isset($this->dtd["namespaces"][$cp])) { |
|
493 | - return $this->dtd["namespaces"][$cp] . ":" . $this->dtd["codes"][$cp][$id]; |
|
494 | - } |
|
495 | - |
|
496 | - return $this->dtd["codes"][$cp][$id]; |
|
497 | - } |
|
259 | + /** |
|
260 | + * Returns the next token. |
|
261 | + * |
|
262 | + * @return token |
|
263 | + */ |
|
264 | + private function getToken() { |
|
265 | + // See if there's something in the ungetBuffer |
|
266 | + if ($this->ungetbuffer) { |
|
267 | + $element = $this->ungetbuffer; |
|
268 | + $this->ungetbuffer = false; |
|
269 | + |
|
270 | + return $element; |
|
271 | + } |
|
272 | + |
|
273 | + $el = $this->_getToken(); |
|
274 | + if ($this->log && $el) { |
|
275 | + $this->logToken($el); |
|
276 | + } |
|
277 | + |
|
278 | + return $el; |
|
279 | + } |
|
280 | + |
|
281 | + /** |
|
282 | + * Log the a token to SLog. |
|
283 | + * |
|
284 | + * @param string $el token |
|
285 | + * |
|
286 | + * @return |
|
287 | + */ |
|
288 | + private function logToken($el) { |
|
289 | + $spaces = str_repeat(" ", count($this->logStack)); |
|
290 | + |
|
291 | + switch ($el[EN_TYPE]) { |
|
292 | + case EN_TYPE_STARTTAG: |
|
293 | + if ($el[EN_FLAGS] & EN_FLAGS_CONTENT) { |
|
294 | + SLog::Write(LOGLEVEL_WBXML, "I " . $spaces . " <" . $el[EN_TAG] . ">"); |
|
295 | + array_push($this->logStack, $el[EN_TAG]); |
|
296 | + } |
|
297 | + else { |
|
298 | + SLog::Write(LOGLEVEL_WBXML, "I " . $spaces . " <" . $el[EN_TAG] . "/>"); |
|
299 | + } |
|
300 | + break; |
|
301 | + |
|
302 | + case EN_TYPE_ENDTAG: |
|
303 | + $tag = array_pop($this->logStack); |
|
304 | + SLog::Write(LOGLEVEL_WBXML, "I " . $spaces . "</" . $tag . ">"); |
|
305 | + break; |
|
306 | + |
|
307 | + case EN_TYPE_CONTENT: |
|
308 | + // as we concatenate the string here, the entire content is copied. |
|
309 | + // when sending an email with an attachment this single log line (which is never logged in INFO) |
|
310 | + // requires easily additional 20 MB of RAM. See https://jira.z-hub.io/browse/ZP-1159 |
|
311 | + $messagesize = strlen($el[EN_CONTENT]); |
|
312 | + if ($messagesize > 10240 && !defined('WBXML_DEBUGGING')) { |
|
313 | + $content = substr($el[EN_CONTENT], 0, 10240) . sprintf(" <log message with %d bytes truncated>", $messagesize); |
|
314 | + } |
|
315 | + else { |
|
316 | + $content = $el[EN_CONTENT]; |
|
317 | + } |
|
318 | + // Log but make sure it's not truncated again (will be slightly bigger than 10KB) |
|
319 | + SLog::Write(LOGLEVEL_WBXML, "I " . $spaces . " " . $content, false); |
|
320 | + break; |
|
321 | + } |
|
322 | + } |
|
323 | + |
|
324 | + /** |
|
325 | + * Returns either a start tag, content or end tag. |
|
326 | + * |
|
327 | + * @return |
|
328 | + */ |
|
329 | + private function _getToken() { |
|
330 | + // Get the data from the input stream |
|
331 | + $element = []; |
|
332 | + |
|
333 | + WBXMLDecoder::ResetInWhile("decoderGetToken"); |
|
334 | + while (WBXMLDecoder::InWhile("decoderGetToken")) { |
|
335 | + $byte = fread($this->in, 1); |
|
336 | + if ($byte === "" || $byte === false) { |
|
337 | + break; |
|
338 | + } |
|
339 | + $byte = ord($byte); |
|
340 | + |
|
341 | + switch ($byte) { |
|
342 | + case self::WBXML_SWITCH_PAGE: |
|
343 | + $this->tagcp = $this->getByte(); |
|
344 | + break; |
|
345 | + |
|
346 | + case self::WBXML_END: |
|
347 | + $element[EN_TYPE] = EN_TYPE_ENDTAG; |
|
348 | + |
|
349 | + return $element; |
|
350 | + |
|
351 | + case self::WBXML_STR_I: |
|
352 | + $element[EN_TYPE] = EN_TYPE_CONTENT; |
|
353 | + $element[EN_CONTENT] = $this->getTermStr(); |
|
354 | + |
|
355 | + return $element; |
|
356 | + |
|
357 | + case self::WBXML_OPAQUE: |
|
358 | + $length = $this->getMBUInt(); |
|
359 | + $element[EN_TYPE] = EN_TYPE_CONTENT; |
|
360 | + $element[EN_CONTENT] = $this->getOpaque($length); |
|
361 | + |
|
362 | + return $element; |
|
363 | + |
|
364 | + case self::WBXML_ENTITY: |
|
365 | + case self::WBXML_LITERAL: |
|
366 | + case self::WBXML_EXT_I_0: |
|
367 | + case self::WBXML_EXT_I_1: |
|
368 | + case self::WBXML_EXT_I_2: |
|
369 | + case self::WBXML_PI: |
|
370 | + case self::WBXML_LITERAL_C: |
|
371 | + case self::WBXML_EXT_T_0: |
|
372 | + case self::WBXML_EXT_T_1: |
|
373 | + case self::WBXML_EXT_T_2: |
|
374 | + case self::WBXML_STR_T: |
|
375 | + case self::WBXML_LITERAL_A: |
|
376 | + case self::WBXML_EXT_0: |
|
377 | + case self::WBXML_EXT_1: |
|
378 | + case self::WBXML_EXT_2: |
|
379 | + case self::WBXML_LITERAL_AC: |
|
380 | + throw new WBXMLException("Invalid token :" . $byte); |
|
381 | + |
|
382 | + default: |
|
383 | + if ($byte & self::WBXML_WITH_ATTRIBUTES) { |
|
384 | + throw new WBXMLException("Attributes are not allowed :" . $byte); |
|
385 | + } |
|
386 | + $element[EN_TYPE] = EN_TYPE_STARTTAG; |
|
387 | + $element[EN_TAG] = $this->getMapping($this->tagcp, $byte & 0x3F); |
|
388 | + $element[EN_FLAGS] = ($byte & self::WBXML_WITH_CONTENT ? EN_FLAGS_CONTENT : 0); |
|
389 | + |
|
390 | + return $element; |
|
391 | + } |
|
392 | + } |
|
393 | + } |
|
394 | + |
|
395 | + /** |
|
396 | + * Reads from the stream until getting a string terminator. |
|
397 | + * |
|
398 | + * @return string |
|
399 | + */ |
|
400 | + private function getTermStr() { |
|
401 | + if (defined('WBXML_DEBUGGING') && WBXML_DEBUGGING === true) { |
|
402 | + $str = ""; |
|
403 | + while (1) { |
|
404 | + $in = $this->getByte(); |
|
405 | + if ($in == 0) { |
|
406 | + break; |
|
407 | + } |
|
408 | + |
|
409 | + $str .= chr($in); |
|
410 | + } |
|
411 | + |
|
412 | + return $str; |
|
413 | + } |
|
414 | + |
|
415 | + // there is no unlimited "length" for stream_get_line, |
|
416 | + // so we use a huge value for "length" param (1Gb) |
|
417 | + // (0 == PHP_SOCK_CHUNK_SIZE (8192)) |
|
418 | + // internally php read at most PHP_SOCK_CHUNK_SIZE at a time, |
|
419 | + // so we can use a huge value for "length" without problem |
|
420 | + return stream_get_line($this->in, 1073741824, "\0"); |
|
421 | + } |
|
422 | + |
|
423 | + /** |
|
424 | + * Reads $len from the input stream. |
|
425 | + * |
|
426 | + * @param int $len |
|
427 | + * |
|
428 | + * @return string |
|
429 | + */ |
|
430 | + private function getOpaque($len) { |
|
431 | + $d = stream_get_contents($this->in, $len); |
|
432 | + if ($d === false) { |
|
433 | + throw new HTTPReturnCodeException("WBXMLDecoder->getOpaque(): stream_get_contents === false", HTTP_CODE_500, null, LOGLEVEL_WARN); |
|
434 | + } |
|
435 | + $l = strlen($d); |
|
436 | + if ($l !== $len) { |
|
437 | + throw new HTTPReturnCodeException("WBXMLDecoder->getOpaque(): only {$l} byte read instead of {$len}", HTTP_CODE_500, null, LOGLEVEL_WARN); |
|
438 | + } |
|
439 | + |
|
440 | + return $d; |
|
441 | + } |
|
442 | + |
|
443 | + /** |
|
444 | + * Reads one byte from the input stream. |
|
445 | + * |
|
446 | + * @return int |
|
447 | + */ |
|
448 | + private function getByte() { |
|
449 | + $ch = fread($this->in, 1); |
|
450 | + if (strlen($ch) > 0) { |
|
451 | + return ord($ch); |
|
452 | + } |
|
453 | + } |
|
454 | + |
|
455 | + /** |
|
456 | + * Reads string length from the input stream. |
|
457 | + * |
|
458 | + * @return |
|
459 | + */ |
|
460 | + private function getMBUInt() { |
|
461 | + $uint = 0; |
|
462 | + |
|
463 | + while (1) { |
|
464 | + $byte = $this->getByte(); |
|
465 | + |
|
466 | + $uint |= $byte & 0x7F; |
|
467 | + |
|
468 | + if ($byte & 0x80) { |
|
469 | + $uint = $uint << 7; |
|
470 | + } |
|
471 | + else { |
|
472 | + break; |
|
473 | + } |
|
474 | + } |
|
475 | + |
|
476 | + return $uint; |
|
477 | + } |
|
478 | + |
|
479 | + /** |
|
480 | + * Returns the mapping for a specified codepage and id. |
|
481 | + * |
|
482 | + * @param $cp codepage |
|
483 | + * @param $id |
|
484 | + * |
|
485 | + * @return string |
|
486 | + */ |
|
487 | + private function getMapping($cp, $id) { |
|
488 | + if (!isset($this->dtd["codes"][$cp]) || !isset($this->dtd["codes"][$cp][$id])) { |
|
489 | + return false; |
|
490 | + } |
|
491 | + |
|
492 | + if (isset($this->dtd["namespaces"][$cp])) { |
|
493 | + return $this->dtd["namespaces"][$cp] . ":" . $this->dtd["codes"][$cp][$id]; |
|
494 | + } |
|
495 | + |
|
496 | + return $this->dtd["codes"][$cp][$id]; |
|
497 | + } |
|
498 | 498 | } |
@@ -34,8 +34,7 @@ discard block |
||
34 | 34 | if (!isset(self::$loopCounter[$name])) { |
35 | 35 | self::$loopCounter[$name] = 0; |
36 | 36 | } |
37 | - else { |
|
38 | - ++self::$loopCounter[$name]; |
|
37 | + else {++self::$loopCounter[$name]; |
|
39 | 38 | } |
40 | 39 | |
41 | 40 | if (self::$loopCounter[$name] > self::MAXLOOP) { |
@@ -81,17 +80,17 @@ discard block |
||
81 | 80 | |
82 | 81 | $publicid = $this->getMBUInt(); |
83 | 82 | if ($publicid !== 1) { |
84 | - throw new WBXMLException("Wrong publicid : " . $publicid); |
|
83 | + throw new WBXMLException("Wrong publicid : ".$publicid); |
|
85 | 84 | } |
86 | 85 | |
87 | 86 | $charsetid = $this->getMBUInt(); |
88 | 87 | if ($charsetid !== 106) { |
89 | - throw new WBXMLException("Wrong charset : " . $charsetid); |
|
88 | + throw new WBXMLException("Wrong charset : ".$charsetid); |
|
90 | 89 | } |
91 | 90 | |
92 | 91 | $stringtablesize = $this->getMBUInt(); |
93 | 92 | if ($stringtablesize !== 0) { |
94 | - throw new WBXMLException("Wrong string table size : " . $stringtablesize); |
|
93 | + throw new WBXMLException("Wrong string table size : ".$stringtablesize); |
|
95 | 94 | } |
96 | 95 | } |
97 | 96 | |
@@ -184,7 +183,7 @@ discard block |
||
184 | 183 | SLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("WBXMLDecoder->getElementEndTag(): unmatched WBXML tag: '%s' type '%s' flags '%s'", ((isset($element[EN_TAG])) ? $element[EN_TAG] : ""), ((isset($element[EN_TYPE])) ? $element[EN_TYPE] : ""), ((isset($element[EN_FLAGS])) ? $element[EN_FLAGS] : ""))); |
185 | 184 | |
186 | 185 | $bt = debug_backtrace(); |
187 | - SLog::Write(LOGLEVEL_ERROR, sprintf("WBXMLDecoder->getElementEndTag(): could not read end tag in '%s'. Please enable the LOGLEVEL_WBXML and send the log to the grommunio-sync dev team.", $bt[0]["file"] . ":" . $bt[0]["line"])); |
|
186 | + SLog::Write(LOGLEVEL_ERROR, sprintf("WBXMLDecoder->getElementEndTag(): could not read end tag in '%s'. Please enable the LOGLEVEL_WBXML and send the log to the grommunio-sync dev team.", $bt[0]["file"].":".$bt[0]["line"])); |
|
188 | 187 | |
189 | 188 | // log the remaining wbxml content |
190 | 189 | $this->ungetElement($element); |
@@ -232,7 +231,7 @@ discard block |
||
232 | 231 | * @return string |
233 | 232 | */ |
234 | 233 | public function GetPlainInputStream() { |
235 | - return $this->inputBuffer . stream_get_contents($this->in); |
|
234 | + return $this->inputBuffer.stream_get_contents($this->in); |
|
236 | 235 | } |
237 | 236 | |
238 | 237 | /** |
@@ -291,17 +290,17 @@ discard block |
||
291 | 290 | switch ($el[EN_TYPE]) { |
292 | 291 | case EN_TYPE_STARTTAG: |
293 | 292 | if ($el[EN_FLAGS] & EN_FLAGS_CONTENT) { |
294 | - SLog::Write(LOGLEVEL_WBXML, "I " . $spaces . " <" . $el[EN_TAG] . ">"); |
|
293 | + SLog::Write(LOGLEVEL_WBXML, "I ".$spaces." <".$el[EN_TAG].">"); |
|
295 | 294 | array_push($this->logStack, $el[EN_TAG]); |
296 | 295 | } |
297 | 296 | else { |
298 | - SLog::Write(LOGLEVEL_WBXML, "I " . $spaces . " <" . $el[EN_TAG] . "/>"); |
|
297 | + SLog::Write(LOGLEVEL_WBXML, "I ".$spaces." <".$el[EN_TAG]."/>"); |
|
299 | 298 | } |
300 | 299 | break; |
301 | 300 | |
302 | 301 | case EN_TYPE_ENDTAG: |
303 | 302 | $tag = array_pop($this->logStack); |
304 | - SLog::Write(LOGLEVEL_WBXML, "I " . $spaces . "</" . $tag . ">"); |
|
303 | + SLog::Write(LOGLEVEL_WBXML, "I ".$spaces."</".$tag.">"); |
|
305 | 304 | break; |
306 | 305 | |
307 | 306 | case EN_TYPE_CONTENT: |
@@ -310,13 +309,13 @@ discard block |
||
310 | 309 | // requires easily additional 20 MB of RAM. See https://jira.z-hub.io/browse/ZP-1159 |
311 | 310 | $messagesize = strlen($el[EN_CONTENT]); |
312 | 311 | if ($messagesize > 10240 && !defined('WBXML_DEBUGGING')) { |
313 | - $content = substr($el[EN_CONTENT], 0, 10240) . sprintf(" <log message with %d bytes truncated>", $messagesize); |
|
312 | + $content = substr($el[EN_CONTENT], 0, 10240).sprintf(" <log message with %d bytes truncated>", $messagesize); |
|
314 | 313 | } |
315 | 314 | else { |
316 | 315 | $content = $el[EN_CONTENT]; |
317 | 316 | } |
318 | 317 | // Log but make sure it's not truncated again (will be slightly bigger than 10KB) |
319 | - SLog::Write(LOGLEVEL_WBXML, "I " . $spaces . " " . $content, false); |
|
318 | + SLog::Write(LOGLEVEL_WBXML, "I ".$spaces." ".$content, false); |
|
320 | 319 | break; |
321 | 320 | } |
322 | 321 | } |
@@ -377,11 +376,11 @@ discard block |
||
377 | 376 | case self::WBXML_EXT_1: |
378 | 377 | case self::WBXML_EXT_2: |
379 | 378 | case self::WBXML_LITERAL_AC: |
380 | - throw new WBXMLException("Invalid token :" . $byte); |
|
379 | + throw new WBXMLException("Invalid token :".$byte); |
|
381 | 380 | |
382 | 381 | default: |
383 | 382 | if ($byte & self::WBXML_WITH_ATTRIBUTES) { |
384 | - throw new WBXMLException("Attributes are not allowed :" . $byte); |
|
383 | + throw new WBXMLException("Attributes are not allowed :".$byte); |
|
385 | 384 | } |
386 | 385 | $element[EN_TYPE] = EN_TYPE_STARTTAG; |
387 | 386 | $element[EN_TAG] = $this->getMapping($this->tagcp, $byte & 0x3F); |
@@ -490,7 +489,7 @@ discard block |
||
490 | 489 | } |
491 | 490 | |
492 | 491 | if (isset($this->dtd["namespaces"][$cp])) { |
493 | - return $this->dtd["namespaces"][$cp] . ":" . $this->dtd["codes"][$cp][$id]; |
|
492 | + return $this->dtd["namespaces"][$cp].":".$this->dtd["codes"][$cp][$id]; |
|
494 | 493 | } |
495 | 494 | |
496 | 495 | return $this->dtd["codes"][$cp][$id]; |
@@ -33,8 +33,7 @@ discard block |
||
33 | 33 | public static function InWhile($name) { |
34 | 34 | if (!isset(self::$loopCounter[$name])) { |
35 | 35 | self::$loopCounter[$name] = 0; |
36 | - } |
|
37 | - else { |
|
36 | + } else { |
|
38 | 37 | ++self::$loopCounter[$name]; |
39 | 38 | } |
40 | 39 | |
@@ -119,8 +118,7 @@ discard block |
||
119 | 118 | } |
120 | 119 | if ($next[EN_TYPE] == EN_CONTENT) { |
121 | 120 | $element[EN_CONTENT] .= $next[EN_CONTENT]; |
122 | - } |
|
123 | - else { |
|
121 | + } else { |
|
124 | 122 | $this->ungetElement($next); |
125 | 123 | |
126 | 124 | break; |
@@ -293,8 +291,7 @@ discard block |
||
293 | 291 | if ($el[EN_FLAGS] & EN_FLAGS_CONTENT) { |
294 | 292 | SLog::Write(LOGLEVEL_WBXML, "I " . $spaces . " <" . $el[EN_TAG] . ">"); |
295 | 293 | array_push($this->logStack, $el[EN_TAG]); |
296 | - } |
|
297 | - else { |
|
294 | + } else { |
|
298 | 295 | SLog::Write(LOGLEVEL_WBXML, "I " . $spaces . " <" . $el[EN_TAG] . "/>"); |
299 | 296 | } |
300 | 297 | break; |
@@ -311,8 +308,7 @@ discard block |
||
311 | 308 | $messagesize = strlen($el[EN_CONTENT]); |
312 | 309 | if ($messagesize > 10240 && !defined('WBXML_DEBUGGING')) { |
313 | 310 | $content = substr($el[EN_CONTENT], 0, 10240) . sprintf(" <log message with %d bytes truncated>", $messagesize); |
314 | - } |
|
315 | - else { |
|
311 | + } else { |
|
316 | 312 | $content = $el[EN_CONTENT]; |
317 | 313 | } |
318 | 314 | // Log but make sure it's not truncated again (will be slightly bigger than 10KB) |
@@ -467,8 +463,7 @@ discard block |
||
467 | 463 | |
468 | 464 | if ($byte & 0x80) { |
469 | 465 | $uint = $uint << 7; |
470 | - } |
|
471 | - else { |
|
466 | + } else { |
|
472 | 467 | break; |
473 | 468 | } |
474 | 469 | } |
@@ -8,25 +8,25 @@ |
||
8 | 8 | */ |
9 | 9 | |
10 | 10 | class ReplaceNullcharFilter extends php_user_filter { |
11 | - /** |
|
12 | - * This method is called whenever data is read from or written to the attached stream. |
|
13 | - * |
|
14 | - * @see php_user_filter::filter() |
|
15 | - * |
|
16 | - * @param resource $in |
|
17 | - * @param resource $out |
|
18 | - * @param int $consumed |
|
19 | - * @param bool $closing |
|
20 | - * |
|
21 | - * @return int |
|
22 | - */ |
|
23 | - public function filter($in, $out, &$consumed, $closing) { |
|
24 | - while ($bucket = stream_bucket_make_writeable($in)) { |
|
25 | - $bucket->data = str_replace("\0", "", $bucket->data); |
|
26 | - $consumed += $bucket->datalen; |
|
27 | - stream_bucket_append($out, $bucket); |
|
28 | - } |
|
11 | + /** |
|
12 | + * This method is called whenever data is read from or written to the attached stream. |
|
13 | + * |
|
14 | + * @see php_user_filter::filter() |
|
15 | + * |
|
16 | + * @param resource $in |
|
17 | + * @param resource $out |
|
18 | + * @param int $consumed |
|
19 | + * @param bool $closing |
|
20 | + * |
|
21 | + * @return int |
|
22 | + */ |
|
23 | + public function filter($in, $out, &$consumed, $closing) { |
|
24 | + while ($bucket = stream_bucket_make_writeable($in)) { |
|
25 | + $bucket->data = str_replace("\0", "", $bucket->data); |
|
26 | + $consumed += $bucket->datalen; |
|
27 | + stream_bucket_append($out, $bucket); |
|
28 | + } |
|
29 | 29 | |
30 | - return PSFS_PASS_ON; |
|
31 | - } |
|
30 | + return PSFS_PASS_ON; |
|
31 | + } |
|
32 | 32 | } |
@@ -21,714 +21,714 @@ |
||
21 | 21 | define('EN_FLAGS_ATTRIBUTES', 2); |
22 | 22 | |
23 | 23 | class WBXMLDefs { |
24 | - public const WBXML_SWITCH_PAGE = 0x00; |
|
25 | - public const WBXML_END = 0x01; |
|
26 | - public const WBXML_ENTITY = 0x02; // not used in ActiveSync |
|
27 | - public const WBXML_STR_I = 0x03; |
|
28 | - public const WBXML_LITERAL = 0x04; // not used in ActiveSync |
|
29 | - public const WBXML_EXT_I_0 = 0x40; // not used in ActiveSync |
|
30 | - public const WBXML_EXT_I_1 = 0x41; // not used in ActiveSync |
|
31 | - public const WBXML_EXT_I_2 = 0x42; // not used in ActiveSync |
|
32 | - public const WBXML_PI = 0x43; // not used in ActiveSync |
|
33 | - public const WBXML_LITERAL_C = 0x44; // not used in ActiveSync |
|
34 | - public const WBXML_EXT_T_0 = 0x80; // not used in ActiveSync |
|
35 | - public const WBXML_EXT_T_1 = 0x81; // not used in ActiveSync |
|
36 | - public const WBXML_EXT_T_2 = 0x82; // not used in ActiveSync |
|
37 | - public const WBXML_STR_T = 0x83; // not used in ActiveSync |
|
38 | - public const WBXML_LITERAL_A = 0x84; // not used in ActiveSync |
|
39 | - public const WBXML_EXT_0 = 0xC0; // not used in ActiveSync |
|
40 | - public const WBXML_EXT_1 = 0xC1; // not used in ActiveSync |
|
41 | - public const WBXML_EXT_2 = 0xC2; // not used in ActiveSync |
|
42 | - public const WBXML_OPAQUE = 0xC3; |
|
43 | - public const WBXML_LITERAL_AC = 0xC4; // not used in ActiveSync |
|
44 | - public const WBXML_WITH_ATTRIBUTES = 0x80; // not used in ActiveSync |
|
45 | - public const WBXML_WITH_CONTENT = 0x40; |
|
24 | + public const WBXML_SWITCH_PAGE = 0x00; |
|
25 | + public const WBXML_END = 0x01; |
|
26 | + public const WBXML_ENTITY = 0x02; // not used in ActiveSync |
|
27 | + public const WBXML_STR_I = 0x03; |
|
28 | + public const WBXML_LITERAL = 0x04; // not used in ActiveSync |
|
29 | + public const WBXML_EXT_I_0 = 0x40; // not used in ActiveSync |
|
30 | + public const WBXML_EXT_I_1 = 0x41; // not used in ActiveSync |
|
31 | + public const WBXML_EXT_I_2 = 0x42; // not used in ActiveSync |
|
32 | + public const WBXML_PI = 0x43; // not used in ActiveSync |
|
33 | + public const WBXML_LITERAL_C = 0x44; // not used in ActiveSync |
|
34 | + public const WBXML_EXT_T_0 = 0x80; // not used in ActiveSync |
|
35 | + public const WBXML_EXT_T_1 = 0x81; // not used in ActiveSync |
|
36 | + public const WBXML_EXT_T_2 = 0x82; // not used in ActiveSync |
|
37 | + public const WBXML_STR_T = 0x83; // not used in ActiveSync |
|
38 | + public const WBXML_LITERAL_A = 0x84; // not used in ActiveSync |
|
39 | + public const WBXML_EXT_0 = 0xC0; // not used in ActiveSync |
|
40 | + public const WBXML_EXT_1 = 0xC1; // not used in ActiveSync |
|
41 | + public const WBXML_EXT_2 = 0xC2; // not used in ActiveSync |
|
42 | + public const WBXML_OPAQUE = 0xC3; |
|
43 | + public const WBXML_LITERAL_AC = 0xC4; // not used in ActiveSync |
|
44 | + public const WBXML_WITH_ATTRIBUTES = 0x80; // not used in ActiveSync |
|
45 | + public const WBXML_WITH_CONTENT = 0x40; |
|
46 | 46 | |
47 | - /** |
|
48 | - * The WBXML DTDs. |
|
49 | - */ |
|
50 | - protected $dtd = [ |
|
51 | - "codes" => [ |
|
52 | - 0 => [ |
|
53 | - 0x05 => "Synchronize", |
|
54 | - 0x06 => "Replies", // Responses |
|
55 | - 0x07 => "Add", |
|
56 | - 0x08 => "Modify", // Change |
|
57 | - 0x09 => "Remove", // Delete |
|
58 | - 0x0A => "Fetch", |
|
59 | - 0x0B => "SyncKey", |
|
60 | - 0x0C => "ClientEntryId", // ClientId |
|
61 | - 0x0D => "ServerEntryId", // ServerId |
|
62 | - 0x0E => "Status", |
|
63 | - 0x0F => "Folder", // collection |
|
64 | - 0x10 => "FolderType", // class |
|
65 | - 0x11 => "Version", // deprecated |
|
66 | - 0x12 => "FolderId", // CollectionId |
|
67 | - 0x13 => "GetChanges", |
|
68 | - 0x14 => "MoreAvailable", |
|
69 | - 0x15 => "WindowSize", // WindowSize - MaxItems before version 2 |
|
70 | - 0x16 => "Perform", // Commands |
|
71 | - 0x17 => "Options", |
|
72 | - 0x18 => "FilterType", |
|
73 | - 0x19 => "Truncation", // 2.0 and 2.5 |
|
74 | - 0x1A => "RtfTruncation", // 2.0 and 2.5 |
|
75 | - 0x1B => "Conflict", |
|
76 | - 0x1C => "Folders", // Collections |
|
77 | - 0x1D => "Data", |
|
78 | - 0x1E => "DeletesAsMoves", |
|
79 | - 0x1F => "NotifyGUID", // 2.0 and 2.5 |
|
80 | - 0x20 => "Supported", |
|
81 | - 0x21 => "SoftDelete", |
|
82 | - 0x22 => "MIMESupport", |
|
83 | - 0x23 => "MIMETruncation", |
|
84 | - 0x24 => "Wait", // Since 12.1 |
|
85 | - 0x25 => "Limit", // Since 12.1 |
|
86 | - 0x26 => "Partial", // Since 12.1 |
|
87 | - 0x27 => "ConversationMode", // Since 14.0 |
|
88 | - 0x28 => "MaxItems", // Since 14.0 |
|
89 | - 0x29 => "HeartbeatInterval", // Since 14.0 Either this tag or the Wait tag can be present, but not both. |
|
90 | - ], |
|
91 | - 1 => [ |
|
92 | - 0x05 => "Anniversary", |
|
93 | - 0x06 => "AssistantName", |
|
94 | - 0x07 => "AssistnamePhoneNumber", // AssistantTelephoneNumber |
|
95 | - 0x08 => "Birthday", |
|
96 | - 0x09 => "Body", // 2.5, AirSyncBase Body is used since version 12.0 |
|
97 | - 0x0A => "BodySize", // 2.0 and 2.5, AirSyncBase is used since version 12.0 |
|
98 | - 0x0B => "BodyTruncated", // 2.0 and 2.5, AirSyncBase is used since version 12.0 |
|
99 | - 0x0C => "Business2PhoneNumber", |
|
100 | - 0x0D => "BusinessCity", |
|
101 | - 0x0E => "BusinessCountry", |
|
102 | - 0x0F => "BusinessPostalCode", |
|
103 | - 0x10 => "BusinessState", |
|
104 | - 0x11 => "BusinessStreet", |
|
105 | - 0x12 => "BusinessFaxNumber", |
|
106 | - 0x13 => "BusinessPhoneNumber", |
|
107 | - 0x14 => "CarPhoneNumber", |
|
108 | - 0x15 => "Categories", |
|
109 | - 0x16 => "Category", |
|
110 | - 0x17 => "Children", |
|
111 | - 0x18 => "Child", |
|
112 | - 0x19 => "CompanyName", |
|
113 | - 0x1A => "Department", |
|
114 | - 0x1B => "Email1Address", |
|
115 | - 0x1C => "Email2Address", |
|
116 | - 0x1D => "Email3Address", |
|
117 | - 0x1E => "FileAs", |
|
118 | - 0x1F => "FirstName", |
|
119 | - 0x20 => "Home2PhoneNumber", |
|
120 | - 0x21 => "HomeCity", |
|
121 | - 0x22 => "HomeCountry", |
|
122 | - 0x23 => "HomePostalCode", |
|
123 | - 0x24 => "HomeState", |
|
124 | - 0x25 => "HomeStreet", |
|
125 | - 0x26 => "HomeFaxNumber", |
|
126 | - 0x27 => "HomePhoneNumber", |
|
127 | - 0x28 => "JobTitle", |
|
128 | - 0x29 => "LastName", |
|
129 | - 0x2A => "MiddleName", |
|
130 | - 0x2B => "MobilePhoneNumber", |
|
131 | - 0x2C => "OfficeLocation", |
|
132 | - 0x2D => "OtherCity", |
|
133 | - 0x2E => "OtherCountry", |
|
134 | - 0x2F => "OtherPostalCode", |
|
135 | - 0x30 => "OtherState", |
|
136 | - 0x31 => "OtherStreet", |
|
137 | - 0x32 => "PagerNumber", |
|
138 | - 0x33 => "RadioPhoneNumber", |
|
139 | - 0x34 => "Spouse", |
|
140 | - 0x35 => "Suffix", |
|
141 | - 0x36 => "Title", |
|
142 | - 0x37 => "WebPage", |
|
143 | - 0x38 => "YomiCompanyName", |
|
144 | - 0x39 => "YomiFirstName", |
|
145 | - 0x3A => "YomiLastName", |
|
146 | - 0x3B => "Rtf", // CompressedRTF - 2.5, deprecated |
|
147 | - 0x3C => "Picture", |
|
148 | - 0x3D => "Alias", // Since 14.0 |
|
149 | - 0x3E => "WeightedRank", // Since 14.0 |
|
150 | - ], |
|
151 | - 2 => [ |
|
152 | - 0x05 => "Attachment", // AirSyncBase Attachments is used since 12.0 |
|
153 | - 0x06 => "Attachments", // AirSyncBase Attachments is used since 12.0 |
|
154 | - 0x07 => "AttName", // AirSyncBase Attachments is used since 12.0 |
|
155 | - 0x08 => "AttSize", // AirSyncBase Attachments is used since 12.0 |
|
156 | - 0x09 => "AttOid", // AirSyncBase Attachments is used since 12.0 |
|
157 | - 0x0A => "AttMethod", // AirSyncBase Attachments is used since 12.0 |
|
158 | - 0x0B => "AttRemoved", // AirSyncBase Attachments is used since 12.0 |
|
159 | - 0x0C => "Body", // AirSyncBase Body is used since 12.0 |
|
160 | - 0x0D => "BodySize", // AirSyncBase Body is used since 12.0 |
|
161 | - 0x0E => "BodyTruncated", // AirSyncBase Body is used since 12.0 |
|
162 | - 0x0F => "DateReceived", |
|
163 | - 0x10 => "DisplayName", // AirSyncBase Attachments is used since 12.0 |
|
164 | - 0x11 => "DisplayTo", |
|
165 | - 0x12 => "Importance", |
|
166 | - 0x13 => "MessageClass", |
|
167 | - 0x14 => "Subject", |
|
168 | - 0x15 => "Read", |
|
169 | - 0x16 => "To", |
|
170 | - 0x17 => "Cc", |
|
171 | - 0x18 => "From", |
|
172 | - 0x19 => "Reply-To", |
|
173 | - 0x1A => "AllDayEvent", |
|
174 | - 0x1B => "Categories", // Since 14.0 |
|
175 | - 0x1C => "Category", // Since 14.0 |
|
176 | - 0x1D => "DtStamp", |
|
177 | - 0x1E => "EndTime", |
|
178 | - 0x1F => "InstanceType", |
|
179 | - 0x20 => "BusyStatus", |
|
180 | - 0x21 => "Location", // 2.5, 12.0, 12.1, 14.0 and 14.1. Since 16.0 AirSyncBase Location is used. |
|
181 | - 0x22 => "MeetingRequest", |
|
182 | - 0x23 => "Organizer", |
|
183 | - 0x24 => "RecurrenceId", |
|
184 | - 0x25 => "Reminder", |
|
185 | - 0x26 => "ResponseRequested", |
|
186 | - 0x27 => "Recurrences", |
|
187 | - 0x28 => "Recurrence", |
|
188 | - 0x29 => "Type", |
|
189 | - 0x2A => "Until", |
|
190 | - 0x2B => "Occurrences", |
|
191 | - 0x2C => "Interval", |
|
192 | - 0x2D => "DayOfWeek", |
|
193 | - 0x2E => "DayOfMonth", |
|
194 | - 0x2F => "WeekOfMonth", |
|
195 | - 0x30 => "MonthOfYear", |
|
196 | - 0x31 => "StartTime", |
|
197 | - 0x32 => "Sensitivity", |
|
198 | - 0x33 => "TimeZone", |
|
199 | - 0x34 => "GlobalObjId", // 2.5, 12.0, 12.1, 14.0 and 14.1. UID of Calendar (Code page 4) is used since 16.0 |
|
200 | - 0x35 => "ThreadTopic", |
|
201 | - 0x36 => "MIMEData", // 2.5 |
|
202 | - 0x37 => "MIMETruncated", // 2.5 |
|
203 | - 0x38 => "MIMESize", // 2.5 |
|
204 | - 0x39 => "InternetCPID", |
|
205 | - 0x3A => "Flag", // Since 12.0 |
|
206 | - 0x3B => "FlagStatus", // Since 12.0 |
|
207 | - 0x3C => "ContentClass", // Since 12.0 |
|
208 | - 0x3D => "FlagType", // Since 12.0 |
|
209 | - 0x3E => "CompleteTime", // Since 12.0 |
|
210 | - 0x3F => "DisallowNewTimeProposal", // Since 14.0 |
|
211 | - ], |
|
212 | - 3 => [ // Code page 3 is no longer in use, however, tokens 05 through 17 have been defined. 20100501 |
|
213 | - 0x05 => "Notify", |
|
214 | - 0x06 => "Notification", |
|
215 | - 0x07 => "Version", |
|
216 | - 0x08 => "Lifetime", |
|
217 | - 0x09 => "DeviceInfo", |
|
218 | - 0x0A => "Enable", |
|
219 | - 0x0B => "Folder", |
|
220 | - 0x0C => "ServerEntryId", |
|
221 | - 0x0D => "DeviceAddress", |
|
222 | - 0x0E => "ValidCarrierProfiles", |
|
223 | - 0x0F => "CarrierProfile", |
|
224 | - 0x10 => "Status", |
|
225 | - 0x11 => "Replies", |
|
226 | - // 0x05 => "Version='1.1'", |
|
227 | - 0x12 => "Devices", |
|
228 | - 0x13 => "Device", |
|
229 | - 0x14 => "Id", |
|
230 | - 0x15 => "Expiry", |
|
231 | - 0x16 => "NotifyGUID", |
|
232 | - ], |
|
233 | - 4 => [ |
|
234 | - 0x05 => "Timezone", |
|
235 | - 0x06 => "AllDayEvent", |
|
236 | - 0x07 => "Attendees", |
|
237 | - 0x08 => "Attendee", |
|
238 | - 0x09 => "Email", |
|
239 | - 0x0A => "Name", |
|
240 | - 0x0B => "Body", // AirSyncBase Body is used since 12.0 |
|
241 | - 0x0C => "BodyTruncated", // AirSyncBase Body is used since 12.0 |
|
242 | - 0x0D => "BusyStatus", |
|
243 | - 0x0E => "Categories", |
|
244 | - 0x0F => "Category", |
|
245 | - 0x10 => "Rtf", // 2.5 - deprecated |
|
246 | - 0x11 => "DtStamp", |
|
247 | - 0x12 => "EndTime", |
|
248 | - 0x13 => "Exception", |
|
249 | - 0x14 => "Exceptions", |
|
250 | - 0x15 => "Deleted", |
|
251 | - 0x16 => "ExceptionStartTime", // 2.5, 12.0, 12.1, 14.0 and 14.1. |
|
252 | - 0x17 => "Location", // 2.5, 12.0, 12.1, 14.0 and 14.1. Since 16.0 AirSyncBase Location is used. |
|
253 | - 0x18 => "MeetingStatus", |
|
254 | - 0x19 => "OrganizerEmail", |
|
255 | - 0x1A => "OrganizerName", |
|
256 | - 0x1B => "Recurrence", |
|
257 | - 0x1C => "Type", |
|
258 | - 0x1D => "Until", |
|
259 | - 0x1E => "Occurrences", |
|
260 | - 0x1F => "Interval", |
|
261 | - 0x20 => "DayOfWeek", |
|
262 | - 0x21 => "DayOfMonth", |
|
263 | - 0x22 => "WeekOfMonth", |
|
264 | - 0x23 => "MonthOfYear", |
|
265 | - 0x24 => "Reminder", |
|
266 | - 0x25 => "Sensitivity", |
|
267 | - 0x26 => "Subject", |
|
268 | - 0x27 => "StartTime", |
|
269 | - 0x28 => "UID", |
|
270 | - 0x29 => "Attendee_Status", // Since 12.0 |
|
271 | - 0x2A => "Attendee_Type", // Since 12.0 |
|
272 | - 0x2B => "Attachment", // Not defined / deprecated |
|
273 | - 0x2C => "Attachments", // Not defined / deprecated |
|
274 | - 0x2D => "AttName", // Not defined / deprecated |
|
275 | - 0x2E => "AttSize", // Not defined / deprecated |
|
276 | - 0x2F => "AttOid", // Not defined / deprecated |
|
277 | - 0x30 => "AttMethod", // Not defined / deprecated |
|
278 | - 0x31 => "AttRemoved", // Not defined / deprecated |
|
279 | - 0x32 => "DisplayName", // Not defined / deprecated |
|
280 | - 0x33 => "DisallowNewTimeProposal", // Since 14.0 |
|
281 | - 0x34 => "ResponseRequested", // Since 14.0 |
|
282 | - 0x35 => "AppointmentReplyTime", // Since 14.0 |
|
283 | - 0x36 => "ResponseType", // Since 14.0 |
|
284 | - 0x37 => "CalendarType", // Since 14.0 |
|
285 | - 0x38 => "IsLeapMonth", // Since 14.0 |
|
286 | - 0x39 => "FirstDayOfWeek", // Since 14.1 |
|
287 | - 0x3A => "OnlineMeetingConfLink", // Since 14.1 |
|
288 | - 0x3B => "OnlineMeetingExternalLink", // Since 14.1 |
|
289 | - ], |
|
290 | - 5 => [ |
|
291 | - 0x05 => "Moves", |
|
292 | - 0x06 => "Move", |
|
293 | - 0x07 => "SrcMsgId", |
|
294 | - 0x08 => "SrcFldId", |
|
295 | - 0x09 => "DstFldId", |
|
296 | - 0x0A => "Response", |
|
297 | - 0x0B => "Status", |
|
298 | - 0x0C => "DstMsgId", |
|
299 | - ], |
|
300 | - 6 => [ |
|
301 | - 0x05 => "GetItemEstimate", |
|
302 | - 0x06 => "Version", // deprecated |
|
303 | - 0x07 => "Folders", // Collections |
|
304 | - 0x08 => "Folder", // Collection |
|
305 | - 0x09 => "FolderType", // AirSync Class(SYNC_FOLDERTYPE) is used since AS 14.0 |
|
306 | - 0x0A => "FolderId", // CollectionId |
|
307 | - 0x0B => "DateTime", // deprecated |
|
308 | - 0x0C => "Estimate", |
|
309 | - 0x0D => "Response", |
|
310 | - 0x0E => "Status", |
|
311 | - ], |
|
312 | - 7 => [ |
|
313 | - 0x05 => "Folders", // 2.5, 12.0 and 12.1 |
|
314 | - 0x06 => "Folder", // 2.5, 12.0 and 12.1 |
|
315 | - 0x07 => "DisplayName", |
|
316 | - 0x08 => "ServerEntryId", // ServerId |
|
317 | - 0x09 => "ParentId", |
|
318 | - 0x0A => "Type", |
|
319 | - 0x0B => "Response", // deprecated |
|
320 | - 0x0C => "Status", |
|
321 | - 0x0D => "ContentClass", // deprecated |
|
322 | - 0x0E => "Changes", |
|
323 | - 0x0F => "Add", |
|
324 | - 0x10 => "Remove", |
|
325 | - 0x11 => "Update", |
|
326 | - 0x12 => "SyncKey", |
|
327 | - 0x13 => "FolderCreate", |
|
328 | - 0x14 => "FolderDelete", |
|
329 | - 0x15 => "FolderUpdate", |
|
330 | - 0x16 => "FolderSync", |
|
331 | - 0x17 => "Count", |
|
332 | - 0x18 => "Version", // 2.0 - not defined in 20100501 |
|
333 | - ], |
|
334 | - 8 => [ |
|
335 | - 0x05 => "CalendarId", |
|
336 | - 0x06 => "FolderId", // CollectionId |
|
337 | - 0x07 => "MeetingResponse", |
|
338 | - 0x08 => "RequestId", |
|
339 | - 0x09 => "Request", |
|
340 | - 0x0A => "Result", |
|
341 | - 0x0B => "Status", |
|
342 | - 0x0C => "UserResponse", |
|
343 | - 0x0D => "Version", // 2.0 - not defined in 20100501 |
|
344 | - 0x0E => "InstanceId", // Since AS 14.1 |
|
345 | - ], |
|
346 | - 9 => [ |
|
347 | - 0x05 => "Body", // AirSyncBase Body is used since 12.0 |
|
348 | - 0x06 => "BodySize", // AirSyncBase Body is used since 12.0 |
|
349 | - 0x07 => "BodyTruncated", // AirSyncBase Body is used since 12.0 |
|
350 | - 0x08 => "Categories", |
|
351 | - 0x09 => "Category", |
|
352 | - 0x0A => "Complete", |
|
353 | - 0x0B => "DateCompleted", |
|
354 | - 0x0C => "DueDate", |
|
355 | - 0x0D => "UtcDueDate", |
|
356 | - 0x0E => "Importance", |
|
357 | - 0x0F => "Recurrence", |
|
358 | - 0x10 => "Type", |
|
359 | - 0x11 => "Start", |
|
360 | - 0x12 => "Until", |
|
361 | - 0x13 => "Occurrences", |
|
362 | - 0x14 => "Interval", |
|
363 | - 0x15 => "DayOfMonth", |
|
364 | - 0x16 => "DayOfWeek", |
|
365 | - 0x17 => "WeekOfMonth", |
|
366 | - 0x18 => "MonthOfYear", |
|
367 | - 0x19 => "Regenerate", |
|
368 | - 0x1A => "DeadOccur", |
|
369 | - 0x1B => "ReminderSet", |
|
370 | - 0x1C => "ReminderTime", |
|
371 | - 0x1D => "Sensitivity", |
|
372 | - 0x1E => "StartDate", |
|
373 | - 0x1F => "UtcStartDate", |
|
374 | - 0x20 => "Subject", |
|
375 | - 0x21 => "Rtf", // deprecated |
|
376 | - 0x22 => "OrdinalDate", // Since 12.0 |
|
377 | - 0x23 => "SubOrdinalDate", // Since 12.0 |
|
378 | - 0x24 => "CalendarType", // Since 14.0 |
|
379 | - 0x25 => "IsLeapMonth", // Since 14.0 |
|
380 | - 0x26 => "FirstDayOfWeek", // Since 14.1 |
|
381 | - ], |
|
382 | - 0xA => [ |
|
383 | - 0x05 => "ResolveRecipients", |
|
384 | - 0x06 => "Response", |
|
385 | - 0x07 => "Status", |
|
386 | - 0x08 => "Type", |
|
387 | - 0x09 => "Recipient", |
|
388 | - 0x0A => "DisplayName", |
|
389 | - 0x0B => "EmailAddress", |
|
390 | - 0x0C => "Certificates", |
|
391 | - 0x0D => "Certificate", |
|
392 | - 0x0E => "MiniCertificate", |
|
393 | - 0x0F => "Options", |
|
394 | - 0x10 => "To", |
|
395 | - 0x11 => "CertificateRetrieval", |
|
396 | - 0x12 => "RecipientCount", |
|
397 | - 0x13 => "MaxCertificates", |
|
398 | - 0x14 => "MaxAmbiguousRecipients", |
|
399 | - 0x15 => "CertificateCount", |
|
400 | - 0x16 => "Availability", // Since 14.0 |
|
401 | - 0x17 => "StartTime", // Since 14.0 |
|
402 | - 0x18 => "EndTime", // Since 14.0 |
|
403 | - 0x19 => "MergedFreeBusy", // Since 14.0 |
|
404 | - 0x1A => "Picture", // Since 14.1 |
|
405 | - 0x1B => "MaxSize", // Since 14.1 |
|
406 | - 0x1C => "Data", // Since 14.1 |
|
407 | - 0x1D => "MaxPictures", // Since 14.1 |
|
408 | - ], |
|
409 | - 0xB => [ |
|
410 | - 0x05 => "ValidateCert", |
|
411 | - 0x06 => "Certificates", |
|
412 | - 0x07 => "Certificate", |
|
413 | - 0x08 => "CertificateChain", |
|
414 | - 0x09 => "CheckCRL", |
|
415 | - 0x0A => "Status", |
|
416 | - ], |
|
417 | - 0xC => [ |
|
418 | - 0x05 => "CustomerId", |
|
419 | - 0x06 => "GovernmentId", |
|
420 | - 0x07 => "IMAddress", |
|
421 | - 0x08 => "IMAddress2", |
|
422 | - 0x09 => "IMAddress3", |
|
423 | - 0x0A => "ManagerName", |
|
424 | - 0x0B => "CompanyMainPhone", |
|
425 | - 0x0C => "AccountName", |
|
426 | - 0x0D => "NickName", |
|
427 | - 0x0E => "MMS", |
|
428 | - ], |
|
429 | - 0xD => [ |
|
430 | - 0x05 => "Ping", |
|
431 | - 0x06 => "AutdState", // (Not used by protocol) |
|
432 | - 0x07 => "Status", |
|
433 | - 0x08 => "LifeTime", // HeartbeatInterval |
|
434 | - 0x09 => "Folders", |
|
435 | - 0x0A => "Folder", |
|
436 | - 0x0B => "ServerEntryId", // Id |
|
437 | - 0x0C => "FolderType", // Class |
|
438 | - 0x0D => "MaxFolders", |
|
439 | - 0x0E => "Version", // not defined / deprecated |
|
440 | - ], |
|
441 | - 0xE => [ |
|
442 | - 0x05 => "Provision", |
|
443 | - 0x06 => "Policies", |
|
444 | - 0x07 => "Policy", |
|
445 | - 0x08 => "PolicyType", |
|
446 | - 0x09 => "PolicyKey", |
|
447 | - 0x0A => "Data", |
|
448 | - 0x0B => "Status", |
|
449 | - 0x0C => "RemoteWipe", |
|
450 | - 0x0D => "EASProvisionDoc", // Since 12.0 |
|
451 | - 0x0E => "DevicePasswordEnabled", // Since 12.0 |
|
452 | - 0x0F => "AlphanumericDevicePasswordRequired", // Since 12.0 |
|
453 | - 0x10 => "DeviceEncryptionEnabled", // Since 12.1 |
|
454 | - // 0x10 => "RequireStorageCardEncryption", //Since 12.1 |
|
455 | - 0x11 => "PasswordRecoveryEnabled", // Since 12.0 |
|
456 | - 0x12 => "DocumentBrowseEnabled", // not defined / deprecated |
|
457 | - 0x13 => "AttachmentsEnabled", // Since 12.0 |
|
458 | - 0x14 => "MinDevicePasswordLength", // Since 12.0 |
|
459 | - 0x15 => "MaxInactivityTimeDeviceLock", // Since 12.0 |
|
460 | - 0x16 => "MaxDevicePasswordFailedAttempts", // Since 12.0 |
|
461 | - 0x17 => "MaxAttachmentSize", // Since 12.0 |
|
462 | - 0x18 => "AllowSimpleDevicePassword", // Since 12.0 |
|
463 | - 0x19 => "DevicePasswordExpiration", // Since 12.0 |
|
464 | - 0x1A => "DevicePasswordHistory", // Since 12.0 |
|
465 | - 0x1B => "AllowStorageCard", // Since 12.1 |
|
466 | - 0x1C => "AllowCamera", // Since 12.1 |
|
467 | - 0x1D => "RequireDeviceEncryption", // Since 12.1 |
|
468 | - 0x1E => "AllowUnsignedApplications", // Since 12.1 |
|
469 | - 0x1F => "AllowUnsignedInstallationPackages", // Since 12.1 |
|
470 | - 0x20 => "MinDevicePasswordComplexCharacters", // Since 12.1 |
|
471 | - 0x21 => "AllowWiFi", // Since 12.1 |
|
472 | - 0x22 => "AllowTextMessaging", // Since 12.1 |
|
473 | - 0x23 => "AllowPOPIMAPEmail", // Since 12.1 |
|
474 | - 0x24 => "AllowBluetooth", // Since 12.1 |
|
475 | - 0x25 => "AllowIrDA", // Since 12.1 |
|
476 | - 0x26 => "RequireManualSyncWhenRoaming", // Since 12.1 |
|
477 | - 0x27 => "AllowDesktopSync", // Since 12.1 |
|
478 | - 0x28 => "MaxCalendarAgeFilter", // Since 12.1 |
|
479 | - 0x29 => "AllowHTMLEmail", // Since 12.1 |
|
480 | - 0x2A => "MaxEmailAgeFilter", // Since 12.1 |
|
481 | - 0x2B => "MaxEmailBodyTruncationSize", // Since 12.1 |
|
482 | - 0x2C => "MaxEmailHTMLBodyTruncationSize", // Since 12.1 |
|
483 | - 0x2D => "RequireSignedSMIMEMessages", // Since 12.1 |
|
484 | - 0x2E => "RequireEncryptedSMIMEMessages", // Since 12.1 |
|
485 | - 0x2F => "RequireSignedSMIMEAlgorithm", // Since 12.1 |
|
486 | - 0x30 => "RequireEncryptionSMIMEAlgorithm", // Since 12.1 |
|
487 | - 0x31 => "AllowSMIMEEncryptionAlgorithmNegotiation", // Since 12.1 |
|
488 | - 0x32 => "AllowSMIMESoftCerts", // Since 12.1 |
|
489 | - 0x33 => "AllowBrowser", // Since 12.1 |
|
490 | - 0x34 => "AllowConsumerEmail", // Since 12.1 |
|
491 | - 0x35 => "AllowRemoteDesktop", // Since 12.1 |
|
492 | - 0x36 => "AllowInternetSharing", // Since 12.1 |
|
493 | - 0x37 => "UnapprovedInROMApplicationList", // Since 12.1 |
|
494 | - 0x38 => "ApplicationName", // Since 12.1 |
|
495 | - 0x39 => "ApprovedApplicationList", // Since 12.1 |
|
496 | - 0x3A => "Hash", // Since 12.1 |
|
497 | - ], |
|
498 | - 0xF => [ |
|
499 | - 0x05 => "Search", |
|
500 | - 0x07 => "Store", |
|
501 | - 0x08 => "Name", |
|
502 | - 0x09 => "Query", |
|
503 | - 0x0A => "Options", |
|
504 | - 0x0B => "Range", |
|
505 | - 0x0C => "Status", |
|
506 | - 0x0D => "Response", |
|
507 | - 0x0E => "Result", |
|
508 | - 0x0F => "Properties", |
|
509 | - 0x10 => "Total", |
|
510 | - 0x11 => "EqualTo", |
|
511 | - 0x12 => "Value", |
|
512 | - 0x13 => "And", |
|
513 | - 0x14 => "Or", |
|
514 | - 0x15 => "FreeText", |
|
515 | - 0x17 => "DeepTraversal", |
|
516 | - 0x18 => "LongId", |
|
517 | - 0x19 => "RebuildResults", |
|
518 | - 0x1A => "LessThan", |
|
519 | - 0x1B => "GreaterThan", |
|
520 | - 0x1C => "Schema", |
|
521 | - 0x1D => "Supported", |
|
522 | - 0x1E => "UserName", // Since 12.1 |
|
523 | - 0x1F => "Password", // Since 12.1 |
|
524 | - 0x20 => "ConversationId", // Since 14.0 |
|
525 | - 0x21 => "Picture", // Since 14.1 |
|
526 | - 0x22 => "MaxSize", // Since 14.1 |
|
527 | - 0x23 => "MaxPictures", // Since 14.1 |
|
528 | - ], |
|
529 | - 0x10 => [ |
|
530 | - 0x05 => "DisplayName", |
|
531 | - 0x06 => "Phone", |
|
532 | - 0x07 => "Office", |
|
533 | - 0x08 => "Title", |
|
534 | - 0x09 => "Company", |
|
535 | - 0x0A => "Alias", |
|
536 | - 0x0B => "FirstName", |
|
537 | - 0x0C => "LastName", |
|
538 | - 0x0D => "HomePhone", |
|
539 | - 0x0E => "MobilePhone", |
|
540 | - 0x0F => "EmailAddress", |
|
541 | - 0x10 => "Picture", // Since 14.1 |
|
542 | - 0x11 => "Status", // Since 14.1 |
|
543 | - 0x12 => "Data", // Since 14.1 |
|
544 | - ], |
|
545 | - 0x11 => [ // Since 12.0 |
|
546 | - 0x05 => "BodyPreference", |
|
547 | - 0x06 => "Type", |
|
548 | - 0x07 => "TruncationSize", |
|
549 | - 0x08 => "AllOrNone", |
|
550 | - 0x0A => "Body", |
|
551 | - 0x0B => "Data", |
|
552 | - 0x0C => "EstimatedDataSize", |
|
553 | - 0x0D => "Truncated", |
|
554 | - 0x0E => "Attachments", |
|
555 | - 0x0F => "Attachment", |
|
556 | - 0x10 => "DisplayName", |
|
557 | - 0x11 => "FileReference", |
|
558 | - 0x12 => "Method", |
|
559 | - 0x13 => "ContentId", |
|
560 | - 0x14 => "ContentLocation", |
|
561 | - 0x15 => "IsInline", |
|
562 | - 0x16 => "NativeBodyType", |
|
563 | - 0x17 => "ContentType", |
|
564 | - 0x18 => "Preview", // Since 14.0 |
|
565 | - 0x19 => "BodyPartPreference", // Since 14.1 |
|
566 | - 0x1A => "BodyPart", // Since 14.1 |
|
567 | - 0x1B => "Status", // Since 14.1 |
|
568 | - ], |
|
569 | - 0x12 => [ // Since 12.0 |
|
570 | - 0x05 => "Settings", |
|
571 | - 0x06 => "Status", |
|
572 | - 0x07 => "Get", |
|
573 | - 0x08 => "Set", |
|
574 | - 0x09 => "Oof", |
|
575 | - 0x0A => "OofState", |
|
576 | - 0x0B => "StartTime", |
|
577 | - 0x0C => "EndTime", |
|
578 | - 0x0D => "OofMessage", |
|
579 | - 0x0E => "AppliesToInternal", |
|
580 | - 0x0F => "AppliesToExternalKnown", |
|
581 | - 0x10 => "AppliesToExternalUnknown", |
|
582 | - 0x11 => "Enabled", |
|
583 | - 0x12 => "ReplyMessage", |
|
584 | - 0x13 => "BodyType", |
|
585 | - 0x14 => "DevicePassword", |
|
586 | - 0x15 => "Password", |
|
587 | - 0x16 => "DeviceInformation", |
|
588 | - 0x17 => "Model", |
|
589 | - 0x18 => "IMEI", |
|
590 | - 0x19 => "FriendlyName", |
|
591 | - 0x1A => "OS", |
|
592 | - 0x1B => "OSLanguage", |
|
593 | - 0x1C => "PhoneNumber", |
|
594 | - 0x1D => "UserInformation", |
|
595 | - 0x1E => "EmailAddresses", |
|
596 | - 0x1F => "SmtpAddress", |
|
597 | - 0x20 => "UserAgent", // Since 12.1 |
|
598 | - 0x21 => "EnableOutboundSMS", // Since 14.0 |
|
599 | - 0x22 => "MobileOperator", // Since 14.0 |
|
600 | - 0x23 => "PrimarySmtpAddress", // Since 14.1 |
|
601 | - 0x24 => "Accounts", // Since 14.1 |
|
602 | - 0x25 => "Account", // Since 14.1 |
|
603 | - 0x26 => "AccountId", // Since 14.1 |
|
604 | - 0x27 => "AccountName", // Since 14.1 |
|
605 | - 0x28 => "UserDisplayName", // Since 14.1 |
|
606 | - 0x29 => "SendDisabled", // Since 14.1 |
|
607 | - 0x2B => "RightsManagementInformation", // Since 14.1 |
|
608 | - ], |
|
609 | - 0x13 => [ // Since 12.0 |
|
610 | - 0x05 => "LinkId", |
|
611 | - 0x06 => "DisplayName", |
|
612 | - 0x07 => "IsFolder", |
|
613 | - 0x08 => "CreationDate", |
|
614 | - 0x09 => "LastModifiedDate", |
|
615 | - 0x0A => "IsHidden", |
|
616 | - 0x0B => "ContentLength", |
|
617 | - 0x0C => "ContentType", |
|
618 | - ], |
|
619 | - 0x14 => [ // Since 12.0 |
|
620 | - 0x05 => "ItemOperations", |
|
621 | - 0x06 => "Fetch", |
|
622 | - 0x07 => "Store", |
|
623 | - 0x08 => "Options", |
|
624 | - 0x09 => "Range", |
|
625 | - 0x0A => "Total", |
|
626 | - 0x0B => "Properties", |
|
627 | - 0x0C => "Data", |
|
628 | - 0x0D => "Status", |
|
629 | - 0x0E => "Response", |
|
630 | - 0x0F => "Version", |
|
631 | - 0x10 => "Schema", |
|
632 | - 0x11 => "Part", |
|
633 | - 0x12 => "EmptyFolderContents", |
|
634 | - 0x13 => "DeleteSubFolders", |
|
635 | - 0x14 => "UserName", // Since 12.1 |
|
636 | - 0x15 => "Password", // Since 12.1 |
|
637 | - 0x16 => "Move", // Since 14.0 |
|
638 | - 0x17 => "DstFldId", // Since 14.0 |
|
639 | - 0x18 => "ConversationId", // Since 14.0 |
|
640 | - 0x19 => "MoveAlways", // Since 14.0 |
|
641 | - ], |
|
642 | - 0x15 => [ // Since 14.0 |
|
643 | - 0x05 => "SendMail", |
|
644 | - 0x06 => "SmartForward", |
|
645 | - 0x07 => "SmartReply", |
|
646 | - 0x08 => "SaveInSentItems", |
|
647 | - 0x09 => "ReplaceMime", |
|
648 | - 0x0A => "Type", // not used |
|
649 | - 0x0B => "Source", |
|
650 | - 0x0C => "FolderId", |
|
651 | - 0x0D => "ItemId", |
|
652 | - 0x0E => "LongId", |
|
653 | - 0x0F => "InstanceId", |
|
654 | - 0x10 => "MIME", |
|
655 | - 0x11 => "ClientId", |
|
656 | - 0x12 => "Status", |
|
657 | - 0x13 => "AccountId", // Since 14.1 |
|
658 | - ], |
|
659 | - 0x16 => [ // Since 14.0 |
|
660 | - 0x05 => "UmCallerId", |
|
661 | - 0x06 => "UmUserNotes", |
|
662 | - 0x07 => "UmAttDuration", |
|
663 | - 0x08 => "UmAttOrder", |
|
664 | - 0x09 => "ConversationId", |
|
665 | - 0x0A => "ConversationIndex", |
|
666 | - 0x0B => "LastVerbExecuted", |
|
667 | - 0x0C => "LastVerbExecutionTime", |
|
668 | - 0x0D => "ReceivedAsBcc", |
|
669 | - 0x0E => "Sender", |
|
670 | - 0x0F => "CalendarType", |
|
671 | - 0x10 => "IsLeapMonth", |
|
672 | - 0x11 => "AccountId", // Since 14.1 |
|
673 | - 0x12 => "FirstDayOfWeek", // Since 14.1 |
|
674 | - 0x13 => "MeetingMessageType", // Since 14.1 |
|
675 | - ], |
|
676 | - 0x17 => [ // Since 14.0 |
|
677 | - 0x05 => "Subject", |
|
678 | - 0x06 => "MessageClass", |
|
679 | - 0x07 => "LastModifiedDate", |
|
680 | - 0x08 => "Categories", |
|
681 | - 0x09 => "Category", |
|
682 | - ], |
|
683 | - 0x18 => [ // Since 14.1 |
|
684 | - 0x05 => "RightsManagementSupport", |
|
685 | - 0x06 => "RightsManagementTemplates", |
|
686 | - 0x07 => "RightsManagementTemplate", |
|
687 | - 0x08 => "RightsManagementLicense", |
|
688 | - 0x09 => "EditAllowed", |
|
689 | - 0x0A => "ReplyAllowed", |
|
690 | - 0x0B => "ReplyAllAllowed", |
|
691 | - 0x0C => "ForwardAllowed", |
|
692 | - 0x0D => "ModifyRecipientsAllowed", |
|
693 | - 0x0E => "ExtractAllowed", |
|
694 | - 0x0F => "PrintAllowed", |
|
695 | - 0x10 => "ExportAllowed", |
|
696 | - 0x11 => "ProgrammaticAccessAllowed", |
|
697 | - 0x12 => "Owner", |
|
698 | - 0x13 => "ContentExpiryDate", |
|
699 | - 0x14 => "TemplateID", |
|
700 | - 0x15 => "TemplateName", |
|
701 | - 0x16 => "TemplateDescription", |
|
702 | - 0x17 => "ContentOwner", |
|
703 | - 0x18 => "RemoveRightsManagementProtection", |
|
704 | - ], |
|
705 | - ], |
|
706 | - "namespaces" => [ |
|
707 | - // 0 => "AirSync", // |
|
708 | - 1 => "POOMCONTACTS", |
|
709 | - 2 => "POOMMAIL", |
|
710 | - 3 => "AirNotify", // no longer used |
|
711 | - 4 => "POOMCAL", |
|
712 | - 5 => "Move", |
|
713 | - 6 => "GetItemEstimate", |
|
714 | - 7 => "FolderHierarchy", |
|
715 | - 8 => "MeetingResponse", |
|
716 | - 9 => "POOMTASKS", |
|
717 | - 0xA => "ResolveRecipients", |
|
718 | - 0xB => "ValidateCert", |
|
719 | - 0xC => "POOMCONTACTS2", |
|
720 | - 0xD => "Ping", |
|
721 | - 0xE => "Provision", |
|
722 | - 0xF => "Search", |
|
723 | - 0x10 => "GAL", |
|
724 | - 0x11 => "AirSyncBase", // 12.0, 12.1 and 14.0 |
|
725 | - 0x12 => "Settings", // 12.0, 12.1 and 14.0. |
|
726 | - 0x13 => "DocumentLibrary", // 12.0, 12.1 and 14.0 |
|
727 | - 0x14 => "ItemOperations", // 12.0, 12.1 and 14.0 |
|
728 | - 0x15 => "ComposeMail", // 14.0 |
|
729 | - 0x16 => "POOMMAIL2", // 14.0 |
|
730 | - 0x17 => "Notes", // 14.0 |
|
731 | - 0x18 => "RightsManagement", |
|
732 | - ], |
|
733 | - ]; |
|
47 | + /** |
|
48 | + * The WBXML DTDs. |
|
49 | + */ |
|
50 | + protected $dtd = [ |
|
51 | + "codes" => [ |
|
52 | + 0 => [ |
|
53 | + 0x05 => "Synchronize", |
|
54 | + 0x06 => "Replies", // Responses |
|
55 | + 0x07 => "Add", |
|
56 | + 0x08 => "Modify", // Change |
|
57 | + 0x09 => "Remove", // Delete |
|
58 | + 0x0A => "Fetch", |
|
59 | + 0x0B => "SyncKey", |
|
60 | + 0x0C => "ClientEntryId", // ClientId |
|
61 | + 0x0D => "ServerEntryId", // ServerId |
|
62 | + 0x0E => "Status", |
|
63 | + 0x0F => "Folder", // collection |
|
64 | + 0x10 => "FolderType", // class |
|
65 | + 0x11 => "Version", // deprecated |
|
66 | + 0x12 => "FolderId", // CollectionId |
|
67 | + 0x13 => "GetChanges", |
|
68 | + 0x14 => "MoreAvailable", |
|
69 | + 0x15 => "WindowSize", // WindowSize - MaxItems before version 2 |
|
70 | + 0x16 => "Perform", // Commands |
|
71 | + 0x17 => "Options", |
|
72 | + 0x18 => "FilterType", |
|
73 | + 0x19 => "Truncation", // 2.0 and 2.5 |
|
74 | + 0x1A => "RtfTruncation", // 2.0 and 2.5 |
|
75 | + 0x1B => "Conflict", |
|
76 | + 0x1C => "Folders", // Collections |
|
77 | + 0x1D => "Data", |
|
78 | + 0x1E => "DeletesAsMoves", |
|
79 | + 0x1F => "NotifyGUID", // 2.0 and 2.5 |
|
80 | + 0x20 => "Supported", |
|
81 | + 0x21 => "SoftDelete", |
|
82 | + 0x22 => "MIMESupport", |
|
83 | + 0x23 => "MIMETruncation", |
|
84 | + 0x24 => "Wait", // Since 12.1 |
|
85 | + 0x25 => "Limit", // Since 12.1 |
|
86 | + 0x26 => "Partial", // Since 12.1 |
|
87 | + 0x27 => "ConversationMode", // Since 14.0 |
|
88 | + 0x28 => "MaxItems", // Since 14.0 |
|
89 | + 0x29 => "HeartbeatInterval", // Since 14.0 Either this tag or the Wait tag can be present, but not both. |
|
90 | + ], |
|
91 | + 1 => [ |
|
92 | + 0x05 => "Anniversary", |
|
93 | + 0x06 => "AssistantName", |
|
94 | + 0x07 => "AssistnamePhoneNumber", // AssistantTelephoneNumber |
|
95 | + 0x08 => "Birthday", |
|
96 | + 0x09 => "Body", // 2.5, AirSyncBase Body is used since version 12.0 |
|
97 | + 0x0A => "BodySize", // 2.0 and 2.5, AirSyncBase is used since version 12.0 |
|
98 | + 0x0B => "BodyTruncated", // 2.0 and 2.5, AirSyncBase is used since version 12.0 |
|
99 | + 0x0C => "Business2PhoneNumber", |
|
100 | + 0x0D => "BusinessCity", |
|
101 | + 0x0E => "BusinessCountry", |
|
102 | + 0x0F => "BusinessPostalCode", |
|
103 | + 0x10 => "BusinessState", |
|
104 | + 0x11 => "BusinessStreet", |
|
105 | + 0x12 => "BusinessFaxNumber", |
|
106 | + 0x13 => "BusinessPhoneNumber", |
|
107 | + 0x14 => "CarPhoneNumber", |
|
108 | + 0x15 => "Categories", |
|
109 | + 0x16 => "Category", |
|
110 | + 0x17 => "Children", |
|
111 | + 0x18 => "Child", |
|
112 | + 0x19 => "CompanyName", |
|
113 | + 0x1A => "Department", |
|
114 | + 0x1B => "Email1Address", |
|
115 | + 0x1C => "Email2Address", |
|
116 | + 0x1D => "Email3Address", |
|
117 | + 0x1E => "FileAs", |
|
118 | + 0x1F => "FirstName", |
|
119 | + 0x20 => "Home2PhoneNumber", |
|
120 | + 0x21 => "HomeCity", |
|
121 | + 0x22 => "HomeCountry", |
|
122 | + 0x23 => "HomePostalCode", |
|
123 | + 0x24 => "HomeState", |
|
124 | + 0x25 => "HomeStreet", |
|
125 | + 0x26 => "HomeFaxNumber", |
|
126 | + 0x27 => "HomePhoneNumber", |
|
127 | + 0x28 => "JobTitle", |
|
128 | + 0x29 => "LastName", |
|
129 | + 0x2A => "MiddleName", |
|
130 | + 0x2B => "MobilePhoneNumber", |
|
131 | + 0x2C => "OfficeLocation", |
|
132 | + 0x2D => "OtherCity", |
|
133 | + 0x2E => "OtherCountry", |
|
134 | + 0x2F => "OtherPostalCode", |
|
135 | + 0x30 => "OtherState", |
|
136 | + 0x31 => "OtherStreet", |
|
137 | + 0x32 => "PagerNumber", |
|
138 | + 0x33 => "RadioPhoneNumber", |
|
139 | + 0x34 => "Spouse", |
|
140 | + 0x35 => "Suffix", |
|
141 | + 0x36 => "Title", |
|
142 | + 0x37 => "WebPage", |
|
143 | + 0x38 => "YomiCompanyName", |
|
144 | + 0x39 => "YomiFirstName", |
|
145 | + 0x3A => "YomiLastName", |
|
146 | + 0x3B => "Rtf", // CompressedRTF - 2.5, deprecated |
|
147 | + 0x3C => "Picture", |
|
148 | + 0x3D => "Alias", // Since 14.0 |
|
149 | + 0x3E => "WeightedRank", // Since 14.0 |
|
150 | + ], |
|
151 | + 2 => [ |
|
152 | + 0x05 => "Attachment", // AirSyncBase Attachments is used since 12.0 |
|
153 | + 0x06 => "Attachments", // AirSyncBase Attachments is used since 12.0 |
|
154 | + 0x07 => "AttName", // AirSyncBase Attachments is used since 12.0 |
|
155 | + 0x08 => "AttSize", // AirSyncBase Attachments is used since 12.0 |
|
156 | + 0x09 => "AttOid", // AirSyncBase Attachments is used since 12.0 |
|
157 | + 0x0A => "AttMethod", // AirSyncBase Attachments is used since 12.0 |
|
158 | + 0x0B => "AttRemoved", // AirSyncBase Attachments is used since 12.0 |
|
159 | + 0x0C => "Body", // AirSyncBase Body is used since 12.0 |
|
160 | + 0x0D => "BodySize", // AirSyncBase Body is used since 12.0 |
|
161 | + 0x0E => "BodyTruncated", // AirSyncBase Body is used since 12.0 |
|
162 | + 0x0F => "DateReceived", |
|
163 | + 0x10 => "DisplayName", // AirSyncBase Attachments is used since 12.0 |
|
164 | + 0x11 => "DisplayTo", |
|
165 | + 0x12 => "Importance", |
|
166 | + 0x13 => "MessageClass", |
|
167 | + 0x14 => "Subject", |
|
168 | + 0x15 => "Read", |
|
169 | + 0x16 => "To", |
|
170 | + 0x17 => "Cc", |
|
171 | + 0x18 => "From", |
|
172 | + 0x19 => "Reply-To", |
|
173 | + 0x1A => "AllDayEvent", |
|
174 | + 0x1B => "Categories", // Since 14.0 |
|
175 | + 0x1C => "Category", // Since 14.0 |
|
176 | + 0x1D => "DtStamp", |
|
177 | + 0x1E => "EndTime", |
|
178 | + 0x1F => "InstanceType", |
|
179 | + 0x20 => "BusyStatus", |
|
180 | + 0x21 => "Location", // 2.5, 12.0, 12.1, 14.0 and 14.1. Since 16.0 AirSyncBase Location is used. |
|
181 | + 0x22 => "MeetingRequest", |
|
182 | + 0x23 => "Organizer", |
|
183 | + 0x24 => "RecurrenceId", |
|
184 | + 0x25 => "Reminder", |
|
185 | + 0x26 => "ResponseRequested", |
|
186 | + 0x27 => "Recurrences", |
|
187 | + 0x28 => "Recurrence", |
|
188 | + 0x29 => "Type", |
|
189 | + 0x2A => "Until", |
|
190 | + 0x2B => "Occurrences", |
|
191 | + 0x2C => "Interval", |
|
192 | + 0x2D => "DayOfWeek", |
|
193 | + 0x2E => "DayOfMonth", |
|
194 | + 0x2F => "WeekOfMonth", |
|
195 | + 0x30 => "MonthOfYear", |
|
196 | + 0x31 => "StartTime", |
|
197 | + 0x32 => "Sensitivity", |
|
198 | + 0x33 => "TimeZone", |
|
199 | + 0x34 => "GlobalObjId", // 2.5, 12.0, 12.1, 14.0 and 14.1. UID of Calendar (Code page 4) is used since 16.0 |
|
200 | + 0x35 => "ThreadTopic", |
|
201 | + 0x36 => "MIMEData", // 2.5 |
|
202 | + 0x37 => "MIMETruncated", // 2.5 |
|
203 | + 0x38 => "MIMESize", // 2.5 |
|
204 | + 0x39 => "InternetCPID", |
|
205 | + 0x3A => "Flag", // Since 12.0 |
|
206 | + 0x3B => "FlagStatus", // Since 12.0 |
|
207 | + 0x3C => "ContentClass", // Since 12.0 |
|
208 | + 0x3D => "FlagType", // Since 12.0 |
|
209 | + 0x3E => "CompleteTime", // Since 12.0 |
|
210 | + 0x3F => "DisallowNewTimeProposal", // Since 14.0 |
|
211 | + ], |
|
212 | + 3 => [ // Code page 3 is no longer in use, however, tokens 05 through 17 have been defined. 20100501 |
|
213 | + 0x05 => "Notify", |
|
214 | + 0x06 => "Notification", |
|
215 | + 0x07 => "Version", |
|
216 | + 0x08 => "Lifetime", |
|
217 | + 0x09 => "DeviceInfo", |
|
218 | + 0x0A => "Enable", |
|
219 | + 0x0B => "Folder", |
|
220 | + 0x0C => "ServerEntryId", |
|
221 | + 0x0D => "DeviceAddress", |
|
222 | + 0x0E => "ValidCarrierProfiles", |
|
223 | + 0x0F => "CarrierProfile", |
|
224 | + 0x10 => "Status", |
|
225 | + 0x11 => "Replies", |
|
226 | + // 0x05 => "Version='1.1'", |
|
227 | + 0x12 => "Devices", |
|
228 | + 0x13 => "Device", |
|
229 | + 0x14 => "Id", |
|
230 | + 0x15 => "Expiry", |
|
231 | + 0x16 => "NotifyGUID", |
|
232 | + ], |
|
233 | + 4 => [ |
|
234 | + 0x05 => "Timezone", |
|
235 | + 0x06 => "AllDayEvent", |
|
236 | + 0x07 => "Attendees", |
|
237 | + 0x08 => "Attendee", |
|
238 | + 0x09 => "Email", |
|
239 | + 0x0A => "Name", |
|
240 | + 0x0B => "Body", // AirSyncBase Body is used since 12.0 |
|
241 | + 0x0C => "BodyTruncated", // AirSyncBase Body is used since 12.0 |
|
242 | + 0x0D => "BusyStatus", |
|
243 | + 0x0E => "Categories", |
|
244 | + 0x0F => "Category", |
|
245 | + 0x10 => "Rtf", // 2.5 - deprecated |
|
246 | + 0x11 => "DtStamp", |
|
247 | + 0x12 => "EndTime", |
|
248 | + 0x13 => "Exception", |
|
249 | + 0x14 => "Exceptions", |
|
250 | + 0x15 => "Deleted", |
|
251 | + 0x16 => "ExceptionStartTime", // 2.5, 12.0, 12.1, 14.0 and 14.1. |
|
252 | + 0x17 => "Location", // 2.5, 12.0, 12.1, 14.0 and 14.1. Since 16.0 AirSyncBase Location is used. |
|
253 | + 0x18 => "MeetingStatus", |
|
254 | + 0x19 => "OrganizerEmail", |
|
255 | + 0x1A => "OrganizerName", |
|
256 | + 0x1B => "Recurrence", |
|
257 | + 0x1C => "Type", |
|
258 | + 0x1D => "Until", |
|
259 | + 0x1E => "Occurrences", |
|
260 | + 0x1F => "Interval", |
|
261 | + 0x20 => "DayOfWeek", |
|
262 | + 0x21 => "DayOfMonth", |
|
263 | + 0x22 => "WeekOfMonth", |
|
264 | + 0x23 => "MonthOfYear", |
|
265 | + 0x24 => "Reminder", |
|
266 | + 0x25 => "Sensitivity", |
|
267 | + 0x26 => "Subject", |
|
268 | + 0x27 => "StartTime", |
|
269 | + 0x28 => "UID", |
|
270 | + 0x29 => "Attendee_Status", // Since 12.0 |
|
271 | + 0x2A => "Attendee_Type", // Since 12.0 |
|
272 | + 0x2B => "Attachment", // Not defined / deprecated |
|
273 | + 0x2C => "Attachments", // Not defined / deprecated |
|
274 | + 0x2D => "AttName", // Not defined / deprecated |
|
275 | + 0x2E => "AttSize", // Not defined / deprecated |
|
276 | + 0x2F => "AttOid", // Not defined / deprecated |
|
277 | + 0x30 => "AttMethod", // Not defined / deprecated |
|
278 | + 0x31 => "AttRemoved", // Not defined / deprecated |
|
279 | + 0x32 => "DisplayName", // Not defined / deprecated |
|
280 | + 0x33 => "DisallowNewTimeProposal", // Since 14.0 |
|
281 | + 0x34 => "ResponseRequested", // Since 14.0 |
|
282 | + 0x35 => "AppointmentReplyTime", // Since 14.0 |
|
283 | + 0x36 => "ResponseType", // Since 14.0 |
|
284 | + 0x37 => "CalendarType", // Since 14.0 |
|
285 | + 0x38 => "IsLeapMonth", // Since 14.0 |
|
286 | + 0x39 => "FirstDayOfWeek", // Since 14.1 |
|
287 | + 0x3A => "OnlineMeetingConfLink", // Since 14.1 |
|
288 | + 0x3B => "OnlineMeetingExternalLink", // Since 14.1 |
|
289 | + ], |
|
290 | + 5 => [ |
|
291 | + 0x05 => "Moves", |
|
292 | + 0x06 => "Move", |
|
293 | + 0x07 => "SrcMsgId", |
|
294 | + 0x08 => "SrcFldId", |
|
295 | + 0x09 => "DstFldId", |
|
296 | + 0x0A => "Response", |
|
297 | + 0x0B => "Status", |
|
298 | + 0x0C => "DstMsgId", |
|
299 | + ], |
|
300 | + 6 => [ |
|
301 | + 0x05 => "GetItemEstimate", |
|
302 | + 0x06 => "Version", // deprecated |
|
303 | + 0x07 => "Folders", // Collections |
|
304 | + 0x08 => "Folder", // Collection |
|
305 | + 0x09 => "FolderType", // AirSync Class(SYNC_FOLDERTYPE) is used since AS 14.0 |
|
306 | + 0x0A => "FolderId", // CollectionId |
|
307 | + 0x0B => "DateTime", // deprecated |
|
308 | + 0x0C => "Estimate", |
|
309 | + 0x0D => "Response", |
|
310 | + 0x0E => "Status", |
|
311 | + ], |
|
312 | + 7 => [ |
|
313 | + 0x05 => "Folders", // 2.5, 12.0 and 12.1 |
|
314 | + 0x06 => "Folder", // 2.5, 12.0 and 12.1 |
|
315 | + 0x07 => "DisplayName", |
|
316 | + 0x08 => "ServerEntryId", // ServerId |
|
317 | + 0x09 => "ParentId", |
|
318 | + 0x0A => "Type", |
|
319 | + 0x0B => "Response", // deprecated |
|
320 | + 0x0C => "Status", |
|
321 | + 0x0D => "ContentClass", // deprecated |
|
322 | + 0x0E => "Changes", |
|
323 | + 0x0F => "Add", |
|
324 | + 0x10 => "Remove", |
|
325 | + 0x11 => "Update", |
|
326 | + 0x12 => "SyncKey", |
|
327 | + 0x13 => "FolderCreate", |
|
328 | + 0x14 => "FolderDelete", |
|
329 | + 0x15 => "FolderUpdate", |
|
330 | + 0x16 => "FolderSync", |
|
331 | + 0x17 => "Count", |
|
332 | + 0x18 => "Version", // 2.0 - not defined in 20100501 |
|
333 | + ], |
|
334 | + 8 => [ |
|
335 | + 0x05 => "CalendarId", |
|
336 | + 0x06 => "FolderId", // CollectionId |
|
337 | + 0x07 => "MeetingResponse", |
|
338 | + 0x08 => "RequestId", |
|
339 | + 0x09 => "Request", |
|
340 | + 0x0A => "Result", |
|
341 | + 0x0B => "Status", |
|
342 | + 0x0C => "UserResponse", |
|
343 | + 0x0D => "Version", // 2.0 - not defined in 20100501 |
|
344 | + 0x0E => "InstanceId", // Since AS 14.1 |
|
345 | + ], |
|
346 | + 9 => [ |
|
347 | + 0x05 => "Body", // AirSyncBase Body is used since 12.0 |
|
348 | + 0x06 => "BodySize", // AirSyncBase Body is used since 12.0 |
|
349 | + 0x07 => "BodyTruncated", // AirSyncBase Body is used since 12.0 |
|
350 | + 0x08 => "Categories", |
|
351 | + 0x09 => "Category", |
|
352 | + 0x0A => "Complete", |
|
353 | + 0x0B => "DateCompleted", |
|
354 | + 0x0C => "DueDate", |
|
355 | + 0x0D => "UtcDueDate", |
|
356 | + 0x0E => "Importance", |
|
357 | + 0x0F => "Recurrence", |
|
358 | + 0x10 => "Type", |
|
359 | + 0x11 => "Start", |
|
360 | + 0x12 => "Until", |
|
361 | + 0x13 => "Occurrences", |
|
362 | + 0x14 => "Interval", |
|
363 | + 0x15 => "DayOfMonth", |
|
364 | + 0x16 => "DayOfWeek", |
|
365 | + 0x17 => "WeekOfMonth", |
|
366 | + 0x18 => "MonthOfYear", |
|
367 | + 0x19 => "Regenerate", |
|
368 | + 0x1A => "DeadOccur", |
|
369 | + 0x1B => "ReminderSet", |
|
370 | + 0x1C => "ReminderTime", |
|
371 | + 0x1D => "Sensitivity", |
|
372 | + 0x1E => "StartDate", |
|
373 | + 0x1F => "UtcStartDate", |
|
374 | + 0x20 => "Subject", |
|
375 | + 0x21 => "Rtf", // deprecated |
|
376 | + 0x22 => "OrdinalDate", // Since 12.0 |
|
377 | + 0x23 => "SubOrdinalDate", // Since 12.0 |
|
378 | + 0x24 => "CalendarType", // Since 14.0 |
|
379 | + 0x25 => "IsLeapMonth", // Since 14.0 |
|
380 | + 0x26 => "FirstDayOfWeek", // Since 14.1 |
|
381 | + ], |
|
382 | + 0xA => [ |
|
383 | + 0x05 => "ResolveRecipients", |
|
384 | + 0x06 => "Response", |
|
385 | + 0x07 => "Status", |
|
386 | + 0x08 => "Type", |
|
387 | + 0x09 => "Recipient", |
|
388 | + 0x0A => "DisplayName", |
|
389 | + 0x0B => "EmailAddress", |
|
390 | + 0x0C => "Certificates", |
|
391 | + 0x0D => "Certificate", |
|
392 | + 0x0E => "MiniCertificate", |
|
393 | + 0x0F => "Options", |
|
394 | + 0x10 => "To", |
|
395 | + 0x11 => "CertificateRetrieval", |
|
396 | + 0x12 => "RecipientCount", |
|
397 | + 0x13 => "MaxCertificates", |
|
398 | + 0x14 => "MaxAmbiguousRecipients", |
|
399 | + 0x15 => "CertificateCount", |
|
400 | + 0x16 => "Availability", // Since 14.0 |
|
401 | + 0x17 => "StartTime", // Since 14.0 |
|
402 | + 0x18 => "EndTime", // Since 14.0 |
|
403 | + 0x19 => "MergedFreeBusy", // Since 14.0 |
|
404 | + 0x1A => "Picture", // Since 14.1 |
|
405 | + 0x1B => "MaxSize", // Since 14.1 |
|
406 | + 0x1C => "Data", // Since 14.1 |
|
407 | + 0x1D => "MaxPictures", // Since 14.1 |
|
408 | + ], |
|
409 | + 0xB => [ |
|
410 | + 0x05 => "ValidateCert", |
|
411 | + 0x06 => "Certificates", |
|
412 | + 0x07 => "Certificate", |
|
413 | + 0x08 => "CertificateChain", |
|
414 | + 0x09 => "CheckCRL", |
|
415 | + 0x0A => "Status", |
|
416 | + ], |
|
417 | + 0xC => [ |
|
418 | + 0x05 => "CustomerId", |
|
419 | + 0x06 => "GovernmentId", |
|
420 | + 0x07 => "IMAddress", |
|
421 | + 0x08 => "IMAddress2", |
|
422 | + 0x09 => "IMAddress3", |
|
423 | + 0x0A => "ManagerName", |
|
424 | + 0x0B => "CompanyMainPhone", |
|
425 | + 0x0C => "AccountName", |
|
426 | + 0x0D => "NickName", |
|
427 | + 0x0E => "MMS", |
|
428 | + ], |
|
429 | + 0xD => [ |
|
430 | + 0x05 => "Ping", |
|
431 | + 0x06 => "AutdState", // (Not used by protocol) |
|
432 | + 0x07 => "Status", |
|
433 | + 0x08 => "LifeTime", // HeartbeatInterval |
|
434 | + 0x09 => "Folders", |
|
435 | + 0x0A => "Folder", |
|
436 | + 0x0B => "ServerEntryId", // Id |
|
437 | + 0x0C => "FolderType", // Class |
|
438 | + 0x0D => "MaxFolders", |
|
439 | + 0x0E => "Version", // not defined / deprecated |
|
440 | + ], |
|
441 | + 0xE => [ |
|
442 | + 0x05 => "Provision", |
|
443 | + 0x06 => "Policies", |
|
444 | + 0x07 => "Policy", |
|
445 | + 0x08 => "PolicyType", |
|
446 | + 0x09 => "PolicyKey", |
|
447 | + 0x0A => "Data", |
|
448 | + 0x0B => "Status", |
|
449 | + 0x0C => "RemoteWipe", |
|
450 | + 0x0D => "EASProvisionDoc", // Since 12.0 |
|
451 | + 0x0E => "DevicePasswordEnabled", // Since 12.0 |
|
452 | + 0x0F => "AlphanumericDevicePasswordRequired", // Since 12.0 |
|
453 | + 0x10 => "DeviceEncryptionEnabled", // Since 12.1 |
|
454 | + // 0x10 => "RequireStorageCardEncryption", //Since 12.1 |
|
455 | + 0x11 => "PasswordRecoveryEnabled", // Since 12.0 |
|
456 | + 0x12 => "DocumentBrowseEnabled", // not defined / deprecated |
|
457 | + 0x13 => "AttachmentsEnabled", // Since 12.0 |
|
458 | + 0x14 => "MinDevicePasswordLength", // Since 12.0 |
|
459 | + 0x15 => "MaxInactivityTimeDeviceLock", // Since 12.0 |
|
460 | + 0x16 => "MaxDevicePasswordFailedAttempts", // Since 12.0 |
|
461 | + 0x17 => "MaxAttachmentSize", // Since 12.0 |
|
462 | + 0x18 => "AllowSimpleDevicePassword", // Since 12.0 |
|
463 | + 0x19 => "DevicePasswordExpiration", // Since 12.0 |
|
464 | + 0x1A => "DevicePasswordHistory", // Since 12.0 |
|
465 | + 0x1B => "AllowStorageCard", // Since 12.1 |
|
466 | + 0x1C => "AllowCamera", // Since 12.1 |
|
467 | + 0x1D => "RequireDeviceEncryption", // Since 12.1 |
|
468 | + 0x1E => "AllowUnsignedApplications", // Since 12.1 |
|
469 | + 0x1F => "AllowUnsignedInstallationPackages", // Since 12.1 |
|
470 | + 0x20 => "MinDevicePasswordComplexCharacters", // Since 12.1 |
|
471 | + 0x21 => "AllowWiFi", // Since 12.1 |
|
472 | + 0x22 => "AllowTextMessaging", // Since 12.1 |
|
473 | + 0x23 => "AllowPOPIMAPEmail", // Since 12.1 |
|
474 | + 0x24 => "AllowBluetooth", // Since 12.1 |
|
475 | + 0x25 => "AllowIrDA", // Since 12.1 |
|
476 | + 0x26 => "RequireManualSyncWhenRoaming", // Since 12.1 |
|
477 | + 0x27 => "AllowDesktopSync", // Since 12.1 |
|
478 | + 0x28 => "MaxCalendarAgeFilter", // Since 12.1 |
|
479 | + 0x29 => "AllowHTMLEmail", // Since 12.1 |
|
480 | + 0x2A => "MaxEmailAgeFilter", // Since 12.1 |
|
481 | + 0x2B => "MaxEmailBodyTruncationSize", // Since 12.1 |
|
482 | + 0x2C => "MaxEmailHTMLBodyTruncationSize", // Since 12.1 |
|
483 | + 0x2D => "RequireSignedSMIMEMessages", // Since 12.1 |
|
484 | + 0x2E => "RequireEncryptedSMIMEMessages", // Since 12.1 |
|
485 | + 0x2F => "RequireSignedSMIMEAlgorithm", // Since 12.1 |
|
486 | + 0x30 => "RequireEncryptionSMIMEAlgorithm", // Since 12.1 |
|
487 | + 0x31 => "AllowSMIMEEncryptionAlgorithmNegotiation", // Since 12.1 |
|
488 | + 0x32 => "AllowSMIMESoftCerts", // Since 12.1 |
|
489 | + 0x33 => "AllowBrowser", // Since 12.1 |
|
490 | + 0x34 => "AllowConsumerEmail", // Since 12.1 |
|
491 | + 0x35 => "AllowRemoteDesktop", // Since 12.1 |
|
492 | + 0x36 => "AllowInternetSharing", // Since 12.1 |
|
493 | + 0x37 => "UnapprovedInROMApplicationList", // Since 12.1 |
|
494 | + 0x38 => "ApplicationName", // Since 12.1 |
|
495 | + 0x39 => "ApprovedApplicationList", // Since 12.1 |
|
496 | + 0x3A => "Hash", // Since 12.1 |
|
497 | + ], |
|
498 | + 0xF => [ |
|
499 | + 0x05 => "Search", |
|
500 | + 0x07 => "Store", |
|
501 | + 0x08 => "Name", |
|
502 | + 0x09 => "Query", |
|
503 | + 0x0A => "Options", |
|
504 | + 0x0B => "Range", |
|
505 | + 0x0C => "Status", |
|
506 | + 0x0D => "Response", |
|
507 | + 0x0E => "Result", |
|
508 | + 0x0F => "Properties", |
|
509 | + 0x10 => "Total", |
|
510 | + 0x11 => "EqualTo", |
|
511 | + 0x12 => "Value", |
|
512 | + 0x13 => "And", |
|
513 | + 0x14 => "Or", |
|
514 | + 0x15 => "FreeText", |
|
515 | + 0x17 => "DeepTraversal", |
|
516 | + 0x18 => "LongId", |
|
517 | + 0x19 => "RebuildResults", |
|
518 | + 0x1A => "LessThan", |
|
519 | + 0x1B => "GreaterThan", |
|
520 | + 0x1C => "Schema", |
|
521 | + 0x1D => "Supported", |
|
522 | + 0x1E => "UserName", // Since 12.1 |
|
523 | + 0x1F => "Password", // Since 12.1 |
|
524 | + 0x20 => "ConversationId", // Since 14.0 |
|
525 | + 0x21 => "Picture", // Since 14.1 |
|
526 | + 0x22 => "MaxSize", // Since 14.1 |
|
527 | + 0x23 => "MaxPictures", // Since 14.1 |
|
528 | + ], |
|
529 | + 0x10 => [ |
|
530 | + 0x05 => "DisplayName", |
|
531 | + 0x06 => "Phone", |
|
532 | + 0x07 => "Office", |
|
533 | + 0x08 => "Title", |
|
534 | + 0x09 => "Company", |
|
535 | + 0x0A => "Alias", |
|
536 | + 0x0B => "FirstName", |
|
537 | + 0x0C => "LastName", |
|
538 | + 0x0D => "HomePhone", |
|
539 | + 0x0E => "MobilePhone", |
|
540 | + 0x0F => "EmailAddress", |
|
541 | + 0x10 => "Picture", // Since 14.1 |
|
542 | + 0x11 => "Status", // Since 14.1 |
|
543 | + 0x12 => "Data", // Since 14.1 |
|
544 | + ], |
|
545 | + 0x11 => [ // Since 12.0 |
|
546 | + 0x05 => "BodyPreference", |
|
547 | + 0x06 => "Type", |
|
548 | + 0x07 => "TruncationSize", |
|
549 | + 0x08 => "AllOrNone", |
|
550 | + 0x0A => "Body", |
|
551 | + 0x0B => "Data", |
|
552 | + 0x0C => "EstimatedDataSize", |
|
553 | + 0x0D => "Truncated", |
|
554 | + 0x0E => "Attachments", |
|
555 | + 0x0F => "Attachment", |
|
556 | + 0x10 => "DisplayName", |
|
557 | + 0x11 => "FileReference", |
|
558 | + 0x12 => "Method", |
|
559 | + 0x13 => "ContentId", |
|
560 | + 0x14 => "ContentLocation", |
|
561 | + 0x15 => "IsInline", |
|
562 | + 0x16 => "NativeBodyType", |
|
563 | + 0x17 => "ContentType", |
|
564 | + 0x18 => "Preview", // Since 14.0 |
|
565 | + 0x19 => "BodyPartPreference", // Since 14.1 |
|
566 | + 0x1A => "BodyPart", // Since 14.1 |
|
567 | + 0x1B => "Status", // Since 14.1 |
|
568 | + ], |
|
569 | + 0x12 => [ // Since 12.0 |
|
570 | + 0x05 => "Settings", |
|
571 | + 0x06 => "Status", |
|
572 | + 0x07 => "Get", |
|
573 | + 0x08 => "Set", |
|
574 | + 0x09 => "Oof", |
|
575 | + 0x0A => "OofState", |
|
576 | + 0x0B => "StartTime", |
|
577 | + 0x0C => "EndTime", |
|
578 | + 0x0D => "OofMessage", |
|
579 | + 0x0E => "AppliesToInternal", |
|
580 | + 0x0F => "AppliesToExternalKnown", |
|
581 | + 0x10 => "AppliesToExternalUnknown", |
|
582 | + 0x11 => "Enabled", |
|
583 | + 0x12 => "ReplyMessage", |
|
584 | + 0x13 => "BodyType", |
|
585 | + 0x14 => "DevicePassword", |
|
586 | + 0x15 => "Password", |
|
587 | + 0x16 => "DeviceInformation", |
|
588 | + 0x17 => "Model", |
|
589 | + 0x18 => "IMEI", |
|
590 | + 0x19 => "FriendlyName", |
|
591 | + 0x1A => "OS", |
|
592 | + 0x1B => "OSLanguage", |
|
593 | + 0x1C => "PhoneNumber", |
|
594 | + 0x1D => "UserInformation", |
|
595 | + 0x1E => "EmailAddresses", |
|
596 | + 0x1F => "SmtpAddress", |
|
597 | + 0x20 => "UserAgent", // Since 12.1 |
|
598 | + 0x21 => "EnableOutboundSMS", // Since 14.0 |
|
599 | + 0x22 => "MobileOperator", // Since 14.0 |
|
600 | + 0x23 => "PrimarySmtpAddress", // Since 14.1 |
|
601 | + 0x24 => "Accounts", // Since 14.1 |
|
602 | + 0x25 => "Account", // Since 14.1 |
|
603 | + 0x26 => "AccountId", // Since 14.1 |
|
604 | + 0x27 => "AccountName", // Since 14.1 |
|
605 | + 0x28 => "UserDisplayName", // Since 14.1 |
|
606 | + 0x29 => "SendDisabled", // Since 14.1 |
|
607 | + 0x2B => "RightsManagementInformation", // Since 14.1 |
|
608 | + ], |
|
609 | + 0x13 => [ // Since 12.0 |
|
610 | + 0x05 => "LinkId", |
|
611 | + 0x06 => "DisplayName", |
|
612 | + 0x07 => "IsFolder", |
|
613 | + 0x08 => "CreationDate", |
|
614 | + 0x09 => "LastModifiedDate", |
|
615 | + 0x0A => "IsHidden", |
|
616 | + 0x0B => "ContentLength", |
|
617 | + 0x0C => "ContentType", |
|
618 | + ], |
|
619 | + 0x14 => [ // Since 12.0 |
|
620 | + 0x05 => "ItemOperations", |
|
621 | + 0x06 => "Fetch", |
|
622 | + 0x07 => "Store", |
|
623 | + 0x08 => "Options", |
|
624 | + 0x09 => "Range", |
|
625 | + 0x0A => "Total", |
|
626 | + 0x0B => "Properties", |
|
627 | + 0x0C => "Data", |
|
628 | + 0x0D => "Status", |
|
629 | + 0x0E => "Response", |
|
630 | + 0x0F => "Version", |
|
631 | + 0x10 => "Schema", |
|
632 | + 0x11 => "Part", |
|
633 | + 0x12 => "EmptyFolderContents", |
|
634 | + 0x13 => "DeleteSubFolders", |
|
635 | + 0x14 => "UserName", // Since 12.1 |
|
636 | + 0x15 => "Password", // Since 12.1 |
|
637 | + 0x16 => "Move", // Since 14.0 |
|
638 | + 0x17 => "DstFldId", // Since 14.0 |
|
639 | + 0x18 => "ConversationId", // Since 14.0 |
|
640 | + 0x19 => "MoveAlways", // Since 14.0 |
|
641 | + ], |
|
642 | + 0x15 => [ // Since 14.0 |
|
643 | + 0x05 => "SendMail", |
|
644 | + 0x06 => "SmartForward", |
|
645 | + 0x07 => "SmartReply", |
|
646 | + 0x08 => "SaveInSentItems", |
|
647 | + 0x09 => "ReplaceMime", |
|
648 | + 0x0A => "Type", // not used |
|
649 | + 0x0B => "Source", |
|
650 | + 0x0C => "FolderId", |
|
651 | + 0x0D => "ItemId", |
|
652 | + 0x0E => "LongId", |
|
653 | + 0x0F => "InstanceId", |
|
654 | + 0x10 => "MIME", |
|
655 | + 0x11 => "ClientId", |
|
656 | + 0x12 => "Status", |
|
657 | + 0x13 => "AccountId", // Since 14.1 |
|
658 | + ], |
|
659 | + 0x16 => [ // Since 14.0 |
|
660 | + 0x05 => "UmCallerId", |
|
661 | + 0x06 => "UmUserNotes", |
|
662 | + 0x07 => "UmAttDuration", |
|
663 | + 0x08 => "UmAttOrder", |
|
664 | + 0x09 => "ConversationId", |
|
665 | + 0x0A => "ConversationIndex", |
|
666 | + 0x0B => "LastVerbExecuted", |
|
667 | + 0x0C => "LastVerbExecutionTime", |
|
668 | + 0x0D => "ReceivedAsBcc", |
|
669 | + 0x0E => "Sender", |
|
670 | + 0x0F => "CalendarType", |
|
671 | + 0x10 => "IsLeapMonth", |
|
672 | + 0x11 => "AccountId", // Since 14.1 |
|
673 | + 0x12 => "FirstDayOfWeek", // Since 14.1 |
|
674 | + 0x13 => "MeetingMessageType", // Since 14.1 |
|
675 | + ], |
|
676 | + 0x17 => [ // Since 14.0 |
|
677 | + 0x05 => "Subject", |
|
678 | + 0x06 => "MessageClass", |
|
679 | + 0x07 => "LastModifiedDate", |
|
680 | + 0x08 => "Categories", |
|
681 | + 0x09 => "Category", |
|
682 | + ], |
|
683 | + 0x18 => [ // Since 14.1 |
|
684 | + 0x05 => "RightsManagementSupport", |
|
685 | + 0x06 => "RightsManagementTemplates", |
|
686 | + 0x07 => "RightsManagementTemplate", |
|
687 | + 0x08 => "RightsManagementLicense", |
|
688 | + 0x09 => "EditAllowed", |
|
689 | + 0x0A => "ReplyAllowed", |
|
690 | + 0x0B => "ReplyAllAllowed", |
|
691 | + 0x0C => "ForwardAllowed", |
|
692 | + 0x0D => "ModifyRecipientsAllowed", |
|
693 | + 0x0E => "ExtractAllowed", |
|
694 | + 0x0F => "PrintAllowed", |
|
695 | + 0x10 => "ExportAllowed", |
|
696 | + 0x11 => "ProgrammaticAccessAllowed", |
|
697 | + 0x12 => "Owner", |
|
698 | + 0x13 => "ContentExpiryDate", |
|
699 | + 0x14 => "TemplateID", |
|
700 | + 0x15 => "TemplateName", |
|
701 | + 0x16 => "TemplateDescription", |
|
702 | + 0x17 => "ContentOwner", |
|
703 | + 0x18 => "RemoveRightsManagementProtection", |
|
704 | + ], |
|
705 | + ], |
|
706 | + "namespaces" => [ |
|
707 | + // 0 => "AirSync", // |
|
708 | + 1 => "POOMCONTACTS", |
|
709 | + 2 => "POOMMAIL", |
|
710 | + 3 => "AirNotify", // no longer used |
|
711 | + 4 => "POOMCAL", |
|
712 | + 5 => "Move", |
|
713 | + 6 => "GetItemEstimate", |
|
714 | + 7 => "FolderHierarchy", |
|
715 | + 8 => "MeetingResponse", |
|
716 | + 9 => "POOMTASKS", |
|
717 | + 0xA => "ResolveRecipients", |
|
718 | + 0xB => "ValidateCert", |
|
719 | + 0xC => "POOMCONTACTS2", |
|
720 | + 0xD => "Ping", |
|
721 | + 0xE => "Provision", |
|
722 | + 0xF => "Search", |
|
723 | + 0x10 => "GAL", |
|
724 | + 0x11 => "AirSyncBase", // 12.0, 12.1 and 14.0 |
|
725 | + 0x12 => "Settings", // 12.0, 12.1 and 14.0. |
|
726 | + 0x13 => "DocumentLibrary", // 12.0, 12.1 and 14.0 |
|
727 | + 0x14 => "ItemOperations", // 12.0, 12.1 and 14.0 |
|
728 | + 0x15 => "ComposeMail", // 14.0 |
|
729 | + 0x16 => "POOMMAIL2", // 14.0 |
|
730 | + 0x17 => "Notes", // 14.0 |
|
731 | + 0x18 => "RightsManagement", |
|
732 | + ], |
|
733 | + ]; |
|
734 | 734 | } |
@@ -209,7 +209,7 @@ discard block |
||
209 | 209 | 0x3E => "CompleteTime", // Since 12.0 |
210 | 210 | 0x3F => "DisallowNewTimeProposal", // Since 14.0 |
211 | 211 | ], |
212 | - 3 => [ // Code page 3 is no longer in use, however, tokens 05 through 17 have been defined. 20100501 |
|
212 | + 3 => [// Code page 3 is no longer in use, however, tokens 05 through 17 have been defined. 20100501 |
|
213 | 213 | 0x05 => "Notify", |
214 | 214 | 0x06 => "Notification", |
215 | 215 | 0x07 => "Version", |
@@ -542,7 +542,7 @@ discard block |
||
542 | 542 | 0x11 => "Status", // Since 14.1 |
543 | 543 | 0x12 => "Data", // Since 14.1 |
544 | 544 | ], |
545 | - 0x11 => [ // Since 12.0 |
|
545 | + 0x11 => [// Since 12.0 |
|
546 | 546 | 0x05 => "BodyPreference", |
547 | 547 | 0x06 => "Type", |
548 | 548 | 0x07 => "TruncationSize", |
@@ -566,7 +566,7 @@ discard block |
||
566 | 566 | 0x1A => "BodyPart", // Since 14.1 |
567 | 567 | 0x1B => "Status", // Since 14.1 |
568 | 568 | ], |
569 | - 0x12 => [ // Since 12.0 |
|
569 | + 0x12 => [// Since 12.0 |
|
570 | 570 | 0x05 => "Settings", |
571 | 571 | 0x06 => "Status", |
572 | 572 | 0x07 => "Get", |
@@ -606,7 +606,7 @@ discard block |
||
606 | 606 | 0x29 => "SendDisabled", // Since 14.1 |
607 | 607 | 0x2B => "RightsManagementInformation", // Since 14.1 |
608 | 608 | ], |
609 | - 0x13 => [ // Since 12.0 |
|
609 | + 0x13 => [// Since 12.0 |
|
610 | 610 | 0x05 => "LinkId", |
611 | 611 | 0x06 => "DisplayName", |
612 | 612 | 0x07 => "IsFolder", |
@@ -616,7 +616,7 @@ discard block |
||
616 | 616 | 0x0B => "ContentLength", |
617 | 617 | 0x0C => "ContentType", |
618 | 618 | ], |
619 | - 0x14 => [ // Since 12.0 |
|
619 | + 0x14 => [// Since 12.0 |
|
620 | 620 | 0x05 => "ItemOperations", |
621 | 621 | 0x06 => "Fetch", |
622 | 622 | 0x07 => "Store", |
@@ -639,7 +639,7 @@ discard block |
||
639 | 639 | 0x18 => "ConversationId", // Since 14.0 |
640 | 640 | 0x19 => "MoveAlways", // Since 14.0 |
641 | 641 | ], |
642 | - 0x15 => [ // Since 14.0 |
|
642 | + 0x15 => [// Since 14.0 |
|
643 | 643 | 0x05 => "SendMail", |
644 | 644 | 0x06 => "SmartForward", |
645 | 645 | 0x07 => "SmartReply", |
@@ -656,7 +656,7 @@ discard block |
||
656 | 656 | 0x12 => "Status", |
657 | 657 | 0x13 => "AccountId", // Since 14.1 |
658 | 658 | ], |
659 | - 0x16 => [ // Since 14.0 |
|
659 | + 0x16 => [// Since 14.0 |
|
660 | 660 | 0x05 => "UmCallerId", |
661 | 661 | 0x06 => "UmUserNotes", |
662 | 662 | 0x07 => "UmAttDuration", |
@@ -673,14 +673,14 @@ discard block |
||
673 | 673 | 0x12 => "FirstDayOfWeek", // Since 14.1 |
674 | 674 | 0x13 => "MeetingMessageType", // Since 14.1 |
675 | 675 | ], |
676 | - 0x17 => [ // Since 14.0 |
|
676 | + 0x17 => [// Since 14.0 |
|
677 | 677 | 0x05 => "Subject", |
678 | 678 | 0x06 => "MessageClass", |
679 | 679 | 0x07 => "LastModifiedDate", |
680 | 680 | 0x08 => "Categories", |
681 | 681 | 0x09 => "Category", |
682 | 682 | ], |
683 | - 0x18 => [ // Since 14.1 |
|
683 | + 0x18 => [// Since 14.1 |
|
684 | 684 | 0x05 => "RightsManagementSupport", |
685 | 685 | 0x06 => "RightsManagementTemplates", |
686 | 686 | 0x07 => "RightsManagementTemplate", |
@@ -8,514 +8,514 @@ |
||
8 | 8 | */ |
9 | 9 | |
10 | 10 | class WBXMLEncoder extends WBXMLDefs { |
11 | - private $_dtd; |
|
12 | - private $_out; |
|
13 | - private $_tagcp = 0; |
|
14 | - private $log = false; |
|
15 | - private $logStack = []; |
|
16 | - |
|
17 | - // We use a delayed output mechanism in which we only output a tag when it actually has something |
|
18 | - // in it. This can cause entire XML trees to disappear if they don't have output data in them; Ie |
|
19 | - // calling 'startTag' 10 times, and then 'endTag' will cause 0 bytes of output apart from the header. |
|
20 | - |
|
21 | - // Only when content() is called do we output the current stack of tags |
|
22 | - |
|
23 | - private $_stack; |
|
24 | - private $multipart; // the content is multipart |
|
25 | - private $bodyparts; |
|
26 | - |
|
27 | - public function __construct($output, $multipart = false) { |
|
28 | - $this->log = SLog::IsWbxmlDebugEnabled(); |
|
29 | - $this->_out = $output; |
|
30 | - |
|
31 | - // reverse-map the DTD |
|
32 | - foreach ($this->dtd["namespaces"] as $nsid => $nsname) { |
|
33 | - $this->_dtd["namespaces"][$nsname] = $nsid; |
|
34 | - } |
|
35 | - |
|
36 | - foreach ($this->dtd["codes"] as $cp => $value) { |
|
37 | - $this->_dtd["codes"][$cp] = []; |
|
38 | - foreach ($this->dtd["codes"][$cp] as $tagid => $tagname) { |
|
39 | - $this->_dtd["codes"][$cp][$tagname] = $tagid; |
|
40 | - } |
|
41 | - } |
|
42 | - $this->_stack = []; |
|
43 | - $this->multipart = $multipart; |
|
44 | - $this->bodyparts = []; |
|
45 | - } |
|
46 | - |
|
47 | - /** |
|
48 | - * Puts the WBXML header on the stream. |
|
49 | - * |
|
50 | - * @return |
|
51 | - */ |
|
52 | - public function startWBXML() { |
|
53 | - if ($this->multipart) { |
|
54 | - header("Content-Type: application/vnd.ms-sync.multipart"); |
|
55 | - SLog::Write(LOGLEVEL_DEBUG, "WBXMLEncoder->startWBXML() type: vnd.ms-sync.multipart"); |
|
56 | - } |
|
57 | - else { |
|
58 | - header("Content-Type: application/vnd.ms-sync.wbxml"); |
|
59 | - SLog::Write(LOGLEVEL_DEBUG, "WBXMLEncoder->startWBXML() type: vnd.ms-sync.wbxml"); |
|
60 | - } |
|
61 | - |
|
62 | - $this->outByte(0x03); // WBXML 1.3 |
|
63 | - $this->outMBUInt(0x01); // Public ID 1 |
|
64 | - $this->outMBUInt(106); // UTF-8 |
|
65 | - $this->outMBUInt(0x00); // string table length (0) |
|
66 | - } |
|
67 | - |
|
68 | - /** |
|
69 | - * Puts a StartTag on the output stack. |
|
70 | - * |
|
71 | - * @param $tag |
|
72 | - * @param $attributes |
|
73 | - * @param $nocontent |
|
74 | - * |
|
75 | - * @return |
|
76 | - */ |
|
77 | - public function startTag($tag, $attributes = false, $nocontent = false) { |
|
78 | - $stackelem = []; |
|
79 | - |
|
80 | - if (!$nocontent) { |
|
81 | - $stackelem['tag'] = $tag; |
|
82 | - $stackelem['nocontent'] = $nocontent; |
|
83 | - $stackelem['sent'] = false; |
|
84 | - |
|
85 | - array_push($this->_stack, $stackelem); |
|
86 | - |
|
87 | - // If 'nocontent' is specified, then apparently the user wants to force |
|
88 | - // output of an empty tag, and we therefore output the stack here |
|
89 | - } |
|
90 | - else { |
|
91 | - $this->_outputStack(); |
|
92 | - $this->_startTag($tag, $nocontent); |
|
93 | - } |
|
94 | - } |
|
95 | - |
|
96 | - /** |
|
97 | - * Puts an EndTag on the stack. |
|
98 | - * |
|
99 | - * @return |
|
100 | - */ |
|
101 | - public function endTag() { |
|
102 | - $stackelem = array_pop($this->_stack); |
|
103 | - |
|
104 | - // Only output end tags for items that have had a start tag sent |
|
105 | - if ($stackelem['sent']) { |
|
106 | - $this->_endTag(); |
|
107 | - |
|
108 | - if (count($this->_stack) == 0) { |
|
109 | - SLog::Write(LOGLEVEL_DEBUG, "WBXMLEncoder->endTag() WBXML output completed"); |
|
110 | - } |
|
111 | - if (count($this->_stack) == 0 && $this->multipart == true) { |
|
112 | - $this->processMultipart(); |
|
113 | - } |
|
114 | - if (count($this->_stack) == 0) { |
|
115 | - $this->writeLog(); |
|
116 | - } |
|
117 | - } |
|
118 | - } |
|
119 | - |
|
120 | - /** |
|
121 | - * Puts content on the output stack. |
|
122 | - * |
|
123 | - * @param string $content |
|
124 | - * |
|
125 | - * @return |
|
126 | - */ |
|
127 | - public function content($content) { |
|
128 | - // We need to filter out any \0 chars because it's the string terminator in WBXML. We currently |
|
129 | - // cannot send \0 characters within the XML content anywhere. |
|
130 | - $content = str_replace("\0", "", $content); |
|
131 | - if ("x" . $content == "x") { |
|
132 | - return; |
|
133 | - } |
|
134 | - $this->_outputStack(); |
|
135 | - $this->_content($content); |
|
136 | - } |
|
137 | - |
|
138 | - /** |
|
139 | - * Puts content of a stream on the output stack AND closes it. |
|
140 | - * |
|
141 | - * @param resource $stream |
|
142 | - * @param bool $asBase64 if true, the data will be encoded as base64, default: false |
|
143 | - * @param bool $opaque if true, output the opaque data, default: false |
|
144 | - * |
|
145 | - * @return |
|
146 | - */ |
|
147 | - public function contentStream($stream, $asBase64 = false, $opaque = false) { |
|
148 | - // Do not append filters to opaque data as it might contain null char |
|
149 | - if (!$asBase64 && !$opaque) { |
|
150 | - stream_filter_register('replacenullchar', 'ReplaceNullcharFilter'); |
|
151 | - $rnc_filter = stream_filter_append($stream, 'replacenullchar'); |
|
152 | - } |
|
153 | - |
|
154 | - $this->_outputStack(); |
|
155 | - $this->_contentStream($stream, $asBase64, $opaque); |
|
156 | - |
|
157 | - if (!$asBase64 && !$opaque) { |
|
158 | - stream_filter_remove($rnc_filter); |
|
159 | - } |
|
160 | - |
|
161 | - fclose($stream); |
|
162 | - } |
|
163 | - |
|
164 | - /** |
|
165 | - * Gets the value of multipart. |
|
166 | - * |
|
167 | - * @return bool |
|
168 | - */ |
|
169 | - public function getMultipart() { |
|
170 | - return $this->multipart; |
|
171 | - } |
|
172 | - |
|
173 | - /** |
|
174 | - * Adds a bodypart. |
|
175 | - * |
|
176 | - * @param Stream $bp |
|
177 | - */ |
|
178 | - public function addBodypartStream($bp) { |
|
179 | - if (!is_resource($bp)) { |
|
180 | - throw new WBXMLException("WBXMLEncoder->addBodypartStream(): trying to add a " . gettype($bp) . " instead of a stream"); |
|
181 | - } |
|
182 | - if ($this->multipart) { |
|
183 | - $this->bodyparts[] = $bp; |
|
184 | - } |
|
185 | - } |
|
186 | - |
|
187 | - /** |
|
188 | - * Gets the number of bodyparts. |
|
189 | - * |
|
190 | - * @return int |
|
191 | - */ |
|
192 | - public function getBodypartsCount() { |
|
193 | - return count($this->bodyparts); |
|
194 | - } |
|
195 | - |
|
196 | - /*---------------------------------------------------------------------------------------------------------- |
|
11 | + private $_dtd; |
|
12 | + private $_out; |
|
13 | + private $_tagcp = 0; |
|
14 | + private $log = false; |
|
15 | + private $logStack = []; |
|
16 | + |
|
17 | + // We use a delayed output mechanism in which we only output a tag when it actually has something |
|
18 | + // in it. This can cause entire XML trees to disappear if they don't have output data in them; Ie |
|
19 | + // calling 'startTag' 10 times, and then 'endTag' will cause 0 bytes of output apart from the header. |
|
20 | + |
|
21 | + // Only when content() is called do we output the current stack of tags |
|
22 | + |
|
23 | + private $_stack; |
|
24 | + private $multipart; // the content is multipart |
|
25 | + private $bodyparts; |
|
26 | + |
|
27 | + public function __construct($output, $multipart = false) { |
|
28 | + $this->log = SLog::IsWbxmlDebugEnabled(); |
|
29 | + $this->_out = $output; |
|
30 | + |
|
31 | + // reverse-map the DTD |
|
32 | + foreach ($this->dtd["namespaces"] as $nsid => $nsname) { |
|
33 | + $this->_dtd["namespaces"][$nsname] = $nsid; |
|
34 | + } |
|
35 | + |
|
36 | + foreach ($this->dtd["codes"] as $cp => $value) { |
|
37 | + $this->_dtd["codes"][$cp] = []; |
|
38 | + foreach ($this->dtd["codes"][$cp] as $tagid => $tagname) { |
|
39 | + $this->_dtd["codes"][$cp][$tagname] = $tagid; |
|
40 | + } |
|
41 | + } |
|
42 | + $this->_stack = []; |
|
43 | + $this->multipart = $multipart; |
|
44 | + $this->bodyparts = []; |
|
45 | + } |
|
46 | + |
|
47 | + /** |
|
48 | + * Puts the WBXML header on the stream. |
|
49 | + * |
|
50 | + * @return |
|
51 | + */ |
|
52 | + public function startWBXML() { |
|
53 | + if ($this->multipart) { |
|
54 | + header("Content-Type: application/vnd.ms-sync.multipart"); |
|
55 | + SLog::Write(LOGLEVEL_DEBUG, "WBXMLEncoder->startWBXML() type: vnd.ms-sync.multipart"); |
|
56 | + } |
|
57 | + else { |
|
58 | + header("Content-Type: application/vnd.ms-sync.wbxml"); |
|
59 | + SLog::Write(LOGLEVEL_DEBUG, "WBXMLEncoder->startWBXML() type: vnd.ms-sync.wbxml"); |
|
60 | + } |
|
61 | + |
|
62 | + $this->outByte(0x03); // WBXML 1.3 |
|
63 | + $this->outMBUInt(0x01); // Public ID 1 |
|
64 | + $this->outMBUInt(106); // UTF-8 |
|
65 | + $this->outMBUInt(0x00); // string table length (0) |
|
66 | + } |
|
67 | + |
|
68 | + /** |
|
69 | + * Puts a StartTag on the output stack. |
|
70 | + * |
|
71 | + * @param $tag |
|
72 | + * @param $attributes |
|
73 | + * @param $nocontent |
|
74 | + * |
|
75 | + * @return |
|
76 | + */ |
|
77 | + public function startTag($tag, $attributes = false, $nocontent = false) { |
|
78 | + $stackelem = []; |
|
79 | + |
|
80 | + if (!$nocontent) { |
|
81 | + $stackelem['tag'] = $tag; |
|
82 | + $stackelem['nocontent'] = $nocontent; |
|
83 | + $stackelem['sent'] = false; |
|
84 | + |
|
85 | + array_push($this->_stack, $stackelem); |
|
86 | + |
|
87 | + // If 'nocontent' is specified, then apparently the user wants to force |
|
88 | + // output of an empty tag, and we therefore output the stack here |
|
89 | + } |
|
90 | + else { |
|
91 | + $this->_outputStack(); |
|
92 | + $this->_startTag($tag, $nocontent); |
|
93 | + } |
|
94 | + } |
|
95 | + |
|
96 | + /** |
|
97 | + * Puts an EndTag on the stack. |
|
98 | + * |
|
99 | + * @return |
|
100 | + */ |
|
101 | + public function endTag() { |
|
102 | + $stackelem = array_pop($this->_stack); |
|
103 | + |
|
104 | + // Only output end tags for items that have had a start tag sent |
|
105 | + if ($stackelem['sent']) { |
|
106 | + $this->_endTag(); |
|
107 | + |
|
108 | + if (count($this->_stack) == 0) { |
|
109 | + SLog::Write(LOGLEVEL_DEBUG, "WBXMLEncoder->endTag() WBXML output completed"); |
|
110 | + } |
|
111 | + if (count($this->_stack) == 0 && $this->multipart == true) { |
|
112 | + $this->processMultipart(); |
|
113 | + } |
|
114 | + if (count($this->_stack) == 0) { |
|
115 | + $this->writeLog(); |
|
116 | + } |
|
117 | + } |
|
118 | + } |
|
119 | + |
|
120 | + /** |
|
121 | + * Puts content on the output stack. |
|
122 | + * |
|
123 | + * @param string $content |
|
124 | + * |
|
125 | + * @return |
|
126 | + */ |
|
127 | + public function content($content) { |
|
128 | + // We need to filter out any \0 chars because it's the string terminator in WBXML. We currently |
|
129 | + // cannot send \0 characters within the XML content anywhere. |
|
130 | + $content = str_replace("\0", "", $content); |
|
131 | + if ("x" . $content == "x") { |
|
132 | + return; |
|
133 | + } |
|
134 | + $this->_outputStack(); |
|
135 | + $this->_content($content); |
|
136 | + } |
|
137 | + |
|
138 | + /** |
|
139 | + * Puts content of a stream on the output stack AND closes it. |
|
140 | + * |
|
141 | + * @param resource $stream |
|
142 | + * @param bool $asBase64 if true, the data will be encoded as base64, default: false |
|
143 | + * @param bool $opaque if true, output the opaque data, default: false |
|
144 | + * |
|
145 | + * @return |
|
146 | + */ |
|
147 | + public function contentStream($stream, $asBase64 = false, $opaque = false) { |
|
148 | + // Do not append filters to opaque data as it might contain null char |
|
149 | + if (!$asBase64 && !$opaque) { |
|
150 | + stream_filter_register('replacenullchar', 'ReplaceNullcharFilter'); |
|
151 | + $rnc_filter = stream_filter_append($stream, 'replacenullchar'); |
|
152 | + } |
|
153 | + |
|
154 | + $this->_outputStack(); |
|
155 | + $this->_contentStream($stream, $asBase64, $opaque); |
|
156 | + |
|
157 | + if (!$asBase64 && !$opaque) { |
|
158 | + stream_filter_remove($rnc_filter); |
|
159 | + } |
|
160 | + |
|
161 | + fclose($stream); |
|
162 | + } |
|
163 | + |
|
164 | + /** |
|
165 | + * Gets the value of multipart. |
|
166 | + * |
|
167 | + * @return bool |
|
168 | + */ |
|
169 | + public function getMultipart() { |
|
170 | + return $this->multipart; |
|
171 | + } |
|
172 | + |
|
173 | + /** |
|
174 | + * Adds a bodypart. |
|
175 | + * |
|
176 | + * @param Stream $bp |
|
177 | + */ |
|
178 | + public function addBodypartStream($bp) { |
|
179 | + if (!is_resource($bp)) { |
|
180 | + throw new WBXMLException("WBXMLEncoder->addBodypartStream(): trying to add a " . gettype($bp) . " instead of a stream"); |
|
181 | + } |
|
182 | + if ($this->multipart) { |
|
183 | + $this->bodyparts[] = $bp; |
|
184 | + } |
|
185 | + } |
|
186 | + |
|
187 | + /** |
|
188 | + * Gets the number of bodyparts. |
|
189 | + * |
|
190 | + * @return int |
|
191 | + */ |
|
192 | + public function getBodypartsCount() { |
|
193 | + return count($this->bodyparts); |
|
194 | + } |
|
195 | + |
|
196 | + /*---------------------------------------------------------------------------------------------------------- |
|
197 | 197 | * Private WBXMLEncoder stuff |
198 | 198 | */ |
199 | 199 | |
200 | - /** |
|
201 | - * Output any tags on the stack that haven't been output yet. |
|
202 | - * |
|
203 | - * @return |
|
204 | - */ |
|
205 | - private function _outputStack() { |
|
206 | - for ($i = 0; $i < count($this->_stack); ++$i) { |
|
207 | - if (!$this->_stack[$i]['sent']) { |
|
208 | - $this->_startTag($this->_stack[$i]['tag'], $this->_stack[$i]['nocontent']); |
|
209 | - $this->_stack[$i]['sent'] = true; |
|
210 | - } |
|
211 | - } |
|
212 | - } |
|
213 | - |
|
214 | - /** |
|
215 | - * Outputs an actual start tag. |
|
216 | - * |
|
217 | - * @param mixed $tag |
|
218 | - * @param mixed $nocontent |
|
219 | - * |
|
220 | - * @return |
|
221 | - */ |
|
222 | - private function _startTag($tag, $nocontent = false) { |
|
223 | - if ($this->log) { |
|
224 | - $this->logStartTag($tag, $nocontent); |
|
225 | - } |
|
226 | - |
|
227 | - $mapping = $this->getMapping($tag); |
|
228 | - |
|
229 | - if (!$mapping) { |
|
230 | - return false; |
|
231 | - } |
|
232 | - |
|
233 | - if ($this->_tagcp != $mapping["cp"]) { |
|
234 | - $this->outSwitchPage($mapping["cp"]); |
|
235 | - $this->_tagcp = $mapping["cp"]; |
|
236 | - } |
|
237 | - |
|
238 | - $code = $mapping["code"]; |
|
239 | - |
|
240 | - if (!isset($nocontent) || !$nocontent) { |
|
241 | - $code |= 0x40; |
|
242 | - } |
|
243 | - |
|
244 | - $this->outByte($code); |
|
245 | - } |
|
246 | - |
|
247 | - /** |
|
248 | - * Outputs actual data. |
|
249 | - * |
|
250 | - * @param string $content |
|
251 | - * |
|
252 | - * @return |
|
253 | - */ |
|
254 | - private function _content($content) { |
|
255 | - if ($this->log) { |
|
256 | - $this->logContent($content); |
|
257 | - } |
|
258 | - $this->outByte(self::WBXML_STR_I); |
|
259 | - $this->outTermStr($content); |
|
260 | - } |
|
261 | - |
|
262 | - /** |
|
263 | - * Outputs actual data coming from a stream, optionally encoded as base64. |
|
264 | - * |
|
265 | - * @param resource $stream |
|
266 | - * @param bool $asBase64 |
|
267 | - * @param mixed $opaque |
|
268 | - * |
|
269 | - * @return |
|
270 | - */ |
|
271 | - private function _contentStream($stream, $asBase64, $opaque) { |
|
272 | - $stat = fstat($stream); |
|
273 | - // write full stream, including the finalizing terminator to the output stream (stuff outTermStr() would do) |
|
274 | - if ($opaque) { |
|
275 | - $this->outByte(self::WBXML_OPAQUE); |
|
276 | - $this->outMBUInt($stat['size']); |
|
277 | - } |
|
278 | - else { |
|
279 | - $this->outByte(self::WBXML_STR_I); |
|
280 | - } |
|
281 | - |
|
282 | - if ($asBase64) { |
|
283 | - $out_filter = stream_filter_append($this->_out, 'convert.base64-encode'); |
|
284 | - } |
|
285 | - $written = stream_copy_to_stream($stream, $this->_out); |
|
286 | - if ($asBase64) { |
|
287 | - stream_filter_remove($out_filter); |
|
288 | - } |
|
289 | - if (!$opaque) { |
|
290 | - fwrite($this->_out, chr(0)); |
|
291 | - } |
|
292 | - |
|
293 | - if ($this->log) { |
|
294 | - // data is out, do some logging |
|
295 | - $this->logContent(sprintf("<<< written %d of %d bytes of %s data >>>", $written, $stat['size'], $asBase64 ? "base64 encoded" : "plain")); |
|
296 | - } |
|
297 | - } |
|
298 | - |
|
299 | - /** |
|
300 | - * Outputs an actual end tag. |
|
301 | - * |
|
302 | - * @return |
|
303 | - */ |
|
304 | - private function _endTag() { |
|
305 | - if ($this->log) { |
|
306 | - $this->logEndTag(); |
|
307 | - } |
|
308 | - $this->outByte(self::WBXML_END); |
|
309 | - } |
|
310 | - |
|
311 | - /** |
|
312 | - * Outputs a byte. |
|
313 | - * |
|
314 | - * @param $byte |
|
315 | - * |
|
316 | - * @return |
|
317 | - */ |
|
318 | - private function outByte($byte) { |
|
319 | - fwrite($this->_out, chr($byte)); |
|
320 | - } |
|
321 | - |
|
322 | - /** |
|
323 | - * Output the multibyte integers to the stream. |
|
324 | - * |
|
325 | - * A multi-byte integer consists of a series of octets, |
|
326 | - * where the most significant bit is the continuation flag |
|
327 | - * and the remaining seven bits are a scalar value. |
|
328 | - * The octets are arranged in a big-endian order, |
|
329 | - * eg, the most significant seven bits are transmitted first. |
|
330 | - * |
|
331 | - * @see https://www.w3.org/1999/06/NOTE-wbxml-19990624/#_Toc443384895 |
|
332 | - * |
|
333 | - * @param int $uint |
|
334 | - */ |
|
335 | - private function outMBUInt($uint) { |
|
336 | - if ($uint == 0x0) { |
|
337 | - return $this->outByte($uint); |
|
338 | - } |
|
339 | - |
|
340 | - $out = ''; |
|
341 | - |
|
342 | - for ($i = 0; $uint != 0; ++$i) { |
|
343 | - $byte = $uint & 0x7F; |
|
344 | - $uint = $uint >> 7; |
|
345 | - if ($i == 0) { |
|
346 | - $out = chr($byte) . $out; |
|
347 | - } |
|
348 | - else { |
|
349 | - $out = chr($byte | 0x80) . $out; |
|
350 | - } |
|
351 | - } |
|
352 | - fwrite($this->_out, $out); |
|
353 | - } |
|
354 | - |
|
355 | - /** |
|
356 | - * Outputs content with string terminator. |
|
357 | - * |
|
358 | - * @param $content |
|
359 | - * |
|
360 | - * @return |
|
361 | - */ |
|
362 | - private function outTermStr($content) { |
|
363 | - fwrite($this->_out, $content); |
|
364 | - fwrite($this->_out, chr(0)); |
|
365 | - } |
|
366 | - |
|
367 | - /** |
|
368 | - * Switches the codepage. |
|
369 | - * |
|
370 | - * @param $page |
|
371 | - * |
|
372 | - * @return |
|
373 | - */ |
|
374 | - private function outSwitchPage($page) { |
|
375 | - $this->outByte(self::WBXML_SWITCH_PAGE); |
|
376 | - $this->outByte($page); |
|
377 | - } |
|
378 | - |
|
379 | - /** |
|
380 | - * Get the mapping for a tag. |
|
381 | - * |
|
382 | - * @param $tag |
|
383 | - * |
|
384 | - * @return array |
|
385 | - */ |
|
386 | - private function getMapping($tag) { |
|
387 | - $mapping = []; |
|
388 | - |
|
389 | - $split = $this->splitTag($tag); |
|
390 | - |
|
391 | - if (isset($split["ns"])) { |
|
392 | - $cp = $this->_dtd["namespaces"][$split["ns"]]; |
|
393 | - } |
|
394 | - else { |
|
395 | - $cp = 0; |
|
396 | - } |
|
397 | - |
|
398 | - $code = $this->_dtd["codes"][$cp][$split["tag"]]; |
|
399 | - |
|
400 | - $mapping["cp"] = $cp; |
|
401 | - $mapping["code"] = $code; |
|
402 | - |
|
403 | - return $mapping; |
|
404 | - } |
|
405 | - |
|
406 | - /** |
|
407 | - * Split a tag from a the fulltag (namespace + tag). |
|
408 | - * |
|
409 | - * @param $fulltag |
|
410 | - * |
|
411 | - * @return array keys: 'ns' (namespace), 'tag' (tag) |
|
412 | - */ |
|
413 | - private function splitTag($fulltag) { |
|
414 | - $ns = false; |
|
415 | - $pos = strpos($fulltag, chr(58)); // chr(58) == ':' |
|
416 | - |
|
417 | - if ($pos) { |
|
418 | - $ns = substr($fulltag, 0, $pos); |
|
419 | - $tag = substr($fulltag, $pos + 1); |
|
420 | - } |
|
421 | - else { |
|
422 | - $tag = $fulltag; |
|
423 | - } |
|
424 | - |
|
425 | - $ret = []; |
|
426 | - if ($ns) { |
|
427 | - $ret["ns"] = $ns; |
|
428 | - } |
|
429 | - $ret["tag"] = $tag; |
|
430 | - |
|
431 | - return $ret; |
|
432 | - } |
|
433 | - |
|
434 | - /** |
|
435 | - * Logs a StartTag to SLog. |
|
436 | - * |
|
437 | - * @param $tag |
|
438 | - * @param $nocontent |
|
439 | - * |
|
440 | - * @return |
|
441 | - */ |
|
442 | - private function logStartTag($tag, $nocontent) { |
|
443 | - $spaces = str_repeat(" ", count($this->logStack)); |
|
444 | - if ($nocontent) { |
|
445 | - SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . " <{$tag}/>"); |
|
446 | - } |
|
447 | - else { |
|
448 | - array_push($this->logStack, $tag); |
|
449 | - SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . " <{$tag}>"); |
|
450 | - } |
|
451 | - } |
|
452 | - |
|
453 | - /** |
|
454 | - * Logs a EndTag to SLog. |
|
455 | - * |
|
456 | - * @return |
|
457 | - */ |
|
458 | - private function logEndTag() { |
|
459 | - $spaces = str_repeat(" ", count($this->logStack)); |
|
460 | - $tag = array_pop($this->logStack); |
|
461 | - SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . "</{$tag}>"); |
|
462 | - } |
|
463 | - |
|
464 | - /** |
|
465 | - * Logs content to SLog. |
|
466 | - * |
|
467 | - * @param string $content |
|
468 | - * |
|
469 | - * @return |
|
470 | - */ |
|
471 | - private function logContent($content) { |
|
472 | - $spaces = str_repeat(" ", count($this->logStack)); |
|
473 | - SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . $content); |
|
474 | - } |
|
475 | - |
|
476 | - /** |
|
477 | - * Processes the multipart response. |
|
478 | - */ |
|
479 | - private function processMultipart() { |
|
480 | - SLog::Write(LOGLEVEL_DEBUG, sprintf("WBXMLEncoder->processMultipart() with %d parts to be processed", $this->getBodypartsCount())); |
|
481 | - $len = ob_get_length(); |
|
482 | - $buffer = ob_get_clean(); |
|
483 | - $nrBodyparts = $this->getBodypartsCount(); |
|
484 | - $blockstart = (($nrBodyparts + 1) * 2) * 4 + 4; |
|
485 | - |
|
486 | - fwrite($this->_out, pack("iii", ($nrBodyparts + 1), $blockstart, $len)); |
|
487 | - |
|
488 | - foreach ($this->bodyparts as $i => $bp) { |
|
489 | - $blockstart = $blockstart + $len; |
|
490 | - $len = fstat($bp); |
|
491 | - $len = (isset($len['size'])) ? $len['size'] : 0; |
|
492 | - if ($len == 0) { |
|
493 | - SLog::Write(LOGLEVEL_WARN, sprintf("WBXMLEncoder->processMultipart(): the length of the body part at position %d is 0", $i)); |
|
494 | - } |
|
495 | - fwrite($this->_out, pack("ii", $blockstart, $len)); |
|
496 | - } |
|
497 | - |
|
498 | - fwrite($this->_out, $buffer); |
|
499 | - |
|
500 | - foreach ($this->bodyparts as $bp) { |
|
501 | - stream_copy_to_stream($bp, $this->_out); |
|
502 | - fclose($bp); |
|
503 | - } |
|
504 | - } |
|
505 | - |
|
506 | - /** |
|
507 | - * Writes the sent WBXML data to the log if it is not bigger than 512K. |
|
508 | - */ |
|
509 | - private function writeLog() { |
|
510 | - if (ob_get_length() === false) { |
|
511 | - $data = "output buffer disabled"; |
|
512 | - } |
|
513 | - elseif (ob_get_length() < 524288) { |
|
514 | - $data = base64_encode(ob_get_contents()); |
|
515 | - } |
|
516 | - else { |
|
517 | - $data = "more than 512K of data"; |
|
518 | - } |
|
519 | - SLog::Write(LOGLEVEL_WBXML, "WBXML-OUT: " . $data, false); |
|
520 | - } |
|
200 | + /** |
|
201 | + * Output any tags on the stack that haven't been output yet. |
|
202 | + * |
|
203 | + * @return |
|
204 | + */ |
|
205 | + private function _outputStack() { |
|
206 | + for ($i = 0; $i < count($this->_stack); ++$i) { |
|
207 | + if (!$this->_stack[$i]['sent']) { |
|
208 | + $this->_startTag($this->_stack[$i]['tag'], $this->_stack[$i]['nocontent']); |
|
209 | + $this->_stack[$i]['sent'] = true; |
|
210 | + } |
|
211 | + } |
|
212 | + } |
|
213 | + |
|
214 | + /** |
|
215 | + * Outputs an actual start tag. |
|
216 | + * |
|
217 | + * @param mixed $tag |
|
218 | + * @param mixed $nocontent |
|
219 | + * |
|
220 | + * @return |
|
221 | + */ |
|
222 | + private function _startTag($tag, $nocontent = false) { |
|
223 | + if ($this->log) { |
|
224 | + $this->logStartTag($tag, $nocontent); |
|
225 | + } |
|
226 | + |
|
227 | + $mapping = $this->getMapping($tag); |
|
228 | + |
|
229 | + if (!$mapping) { |
|
230 | + return false; |
|
231 | + } |
|
232 | + |
|
233 | + if ($this->_tagcp != $mapping["cp"]) { |
|
234 | + $this->outSwitchPage($mapping["cp"]); |
|
235 | + $this->_tagcp = $mapping["cp"]; |
|
236 | + } |
|
237 | + |
|
238 | + $code = $mapping["code"]; |
|
239 | + |
|
240 | + if (!isset($nocontent) || !$nocontent) { |
|
241 | + $code |= 0x40; |
|
242 | + } |
|
243 | + |
|
244 | + $this->outByte($code); |
|
245 | + } |
|
246 | + |
|
247 | + /** |
|
248 | + * Outputs actual data. |
|
249 | + * |
|
250 | + * @param string $content |
|
251 | + * |
|
252 | + * @return |
|
253 | + */ |
|
254 | + private function _content($content) { |
|
255 | + if ($this->log) { |
|
256 | + $this->logContent($content); |
|
257 | + } |
|
258 | + $this->outByte(self::WBXML_STR_I); |
|
259 | + $this->outTermStr($content); |
|
260 | + } |
|
261 | + |
|
262 | + /** |
|
263 | + * Outputs actual data coming from a stream, optionally encoded as base64. |
|
264 | + * |
|
265 | + * @param resource $stream |
|
266 | + * @param bool $asBase64 |
|
267 | + * @param mixed $opaque |
|
268 | + * |
|
269 | + * @return |
|
270 | + */ |
|
271 | + private function _contentStream($stream, $asBase64, $opaque) { |
|
272 | + $stat = fstat($stream); |
|
273 | + // write full stream, including the finalizing terminator to the output stream (stuff outTermStr() would do) |
|
274 | + if ($opaque) { |
|
275 | + $this->outByte(self::WBXML_OPAQUE); |
|
276 | + $this->outMBUInt($stat['size']); |
|
277 | + } |
|
278 | + else { |
|
279 | + $this->outByte(self::WBXML_STR_I); |
|
280 | + } |
|
281 | + |
|
282 | + if ($asBase64) { |
|
283 | + $out_filter = stream_filter_append($this->_out, 'convert.base64-encode'); |
|
284 | + } |
|
285 | + $written = stream_copy_to_stream($stream, $this->_out); |
|
286 | + if ($asBase64) { |
|
287 | + stream_filter_remove($out_filter); |
|
288 | + } |
|
289 | + if (!$opaque) { |
|
290 | + fwrite($this->_out, chr(0)); |
|
291 | + } |
|
292 | + |
|
293 | + if ($this->log) { |
|
294 | + // data is out, do some logging |
|
295 | + $this->logContent(sprintf("<<< written %d of %d bytes of %s data >>>", $written, $stat['size'], $asBase64 ? "base64 encoded" : "plain")); |
|
296 | + } |
|
297 | + } |
|
298 | + |
|
299 | + /** |
|
300 | + * Outputs an actual end tag. |
|
301 | + * |
|
302 | + * @return |
|
303 | + */ |
|
304 | + private function _endTag() { |
|
305 | + if ($this->log) { |
|
306 | + $this->logEndTag(); |
|
307 | + } |
|
308 | + $this->outByte(self::WBXML_END); |
|
309 | + } |
|
310 | + |
|
311 | + /** |
|
312 | + * Outputs a byte. |
|
313 | + * |
|
314 | + * @param $byte |
|
315 | + * |
|
316 | + * @return |
|
317 | + */ |
|
318 | + private function outByte($byte) { |
|
319 | + fwrite($this->_out, chr($byte)); |
|
320 | + } |
|
321 | + |
|
322 | + /** |
|
323 | + * Output the multibyte integers to the stream. |
|
324 | + * |
|
325 | + * A multi-byte integer consists of a series of octets, |
|
326 | + * where the most significant bit is the continuation flag |
|
327 | + * and the remaining seven bits are a scalar value. |
|
328 | + * The octets are arranged in a big-endian order, |
|
329 | + * eg, the most significant seven bits are transmitted first. |
|
330 | + * |
|
331 | + * @see https://www.w3.org/1999/06/NOTE-wbxml-19990624/#_Toc443384895 |
|
332 | + * |
|
333 | + * @param int $uint |
|
334 | + */ |
|
335 | + private function outMBUInt($uint) { |
|
336 | + if ($uint == 0x0) { |
|
337 | + return $this->outByte($uint); |
|
338 | + } |
|
339 | + |
|
340 | + $out = ''; |
|
341 | + |
|
342 | + for ($i = 0; $uint != 0; ++$i) { |
|
343 | + $byte = $uint & 0x7F; |
|
344 | + $uint = $uint >> 7; |
|
345 | + if ($i == 0) { |
|
346 | + $out = chr($byte) . $out; |
|
347 | + } |
|
348 | + else { |
|
349 | + $out = chr($byte | 0x80) . $out; |
|
350 | + } |
|
351 | + } |
|
352 | + fwrite($this->_out, $out); |
|
353 | + } |
|
354 | + |
|
355 | + /** |
|
356 | + * Outputs content with string terminator. |
|
357 | + * |
|
358 | + * @param $content |
|
359 | + * |
|
360 | + * @return |
|
361 | + */ |
|
362 | + private function outTermStr($content) { |
|
363 | + fwrite($this->_out, $content); |
|
364 | + fwrite($this->_out, chr(0)); |
|
365 | + } |
|
366 | + |
|
367 | + /** |
|
368 | + * Switches the codepage. |
|
369 | + * |
|
370 | + * @param $page |
|
371 | + * |
|
372 | + * @return |
|
373 | + */ |
|
374 | + private function outSwitchPage($page) { |
|
375 | + $this->outByte(self::WBXML_SWITCH_PAGE); |
|
376 | + $this->outByte($page); |
|
377 | + } |
|
378 | + |
|
379 | + /** |
|
380 | + * Get the mapping for a tag. |
|
381 | + * |
|
382 | + * @param $tag |
|
383 | + * |
|
384 | + * @return array |
|
385 | + */ |
|
386 | + private function getMapping($tag) { |
|
387 | + $mapping = []; |
|
388 | + |
|
389 | + $split = $this->splitTag($tag); |
|
390 | + |
|
391 | + if (isset($split["ns"])) { |
|
392 | + $cp = $this->_dtd["namespaces"][$split["ns"]]; |
|
393 | + } |
|
394 | + else { |
|
395 | + $cp = 0; |
|
396 | + } |
|
397 | + |
|
398 | + $code = $this->_dtd["codes"][$cp][$split["tag"]]; |
|
399 | + |
|
400 | + $mapping["cp"] = $cp; |
|
401 | + $mapping["code"] = $code; |
|
402 | + |
|
403 | + return $mapping; |
|
404 | + } |
|
405 | + |
|
406 | + /** |
|
407 | + * Split a tag from a the fulltag (namespace + tag). |
|
408 | + * |
|
409 | + * @param $fulltag |
|
410 | + * |
|
411 | + * @return array keys: 'ns' (namespace), 'tag' (tag) |
|
412 | + */ |
|
413 | + private function splitTag($fulltag) { |
|
414 | + $ns = false; |
|
415 | + $pos = strpos($fulltag, chr(58)); // chr(58) == ':' |
|
416 | + |
|
417 | + if ($pos) { |
|
418 | + $ns = substr($fulltag, 0, $pos); |
|
419 | + $tag = substr($fulltag, $pos + 1); |
|
420 | + } |
|
421 | + else { |
|
422 | + $tag = $fulltag; |
|
423 | + } |
|
424 | + |
|
425 | + $ret = []; |
|
426 | + if ($ns) { |
|
427 | + $ret["ns"] = $ns; |
|
428 | + } |
|
429 | + $ret["tag"] = $tag; |
|
430 | + |
|
431 | + return $ret; |
|
432 | + } |
|
433 | + |
|
434 | + /** |
|
435 | + * Logs a StartTag to SLog. |
|
436 | + * |
|
437 | + * @param $tag |
|
438 | + * @param $nocontent |
|
439 | + * |
|
440 | + * @return |
|
441 | + */ |
|
442 | + private function logStartTag($tag, $nocontent) { |
|
443 | + $spaces = str_repeat(" ", count($this->logStack)); |
|
444 | + if ($nocontent) { |
|
445 | + SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . " <{$tag}/>"); |
|
446 | + } |
|
447 | + else { |
|
448 | + array_push($this->logStack, $tag); |
|
449 | + SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . " <{$tag}>"); |
|
450 | + } |
|
451 | + } |
|
452 | + |
|
453 | + /** |
|
454 | + * Logs a EndTag to SLog. |
|
455 | + * |
|
456 | + * @return |
|
457 | + */ |
|
458 | + private function logEndTag() { |
|
459 | + $spaces = str_repeat(" ", count($this->logStack)); |
|
460 | + $tag = array_pop($this->logStack); |
|
461 | + SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . "</{$tag}>"); |
|
462 | + } |
|
463 | + |
|
464 | + /** |
|
465 | + * Logs content to SLog. |
|
466 | + * |
|
467 | + * @param string $content |
|
468 | + * |
|
469 | + * @return |
|
470 | + */ |
|
471 | + private function logContent($content) { |
|
472 | + $spaces = str_repeat(" ", count($this->logStack)); |
|
473 | + SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . $content); |
|
474 | + } |
|
475 | + |
|
476 | + /** |
|
477 | + * Processes the multipart response. |
|
478 | + */ |
|
479 | + private function processMultipart() { |
|
480 | + SLog::Write(LOGLEVEL_DEBUG, sprintf("WBXMLEncoder->processMultipart() with %d parts to be processed", $this->getBodypartsCount())); |
|
481 | + $len = ob_get_length(); |
|
482 | + $buffer = ob_get_clean(); |
|
483 | + $nrBodyparts = $this->getBodypartsCount(); |
|
484 | + $blockstart = (($nrBodyparts + 1) * 2) * 4 + 4; |
|
485 | + |
|
486 | + fwrite($this->_out, pack("iii", ($nrBodyparts + 1), $blockstart, $len)); |
|
487 | + |
|
488 | + foreach ($this->bodyparts as $i => $bp) { |
|
489 | + $blockstart = $blockstart + $len; |
|
490 | + $len = fstat($bp); |
|
491 | + $len = (isset($len['size'])) ? $len['size'] : 0; |
|
492 | + if ($len == 0) { |
|
493 | + SLog::Write(LOGLEVEL_WARN, sprintf("WBXMLEncoder->processMultipart(): the length of the body part at position %d is 0", $i)); |
|
494 | + } |
|
495 | + fwrite($this->_out, pack("ii", $blockstart, $len)); |
|
496 | + } |
|
497 | + |
|
498 | + fwrite($this->_out, $buffer); |
|
499 | + |
|
500 | + foreach ($this->bodyparts as $bp) { |
|
501 | + stream_copy_to_stream($bp, $this->_out); |
|
502 | + fclose($bp); |
|
503 | + } |
|
504 | + } |
|
505 | + |
|
506 | + /** |
|
507 | + * Writes the sent WBXML data to the log if it is not bigger than 512K. |
|
508 | + */ |
|
509 | + private function writeLog() { |
|
510 | + if (ob_get_length() === false) { |
|
511 | + $data = "output buffer disabled"; |
|
512 | + } |
|
513 | + elseif (ob_get_length() < 524288) { |
|
514 | + $data = base64_encode(ob_get_contents()); |
|
515 | + } |
|
516 | + else { |
|
517 | + $data = "more than 512K of data"; |
|
518 | + } |
|
519 | + SLog::Write(LOGLEVEL_WBXML, "WBXML-OUT: " . $data, false); |
|
520 | + } |
|
521 | 521 | } |
@@ -128,7 +128,7 @@ discard block |
||
128 | 128 | // We need to filter out any \0 chars because it's the string terminator in WBXML. We currently |
129 | 129 | // cannot send \0 characters within the XML content anywhere. |
130 | 130 | $content = str_replace("\0", "", $content); |
131 | - if ("x" . $content == "x") { |
|
131 | + if ("x".$content == "x") { |
|
132 | 132 | return; |
133 | 133 | } |
134 | 134 | $this->_outputStack(); |
@@ -177,7 +177,7 @@ discard block |
||
177 | 177 | */ |
178 | 178 | public function addBodypartStream($bp) { |
179 | 179 | if (!is_resource($bp)) { |
180 | - throw new WBXMLException("WBXMLEncoder->addBodypartStream(): trying to add a " . gettype($bp) . " instead of a stream"); |
|
180 | + throw new WBXMLException("WBXMLEncoder->addBodypartStream(): trying to add a ".gettype($bp)." instead of a stream"); |
|
181 | 181 | } |
182 | 182 | if ($this->multipart) { |
183 | 183 | $this->bodyparts[] = $bp; |
@@ -343,10 +343,10 @@ discard block |
||
343 | 343 | $byte = $uint & 0x7F; |
344 | 344 | $uint = $uint >> 7; |
345 | 345 | if ($i == 0) { |
346 | - $out = chr($byte) . $out; |
|
346 | + $out = chr($byte).$out; |
|
347 | 347 | } |
348 | 348 | else { |
349 | - $out = chr($byte | 0x80) . $out; |
|
349 | + $out = chr($byte|0x80).$out; |
|
350 | 350 | } |
351 | 351 | } |
352 | 352 | fwrite($this->_out, $out); |
@@ -442,11 +442,11 @@ discard block |
||
442 | 442 | private function logStartTag($tag, $nocontent) { |
443 | 443 | $spaces = str_repeat(" ", count($this->logStack)); |
444 | 444 | if ($nocontent) { |
445 | - SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . " <{$tag}/>"); |
|
445 | + SLog::Write(LOGLEVEL_WBXML, "O ".$spaces." <{$tag}/>"); |
|
446 | 446 | } |
447 | 447 | else { |
448 | 448 | array_push($this->logStack, $tag); |
449 | - SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . " <{$tag}>"); |
|
449 | + SLog::Write(LOGLEVEL_WBXML, "O ".$spaces." <{$tag}>"); |
|
450 | 450 | } |
451 | 451 | } |
452 | 452 | |
@@ -458,7 +458,7 @@ discard block |
||
458 | 458 | private function logEndTag() { |
459 | 459 | $spaces = str_repeat(" ", count($this->logStack)); |
460 | 460 | $tag = array_pop($this->logStack); |
461 | - SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . "</{$tag}>"); |
|
461 | + SLog::Write(LOGLEVEL_WBXML, "O ".$spaces."</{$tag}>"); |
|
462 | 462 | } |
463 | 463 | |
464 | 464 | /** |
@@ -470,7 +470,7 @@ discard block |
||
470 | 470 | */ |
471 | 471 | private function logContent($content) { |
472 | 472 | $spaces = str_repeat(" ", count($this->logStack)); |
473 | - SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . $content); |
|
473 | + SLog::Write(LOGLEVEL_WBXML, "O ".$spaces.$content); |
|
474 | 474 | } |
475 | 475 | |
476 | 476 | /** |
@@ -516,6 +516,6 @@ discard block |
||
516 | 516 | else { |
517 | 517 | $data = "more than 512K of data"; |
518 | 518 | } |
519 | - SLog::Write(LOGLEVEL_WBXML, "WBXML-OUT: " . $data, false); |
|
519 | + SLog::Write(LOGLEVEL_WBXML, "WBXML-OUT: ".$data, false); |
|
520 | 520 | } |
521 | 521 | } |
@@ -53,8 +53,7 @@ discard block |
||
53 | 53 | if ($this->multipart) { |
54 | 54 | header("Content-Type: application/vnd.ms-sync.multipart"); |
55 | 55 | SLog::Write(LOGLEVEL_DEBUG, "WBXMLEncoder->startWBXML() type: vnd.ms-sync.multipart"); |
56 | - } |
|
57 | - else { |
|
56 | + } else { |
|
58 | 57 | header("Content-Type: application/vnd.ms-sync.wbxml"); |
59 | 58 | SLog::Write(LOGLEVEL_DEBUG, "WBXMLEncoder->startWBXML() type: vnd.ms-sync.wbxml"); |
60 | 59 | } |
@@ -86,8 +85,7 @@ discard block |
||
86 | 85 | |
87 | 86 | // If 'nocontent' is specified, then apparently the user wants to force |
88 | 87 | // output of an empty tag, and we therefore output the stack here |
89 | - } |
|
90 | - else { |
|
88 | + } else { |
|
91 | 89 | $this->_outputStack(); |
92 | 90 | $this->_startTag($tag, $nocontent); |
93 | 91 | } |
@@ -274,8 +272,7 @@ discard block |
||
274 | 272 | if ($opaque) { |
275 | 273 | $this->outByte(self::WBXML_OPAQUE); |
276 | 274 | $this->outMBUInt($stat['size']); |
277 | - } |
|
278 | - else { |
|
275 | + } else { |
|
279 | 276 | $this->outByte(self::WBXML_STR_I); |
280 | 277 | } |
281 | 278 | |
@@ -344,8 +341,7 @@ discard block |
||
344 | 341 | $uint = $uint >> 7; |
345 | 342 | if ($i == 0) { |
346 | 343 | $out = chr($byte) . $out; |
347 | - } |
|
348 | - else { |
|
344 | + } else { |
|
349 | 345 | $out = chr($byte | 0x80) . $out; |
350 | 346 | } |
351 | 347 | } |
@@ -390,8 +386,7 @@ discard block |
||
390 | 386 | |
391 | 387 | if (isset($split["ns"])) { |
392 | 388 | $cp = $this->_dtd["namespaces"][$split["ns"]]; |
393 | - } |
|
394 | - else { |
|
389 | + } else { |
|
395 | 390 | $cp = 0; |
396 | 391 | } |
397 | 392 | |
@@ -417,8 +412,7 @@ discard block |
||
417 | 412 | if ($pos) { |
418 | 413 | $ns = substr($fulltag, 0, $pos); |
419 | 414 | $tag = substr($fulltag, $pos + 1); |
420 | - } |
|
421 | - else { |
|
415 | + } else { |
|
422 | 416 | $tag = $fulltag; |
423 | 417 | } |
424 | 418 | |
@@ -443,8 +437,7 @@ discard block |
||
443 | 437 | $spaces = str_repeat(" ", count($this->logStack)); |
444 | 438 | if ($nocontent) { |
445 | 439 | SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . " <{$tag}/>"); |
446 | - } |
|
447 | - else { |
|
440 | + } else { |
|
448 | 441 | array_push($this->logStack, $tag); |
449 | 442 | SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . " <{$tag}>"); |
450 | 443 | } |
@@ -509,11 +502,9 @@ discard block |
||
509 | 502 | private function writeLog() { |
510 | 503 | if (ob_get_length() === false) { |
511 | 504 | $data = "output buffer disabled"; |
512 | - } |
|
513 | - elseif (ob_get_length() < 524288) { |
|
505 | + } elseif (ob_get_length() < 524288) { |
|
514 | 506 | $data = base64_encode(ob_get_contents()); |
515 | - } |
|
516 | - else { |
|
507 | + } else { |
|
517 | 508 | $data = "more than 512K of data"; |
518 | 509 | } |
519 | 510 | SLog::Write(LOGLEVEL_WBXML, "WBXML-OUT: " . $data, false); |
@@ -8,227 +8,227 @@ |
||
8 | 8 | */ |
9 | 9 | |
10 | 10 | if (!function_exists('apache_request_headers')) { |
11 | - /** |
|
12 | - * When using other webservers or using php as cgi in apache |
|
13 | - * the function apache_request_headers() is not available. |
|
14 | - * This function parses the environment variables to extract |
|
15 | - * the necessary headers for grommunio-sync. |
|
16 | - */ |
|
17 | - function apache_request_headers() { |
|
18 | - $headers = []; |
|
19 | - foreach ($_SERVER as $key => $value) { |
|
20 | - if (substr($key, 0, 5) == 'HTTP_') { |
|
21 | - $headers[strtr(substr($key, 5), '_', '-')] = $value; |
|
22 | - } |
|
23 | - } |
|
24 | - |
|
25 | - return $headers; |
|
26 | - } |
|
11 | + /** |
|
12 | + * When using other webservers or using php as cgi in apache |
|
13 | + * the function apache_request_headers() is not available. |
|
14 | + * This function parses the environment variables to extract |
|
15 | + * the necessary headers for grommunio-sync. |
|
16 | + */ |
|
17 | + function apache_request_headers() { |
|
18 | + $headers = []; |
|
19 | + foreach ($_SERVER as $key => $value) { |
|
20 | + if (substr($key, 0, 5) == 'HTTP_') { |
|
21 | + $headers[strtr(substr($key, 5), '_', '-')] = $value; |
|
22 | + } |
|
23 | + } |
|
24 | + |
|
25 | + return $headers; |
|
26 | + } |
|
27 | 27 | } |
28 | 28 | |
29 | 29 | if (!function_exists('hex2bin')) { |
30 | - /** |
|
31 | - * Complementary function to bin2hex() which converts a hex entryid to a binary entryid. |
|
32 | - * Since PHP 5.4 an internal hex2bin() implementation is available. |
|
33 | - * |
|
34 | - * @param string $data the hexadecimal string |
|
35 | - * |
|
36 | - * @returns string |
|
37 | - */ |
|
38 | - function hex2bin($data) { |
|
39 | - return pack("H*", $data); |
|
40 | - } |
|
30 | + /** |
|
31 | + * Complementary function to bin2hex() which converts a hex entryid to a binary entryid. |
|
32 | + * Since PHP 5.4 an internal hex2bin() implementation is available. |
|
33 | + * |
|
34 | + * @param string $data the hexadecimal string |
|
35 | + * |
|
36 | + * @returns string |
|
37 | + */ |
|
38 | + function hex2bin($data) { |
|
39 | + return pack("H*", $data); |
|
40 | + } |
|
41 | 41 | } |
42 | 42 | |
43 | 43 | if (!function_exists('http_response_code')) { |
44 | - /** |
|
45 | - * http_response_code does not exists in PHP < 5.4 |
|
46 | - * http://php.net/manual/en/function.http-response-code.php. |
|
47 | - * |
|
48 | - * @param null|mixed $code |
|
49 | - */ |
|
50 | - function http_response_code($code = null) { |
|
51 | - if ($code !== null) { |
|
52 | - switch ($code) { |
|
53 | - case 100: |
|
54 | - $text = 'Continue'; |
|
55 | - break; |
|
56 | - |
|
57 | - case 101: |
|
58 | - $text = 'Switching Protocols'; |
|
59 | - break; |
|
60 | - |
|
61 | - case 200: |
|
62 | - $text = 'OK'; |
|
63 | - break; |
|
64 | - |
|
65 | - case 201: |
|
66 | - $text = 'Created'; |
|
67 | - break; |
|
68 | - |
|
69 | - case 202: |
|
70 | - $text = 'Accepted'; |
|
71 | - break; |
|
72 | - |
|
73 | - case 203: |
|
74 | - $text = 'Non-Authoritative Information'; |
|
75 | - break; |
|
76 | - |
|
77 | - case 204: |
|
78 | - $text = 'No Content'; |
|
79 | - break; |
|
80 | - |
|
81 | - case 205: |
|
82 | - $text = 'Reset Content'; |
|
83 | - break; |
|
84 | - |
|
85 | - case 206: |
|
86 | - $text = 'Partial Content'; |
|
87 | - break; |
|
88 | - |
|
89 | - case 300: |
|
90 | - $text = 'Multiple Choices'; |
|
91 | - break; |
|
92 | - |
|
93 | - case 301: |
|
94 | - $text = 'Moved Permanently'; |
|
95 | - break; |
|
96 | - |
|
97 | - case 302: |
|
98 | - $text = 'Moved Temporarily'; |
|
99 | - break; |
|
100 | - |
|
101 | - case 303: |
|
102 | - $text = 'See Other'; |
|
103 | - break; |
|
104 | - |
|
105 | - case 304: |
|
106 | - $text = 'Not Modified'; |
|
107 | - break; |
|
108 | - |
|
109 | - case 305: |
|
110 | - $text = 'Use Proxy'; |
|
111 | - break; |
|
112 | - |
|
113 | - case 400: |
|
114 | - $text = 'Bad Request'; |
|
115 | - break; |
|
116 | - |
|
117 | - case 401: |
|
118 | - $text = 'Unauthorized'; |
|
119 | - break; |
|
120 | - |
|
121 | - case 402: |
|
122 | - $text = 'Payment Required'; |
|
123 | - break; |
|
124 | - |
|
125 | - case 403: |
|
126 | - $text = 'Forbidden'; |
|
127 | - break; |
|
128 | - |
|
129 | - case 404: |
|
130 | - $text = 'Not Found'; |
|
131 | - break; |
|
132 | - |
|
133 | - case 405: |
|
134 | - $text = 'Method Not Allowed'; |
|
135 | - break; |
|
136 | - |
|
137 | - case 406: |
|
138 | - $text = 'Not Acceptable'; |
|
139 | - break; |
|
140 | - |
|
141 | - case 407: |
|
142 | - $text = 'Proxy Authentication Required'; |
|
143 | - break; |
|
144 | - |
|
145 | - case 408: |
|
146 | - $text = 'Request Time-out'; |
|
147 | - break; |
|
148 | - |
|
149 | - case 409: |
|
150 | - $text = 'Conflict'; |
|
151 | - break; |
|
152 | - |
|
153 | - case 410: |
|
154 | - $text = 'Gone'; |
|
155 | - break; |
|
44 | + /** |
|
45 | + * http_response_code does not exists in PHP < 5.4 |
|
46 | + * http://php.net/manual/en/function.http-response-code.php. |
|
47 | + * |
|
48 | + * @param null|mixed $code |
|
49 | + */ |
|
50 | + function http_response_code($code = null) { |
|
51 | + if ($code !== null) { |
|
52 | + switch ($code) { |
|
53 | + case 100: |
|
54 | + $text = 'Continue'; |
|
55 | + break; |
|
56 | + |
|
57 | + case 101: |
|
58 | + $text = 'Switching Protocols'; |
|
59 | + break; |
|
60 | + |
|
61 | + case 200: |
|
62 | + $text = 'OK'; |
|
63 | + break; |
|
64 | + |
|
65 | + case 201: |
|
66 | + $text = 'Created'; |
|
67 | + break; |
|
68 | + |
|
69 | + case 202: |
|
70 | + $text = 'Accepted'; |
|
71 | + break; |
|
72 | + |
|
73 | + case 203: |
|
74 | + $text = 'Non-Authoritative Information'; |
|
75 | + break; |
|
76 | + |
|
77 | + case 204: |
|
78 | + $text = 'No Content'; |
|
79 | + break; |
|
80 | + |
|
81 | + case 205: |
|
82 | + $text = 'Reset Content'; |
|
83 | + break; |
|
84 | + |
|
85 | + case 206: |
|
86 | + $text = 'Partial Content'; |
|
87 | + break; |
|
88 | + |
|
89 | + case 300: |
|
90 | + $text = 'Multiple Choices'; |
|
91 | + break; |
|
92 | + |
|
93 | + case 301: |
|
94 | + $text = 'Moved Permanently'; |
|
95 | + break; |
|
96 | + |
|
97 | + case 302: |
|
98 | + $text = 'Moved Temporarily'; |
|
99 | + break; |
|
100 | + |
|
101 | + case 303: |
|
102 | + $text = 'See Other'; |
|
103 | + break; |
|
104 | + |
|
105 | + case 304: |
|
106 | + $text = 'Not Modified'; |
|
107 | + break; |
|
108 | + |
|
109 | + case 305: |
|
110 | + $text = 'Use Proxy'; |
|
111 | + break; |
|
112 | + |
|
113 | + case 400: |
|
114 | + $text = 'Bad Request'; |
|
115 | + break; |
|
116 | + |
|
117 | + case 401: |
|
118 | + $text = 'Unauthorized'; |
|
119 | + break; |
|
120 | + |
|
121 | + case 402: |
|
122 | + $text = 'Payment Required'; |
|
123 | + break; |
|
124 | + |
|
125 | + case 403: |
|
126 | + $text = 'Forbidden'; |
|
127 | + break; |
|
128 | + |
|
129 | + case 404: |
|
130 | + $text = 'Not Found'; |
|
131 | + break; |
|
132 | + |
|
133 | + case 405: |
|
134 | + $text = 'Method Not Allowed'; |
|
135 | + break; |
|
136 | + |
|
137 | + case 406: |
|
138 | + $text = 'Not Acceptable'; |
|
139 | + break; |
|
140 | + |
|
141 | + case 407: |
|
142 | + $text = 'Proxy Authentication Required'; |
|
143 | + break; |
|
144 | + |
|
145 | + case 408: |
|
146 | + $text = 'Request Time-out'; |
|
147 | + break; |
|
148 | + |
|
149 | + case 409: |
|
150 | + $text = 'Conflict'; |
|
151 | + break; |
|
152 | + |
|
153 | + case 410: |
|
154 | + $text = 'Gone'; |
|
155 | + break; |
|
156 | 156 | |
157 | - case 411: |
|
158 | - $text = 'Length Required'; |
|
159 | - break; |
|
157 | + case 411: |
|
158 | + $text = 'Length Required'; |
|
159 | + break; |
|
160 | 160 | |
161 | - case 412: |
|
162 | - $text = 'Precondition Failed'; |
|
163 | - break; |
|
161 | + case 412: |
|
162 | + $text = 'Precondition Failed'; |
|
163 | + break; |
|
164 | 164 | |
165 | - case 413: |
|
166 | - $text = 'Request Entity Too Large'; |
|
167 | - break; |
|
165 | + case 413: |
|
166 | + $text = 'Request Entity Too Large'; |
|
167 | + break; |
|
168 | 168 | |
169 | - case 414: |
|
170 | - $text = 'Request-URI Too Large'; |
|
171 | - break; |
|
169 | + case 414: |
|
170 | + $text = 'Request-URI Too Large'; |
|
171 | + break; |
|
172 | 172 | |
173 | - case 415: |
|
174 | - $text = 'Unsupported Media Type'; |
|
175 | - break; |
|
173 | + case 415: |
|
174 | + $text = 'Unsupported Media Type'; |
|
175 | + break; |
|
176 | 176 | |
177 | - case 500: |
|
178 | - $text = 'Internal Server Error'; |
|
179 | - break; |
|
177 | + case 500: |
|
178 | + $text = 'Internal Server Error'; |
|
179 | + break; |
|
180 | 180 | |
181 | - case 501: |
|
182 | - $text = 'Not Implemented'; |
|
183 | - break; |
|
181 | + case 501: |
|
182 | + $text = 'Not Implemented'; |
|
183 | + break; |
|
184 | 184 | |
185 | - case 502: |
|
186 | - $text = 'Bad Gateway'; |
|
187 | - break; |
|
185 | + case 502: |
|
186 | + $text = 'Bad Gateway'; |
|
187 | + break; |
|
188 | 188 | |
189 | - case 503: |
|
190 | - $text = 'Service Unavailable'; |
|
191 | - break; |
|
189 | + case 503: |
|
190 | + $text = 'Service Unavailable'; |
|
191 | + break; |
|
192 | 192 | |
193 | - case 504: |
|
194 | - $text = 'Gateway Time-out'; |
|
195 | - break; |
|
193 | + case 504: |
|
194 | + $text = 'Gateway Time-out'; |
|
195 | + break; |
|
196 | 196 | |
197 | - case 505: |
|
198 | - $text = 'HTTP Version not supported'; |
|
199 | - break; |
|
197 | + case 505: |
|
198 | + $text = 'HTTP Version not supported'; |
|
199 | + break; |
|
200 | 200 | |
201 | - default: |
|
202 | - exit('Unknown http status code "' . htmlentities($code) . '"'); |
|
203 | - break; |
|
204 | - } |
|
205 | - |
|
206 | - $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'); |
|
207 | - header($protocol . ' ' . $code . ' ' . $text); |
|
201 | + default: |
|
202 | + exit('Unknown http status code "' . htmlentities($code) . '"'); |
|
203 | + break; |
|
204 | + } |
|
205 | + |
|
206 | + $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'); |
|
207 | + header($protocol . ' ' . $code . ' ' . $text); |
|
208 | 208 | |
209 | - $GLOBALS['http_response_code'] = $code; |
|
210 | - } |
|
211 | - else { |
|
212 | - $code = (isset($GLOBALS['http_response_code']) ? $GLOBALS['http_response_code'] : 200); |
|
213 | - } |
|
209 | + $GLOBALS['http_response_code'] = $code; |
|
210 | + } |
|
211 | + else { |
|
212 | + $code = (isset($GLOBALS['http_response_code']) ? $GLOBALS['http_response_code'] : 200); |
|
213 | + } |
|
214 | 214 | |
215 | - return $code; |
|
216 | - } |
|
215 | + return $code; |
|
216 | + } |
|
217 | 217 | } |
218 | 218 | |
219 | 219 | if (!function_exists('memory_get_peak_usage')) { |
220 | - /** |
|
221 | - * memory_get_peak_usage is not available prior to PHP 5.2. |
|
222 | - * This complementary function will return the value of memory_get_usage();. |
|
223 | - * |
|
224 | - * @see http://php.net/manual/en/function.memory-get-usage.php |
|
225 | - * @see http://php.net/manual/en/function.memory-get-peak-usage.php |
|
226 | - * |
|
227 | - * @param bool $real_usage |
|
228 | - */ |
|
229 | - function memory_get_peak_usage($real_usage = false) { |
|
230 | - SLog::Write(LOGLEVEL_DEBUG, "memory_get_peak_usage() is not available on this system. The value of memory_get_usage() will be used."); |
|
231 | - |
|
232 | - return memory_get_usage(); |
|
233 | - } |
|
220 | + /** |
|
221 | + * memory_get_peak_usage is not available prior to PHP 5.2. |
|
222 | + * This complementary function will return the value of memory_get_usage();. |
|
223 | + * |
|
224 | + * @see http://php.net/manual/en/function.memory-get-usage.php |
|
225 | + * @see http://php.net/manual/en/function.memory-get-peak-usage.php |
|
226 | + * |
|
227 | + * @param bool $real_usage |
|
228 | + */ |
|
229 | + function memory_get_peak_usage($real_usage = false) { |
|
230 | + SLog::Write(LOGLEVEL_DEBUG, "memory_get_peak_usage() is not available on this system. The value of memory_get_usage() will be used."); |
|
231 | + |
|
232 | + return memory_get_usage(); |
|
233 | + } |
|
234 | 234 | } |
@@ -199,12 +199,12 @@ |
||
199 | 199 | break; |
200 | 200 | |
201 | 201 | default: |
202 | - exit('Unknown http status code "' . htmlentities($code) . '"'); |
|
202 | + exit('Unknown http status code "'.htmlentities($code).'"'); |
|
203 | 203 | break; |
204 | 204 | } |
205 | 205 | |
206 | 206 | $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'); |
207 | - header($protocol . ' ' . $code . ' ' . $text); |
|
207 | + header($protocol.' '.$code.' '.$text); |
|
208 | 208 | |
209 | 209 | $GLOBALS['http_response_code'] = $code; |
210 | 210 | } |
@@ -207,8 +207,7 @@ |
||
207 | 207 | header($protocol . ' ' . $code . ' ' . $text); |
208 | 208 | |
209 | 209 | $GLOBALS['http_response_code'] = $code; |
210 | - } |
|
211 | - else { |
|
210 | + } else { |
|
212 | 211 | $code = (isset($GLOBALS['http_response_code']) ? $GLOBALS['http_response_code'] : 200); |
213 | 212 | } |
214 | 213 |