Total Complexity | 140 |
Total Lines | 832 |
Duplicated Lines | 0 % |
Changes | 1 | ||
Bugs | 1 | Features | 0 |
Complex classes like ImportChangesICS often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use ImportChangesICS, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
20 | class ImportChangesICS implements IImportChanges { |
||
21 | private $folderid; |
||
22 | private $folderidHex; |
||
23 | private $store; |
||
24 | private $session; |
||
25 | private $flags; |
||
26 | private $statestream; |
||
27 | private $importer; |
||
28 | private $memChanges; |
||
29 | private $mapiprovider; |
||
30 | private $conflictsLoaded; |
||
31 | private $conflictsContentParameters; |
||
32 | private $conflictsState; |
||
33 | private $cutoffdate; |
||
34 | private $contentClass; |
||
35 | private $prefix; |
||
36 | |||
37 | /** |
||
38 | * Constructor. |
||
39 | * |
||
40 | * @param mapisession $session |
||
|
|||
41 | * @param mapistore $store |
||
42 | * @param string $folderid (opt) |
||
43 | * |
||
44 | * @throws StatusException |
||
45 | */ |
||
46 | public function __construct($session, $store, $folderid = false) { |
||
94 | } |
||
95 | } |
||
96 | |||
97 | /** |
||
98 | * Initializes the importer. |
||
99 | * |
||
100 | * @param string $state |
||
101 | * @param int $flags |
||
102 | * |
||
103 | * @return bool |
||
104 | * |
||
105 | * @throws StatusException |
||
106 | */ |
||
107 | public function Config($state, $flags = 0) { |
||
140 | } |
||
141 | |||
142 | /** |
||
143 | * Configures additional parameters for content selection. |
||
144 | * |
||
145 | * @param ContentParameters $contentparameters |
||
146 | * |
||
147 | * @return bool |
||
148 | * |
||
149 | * @throws StatusException |
||
150 | */ |
||
151 | public function ConfigContentParameters($contentparameters) { |
||
152 | $filtertype = $contentparameters->GetFilterType(); |
||
153 | |||
154 | if ($filtertype == SYNC_FILTERTYPE_DISABLE) { |
||
155 | $filtertype = false; |
||
156 | } |
||
157 | |||
158 | switch ($contentparameters->GetContentClass()) { |
||
159 | case "Email": |
||
160 | $this->cutoffdate = ($filtertype) ? Utils::GetCutOffDate($filtertype) : false; |
||
161 | break; |
||
162 | |||
163 | case "Calendar": |
||
164 | $this->cutoffdate = ($filtertype) ? Utils::GetCutOffDate($filtertype) : false; |
||
165 | break; |
||
166 | |||
167 | default: |
||
168 | case "Contacts": |
||
169 | case "Tasks": |
||
170 | $this->cutoffdate = false; |
||
171 | break; |
||
172 | } |
||
173 | $this->contentClass = $contentparameters->GetContentClass(); |
||
174 | |||
175 | return true; |
||
176 | } |
||
177 | |||
178 | /** |
||
179 | * Reads state from the Importer. |
||
180 | * |
||
181 | * @return string |
||
182 | * |
||
183 | * @throws StatusException |
||
184 | */ |
||
185 | public function GetState() { |
||
186 | $error = false; |
||
187 | if (!isset($this->statestream) || $this->importer === false) { |
||
188 | $error = true; |
||
189 | } |
||
190 | |||
191 | if ($error === false && $this->folderid !== false && function_exists("mapi_importcontentschanges_updatestate")) { |
||
192 | if (mapi_importcontentschanges_updatestate($this->importer, $this->statestream) != true) { |
||
193 | $error = true; |
||
194 | } |
||
195 | } |
||
196 | |||
197 | if ($error === true) { |
||
198 | throw new StatusException(sprintf("ImportChangesICS->GetState(): Error, state not available or unable to update: 0x%X", mapi_last_hresult()), ($this->folderid) ? SYNC_STATUS_FOLDERHIERARCHYCHANGED : SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_WARN); |
||
199 | } |
||
200 | |||
201 | mapi_stream_seek($this->statestream, 0, STREAM_SEEK_SET); |
||
202 | |||
203 | $state = ""; |
||
204 | while (true) { |
||
205 | $data = mapi_stream_read($this->statestream, 4096); |
||
206 | if (strlen($data)) { |
||
207 | $state .= $data; |
||
208 | } |
||
209 | else { |
||
210 | break; |
||
211 | } |
||
212 | } |
||
213 | |||
214 | return $state; |
||
215 | } |
||
216 | |||
217 | /** |
||
218 | * Checks if a message may be modified. This involves checking: |
||
219 | * - if there is a synchronization interval and if so, if the message is in it (sync window). |
||
220 | * These checks only apply to Emails and Appointments only, Contacts, Tasks and Notes do not have time restrictions. |
||
221 | * - if the message is not marked as private in a shared folder. |
||
222 | * |
||
223 | * @param string $messageid the message id to be checked |
||
224 | * |
||
225 | * @return bool |
||
226 | */ |
||
227 | private function isModificationAllowed($messageid) { |
||
228 | $sharedUser = GSync::GetAdditionalSyncFolderStore(bin2hex($this->folderid)); |
||
229 | // if this is either a user folder or SYSTEM and no restriction is set, we don't need to check |
||
230 | if (($sharedUser == false || $sharedUser == 'SYSTEM') && $this->cutoffdate === false && !GSync::GetBackend()->GetImpersonatedUser()) { |
||
231 | return true; |
||
232 | } |
||
233 | |||
234 | // open the existing object |
||
235 | $entryid = mapi_msgstore_entryidfromsourcekey($this->store, $this->folderid, hex2bin($messageid)); |
||
236 | if (!$entryid) { |
||
237 | SLog::Write(LOGLEVEL_WARN, sprintf("ImportChangesICS->isModificationAllowed('%s'): Error, unable to resolve message id: 0x%X", $messageid, mapi_last_hresult())); |
||
238 | |||
239 | return false; |
||
240 | } |
||
241 | |||
242 | $mapimessage = mapi_msgstore_openentry($this->store, $entryid); |
||
243 | if (!$mapimessage) { |
||
244 | SLog::Write(LOGLEVEL_WARN, sprintf("ImportChangesICS->isModificationAllowed('%s'): Error, unable to open entry id: 0x%X", $messageid, mapi_last_hresult())); |
||
245 | |||
246 | return false; |
||
247 | } |
||
248 | |||
249 | // check the sync interval |
||
250 | if ($this->cutoffdate !== false) { |
||
251 | SLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesICS->isModificationAllowed('%s'): cut off date is: %s (%s)", $messageid, Utils::FormatDate($this->cutoffdate), $this->cutoffdate)); |
||
252 | if (($this->contentClass == "Email" && !MAPIUtils::IsInEmailSyncInterval($this->store, $mapimessage, $this->cutoffdate)) || |
||
253 | ($this->contentClass == "Calendar" && !MAPIUtils::IsInCalendarSyncInterval($this->store, $mapimessage, $this->cutoffdate))) { |
||
254 | SLog::Write(LOGLEVEL_WARN, sprintf("ImportChangesICS->isModificationAllowed('%s'): Message in %s is outside the sync interval. Data not saved.", $messageid, $this->contentClass)); |
||
255 | |||
256 | return false; |
||
257 | } |
||
258 | } |
||
259 | |||
260 | // check if not private |
||
261 | if (MAPIUtils::IsMessageSharedAndPrivate($this->folderid, $mapimessage)) { |
||
262 | SLog::Write(LOGLEVEL_WARN, sprintf("ImportChangesICS->isModificationAllowed('%s'): Message is shared and marked as private. Data not saved.", $messageid)); |
||
263 | |||
264 | return false; |
||
265 | } |
||
266 | |||
267 | // yes, modification allowed |
||
268 | return true; |
||
269 | } |
||
270 | |||
271 | /*---------------------------------------------------------------------------------------------------------- |
||
272 | * Methods for ContentsExporter |
||
273 | */ |
||
274 | |||
275 | /** |
||
276 | * Loads objects which are expected to be exported with the state |
||
277 | * Before importing/saving the actual message from the mobile, a conflict detection should be done. |
||
278 | * |
||
279 | * @param ContentParameters $contentparameters class of objects |
||
280 | * @param string $state |
||
281 | * |
||
282 | * @return bool |
||
283 | * |
||
284 | * @throws StatusException |
||
285 | */ |
||
286 | public function LoadConflicts($contentparameters, $state) { |
||
287 | if (!isset($this->session) || !isset($this->store) || !isset($this->folderid)) { |
||
288 | throw new StatusException("ImportChangesICS->LoadConflicts(): Error, can not load changes for conflict detection. Session, store or folder information not available", SYNC_STATUS_SERVERERROR); |
||
289 | } |
||
290 | |||
291 | // save data to load changes later if necessary |
||
292 | $this->conflictsLoaded = false; |
||
293 | $this->conflictsContentParameters = $contentparameters; |
||
294 | $this->conflictsState = $state; |
||
295 | |||
296 | SLog::Write(LOGLEVEL_DEBUG, "ImportChangesICS->LoadConflicts(): will be loaded later if necessary"); |
||
297 | |||
298 | return true; |
||
299 | } |
||
300 | |||
301 | /** |
||
302 | * Potential conflicts are only loaded when really necessary, |
||
303 | * e.g. on ADD or MODIFY. |
||
304 | * |
||
305 | * @return bool |
||
306 | */ |
||
307 | private function lazyLoadConflicts() { |
||
358 | } |
||
359 | |||
360 | /** |
||
361 | * Imports a single message. |
||
362 | * |
||
363 | * @param string $id |
||
364 | * @param SyncObject $message |
||
365 | * |
||
366 | * @return bool|SyncObject - failure / response |
||
367 | * |
||
368 | * @throws StatusException |
||
369 | */ |
||
370 | public function ImportMessageChange($id, $message) { |
||
371 | $flags = 0; |
||
372 | $props = []; |
||
373 | $props[PR_PARENT_SOURCE_KEY] = $this->folderid; |
||
374 | $messageClass = get_class($message); |
||
375 | |||
376 | // set the PR_SOURCE_KEY if available or mark it as new message |
||
377 | if ($id) { |
||
378 | list(, $sk) = Utils::SplitMessageId($id); |
||
379 | $props[PR_SOURCE_KEY] = hex2bin($sk); |
||
380 | |||
381 | // check if message is in the synchronization interval and/or shared+private |
||
382 | if (!$this->isModificationAllowed($sk)) { |
||
383 | throw new StatusException(sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Message modification is not allowed. Data not saved.", $id, $messageClass), SYNC_STATUS_SYNCCANNOTBECOMPLETED); |
||
384 | } |
||
385 | |||
386 | // check for conflicts |
||
387 | $this->lazyLoadConflicts(); |
||
388 | if ($this->memChanges->IsChanged($id)) { |
||
389 | if ($this->flags & SYNC_CONFLICT_OVERWRITE_PIM) { |
||
390 | // in these cases the status SYNC_STATUS_CONFLICTCLIENTSERVEROBJECT should be returned, so the mobile client can inform the end user |
||
391 | throw new StatusException(sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Conflict detected. Data from PIM will be dropped! Server overwrites PIM. User is informed.", $id, $messageClass), SYNC_STATUS_CONFLICTCLIENTSERVEROBJECT, null, LOGLEVEL_INFO); |
||
392 | } |
||
393 | |||
394 | SLog::Write(LOGLEVEL_INFO, sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Conflict detected. Data from Server will be dropped! PIM overwrites server.", $id, $messageClass)); |
||
395 | } |
||
396 | if ($this->memChanges->IsDeleted($id)) { |
||
397 | SLog::Write(LOGLEVEL_INFO, sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Conflict detected. Data from PIM will be dropped! Object was deleted on server.", $id, $messageClass)); |
||
398 | $response = Utils::GetResponseFromMessageClass($messageClass); |
||
399 | $response->hasResponse = false; |
||
400 | |||
401 | return $response; |
||
402 | } |
||
403 | } |
||
404 | else { |
||
405 | $flags = SYNC_NEW_MESSAGE; |
||
406 | } |
||
407 | |||
408 | if (mapi_importcontentschanges_importmessagechange($this->importer, $props, $flags, $mapimessage)) { |
||
409 | // grommunio-sync #113: workaround blocking notifications on this item |
||
410 | $sourcekeyprops = mapi_getprops($mapimessage, [PR_ENTRYID]); |
||
411 | if (isset($sourcekeyprops[PR_ENTRYID])) { |
||
412 | GSync::ReplyCatchMark(bin2hex($sourcekeyprops[PR_ENTRYID])); |
||
413 | } |
||
414 | |||
415 | $response = $this->mapiprovider->SetMessage($mapimessage, $message); |
||
416 | mapi_savechanges($mapimessage); |
||
417 | |||
418 | if (mapi_last_hresult()) { |
||
419 | throw new StatusException(sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Error, mapi_savechanges() failed: 0x%X", $id, $messageClass, mapi_last_hresult()), SYNC_STATUS_SYNCCANNOTBECOMPLETED); |
||
420 | } |
||
421 | |||
422 | $sourcekeyprops = mapi_getprops($mapimessage, [PR_SOURCE_KEY]); |
||
423 | |||
424 | $response->serverid = $this->prefix . bin2hex($sourcekeyprops[PR_SOURCE_KEY]); |
||
425 | |||
426 | return $response; |
||
427 | } |
||
428 | |||
429 | throw new StatusException(sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Error updating object: 0x%X", $id, $messageClass, mapi_last_hresult()), SYNC_STATUS_OBJECTNOTFOUND); |
||
430 | } |
||
431 | |||
432 | /** |
||
433 | * Imports a deletion. This may conflict if the local object has been modified. |
||
434 | * |
||
435 | * @param string $id |
||
436 | * @param bool $asSoftDelete (opt) if true, the deletion is exported as "SoftDelete", else as "Remove" - default: false |
||
437 | * |
||
438 | * @return bool |
||
439 | */ |
||
440 | public function ImportMessageDeletion($id, $asSoftDelete = false) { |
||
476 | } |
||
477 | |||
478 | /** |
||
479 | * Imports a change in 'read' flag |
||
480 | * This can never conflict. |
||
481 | * |
||
482 | * @param string $id |
||
483 | * @param int $flags - read/unread |
||
484 | * @param array $categories |
||
485 | * |
||
486 | * @return bool |
||
487 | * |
||
488 | * @throws StatusException |
||
489 | */ |
||
490 | public function ImportMessageReadFlag($id, $flags, $categories = []) { |
||
491 | $response = new SyncMailResponse(); |
||
492 | list($fsk, $sk) = Utils::SplitMessageId($id); |
||
493 | |||
494 | // if $fsk is set, we convert it into a backend id. |
||
495 | if ($fsk) { |
||
496 | $fsk = GSync::GetDeviceManager()->GetBackendIdForFolderId($fsk); |
||
497 | } |
||
498 | |||
499 | // read flag change for our current folder |
||
500 | if ($this->folderidHex == $fsk || empty($fsk)) { |
||
501 | // check if it is in the synchronization interval and/or shared+private |
||
502 | if (!$this->isModificationAllowed($sk)) { |
||
503 | throw new StatusException(sprintf("ImportChangesICS->ImportMessageReadFlag('%s','%d'): Flag update is not allowed. Flags not updated.", $id, $flags), SYNC_STATUS_OBJECTNOTFOUND); |
||
504 | } |
||
505 | |||
506 | // check for conflicts |
||
507 | /* |
||
508 | * Checking for conflicts is correct at this point, but is a very expensive operation. |
||
509 | * If the message was deleted, only an error will be shown. |
||
510 | * |
||
511 | $this->lazyLoadConflicts(); |
||
512 | if($this->memChanges->IsDeleted($id)) { |
||
513 | SLog::Write(LOGLEVEL_INFO, sprintf("ImportChangesICS->ImportMessageReadFlag('%s'): Conflict detected. Data is already deleted. Request will be ignored.", $id)); |
||
514 | return true; |
||
515 | } |
||
516 | */ |
||
517 | |||
518 | // grommunio-sync #113: workaround blocking notifications on this item |
||
519 | $entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($fsk), hex2bin($sk)); |
||
520 | if ($entryid !== false) { |
||
521 | GSync::ReplyCatchMark(bin2hex($entryid)); |
||
522 | } |
||
523 | |||
524 | $readstate = ["sourcekey" => hex2bin($sk), "flags" => $flags]; |
||
525 | |||
526 | if (!mapi_importcontentschanges_importperuserreadstatechange($this->importer, [$readstate])) { |
||
527 | throw new StatusException(sprintf("ImportChangesICS->ImportMessageReadFlag('%s','%d'): Error setting read state: 0x%X", $id, $flags, mapi_last_hresult()), SYNC_STATUS_OBJECTNOTFOUND); |
||
528 | } |
||
529 | } |
||
530 | // yeah OL sucks |
||
531 | else { |
||
532 | if (!$fsk) { |
||
533 | throw new StatusException(sprintf("ImportChangesICS->ImportMessageReadFlag('%s','%d'): Error setting read state. The message is in another folder but id is unknown as no short folder id is available. Please remove your device states to fully resync your device. Operation ignored.", $id, $flags), SYNC_STATUS_OBJECTNOTFOUND); |
||
534 | } |
||
535 | $store = GSync::GetBackend()->GetMAPIStoreForFolderId(GSync::GetAdditionalSyncFolderStore($fsk), $fsk); |
||
536 | $entryid = mapi_msgstore_entryidfromsourcekey($store, hex2bin($fsk), hex2bin($sk)); |
||
537 | $realMessage = mapi_msgstore_openentry($store, $entryid); |
||
538 | $flag = 0; |
||
539 | if ($flags == 0) { |
||
540 | $flag |= CLEAR_READ_FLAG; |
||
541 | } |
||
542 | $p = mapi_message_setreadflag($realMessage, $flag); |
||
543 | SLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesICS->ImportMessageReadFlag('%s','%d'): setting readflag on message: 0x%X", $id, $flags, mapi_last_hresult())); |
||
544 | } |
||
545 | |||
546 | return $response; |
||
547 | } |
||
548 | |||
549 | /** |
||
550 | * Imports a move of a message. This occurs when a user moves an item to another folder. |
||
551 | * |
||
552 | * Normally, we would implement this via the 'offical' importmessagemove() function on the ICS importer, |
||
553 | * but the grommunio importer does not support this. Therefore we currently implement it via a standard mapi |
||
554 | * call. This causes a mirror 'add/delete' to be sent to the PDA at the next sync. |
||
555 | * Manfred, 2010-10-21. For some mobiles import was causing duplicate messages in the destination folder |
||
556 | * (Mantis #202). Therefore we will create a new message in the destination folder, copy properties |
||
557 | * of the source message to the new one and then delete the source message. |
||
558 | * |
||
559 | * @param string $id |
||
560 | * @param string $newfolder destination folder |
||
561 | * |
||
562 | * @return bool|string |
||
563 | * |
||
564 | * @throws StatusException |
||
565 | */ |
||
566 | public function ImportMessageMove($id, $newfolder) { |
||
659 | } |
||
660 | |||
661 | /*---------------------------------------------------------------------------------------------------------- |
||
662 | * Methods for HierarchyExporter |
||
663 | */ |
||
664 | |||
665 | /** |
||
666 | * Imports a change on a folder. |
||
667 | * |
||
668 | * @param object $folder SyncFolder |
||
669 | * |
||
670 | * @return bool|SyncFolder false on error or a SyncFolder object with serverid and BackendId set (if available) |
||
671 | * |
||
672 | * @throws StatusException |
||
673 | */ |
||
674 | public function ImportFolderChange($folder) { |
||
818 | } |
||
819 | |||
820 | /** |
||
821 | * Imports a folder deletion. |
||
822 | * |
||
823 | * @param SyncFolder $folder at least "serverid" needs to be set |
||
824 | * |
||
825 | * @return int SYNC_FOLDERHIERARCHY_STATUS |
||
826 | * |
||
827 | * @throws StatusException |
||
828 | */ |
||
829 | public function ImportFolderDeletion($folder) { |
||
852 | } |
||
853 | } |
||
854 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths