Completed
Pull Request — master (#340)
by Luc
05:42
created

OfferLDProjector::applyFacilitiesUpdated()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 25
rs 8.8571
cc 3
eloc 12
nc 4
nop 1
1
<?php
2
3
namespace CultuurNet\UDB3\Offer\ReadModel\JSONLD;
4
5
use Broadway\Domain\DateTime;
6
use Broadway\Domain\DomainMessage;
7
use CultuurNet\UDB3\Category;
8
use CultuurNet\UDB3\CulturefeedSlugger;
9
use CultuurNet\UDB3\EntityNotFoundException;
10
use CultuurNet\UDB3\EntityServiceInterface;
11
use CultuurNet\UDB3\Event\ReadModel\DocumentRepositoryInterface;
12
use CultuurNet\UDB3\Event\ReadModel\JSONLD\OrganizerServiceInterface;
13
use CultuurNet\UDB3\EventHandling\DelegateEventHandlingToSpecificMethodTrait;
14
use CultuurNet\UDB3\EventListener\EventSpecification;
15
use CultuurNet\UDB3\Facility;
16
use CultuurNet\UDB3\Iri\IriGeneratorInterface;
17
use CultuurNet\UDB3\Label;
18
use CultuurNet\UDB3\Media\Image;
19
use CultuurNet\UDB3\Offer\AvailableTo;
20
use CultuurNet\UDB3\Offer\Events\AbstractBookingInfoUpdated;
21
use CultuurNet\UDB3\Offer\Events\AbstractCalendarUpdated;
22
use CultuurNet\UDB3\Offer\Events\AbstractContactPointUpdated;
23
use CultuurNet\UDB3\Offer\Events\AbstractDescriptionTranslated;
24
use CultuurNet\UDB3\Offer\Events\AbstractDescriptionUpdated;
25
use CultuurNet\UDB3\Offer\Events\AbstractEvent;
26
use CultuurNet\UDB3\Offer\Events\AbstractFacilitiesUpdated;
27
use CultuurNet\UDB3\Offer\Events\AbstractGeoCoordinatesUpdated;
28
use CultuurNet\UDB3\Offer\Events\AbstractLabelAdded;
29
use CultuurNet\UDB3\Offer\Events\AbstractLabelRemoved;
30
use CultuurNet\UDB3\Offer\Events\AbstractOrganizerDeleted;
31
use CultuurNet\UDB3\Offer\Events\AbstractOrganizerUpdated;
32
use CultuurNet\UDB3\Offer\Events\AbstractPriceInfoUpdated;
33
use CultuurNet\UDB3\Offer\Events\AbstractThemeUpdated;
34
use CultuurNet\UDB3\Offer\Events\AbstractTitleTranslated;
35
use CultuurNet\UDB3\Offer\Events\AbstractTitleUpdated;
36
use CultuurNet\UDB3\Offer\Events\AbstractTypeUpdated;
37
use CultuurNet\UDB3\Offer\Events\AbstractTypicalAgeRangeDeleted;
38
use CultuurNet\UDB3\Offer\Events\AbstractTypicalAgeRangeUpdated;
39
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageAdded;
40
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageRemoved;
41
use CultuurNet\UDB3\Offer\Events\Image\AbstractImagesEvent;
42
use CultuurNet\UDB3\Offer\Events\Image\AbstractImagesImportedFromUDB2;
43
use CultuurNet\UDB3\Offer\Events\Image\AbstractImagesUpdatedFromUDB2;
44
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageUpdated;
45
use CultuurNet\UDB3\Offer\Events\Image\AbstractMainImageSelected;
46
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractApproved;
47
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractFlaggedAsDuplicate;
48
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractFlaggedAsInappropriate;
49
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractPublished;
50
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractRejected;
51
use CultuurNet\UDB3\Offer\WorkflowStatus;
52
use CultuurNet\UDB3\ReadModel\JsonDocument;
53
use CultuurNet\UDB3\ReadModel\JsonDocumentMetaDataEnricherInterface;
54
use CultuurNet\UDB3\ReadModel\MultilingualJsonLDProjectorTrait;
55
use CultuurNet\UDB3\RecordedOn;
56
use CultuurNet\UDB3\SluggerInterface;
57
use Symfony\Component\Serializer\SerializerInterface;
58
use ValueObjects\Identity\UUID;
59
60
abstract class OfferLDProjector implements OrganizerServiceInterface
61
{
62
    use MultilingualJsonLDProjectorTrait;
63
    use DelegateEventHandlingToSpecificMethodTrait {
64
        DelegateEventHandlingToSpecificMethodTrait::handle as handleUnknownEvents;
65
    }
66
67
    /**
68
     * @var DocumentRepositoryInterface
69
     */
70
    protected $repository;
71
72
    /**
73
     * @var IriGeneratorInterface
74
     */
75
    protected $iriGenerator;
76
77
    /**
78
     * @var EntityServiceInterface
79
     */
80
    protected $organizerService;
81
82
    /**
83
     * @var JsonDocumentMetaDataEnricherInterface
84
     */
85
    protected $jsonDocumentMetaDataEnricher;
86
87
    /**
88
     * @var SerializerInterface
89
     */
90
    protected $mediaObjectSerializer;
91
92
    /**
93
     * @var EventSpecification
94
     */
95
    protected $eventsNotTriggeringUpdateModified;
96
97
    /**
98
     * @var SluggerInterface
99
     */
100
    protected $slugger;
101
102
    /**
103
     * @param DocumentRepositoryInterface $repository
104
     * @param IriGeneratorInterface $iriGenerator
105
     * @param EntityServiceInterface $organizerService
106
     * @param SerializerInterface $mediaObjectSerializer
107
     * @param JsonDocumentMetaDataEnricherInterface $jsonDocumentMetaDataEnricher
108
     * @param EventSpecification $eventsNotTriggeringUpdateModified
109
     */
110
    public function __construct(
111
        DocumentRepositoryInterface $repository,
112
        IriGeneratorInterface $iriGenerator,
113
        EntityServiceInterface $organizerService,
114
        SerializerInterface $mediaObjectSerializer,
115
        JsonDocumentMetaDataEnricherInterface $jsonDocumentMetaDataEnricher,
116
        EventSpecification $eventsNotTriggeringUpdateModified
117
    ) {
118
        $this->repository = $repository;
119
        $this->iriGenerator = $iriGenerator;
120
        $this->organizerService = $organizerService;
121
        $this->jsonDocumentMetaDataEnricher = $jsonDocumentMetaDataEnricher;
122
        $this->mediaObjectSerializer = $mediaObjectSerializer;
123
        $this->eventsNotTriggeringUpdateModified = $eventsNotTriggeringUpdateModified;
124
125
        $this->slugger = new CulturefeedSlugger();
126
    }
127
128
    /**
129
     * {@inheritdoc}
130
     */
131
    public function handle(DomainMessage $domainMessage)
132
    {
133
        $event = $domainMessage->getPayload();
134
135
        $eventName = get_class($event);
136
        $eventHandlers = $this->getEventHandlers();
137
138
        if (isset($eventHandlers[$eventName])) {
139
            $handler = $eventHandlers[$eventName];
140
            $jsonDocuments = call_user_func(array($this, $handler), $event, $domainMessage);
141
        } elseif ($methodName = $this->getHandleMethodName($event)) {
142
            $jsonDocuments = $this->{$methodName}($event, $domainMessage);
143
        } else {
144
            return;
145
        }
146
147
        if (!$jsonDocuments) {
148
            return;
149
        }
150
151
        if (!is_array($jsonDocuments)) {
152
            $jsonDocuments = [$jsonDocuments];
153
        }
154
155
        foreach ($jsonDocuments as $jsonDocument) {
156
            $jsonDocument = $this->jsonDocumentMetaDataEnricher->enrich($jsonDocument, $domainMessage->getMetadata());
157
158
            if (!$this->eventsNotTriggeringUpdateModified->matches($event)) {
159
                $jsonDocument = $this->updateModified($jsonDocument, $domainMessage);
160
            }
161
162
            $this->repository->save($jsonDocument);
163
        }
164
    }
165
166
    /**
167
     * @return string[]
168
     *   An associative array of commands and their handler methods.
169
     */
170 View Code Duplication
    private function getEventHandlers()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
171
    {
172
        $events = [];
173
174
        foreach (get_class_methods($this) as $method) {
175
            $matches = [];
176
177
            if (preg_match('/^apply(.+)$/', $method, $matches)) {
178
                $event = $matches[1];
179
                $classNameMethod = 'get' . $event . 'ClassName';
180
181
                if (method_exists($this, $classNameMethod)) {
182
                    $eventFullClassName = call_user_func(array($this, $classNameMethod));
183
                    $events[$eventFullClassName] = $method;
184
                }
185
            }
186
        }
187
188
        return $events;
189
    }
190
191
    /**
192
     * @return string
193
     */
194
    abstract protected function getLabelAddedClassName();
195
196
    /**
197
     * @return string
198
     */
199
    abstract protected function getLabelRemovedClassName();
200
201
    /**
202
     * @return string
203
     */
204
    abstract protected function getImageAddedClassName();
205
206
    /**
207
     * @return string
208
     */
209
    abstract protected function getImageRemovedClassName();
210
211
    /**
212
     * @return string
213
     */
214
    abstract protected function getImageUpdatedClassName();
215
216
    /**
217
     * @return string
218
     */
219
    abstract protected function getMainImageSelectedClassName();
220
221
    /**
222
     * @return string
223
     */
224
    abstract protected function getTitleTranslatedClassName();
225
226
    /**
227
     * @return string
228
     */
229
    abstract protected function getTitleUpdatedClassName();
230
231
    /**
232
     * @return string
233
     */
234
    abstract protected function getDescriptionTranslatedClassName();
235
236
    /**
237
     * @return string
238
     */
239
    abstract protected function getOrganizerUpdatedClassName();
240
241
    /**
242
     * @return string
243
     */
244
    abstract protected function getOrganizerDeletedClassName();
245
246
    /**
247
     * @return string
248
     */
249
    abstract protected function getBookingInfoUpdatedClassName();
250
251
    /**
252
     * @return string
253
     */
254
    abstract protected function getPriceInfoUpdatedClassName();
255
256
    /**
257
     * @return string
258
     */
259
    abstract protected function getContactPointUpdatedClassName();
260
261
    /**
262
     * @return string
263
     */
264
    abstract protected function getGeoCoordinatesUpdatedClassName();
265
266
    /**
267
     * @return string
268
     */
269
    abstract protected function getDescriptionUpdatedClassName();
270
271
    /**
272
     * @return string
273
     */
274
    abstract protected function getCalendarUpdatedClassName();
275
276
    /**
277
     * @return string
278
     */
279
    abstract protected function getTypicalAgeRangeUpdatedClassName();
280
281
    /**
282
     * @return string
283
     */
284
    abstract protected function getTypicalAgeRangeDeletedClassName();
285
286
    /**
287
     * @return string
288
     */
289
    abstract protected function getPublishedClassName();
290
291
    /**
292
     * @return string
293
     */
294
    abstract protected function getApprovedClassName();
295
296
    /**
297
     * @return string
298
     */
299
    abstract protected function getRejectedClassName();
300
301
    /**
302
     * @return string
303
     */
304
    abstract protected function getFlaggedAsDuplicateClassName();
305
306
    /**
307
     * @return string
308
     */
309
    abstract protected function getFlaggedAsInappropriateClassName();
310
311
    /**
312
     * @return string
313
     */
314
    abstract protected function getImagesImportedFromUdb2ClassName();
315
316
    /**
317
     * @return string
318
     */
319
    abstract protected function getImagesUpdatedFromUdb2ClassName();
320
321
    /**
322
     * @return string
323
     */
324
    abstract protected function getTypeUpdatedClassName();
325
326
    /**
327
     * @return string
328
     */
329
    abstract protected function getThemeUpdatedClassName();
330
331
    /**
332
     * @return string
333
     */
334
    abstract protected function getFacilitiesUpdatedClassName();
335
336
    /**
337
     * @param AbstractTypeUpdated $typeUpdated
338
     * @return JsonDocument
339
     */
340
    protected function applyTypeUpdated(AbstractTypeUpdated $typeUpdated)
341
    {
342
        $document = $this->loadDocumentFromRepository($typeUpdated);
343
344
        return $this->updateTerm($document, $typeUpdated->getType());
345
    }
346
347
    /**
348
     * @param AbstractThemeUpdated $themeUpdated
349
     * @return JsonDocument
350
     */
351
    protected function applyThemeUpdated(AbstractThemeUpdated $themeUpdated)
352
    {
353
        $document = $this->loadDocumentFromRepository($themeUpdated);
354
355
        return $this->updateTerm($document, $themeUpdated->getTheme());
356
    }
357
358
    /**
359
     * @param JsonDocument $document
360
     * @param Category $category
361
     *
362
     * @return JsonDocument
363
     */
364
    private function updateTerm(JsonDocument $document, Category $category)
365
    {
366
        $offerLD = $document->getBody();
367
368
        $oldTerms = property_exists($offerLD, 'terms') ? $offerLD->terms : [];
369
        $newTerm = (object) $category->serialize();
370
371
        $newTerms = array_filter(
372
            $oldTerms,
373
            function ($term) use ($category) {
374
                return !property_exists($term, 'domain') || $term->domain !== $category->getDomain();
375
            }
376
        );
377
378
        array_push($newTerms, $newTerm);
379
380
        $offerLD->terms = array_values($newTerms);
381
382
        return $document->withBody($offerLD);
383
    }
384
385
    /**
386
     * @param AbstractFacilitiesUpdated $facilitiesUpdated
387
     * @return JsonDocument
388
     */
389
    protected function applyFacilitiesUpdated(AbstractFacilitiesUpdated $facilitiesUpdated)
390
    {
391
        $document = $this->loadDocumentFromRepository($facilitiesUpdated);
392
393
        $offerLd = $document->getBody();
394
395
        $terms = isset($offerLd->terms) ? $offerLd->terms : array();
396
397
        // Remove all old facilities + get numeric keys.
398
        $terms = array_values(array_filter(
399
            $terms,
400
            function ($term) {
401
                return $term->domain !== Facility::DOMAIN;
402
            }
403
        ));
404
405
        // Add the new facilities.
406
        foreach ($facilitiesUpdated->getFacilities() as $facility) {
407
            $terms[] = $facility->toJsonLd();
408
        }
409
410
        $offerLd->terms = $terms;
411
412
        return $document->withBody($offerLd);
413
    }
414
415
    /**
416
     * @param AbstractLabelAdded $labelAdded
417
     * @return JsonDocument
418
     */
419
    protected function applyLabelAdded(AbstractLabelAdded $labelAdded)
420
    {
421
        $document = $this->loadDocumentFromRepository($labelAdded);
422
423
        $offerLd = $document->getBody();
424
425
        // Check the visibility of the label to update the right property.
426
        $labelsProperty = $labelAdded->getLabel()->isVisible() ? 'labels' : 'hiddenLabels';
427
428
        $labels = isset($offerLd->{$labelsProperty}) ? $offerLd->{$labelsProperty} : [];
429
        $label = (string) $labelAdded->getLabel();
430
431
        $labels[] = $label;
432
        $offerLd->{$labelsProperty} = array_unique($labels);
433
434
        return $document->withBody($offerLd);
435
    }
436
437
    /**
438
     * @param AbstractLabelRemoved $labelRemoved
439
     * @return JsonDocument
440
     */
441 View Code Duplication
    protected function applyLabelRemoved(AbstractLabelRemoved $labelRemoved)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
442
    {
443
        $document = $this->loadDocumentFromRepository($labelRemoved);
444
445
        $offerLd = $document->getBody();
446
447
        // Don't presume that the label visibility is correct when removing.
448
        // So iterate over both the visible and invisible labels.
449
        $labelsProperties = ['labels', 'hiddenLabels'];
450
451
        foreach ($labelsProperties as $labelsProperty) {
452
            if (isset($offerLd->{$labelsProperty}) && is_array($offerLd->{$labelsProperty})) {
453
                $offerLd->{$labelsProperty} = array_filter(
454
                    $offerLd->{$labelsProperty},
455
                    function ($label) use ($labelRemoved) {
456
                        return !$labelRemoved->getLabel()->equals(
457
                            new Label($label)
458
                        );
459
                    }
460
                );
461
                // Ensure array keys start with 0 so json_encode() does encode it
462
                // as an array and not as an object.
463
                if (count($offerLd->{$labelsProperty}) > 0) {
464
                    $offerLd->{$labelsProperty} = array_values($offerLd->{$labelsProperty});
465
                } else {
466
                    unset($offerLd->{$labelsProperty});
467
                }
468
            }
469
        }
470
471
        return $document->withBody($offerLd);
472
    }
473
474
    /**
475
     * Apply the imageAdded event to the item repository.
476
     *
477
     * @param AbstractImageAdded $imageAdded
478
     * @return JsonDocument
479
     */
480
    protected function applyImageAdded(AbstractImageAdded $imageAdded)
481
    {
482
        $document = $this->loadDocumentFromRepository($imageAdded);
483
484
        $offerLd = $document->getBody();
485
        $offerLd->mediaObject = isset($offerLd->mediaObject) ? $offerLd->mediaObject : [];
486
487
        $imageData = $this->mediaObjectSerializer
488
            ->serialize($imageAdded->getImage(), 'json-ld');
489
        $offerLd->mediaObject[] = $imageData;
490
491
        if (count($offerLd->mediaObject) === 1) {
492
            $offerLd->image = $imageData['contentUrl'];
493
        }
494
495
        return $document->withBody($offerLd);
496
    }
497
498
    /**
499
     * Apply the ImageUpdated event to the item repository.
500
     *
501
     * @param AbstractImageUpdated $imageUpdated
502
     * @return JsonDocument
503
     * @throws \Exception
504
     */
505
    protected function applyImageUpdated(AbstractImageUpdated $imageUpdated)
506
    {
507
        $document = $this->loadDocumentFromRepository($imageUpdated);
508
509
        $offerLd = $document->getBody();
510
511
        if (!isset($offerLd->mediaObject)) {
512
            throw new \Exception('The image to update could not be found.');
513
        }
514
515
        $updatedMediaObjects = [];
516
517
        foreach ($offerLd->mediaObject as $mediaObject) {
518
            $mediaObjectMatches = (
519
                strpos(
520
                    $mediaObject->{'@id'},
521
                    (string)$imageUpdated->getMediaObjectId()
522
                ) > 0
523
            );
524
525
            if ($mediaObjectMatches) {
526
                $mediaObject->description = (string)$imageUpdated->getDescription();
527
                $mediaObject->copyrightHolder = (string)$imageUpdated->getCopyrightHolder();
528
529
                $updatedMediaObjects[] = $mediaObject;
530
            }
531
        };
532
533
        if (empty($updatedMediaObjects)) {
534
            throw new \Exception('The image to update could not be found.');
535
        }
536
537
        return $document->withBody($offerLd);
538
    }
539
540
    /**
541
     * @param AbstractImageRemoved $imageRemoved
542
     * @return JsonDocument
543
     */
544
    protected function applyImageRemoved(AbstractImageRemoved $imageRemoved)
545
    {
546
        $document = $this->loadDocumentFromRepository($imageRemoved);
547
548
        $offerLd = $document->getBody();
549
550
        // Nothing to remove if there are no media objects!
551
        if (!isset($offerLd->mediaObject)) {
552
            return;
553
        }
554
555
        $imageId = (string) $imageRemoved->getImage()->getMediaObjectId();
556
557
        /**
558
         * Matches any object that is not the removed image.
559
         *
560
         * @param Object $mediaObject
561
         *  An existing projection of a media object.
562
         *
563
         * @return bool
564
         *  Returns true when the media object does not match the image to remove.
565
         */
566
        $shouldNotBeRemoved = function ($mediaObject) use ($imageId) {
567
            $containsId = !!strpos($mediaObject->{'@id'}, $imageId);
568
            return !$containsId;
569
        };
570
571
        // Remove any media objects that match the image.
572
        $filteredMediaObjects = array_filter(
573
            $offerLd->mediaObject,
574
            $shouldNotBeRemoved
575
        );
576
577
        // Unset the main image if it matches the removed image
578
        if (isset($offerLd->image) && strpos($offerLd->{'image'}, $imageId)) {
579
            unset($offerLd->{"image"});
580
        }
581
582
        if (!isset($offerLd->image) && count($filteredMediaObjects) > 0) {
583
            $offerLd->image = array_values($filteredMediaObjects)[0]->contentUrl;
584
        }
585
586
        // If no media objects are left remove the attribute.
587
        if (empty($filteredMediaObjects)) {
588
            unset($offerLd->{"mediaObject"});
589
        } else {
590
            $offerLd->mediaObject = array_values($filteredMediaObjects);
591
        }
592
593
        return $document->withBody($offerLd);
594
    }
595
596
    /**
597
     * @param AbstractMainImageSelected $mainImageSelected
598
     * @return JsonDocument
599
     */
600
    protected function applyMainImageSelected(AbstractMainImageSelected $mainImageSelected)
601
    {
602
        $document = $this->loadDocumentFromRepository($mainImageSelected);
603
        $offerLd = $document->getBody();
604
        $imageId = $mainImageSelected->getImage()->getMediaObjectId();
605
        $mediaObjectMatcher = function ($matchingMediaObject, $currentMediaObject) use ($imageId) {
606
            if (!$matchingMediaObject && $this->mediaObjectMatchesId($currentMediaObject, $imageId)) {
607
                $matchingMediaObject = $currentMediaObject;
608
            }
609
610
            return $matchingMediaObject;
611
        };
612
        $mediaObject = array_reduce(
613
            $offerLd->mediaObject,
614
            $mediaObjectMatcher
615
        );
616
617
        $offerLd->image = $mediaObject->contentUrl;
618
619
        return $document->withBody($offerLd);
620
    }
621
622
    /**
623
     * @param Object $mediaObject
624
     * @param UUID $mediaObjectId
625
     *
626
     * @return bool
627
     */
628
    protected function mediaObjectMatchesId($mediaObject, UUID $mediaObjectId)
629
    {
630
        return strpos($mediaObject->{'@id'}, (string) $mediaObjectId) > 0;
631
    }
632
633
    /**
634
     * @param AbstractTitleTranslated $titleTranslated
635
     * @return JsonDocument
636
     */
637
    protected function applyTitleTranslated(AbstractTitleTranslated $titleTranslated)
638
    {
639
        $document = $this->loadDocumentFromRepository($titleTranslated);
640
641
        $offerLd = $document->getBody();
642
        $offerLd->name->{$titleTranslated->getLanguage()->getCode(
643
        )} = $titleTranslated->getTitle()->toNative();
644
645
        return $document->withBody($offerLd);
646
    }
647
648
    /**
649
     * @param AbstractTitleUpdated $titleUpdated
650
     * @return JsonDocument
651
     */
652
    protected function applyTitleUpdated(AbstractTitleUpdated $titleUpdated)
653
    {
654
        $document = $this->loadDocumentFromRepository($titleUpdated);
655
        $offerLd = $document->getBody();
656
        $mainLanguage = isset($offerLd->mainLanguage) ? $offerLd->mainLanguage : 'nl';
657
658
        $offerLd->name->{$mainLanguage} = $titleUpdated->getTitle()->toNative();
659
660
        return $document->withBody($offerLd);
661
    }
662
663
    /**
664
     * @param AbstractDescriptionTranslated $descriptionTranslated
665
     * @return JsonDocument
666
     */
667
    protected function applyDescriptionTranslated(
668
        AbstractDescriptionTranslated $descriptionTranslated
669
    ) {
670
        $document = $this->loadDocumentFromRepository($descriptionTranslated);
671
672
        $offerLd = $document->getBody();
673
        $languageCode = $descriptionTranslated->getLanguage()->getCode();
674
        $description = $descriptionTranslated->getDescription()->toNative();
0 ignored issues
show
Bug introduced by
The method toNative cannot be called on $descriptionTranslated->getDescription() (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
675
        if (empty($offerLd->description)) {
676
            $offerLd->description = new \stdClass();
677
        }
678
        $offerLd->description->{$languageCode} = $description;
679
680
        return $document->withBody($offerLd);
681
    }
682
683
    /**
684
     * @param AbstractCalendarUpdated $calendarUpdated
685
     *
686
     * @return JsonDocument
687
     */
688
    protected function applyCalendarUpdated(AbstractCalendarUpdated $calendarUpdated)
689
    {
690
        $document = $this->loadDocumentFromRepository($calendarUpdated)
691
            ->apply(OfferUpdate::calendar($calendarUpdated->getCalendar()));
692
693
        $offerLd = $document->getBody();
694
695
        $availableTo = AvailableTo::createFromCalendar($calendarUpdated->getCalendar());
696
        $offerLd->availableTo = (string)$availableTo;
697
698
        return $document->withBody($offerLd);
699
    }
700
701
    /**
702
     * Apply the organizer updated event to the offer repository.
703
     * @param AbstractOrganizerUpdated $organizerUpdated
704
     * @return JsonDocument
705
     */
706
    protected function applyOrganizerUpdated(AbstractOrganizerUpdated $organizerUpdated)
707
    {
708
        $document = $this->loadDocumentFromRepository($organizerUpdated);
709
710
        $offerLd = $document->getBody();
711
712
        $offerLd->organizer = array(
713
                '@type' => 'Organizer',
714
            ) + (array)$this->organizerJSONLD($organizerUpdated->getOrganizerId());
715
716
        return $document->withBody($offerLd);
717
    }
718
719
    /**
720
     * Apply the organizer delete event to the offer repository.
721
     * @param AbstractOrganizerDeleted $organizerDeleted
722
     * @return JsonDocument
723
     */
724
    protected function applyOrganizerDeleted(AbstractOrganizerDeleted $organizerDeleted)
725
    {
726
        $document = $this->loadDocumentFromRepository($organizerDeleted);
727
728
        $offerLd = $document->getBody();
729
730
        unset($offerLd->organizer);
731
732
        return $document->withBody($offerLd);
733
    }
734
735
    /**
736
     * Apply the booking info updated event to the offer repository.
737
     * @param AbstractBookingInfoUpdated $bookingInfoUpdated
738
     * @return JsonDocument
739
     */
740
    protected function applyBookingInfoUpdated(AbstractBookingInfoUpdated $bookingInfoUpdated)
741
    {
742
        $document = $this->loadDocumentFromRepository($bookingInfoUpdated);
743
744
        $offerLd = $document->getBody();
745
        $offerLd->bookingInfo = $bookingInfoUpdated->getBookingInfo()->toJsonLd();
746
747
        return $document->withBody($offerLd);
748
    }
749
750
    /**
751
     * @param AbstractPriceInfoUpdated $priceInfoUpdated
752
     * @return JsonDocument
753
     */
754
    protected function applyPriceInfoUpdated(AbstractPriceInfoUpdated $priceInfoUpdated)
755
    {
756
        $document = $this->loadDocumentFromRepository($priceInfoUpdated);
757
758
        $offerLd = $document->getBody();
759
        $offerLd->priceInfo = [];
760
761
        $basePrice = $priceInfoUpdated->getPriceInfo()->getBasePrice();
762
763
        $offerLd->priceInfo[] = [
764
            'category' => 'base',
765
            'name' => 'Basistarief',
766
            'price' => $basePrice->getPrice()->toFloat(),
767
            'priceCurrency' => $basePrice->getCurrency()->getCode()->toNative(),
768
        ];
769
770
        foreach ($priceInfoUpdated->getPriceInfo()->getTariffs() as $tariff) {
771
            $offerLd->priceInfo[] = [
772
                'category' => 'tariff',
773
                'name' => $tariff->getName()->toNative(),
774
                'price' => $tariff->getPrice()->toFloat(),
775
                'priceCurrency' => $tariff->getCurrency()->getCode()->toNative(),
776
            ];
777
        }
778
779
        return $document->withBody($offerLd);
780
    }
781
782
    /**
783
     * Apply the contact point updated event to the offer repository.
784
     * @param AbstractContactPointUpdated $contactPointUpdated
785
     * @return JsonDocument
786
     */
787
    protected function applyContactPointUpdated(AbstractContactPointUpdated $contactPointUpdated)
788
    {
789
        $document = $this->loadDocumentFromRepository($contactPointUpdated);
790
791
        $offerLd = $document->getBody();
792
        $offerLd->contactPoint = $contactPointUpdated->getContactPoint()->toJsonLd();
793
794
        return $document->withBody($offerLd);
795
    }
796
797
    /**
798
     * @param AbstractGeoCoordinatesUpdated $geoCoordinatesUpdated
799
     * @return JsonDocument
800
     */
801
    protected function applyGeoCoordinatesUpdated(AbstractGeoCoordinatesUpdated $geoCoordinatesUpdated)
802
    {
803
        $document = $this->loadDocumentFromRepositoryByItemId($geoCoordinatesUpdated->getItemId());
804
805
        $placeLd = $document->getBody();
806
807
        $placeLd->geo = (object) [
808
            'latitude' => $geoCoordinatesUpdated->getCoordinates()->getLatitude()->toDouble(),
809
            'longitude' => $geoCoordinatesUpdated->getCoordinates()->getLongitude()->toDouble(),
810
        ];
811
812
        return $document->withBody($placeLd);
813
    }
814
815
    /**
816
     * Apply the description updated event to the offer repository.
817
     * @param AbstractDescriptionUpdated $descriptionUpdated
818
     * @return JsonDocument
819
     */
820
    protected function applyDescriptionUpdated(
821
        AbstractDescriptionUpdated $descriptionUpdated
822
    ) {
823
        $document = $this->loadDocumentFromRepository($descriptionUpdated);
824
825
        $offerLd = $document->getBody();
826
        if (empty($offerLd->description)) {
827
            $offerLd->description = new \stdClass();
828
        }
829
        $offerLd->description->{'nl'} = $descriptionUpdated->getDescription();
830
831
        return $document->withBody($offerLd);
832
    }
833
834
    /**
835
     * Apply the typical age range updated event to the offer repository.
836
     * @param AbstractTypicalAgeRangeUpdated $typicalAgeRangeUpdated
837
     * @return JsonDocument
838
     */
839
    protected function applyTypicalAgeRangeUpdated(
840
        AbstractTypicalAgeRangeUpdated $typicalAgeRangeUpdated
841
    ) {
842
        $document = $this->loadDocumentFromRepository($typicalAgeRangeUpdated);
843
844
        $offerLd = $document->getBody();
845
        $offerLd->typicalAgeRange = (string) $typicalAgeRangeUpdated->getTypicalAgeRange();
846
847
        return $document->withBody($offerLd);
848
    }
849
850
    /**
851
     * Apply the typical age range deleted event to the offer repository.
852
     * @param AbstractTypicalAgeRangeDeleted $typicalAgeRangeDeleted
853
     * @return JsonDocument
854
     */
855
    protected function applyTypicalAgeRangeDeleted(
856
        AbstractTypicalAgeRangeDeleted $typicalAgeRangeDeleted
857
    ) {
858
        $document = $this->loadDocumentFromRepository($typicalAgeRangeDeleted);
859
860
        $offerLd = $document->getBody();
861
862
        unset($offerLd->typicalAgeRange);
863
864
        return $document->withBody($offerLd);
865
    }
866
867
    /**
868
     * @param AbstractPublished $published
869
     * @return JsonDocument
870
     */
871
    protected function applyPublished(AbstractPublished $published)
872
    {
873
        $document = $this->loadDocumentFromRepository($published);
874
875
        $offerLd = $document->getBody();
876
877
        $offerLd->workflowStatus = WorkflowStatus::READY_FOR_VALIDATION()->getName();
878
879
        $publicationDate = $published->getPublicationDate();
880
        $offerLd->availableFrom = $publicationDate->format(\DateTime::ATOM);
881
882
        return $document->withBody($offerLd);
883
    }
884
885
    /**
886
     * @param AbstractApproved $approved
887
     * @return JsonDocument
888
     */
889
    protected function applyApproved(AbstractApproved $approved)
890
    {
891
        $document = $this->loadDocumentFromRepository($approved);
892
        $offerLd = $document->getBody();
893
        $offerLd->workflowStatus = WorkflowStatus::APPROVED()->getName();
894
        return $document->withBody($offerLd);
895
    }
896
897
    /**
898
     * @param AbstractRejected $rejected
899
     * @return JsonDocument
900
     */
901 View Code Duplication
    protected function applyRejected(AbstractRejected $rejected)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
902
    {
903
        $document = $this->loadDocumentFromRepository($rejected);
904
        $offerLd = $document->getBody();
905
        $offerLd->workflowStatus = WorkflowStatus::REJECTED()->getName();
906
        return $document->withBody($offerLd);
907
    }
908
909
    /**
910
     * @param AbstractFlaggedAsDuplicate $flaggedAsDuplicate
911
     * @return JsonDocument
912
     */
913 View Code Duplication
    protected function applyFlaggedAsDuplicate(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
914
        AbstractFlaggedAsDuplicate $flaggedAsDuplicate
915
    ) {
916
        $document = $this->loadDocumentFromRepository($flaggedAsDuplicate);
917
        $offerLd = $document->getBody();
918
        $offerLd->workflowStatus = WorkflowStatus::REJECTED()->getName();
919
        return $document->withBody($offerLd);
920
    }
921
922
    /**
923
     * @param AbstractFlaggedAsInappropriate $flaggedAsInappropriate
924
     * @return JsonDocument
925
     */
926 View Code Duplication
    protected function applyFlaggedAsInappropriate(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
927
        AbstractFlaggedAsInappropriate $flaggedAsInappropriate
928
    ) {
929
        $document = $this->loadDocumentFromRepository($flaggedAsInappropriate);
930
        $offerLd = $document->getBody();
931
        $offerLd->workflowStatus = WorkflowStatus::REJECTED()->getName();
932
        return $document->withBody($offerLd);
933
    }
934
935
    /**
936
     * @param AbstractImagesImportedFromUDB2 $imagesImportedFromUDB2
937
     * @return JsonDocument
938
     */
939 View Code Duplication
    protected function applyImagesImportedFromUdb2(AbstractImagesImportedFromUDB2 $imagesImportedFromUDB2)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
940
    {
941
        $document = $this->loadDocumentFromRepository($imagesImportedFromUDB2);
942
        $offerLd = $document->getBody();
943
        $this->applyUdb2ImagesEvent($offerLd, $imagesImportedFromUDB2);
944
        return $document->withBody($offerLd);
945
    }
946
947
    /**
948
     * @param AbstractImagesUpdatedFromUDB2 $imagesUpdatedFromUDB2
949
     * @return JsonDocument
950
     */
951 View Code Duplication
    protected function applyImagesUpdatedFromUdb2(AbstractImagesUpdatedFromUDB2 $imagesUpdatedFromUDB2)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
952
    {
953
        $document = $this->loadDocumentFromRepository($imagesUpdatedFromUDB2);
954
        $offerLd = $document->getBody();
955
        $this->applyUdb2ImagesEvent($offerLd, $imagesUpdatedFromUDB2);
956
        return $document->withBody($offerLd);
957
    }
958
959
    /**
960
     * This indirect apply method can be called internally to deal with images coming from UDB2.
961
     * Imports from UDB2 only contain the native Dutch content.
962
     * @see https://github.com/cultuurnet/udb3-udb2-bridge/blob/db0a7ab2444f55bb3faae3d59b82b39aaeba253b/test/Media/ImageCollectionFactoryTest.php#L79-L103
963
     * Because of this we have to make sure translated images are left in place.
964
     *
965
     * @param \stdClass $offerLd
966
     * @param AbstractImagesEvent $imagesEvent
967
     */
968
    private function applyUdb2ImagesEvent(\stdClass $offerLd, AbstractImagesEvent $imagesEvent)
969
    {
970
        $images = $imagesEvent->getImages();
971
        $currentMediaObjects = isset($offerLd->mediaObject) ? $offerLd->mediaObject : [];
972
        $dutchMediaObjects = array_map(
973
            function (Image $image) {
974
                return $this->mediaObjectSerializer->serialize($image, 'json-ld');
975
            },
976
            $images->toArray()
977
        );
978
        $translatedMediaObjects = array_filter(
979
            $currentMediaObjects,
980
            function ($image) {
981
                return $image->inLanguage !== 'nl';
982
            }
983
        );
984
        $mainImage = $images->getMain();
985
986
        unset($offerLd->mediaObject, $offerLd->image);
987
988
        if (!empty($dutchMediaObjects) || !empty($translatedMediaObjects)) {
989
            $offerLd->mediaObject = array_merge($dutchMediaObjects, $translatedMediaObjects);
990
        }
991
992
        if (isset($mainImage)) {
993
            $offerLd->image = (string) $mainImage->getSourceLocation();
994
        }
995
    }
996
997
    /**
998
     * @param string $id
999
     * @return JsonDocument
1000
     */
1001 View Code Duplication
    protected function newDocument($id)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1002
    {
1003
        $document = new JsonDocument($id);
1004
1005
        $offerLd = $document->getBody();
1006
        $offerLd->{'@id'} = $this->iriGenerator->iri($id);
1007
1008
        return $document->withBody($offerLd);
1009
    }
1010
1011
    /**
1012
     * @param AbstractEvent $event
1013
     * @return JsonDocument
1014
     */
1015
    protected function loadDocumentFromRepository(AbstractEvent $event)
1016
    {
1017
        return $this->loadDocumentFromRepositoryByItemId($event->getItemId());
1018
    }
1019
1020
    /**
1021
     * @param string $itemId
1022
     * @return JsonDocument
1023
     */
1024
    protected function loadDocumentFromRepositoryByItemId($itemId)
1025
    {
1026
        $document = $this->repository->get($itemId);
1027
1028
        if (!$document) {
1029
            return $this->newDocument($itemId);
1030
        }
1031
1032
        return $document;
1033
    }
1034
1035
    /**
1036
     * @inheritdoc
1037
     */
1038
    public function organizerJSONLD($organizerId)
1039
    {
1040
        try {
1041
            $organizerJSONLD = $this->organizerService->getEntity(
1042
                $organizerId
1043
            );
1044
1045
            return json_decode($organizerJSONLD);
1046
        } catch (EntityNotFoundException $e) {
1047
            // In case the place can not be found at the moment, just add its ID
1048
            return array(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array('@id' => $t...ce->iri($organizerId)); (array) is incompatible with the return type declared by the interface CultuurNet\UDB3\Event\Re...erface::organizerJSONLD of type stdClass.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1049
                '@id' => $this->organizerService->iri($organizerId),
1050
            );
1051
        }
1052
    }
1053
1054
    /**
1055
     * @param JsonDocument $jsonDocument
1056
     * @param DomainMessage $domainMessage
1057
     * @return JsonDocument
1058
     */
1059 View Code Duplication
    private function updateModified(JsonDocument $jsonDocument, DomainMessage $domainMessage)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1060
    {
1061
        $body = $jsonDocument->getBody();
1062
1063
        $recordedDateTime = RecordedOn::fromDomainMessage($domainMessage);
1064
        $body->modified = $recordedDateTime->toString();
1065
1066
        return $jsonDocument->withBody($body);
1067
    }
1068
}
1069