DocumentManager::read()   A
last analyzed

Complexity

Conditions 5
Paths 6

Size

Total Lines 29
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 14
dl 0
loc 29
rs 9.4888
c 1
b 0
f 0
cc 5
nc 6
nop 1
1
<?php
2
namespace EWW\Dpf\Services\Document;
3
4
use EWW\Dpf\Domain\Model\Bookmark;
5
use EWW\Dpf\Domain\Model\Document;
6
use EWW\Dpf\Domain\Model\File;
7
use EWW\Dpf\Domain\Model\FrontendUser;
8
use EWW\Dpf\Security\Security;
9
use EWW\Dpf\Services\ElasticSearch\ElasticSearch;
10
use EWW\Dpf\Services\Transfer\FedoraRepository;
11
use EWW\Dpf\Services\Transfer\DocumentTransferManager;
12
use EWW\Dpf\Domain\Workflow\DocumentWorkflow;
13
use EWW\Dpf\Controller\AbstractController;
14
use EWW\Dpf\Services\Email\Notifier;
15
use Symfony\Component\Workflow\Workflow;
16
use Httpful\Request;
17
18
class DocumentManager
19
{
20
    /**
21
     * objectManager
22
     *
23
     * @var \TYPO3\CMS\Extbase\Object\ObjectManager
24
     * @TYPO3\CMS\Extbase\Annotation\Inject
25
     */
26
    protected $objectManager = null;
27
28
    /**
29
     * documentRepository
30
     *
31
     * @var \EWW\Dpf\Domain\Repository\DocumentRepository
32
     * @TYPO3\CMS\Extbase\Annotation\Inject
33
     */
34
    protected $documentRepository = null;
35
36
    /**
37
     * fileRepository
38
     *
39
     * @var \EWW\Dpf\Domain\Repository\FileRepository
40
     * @TYPO3\CMS\Extbase\Annotation\Inject
41
     */
42
    protected $fileRepository = null;
43
44
    /**
45
     * bookmarkRepository
46
     *
47
     * @var \EWW\Dpf\Domain\Repository\BookmarkRepository
48
     * @TYPO3\CMS\Extbase\Annotation\Inject
49
     */
50
    protected $bookmarkRepository = null;
51
52
    /**
53
     * persistence manager
54
     *
55
     * @var \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface
56
     * @TYPO3\CMS\Extbase\Annotation\Inject
57
     */
58
    protected $persistenceManager;
59
60
    /**
61
     * signalSlotDispatcher
62
     *
63
     * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
64
     * @TYPO3\CMS\Extbase\Annotation\Inject
65
     */
66
    protected $signalSlotDispatcher = null;
67
68
    /**
69
     * notifier
70
     *
71
     * @var \EWW\Dpf\Services\Email\Notifier
72
     * @TYPO3\CMS\Extbase\Annotation\Inject
73
     */
74
    protected $notifier = null;
75
76
    /**
77
     * security
78
     *
79
     * @var \EWW\Dpf\Security\Security
80
     * @TYPO3\CMS\Extbase\Annotation\Inject
81
     */
82
    protected $security = null;
83
84
    /**
85
     * frontendUserRepository
86
     *
87
     * @var \EWW\Dpf\Domain\Repository\FrontendUserRepository
88
     * @TYPO3\CMS\Extbase\Annotation\Inject
89
     */
90
    protected $frontendUserRepository = null;
91
92
    /**
93
     * elasticSearch
94
     *
95
     * @var \EWW\Dpf\Services\ElasticSearch\ElasticSearch
96
     * @TYPO3\CMS\Extbase\Annotation\Inject
97
     */
98
    protected $elasticSearch = null;
99
100
    /**
101
     * queryBuilder
102
     *
103
     * @var \EWW\Dpf\Services\ElasticSearch\QueryBuilder
104
     * @TYPO3\CMS\Extbase\Annotation\Inject
105
     */
106
    protected $queryBuilder = null;
107
108
    /**
109
     * Returns the localized document identifiers (uid/objectIdentifier).
110
     *
111
     * @param $identifier
112
     * @return array
113
     */
114
    public function resolveIdentifier($identifier) {
115
116
        $localizedIdentifiers = [];
117
118
        $document = $this->documentRepository->findByIdentifier($identifier);
119
120
        if ($document instanceof Document) {
0 ignored issues
show
introduced by
$document is always a sub-type of EWW\Dpf\Domain\Model\Document.
Loading history...
121
122
            if ($document->getObjectIdentifier()) {
123
                $localizedIdentifiers['objectIdentifier'] = $document->getObjectIdentifier();
124
            }
125
126
            if ($document->getUid()) {
127
                $localizedIdentifiers['uid'] = $document->getUid();
128
            }
129
        } else {
130
131
            $query = $this->queryBuilder->buildQuery(
132
                1, [], 0,
133
                [], [], [], null, null,
134
                'identifier:"'.$identifier.'"'
135
            );
136
137
            try {
138
                $results =  $this->elasticSearch->search($query, 'object');
139
                if (is_array($results) && $results['hits']['total']['value'] > 0) {
140
                    $localizedIdentifiers['objectIdentifier'] = $results['hits']['hits'][0]['_id'];
141
                }
142
            } catch (\Exception $e) {
143
                return [];
144
            }
145
146
        }
147
148
        return $localizedIdentifiers;
149
    }
150
151
152
    /**
153
     * Returns a document specified by repository object identifier, a typo3 uid or a process number.
154
     *
155
     * @param string $identifier
156
     * @return Document|null
157
     */
158
    public function read($identifier)
159
    {
160
        if (!$identifier) {
161
            return null;
162
        }
163
164
        $localizedIdentifiers = $this->resolveIdentifier($identifier);
165
166
        if (array_key_exists('uid', $localizedIdentifiers)) {
167
            return $this->documentRepository->findByUid($localizedIdentifiers['uid']);
168
        }
169
170
        if (array_key_exists('objectIdentifier', $localizedIdentifiers)) {
171
            try {
172
                /** @var \EWW\Dpf\Domain\Model\Document $document */
173
                $document = $this->getDocumentTransferManager()->retrieve($localizedIdentifiers['objectIdentifier']);
174
175
                // index the document
176
                $this->signalSlotDispatcher->dispatch(
177
                    AbstractController::class, 'indexDocument', [$document]
178
                );
179
180
                return $document;
181
            } catch (\Exception $exception) {
182
                return null;
183
            }
184
        }
185
186
        return null;
187
    }
188
189
    /**
190
     * Updates a document locally or remotely.
191
     *
192
     * @param Document $document
193
     * @param string $workflowTransition
194
     * @param bool $addedFisIdOnly
195
     * @return string|false
196
     * @throws \EWW\Dpf\Exceptions\DocumentMaxSizeErrorException
197
     * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException
198
     * @throws \TYPO3\CMS\Extbase\Persistence\Exception\UnknownObjectException
199
     */
200
    public function update(
201
        Document $document, $workflowTransition = null, $addedFisIdOnly = false
202
    )
203
    {
204
        // xml data fields are limited to 64 KB
205
        // FIXME: Code duplication should be removed and it should be encapsulated or made configurable.
206
        if (strlen($document->getXmlData()) >= Document::XML_DATA_SIZE_LIMIT) {
207
            throw new \EWW\Dpf\Exceptions\DocumentMaxSizeErrorException("Maximum document size exceeded.");
208
        }
209
210
        /** @var \Symfony\Component\Workflow\Workflow $workflow */
211
        $workflow = $this->objectManager->get(DocumentWorkflow::class)->getWorkflow();
212
213
        if ($workflowTransition) {
214
            if (!$workflow->can($document, $workflowTransition)) {
215
                return false;
216
            }
217
            $workflow->apply($document, $workflowTransition);
218
        }
219
220
        if ($document->isSuggestion()) {
221
222
            // if local suggestion copy
223
            $updateResult = false;
224
225
        } elseif ($document->isTemporaryCopy()) {
226
227
            // if temporary working copy
228
            $updateResult = $this->updateRemotely($document, $workflowTransition);
229
230
        } elseif (
231
            $document->isWorkingCopy() &&
232
            (
233
                $workflowTransition === DocumentWorkflow::TRANSITION_POSTPONE ||
234
                $workflowTransition === DocumentWorkflow::TRANSITION_DISCARD ||
235
                $workflowTransition === DocumentWorkflow::TRANSITION_RELEASE_ACTIVATE
236
            )
237
        ) {
238
            // if local working copy with state change
239
            $updateResult = $this->updateRemotely($document, $workflowTransition);
240
241
        } elseif ($document->isWorkingCopy()) {
242
243
            // if local working copy with no state change
244
            $this->documentRepository->update($document);
245
            $updateResult = $document->getDocumentIdentifier();
246
247
        } elseif ($workflowTransition == DocumentWorkflow::TRANSITION_RELEASE_PUBLISH) {
248
            // Fedora ingest
249
            if ($ingestedDocument = $this->getDocumentTransferManager()->ingest($document)) {
250
251
                // After ingest all related bookmarks need an update of the identifier into an fedora object identifier.
252
                if ($ingestedDocument instanceof Document) {
0 ignored issues
show
introduced by
$ingestedDocument is always a sub-type of EWW\Dpf\Domain\Model\Document.
Loading history...
253
                    /** @var Bookmark $bookmark */
254
                    foreach ($this->bookmarkRepository->findByDocumentIdentifier($ingestedDocument->getUid()) as $bookmark) {
0 ignored issues
show
Bug introduced by
The method findByDocumentIdentifier() does not exist on EWW\Dpf\Domain\Repository\BookmarkRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

254
                    foreach ($this->bookmarkRepository->/** @scrutinizer ignore-call */ findByDocumentIdentifier($ingestedDocument->getUid()) as $bookmark) {
Loading history...
255
                        $bookmark->setDocumentIdentifier($ingestedDocument->getDocumentIdentifier());
256
                        $this->bookmarkRepository->update($bookmark);
257
                    }
258
                    $this->persistenceManager->persistAll();
259
                } else {
260
                    throw new \Exception("Logical exception while updating bookmarks.");
261
                }
262
263
                // check embargo
264
                if(!$this->hasActiveEmbargo($document)){
265
                    $this->removeDocument($document);
266
                } else {
267
                    $document->setState(DocumentWorkflow::constructState(DocumentWorkflow::LOCAL_STATE_IN_PROGRESS, $document->getRemoteState()));
268
                }
269
                $updateResult = $document->getDocumentIdentifier();
270
            } else {
271
                $updateResult = false;
272
            }
273
        } else {
274
            $this->documentRepository->update($document);
275
            $updateResult = $document->getDocumentIdentifier();
276
        }
277
278
        if ($updateResult) {
279
280
            if (DocumentWorkflow::TRANSITION_RELEASE_PUBLISH) {
281
                // delete local document from index
282
                $this->signalSlotDispatcher->dispatch(
283
                    AbstractController::class, 'deleteDocumentFromIndex', [$document->getUid()]
284
                );
285
            }
286
287
            // index the document
288
            $this->signalSlotDispatcher->dispatch(
289
                AbstractController::class, 'indexDocument', [$document]
290
            );
291
292
            // Notify assigned users
293
            $recipients = $this->getUpdateNotificationRecipients($document);
294
            $this->notifier->sendMyPublicationUpdateNotification($document, $recipients);
295
296
            $recipients = $this->getNewPublicationNotificationRecipients($document);
297
            $this->notifier->sendMyPublicationNewNotification($document, $recipients);
298
299
            /** @var Notifier $notifier */
300
            $notifier = $this->objectManager->get(Notifier::class);
301
            $notifier->sendChangedDocumentNotification($document, $addedFisIdOnly);
302
        }
303
304
        return $updateResult;
305
    }
306
307
    /**
308
     * @return DocumentTransferManager
309
     */
310
    protected function getDocumentTransferManager()
311
    {
312
        /** @var DocumentTransferManager $documentTransferManager */
313
        $documentTransferManager = $this->objectManager->get(DocumentTransferManager::class);
314
315
        /** @var  FedoraRepository $remoteRepository */
316
        $remoteRepository = $this->objectManager->get(FedoraRepository::class);
317
318
        $documentTransferManager->setRemoteRepository($remoteRepository);
319
320
        return $documentTransferManager;
321
    }
322
323
    /**
324
     * Removes the document from the local database.
325
     *
326
     * @param $document
327
     * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException
328
     */
329
    protected function removeDocument($document)
330
    {
331
        $files = $this->fileRepository->findByDocument($document->getUid());
0 ignored issues
show
Bug introduced by
The method findByDocument() does not exist on EWW\Dpf\Domain\Repository\FileRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

331
        /** @scrutinizer ignore-call */ 
332
        $files = $this->fileRepository->findByDocument($document->getUid());
Loading history...
332
        foreach ($files as $file) {
333
            $this->fileRepository->remove($file);
334
        }
335
        $this->documentRepository->remove($document);
336
    }
337
338
    /**
339
     * @param Document $document
340
     * @param string $workflowTransition
341
     * @return string
342
     * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException
343
     * @throws \TYPO3\CMS\Extbase\Persistence\Exception\UnknownObjectException
344
     */
345
    protected function updateRemotely($document, $workflowTransition = null)
346
    {
347
        $lastModDate = $this->getDocumentTransferManager()->getLastModDate($document->getObjectIdentifier());
348
        $docLastModDate = $document->getRemoteLastModDate();
349
        if ($lastModDate !== $docLastModDate && !empty($docLastModDate)) {
350
            // There is a newer version in the fedora repository.
351
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
352
        }
353
354
        $this->documentRepository->update($document);
355
356
        switch ($workflowTransition) {
357
            case DocumentWorkflow::TRANSITION_POSTPONE:
358
                $transferState = DocumentTransferManager::INACTIVATE;
359
                break;
360
361
            case DocumentWorkflow::TRANSITION_DISCARD:
362
                $transferState = DocumentTransferManager::DELETE;
363
                break;
364
365
            case DocumentWorkflow::TRANSITION_RELEASE_ACTIVATE:
366
                $transferState = DocumentTransferManager::REVERT;
367
                break;
368
369
            default:
370
                $transferState = null;
371
                break;
372
        }
373
374
        if ($transferState) {
375
            if (!$this->getDocumentTransferManager()->delete($document, $transferState)) {
376
                return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
377
            }
378
        }
379
380
        if ($this->getDocumentTransferManager()->update($document)) {
381
382
            if(!$this->hasActiveEmbargo($document)){
383
                $this->removeDocument($document);
384
            } else {
385
                $document->setState(DocumentWorkflow::LOCAL_STATE_IN_PROGRESS . ':' . $document->getRemoteState());
386
            }
387
            return $document->getDocumentIdentifier();
388
        }
389
390
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
391
    }
392
393
    public function addSuggestion($editDocument, $restore = false, $comment = '') {
394
        // add new document
395
        /** @var Document $suggestionDocument */
396
        $suggestionDocument = $this->objectManager->get(Document::class);
397
        $this->documentRepository->add($suggestionDocument);
398
        $this->persistenceManager->persistAll();
399
400
        // copy properties from origin
401
        $suggestionDocument = $suggestionDocument->copy($editDocument);
402
        $suggestionDocument->setCreator($editDocument->getCreator());
403
404
        if ($suggestionDocument->isTemporary()) {
405
            $suggestionDocument->setTemporary(false);
406
        }
407
408
        if ($editDocument->getObjectIdentifier()) {
409
            $suggestionDocument->setLinkedUid($editDocument->getObjectIdentifier());
410
        } else {
411
            $suggestionDocument->setLinkedUid($editDocument->getUid());
412
        }
413
414
        $suggestionDocument->setSuggestion(true);
415
        if ($comment) {
416
            $suggestionDocument->setComment($comment);
417
        }
418
419
        if ($restore) {
420
            $suggestionDocument->setTransferStatus("RESTORE");
421
        }
422
423
        try {
424
            $this->documentRepository->add($suggestionDocument);
425
        } catch (\Throwable $t) {
426
            return null;
427
        }
428
429
        return $suggestionDocument;
430
431
    }
432
433
    /**
434
     * @param $document
435
     * @return bool (true: if no embargo is set or embargo is expired, false: embargo is active)
436
     * @throws \Exception
437
     */
438
    protected function hasActiveEmbargo($document)
439
    {
440
        $currentDate = new \DateTime('now');
441
        if($currentDate > $document->getEmbargoDate()){
442
            // embargo is expired
443
            return false;
444
        } else {
445
            return true;
446
        }
447
448
    }
449
450
451
    /**
452
     * @param Document $document
453
     * @return FrontendUser
454
     */
455
    public function getCreatorUser(Document $document) {
456
        return $this->frontendUserRepository->findByUid($document->getCreator());
457
    }
458
459
    /**
460
     * @param Document $document
461
     * @return array
462
     */
463
    public function getAssignedUsers(Document $document)
464
    {
465
        $assignedUsers = [];
466
467
        foreach ($document->getAssignedFobIdentifiers() as $fobId) {
468
            $feUsers = $this->frontendUserRepository->findByFisPersId($fobId);
0 ignored issues
show
Bug introduced by
The method findByFisPersId() does not exist on EWW\Dpf\Domain\Repository\FrontendUserRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

468
            /** @scrutinizer ignore-call */ 
469
            $feUsers = $this->frontendUserRepository->findByFisPersId($fobId);
Loading history...
469
            foreach ($feUsers as $feUser) {
470
471
                $assignedUsers[$feUser->getUid()] = $feUser;
472
            }
473
        }
474
475
        return $assignedUsers;
476
    }
477
478
    /**
479
     * @param Document $document
480
     * @return array
481
     */
482
    public function getNewlyAssignedUsers(Document $document)
483
    {
484
        $assignedUsers = [];
485
486
        foreach ($document->getNewlyAssignedFobIdentifiers() as $fobId) {
487
            $feUsers = $this->frontendUserRepository->findByFisPersId($fobId);
488
            foreach ($feUsers as $feUser) {
489
                $assignedUsers[$feUser->getUid()] = $feUser;
490
            }
491
        }
492
493
        return $assignedUsers;
494
    }
495
496
    /**
497
     * @param Document $document
498
     * @return array
499
     */
500
    public function getDocumentBookmarkUsers(Document $document) {
501
502
        $users = [];
503
504
        /** @var Bookmark $bookmark */
505
        $bookmarks = $this->bookmarkRepository->findByDocumentIdentifier($document->getDocumentIdentifier());
506
        foreach ($bookmarks as $bookmark) {
507
            $feUser = $this->frontendUserRepository->findByUid($bookmark->getFeUserUid());
508
            $users[$feUser->getUid()] = $feUser;
509
        }
510
511
        return $users;
512
    }
513
514
    /**
515
     * @param Document $document
516
     * @return array
517
     */
518
    public function getUpdateNotificationRecipients(Document $document)
519
    {
520
        $users = [];
521
522
        if ($document->getCreator()) {
523
            $users[$this->getCreatorUser($document)->getUid()] = $this->getCreatorUser($document);
524
        }
525
526
        foreach ($this->getAssignedUsers($document) as $user) {
527
            $users[$user->getUid()] = $user;
528
        }
529
530
        foreach ($this->getDocumentBookmarkUsers($document) as $user) {
531
            $users[$user->getUid()] = $user;
532
        }
533
534
        $recipients = [];
535
536
        /** @var FrontendUser $recipient */
537
        foreach ($users as $recipient) {
538
            // Fixme:  Refactoring is needed. The whole code inside this foreach is way too confusing.
539
            // Give expressions at least a name. Minize the deeply nested structure.
540
            // Maybe rethinking of the whole process of notifying could help, e.g. the recipients
541
            // could decide if a notification is wanted.
542
            if (
543
                $recipient->getUid() !== $this->security->getUser()->getUid() &&
544
                $document->getState() !== DocumentWorkflow::STATE_NEW_NONE &&
545
                !(
546
                    in_array(
547
                        $recipient->getFisPersId(), $document->getNewlyAssignedFobIdentifiers()
548
                    ) ||
549
                    $document->isStateChange() &&
550
                    $document->getState() === DocumentWorkflow::STATE_REGISTERED_NONE
551
                )
552
            ) {
553
554
                if ($recipient->isNotifyOnChanges()) {
555
556
                    if (
557
                        $recipient->isNotifyPersonalLink() ||
558
                        $recipient->isNotifyStatusChange() ||
559
                        $recipient->isNotifyFulltextPublished()
560
                    ) {
561
                        if (
562
                            $recipient->isNotifyPersonalLink() &&
563
                            in_array(
564
                                $recipient->getFisPersId(), $document->getAssignedFobIdentifiers()
565
                            ) &&
566
                            !(
567
                                $recipient->isNotifyNewPublicationMyPublication() &&
568
                                (
569
                                    in_array(
570
                                        $recipient->getFisPersId(), $document->getNewlyAssignedFobIdentifiers()
571
                                    ) ||
572
                                    $document->isStateChange() &&
573
                                    $document->getState() === DocumentWorkflow::STATE_REGISTERED_NONE
574
                                )
575
                            )
576
                        ) {
577
                            $recipients[$recipient->getUid()] = $recipient;
578
                        }
579
580
                        if ($recipient->isNotifyStatusChange() && $document->isStateChange()) {
581
                            $recipients[$recipient->getUid()] = $recipient;
582
                        }
583
584
                        if ($recipient->isNotifyFulltextPublished()) {
585
586
                            $embargoDate = $document->getEmbargoDate();
587
                            $currentDate = new \DateTime('now');
588
589
                            $fulltextPublished = false;
590
                            foreach ($document->getFile() as $file) {
591
                                if ($file->getStatus() != 'added') {
592
                                    $fulltextPublished = false;
593
                                    break;
594
                                } else {
595
                                    $fulltextPublished = true;
596
                                }
597
                            }
598
599
                            if (
600
                                $document->getState() === DocumentWorkflow::STATE_NONE_ACTIVE &&
601
                                $fulltextPublished &&
602
                                (
603
                                   empty($embargoDate) ||
604
                                   $embargoDate < $currentDate
605
                                )
606
                            ) {
607
                                $recipients[$recipient->getUid()] = $recipient;
608
                            }
609
                        }
610
611
                    } else {
612
                       $recipients[$recipient->getUid()] = $recipient;
613
                    }
614
                }
615
            }
616
        }
617
        return $recipients;
618
    }
619
620
    /**
621
     * @param Document $document
622
     * @return array
623
     */
624
    public function getNewPublicationNotificationRecipients(Document $document)
625
    {
626
        $users = [];
627
628
        /** @var FrontendUser $user */
629
        foreach ($this->getAssignedUsers($document) as $user) {
630
            if (
631
                $user->getUid() !== $this->security->getUser()->getUid() &&
632
                $document->getState() !== DocumentWorkflow::STATE_NEW_NONE &&
633
                $user->getUid() !== $document->getCreator()
634
            ) {
635
                if (
636
                    $user->isNotifyNewPublicationMyPublication() &&
637
                    (
638
                        in_array(
639
                            $user->getFisPersId(), $document->getNewlyAssignedFobIdentifiers()
640
                        ) ||
641
                        $document->isStateChange() &&
642
                        $document->getState() === DocumentWorkflow::STATE_REGISTERED_NONE
643
                    )
644
                ) {
645
                    $users[$user->getUid()] = $user;
646
                }
647
            }
648
        }
649
650
        return $users;
651
    }
652
}
653
654