Completed
Pull Request — master (#337)
by Luc
06:44
created

getImagesImportedFromUdb2ClassName()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 1
c 0
b 0
f 0
nc 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\Facility;
15
use CultuurNet\UDB3\Iri\IriGeneratorInterface;
16
use CultuurNet\UDB3\Label;
17
use CultuurNet\UDB3\Media\Image;
18
use CultuurNet\UDB3\Offer\AvailableTo;
19
use CultuurNet\UDB3\Offer\Events\AbstractBookingInfoUpdated;
20
use CultuurNet\UDB3\Offer\Events\AbstractCalendarUpdated;
21
use CultuurNet\UDB3\Offer\Events\AbstractContactPointUpdated;
22
use CultuurNet\UDB3\Offer\Events\AbstractDescriptionTranslated;
23
use CultuurNet\UDB3\Offer\Events\AbstractDescriptionUpdated;
24
use CultuurNet\UDB3\Offer\Events\AbstractEvent;
25
use CultuurNet\UDB3\Offer\Events\AbstractFacilitiesUpdated;
26
use CultuurNet\UDB3\Offer\Events\AbstractLabelAdded;
27
use CultuurNet\UDB3\Offer\Events\AbstractLabelRemoved;
28
use CultuurNet\UDB3\Offer\Events\AbstractOrganizerDeleted;
29
use CultuurNet\UDB3\Offer\Events\AbstractOrganizerUpdated;
30
use CultuurNet\UDB3\Offer\Events\AbstractPriceInfoUpdated;
31
use CultuurNet\UDB3\Offer\Events\AbstractThemeUpdated;
32
use CultuurNet\UDB3\Offer\Events\AbstractTitleTranslated;
33
use CultuurNet\UDB3\Offer\Events\AbstractTitleUpdated;
34
use CultuurNet\UDB3\Offer\Events\AbstractTypeUpdated;
35
use CultuurNet\UDB3\Offer\Events\AbstractTypicalAgeRangeDeleted;
36
use CultuurNet\UDB3\Offer\Events\AbstractTypicalAgeRangeUpdated;
37
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageAdded;
38
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageRemoved;
39
use CultuurNet\UDB3\Offer\Events\Image\AbstractImagesEvent;
40
use CultuurNet\UDB3\Offer\Events\Image\AbstractImagesImportedFromUDB2;
41
use CultuurNet\UDB3\Offer\Events\Image\AbstractImagesUpdatedFromUDB2;
42
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageUpdated;
43
use CultuurNet\UDB3\Offer\Events\Image\AbstractMainImageSelected;
44
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractApproved;
45
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractFlaggedAsDuplicate;
46
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractFlaggedAsInappropriate;
47
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractPublished;
48
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractRejected;
49
use CultuurNet\UDB3\Offer\WorkflowStatus;
50
use CultuurNet\UDB3\ReadModel\JsonDocument;
51
use CultuurNet\UDB3\ReadModel\JsonDocumentMetaDataEnricherInterface;
52
use CultuurNet\UDB3\ReadModel\MultilingualJsonLDProjectorTrait;
53
use CultuurNet\UDB3\RecordedOn;
54
use CultuurNet\UDB3\SluggerInterface;
55
use Symfony\Component\Serializer\SerializerInterface;
56
use ValueObjects\Identity\UUID;
57
58
abstract class OfferLDProjector implements OrganizerServiceInterface
59
{
60
    use MultilingualJsonLDProjectorTrait;
61
    use DelegateEventHandlingToSpecificMethodTrait {
62
        DelegateEventHandlingToSpecificMethodTrait::handle as handleUnknownEvents;
63
    }
64
65
    /**
66
     * @var DocumentRepositoryInterface
67
     */
68
    protected $repository;
69
70
    /**
71
     * @var IriGeneratorInterface
72
     */
73
    protected $iriGenerator;
74
75
    /**
76
     * @var EntityServiceInterface
77
     */
78
    protected $organizerService;
79
80
    /**
81
     * @var JsonDocumentMetaDataEnricherInterface
82
     */
83
    protected $jsonDocumentMetaDataEnricher;
84
85
    /**
86
     * @var SerializerInterface
87
     */
88
    protected $mediaObjectSerializer;
89
90
    /**
91
     * @var SluggerInterface
92
     */
93
    protected $slugger;
94
95
    /**
96
     * @param DocumentRepositoryInterface $repository
97
     * @param IriGeneratorInterface $iriGenerator
98
     * @param EntityServiceInterface $organizerService
99
     * @param SerializerInterface $mediaObjectSerializer
100
     * @param JsonDocumentMetaDataEnricherInterface $jsonDocumentMetaDataEnricher
101
     */
102
    public function __construct(
103
        DocumentRepositoryInterface $repository,
104
        IriGeneratorInterface $iriGenerator,
105
        EntityServiceInterface $organizerService,
106
        SerializerInterface $mediaObjectSerializer,
107
        JsonDocumentMetaDataEnricherInterface $jsonDocumentMetaDataEnricher
108
    ) {
109
        $this->repository = $repository;
110
        $this->iriGenerator = $iriGenerator;
111
        $this->organizerService = $organizerService;
112
        $this->jsonDocumentMetaDataEnricher = $jsonDocumentMetaDataEnricher;
113
        $this->mediaObjectSerializer = $mediaObjectSerializer;
114
115
        $this->slugger = new CulturefeedSlugger();
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121
    public function handle(DomainMessage $domainMessage)
122
    {
123
        $event = $domainMessage->getPayload();
124
125
        $eventName = get_class($event);
126
        $eventHandlers = $this->getEventHandlers();
127
128
        if (isset($eventHandlers[$eventName])) {
129
            $handler = $eventHandlers[$eventName];
130
            $jsonDocuments = call_user_func(array($this, $handler), $event, $domainMessage);
131
        } elseif ($methodName = $this->getHandleMethodName($event)) {
132
            $jsonDocuments = $this->{$methodName}($event, $domainMessage);
133
        } else {
134
            return;
135
        }
136
137
        if (!$jsonDocuments) {
138
            return;
139
        }
140
141
        if (!is_array($jsonDocuments)) {
142
            $jsonDocuments = [$jsonDocuments];
143
        }
144
145
        foreach ($jsonDocuments as $jsonDocument) {
146
            $jsonDocument = $this->jsonDocumentMetaDataEnricher->enrich($jsonDocument, $domainMessage->getMetadata());
147
148
            $jsonDocument = $this->updateModified($jsonDocument, $domainMessage);
149
150
            $this->repository->save($jsonDocument);
151
        }
152
    }
153
154
    /**
155
     * @return string[]
156
     *   An associative array of commands and their handler methods.
157
     */
158 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...
159
    {
160
        $events = [];
161
162
        foreach (get_class_methods($this) as $method) {
163
            $matches = [];
164
165
            if (preg_match('/^apply(.+)$/', $method, $matches)) {
166
                $event = $matches[1];
167
                $classNameMethod = 'get' . $event . 'ClassName';
168
169
                if (method_exists($this, $classNameMethod)) {
170
                    $eventFullClassName = call_user_func(array($this, $classNameMethod));
171
                    $events[$eventFullClassName] = $method;
172
                }
173
            }
174
        }
175
176
        return $events;
177
    }
178
179
    /**
180
     * @return string
181
     */
182
    abstract protected function getLabelAddedClassName();
183
184
    /**
185
     * @return string
186
     */
187
    abstract protected function getLabelRemovedClassName();
188
189
    /**
190
     * @return string
191
     */
192
    abstract protected function getImageAddedClassName();
193
194
    /**
195
     * @return string
196
     */
197
    abstract protected function getImageRemovedClassName();
198
199
    /**
200
     * @return string
201
     */
202
    abstract protected function getImageUpdatedClassName();
203
204
    /**
205
     * @return string
206
     */
207
    abstract protected function getMainImageSelectedClassName();
208
209
    /**
210
     * @return string
211
     */
212
    abstract protected function getTitleTranslatedClassName();
213
214
    /**
215
     * @return string
216
     */
217
    abstract protected function getTitleUpdatedClassName();
218
219
    /**
220
     * @return string
221
     */
222
    abstract protected function getDescriptionTranslatedClassName();
223
224
    /**
225
     * @return string
226
     */
227
    abstract protected function getOrganizerUpdatedClassName();
228
229
    /**
230
     * @return string
231
     */
232
    abstract protected function getOrganizerDeletedClassName();
233
234
    /**
235
     * @return string
236
     */
237
    abstract protected function getBookingInfoUpdatedClassName();
238
239
    /**
240
     * @return string
241
     */
242
    abstract protected function getPriceInfoUpdatedClassName();
243
244
    /**
245
     * @return string
246
     */
247
    abstract protected function getContactPointUpdatedClassName();
248
249
    /**
250
     * @return string
251
     */
252
    abstract protected function getDescriptionUpdatedClassName();
253
254
    /**
255
     * @return string
256
     */
257
    abstract protected function getCalendarUpdatedClassName();
258
259
    /**
260
     * @return string
261
     */
262
    abstract protected function getTypicalAgeRangeUpdatedClassName();
263
264
    /**
265
     * @return string
266
     */
267
    abstract protected function getTypicalAgeRangeDeletedClassName();
268
269
    /**
270
     * @return string
271
     */
272
    abstract protected function getPublishedClassName();
273
274
    /**
275
     * @return string
276
     */
277
    abstract protected function getApprovedClassName();
278
279
    /**
280
     * @return string
281
     */
282
    abstract protected function getRejectedClassName();
283
284
    /**
285
     * @return string
286
     */
287
    abstract protected function getFlaggedAsDuplicateClassName();
288
289
    /**
290
     * @return string
291
     */
292
    abstract protected function getFlaggedAsInappropriateClassName();
293
294
    /**
295
     * @return string
296
     */
297
    abstract protected function getImagesImportedFromUdb2ClassName();
298
299
    /**
300
     * @return string
301
     */
302
    abstract protected function getImagesUpdatedFromUdb2ClassName();
303
304
    /**
305
     * @return string
306
     */
307
    abstract protected function getTypeUpdatedClassName();
308
309
    /**
310
     * @return string
311
     */
312
    abstract protected function getThemeUpdatedClassName();
313
314
    /**
315
     * @return string
316
     */
317
    abstract protected function getFacilitiesUpdatedClassName();
318
319
    /**
320
     * @param AbstractTypeUpdated $typeUpdated
321
     * @return JsonDocument
322
     */
323
    protected function applyTypeUpdated(AbstractTypeUpdated $typeUpdated)
324
    {
325
        $document = $this->loadDocumentFromRepository($typeUpdated);
326
327
        return $this->updateTerm($document, $typeUpdated->getType());
328
    }
329
330
    /**
331
     * @param AbstractThemeUpdated $themeUpdated
332
     * @return JsonDocument
333
     */
334
    protected function applyThemeUpdated(AbstractThemeUpdated $themeUpdated)
335
    {
336
        $document = $this->loadDocumentFromRepository($themeUpdated);
337
338
        return $this->updateTerm($document, $themeUpdated->getTheme());
339
    }
340
341
    /**
342
     * @param JsonDocument $document
343
     * @param Category $category
344
     *
345
     * @return JsonDocument
346
     */
347
    private function updateTerm(JsonDocument $document, Category $category)
348
    {
349
        $offerLD = $document->getBody();
350
351
        $oldTerms = property_exists($offerLD, 'terms') ? $offerLD->terms : [];
352
        $newTerm = (object) $category->serialize();
353
354
        $newTerms = array_filter(
355
            $oldTerms,
356
            function ($term) use ($category) {
357
                return !property_exists($term, 'domain') || $term->domain !== $category->getDomain();
358
            }
359
        );
360
361
        array_push($newTerms, $newTerm);
362
363
        $offerLD->terms = array_values($newTerms);
364
365
        return $document->withBody($offerLD);
366
    }
367
368
    /**
369
     * @param AbstractFacilitiesUpdated $facilitiesUpdated
370
     * @return JsonDocument
371
     */
372
    protected function applyFacilitiesUpdated(AbstractFacilitiesUpdated $facilitiesUpdated)
373
    {
374
        $document = $this->loadDocumentFromRepository($facilitiesUpdated);
375
376
        $offerLd = $document->getBody();
377
378
        $terms = isset($offerLd->terms) ? $offerLd->terms : array();
379
380
        // Remove all old facilities + get numeric keys.
381
        $terms = array_values(array_filter(
382
            $terms,
383
            function ($term) {
384
                return $term->domain !== Facility::DOMAIN;
385
            }
386
        ));
387
388
        // Add the new facilities.
389
        foreach ($facilitiesUpdated->getFacilities() as $facility) {
390
            $terms[] = $facility->toJsonLd();
391
        }
392
393
        $offerLd->terms = $terms;
394
395
        return $document->withBody($offerLd);
396
    }
397
398
    /**
399
     * @param AbstractLabelAdded $labelAdded
400
     * @return JsonDocument
401
     */
402
    protected function applyLabelAdded(AbstractLabelAdded $labelAdded)
403
    {
404
        $document = $this->loadDocumentFromRepository($labelAdded);
405
406
        $offerLd = $document->getBody();
407
408
        // Check the visibility of the label to update the right property.
409
        $labelsProperty = $labelAdded->getLabel()->isVisible() ? 'labels' : 'hiddenLabels';
410
411
        $labels = isset($offerLd->{$labelsProperty}) ? $offerLd->{$labelsProperty} : [];
412
        $label = (string) $labelAdded->getLabel();
413
414
        $labels[] = $label;
415
        $offerLd->{$labelsProperty} = array_unique($labels);
416
417
        return $document->withBody($offerLd);
418
    }
419
420
    /**
421
     * @param AbstractLabelRemoved $labelRemoved
422
     * @return JsonDocument
423
     */
424 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...
425
    {
426
        $document = $this->loadDocumentFromRepository($labelRemoved);
427
428
        $offerLd = $document->getBody();
429
430
        // Don't presume that the label visibility is correct when removing.
431
        // So iterate over both the visible and invisible labels.
432
        $labelsProperties = ['labels', 'hiddenLabels'];
433
434
        foreach ($labelsProperties as $labelsProperty) {
435
            if (isset($offerLd->{$labelsProperty}) && is_array($offerLd->{$labelsProperty})) {
436
                $offerLd->{$labelsProperty} = array_filter(
437
                    $offerLd->{$labelsProperty},
438
                    function ($label) use ($labelRemoved) {
439
                        return !$labelRemoved->getLabel()->equals(
440
                            new Label($label)
441
                        );
442
                    }
443
                );
444
                // Ensure array keys start with 0 so json_encode() does encode it
445
                // as an array and not as an object.
446
                if (count($offerLd->{$labelsProperty}) > 0) {
447
                    $offerLd->{$labelsProperty} = array_values($offerLd->{$labelsProperty});
448
                } else {
449
                    unset($offerLd->{$labelsProperty});
450
                }
451
            }
452
        }
453
454
        return $document->withBody($offerLd);
455
    }
456
457
    /**
458
     * Apply the imageAdded event to the item repository.
459
     *
460
     * @param AbstractImageAdded $imageAdded
461
     * @return JsonDocument
462
     */
463
    protected function applyImageAdded(AbstractImageAdded $imageAdded)
464
    {
465
        $document = $this->loadDocumentFromRepository($imageAdded);
466
467
        $offerLd = $document->getBody();
468
        $offerLd->mediaObject = isset($offerLd->mediaObject) ? $offerLd->mediaObject : [];
469
470
        $imageData = $this->mediaObjectSerializer
471
            ->serialize($imageAdded->getImage(), 'json-ld');
472
        $offerLd->mediaObject[] = $imageData;
473
474
        if (count($offerLd->mediaObject) === 1) {
475
            $offerLd->image = $imageData['contentUrl'];
476
        }
477
478
        return $document->withBody($offerLd);
479
    }
480
481
    /**
482
     * Apply the ImageUpdated event to the item repository.
483
     *
484
     * @param AbstractImageUpdated $imageUpdated
485
     * @return JsonDocument
486
     * @throws \Exception
487
     */
488
    protected function applyImageUpdated(AbstractImageUpdated $imageUpdated)
489
    {
490
        $document = $this->loadDocumentFromRepository($imageUpdated);
491
492
        $offerLd = $document->getBody();
493
494
        if (!isset($offerLd->mediaObject)) {
495
            throw new \Exception('The image to update could not be found.');
496
        }
497
498
        $updatedMediaObjects = [];
499
500
        foreach ($offerLd->mediaObject as $mediaObject) {
501
            $mediaObjectMatches = (
502
                strpos(
503
                    $mediaObject->{'@id'},
504
                    (string)$imageUpdated->getMediaObjectId()
505
                ) > 0
506
            );
507
508
            if ($mediaObjectMatches) {
509
                $mediaObject->description = (string)$imageUpdated->getDescription();
510
                $mediaObject->copyrightHolder = (string)$imageUpdated->getCopyrightHolder();
511
512
                $updatedMediaObjects[] = $mediaObject;
513
            }
514
        };
515
516
        if (empty($updatedMediaObjects)) {
517
            throw new \Exception('The image to update could not be found.');
518
        }
519
520
        return $document->withBody($offerLd);
521
    }
522
523
    /**
524
     * @param AbstractImageRemoved $imageRemoved
525
     * @return JsonDocument
526
     */
527
    protected function applyImageRemoved(AbstractImageRemoved $imageRemoved)
528
    {
529
        $document = $this->loadDocumentFromRepository($imageRemoved);
530
531
        $offerLd = $document->getBody();
532
533
        // Nothing to remove if there are no media objects!
534
        if (!isset($offerLd->mediaObject)) {
535
            return;
536
        }
537
538
        $imageId = (string) $imageRemoved->getImage()->getMediaObjectId();
539
540
        /**
541
         * Matches any object that is not the removed image.
542
         *
543
         * @param Object $mediaObject
544
         *  An existing projection of a media object.
545
         *
546
         * @return bool
547
         *  Returns true when the media object does not match the image to remove.
548
         */
549
        $shouldNotBeRemoved = function ($mediaObject) use ($imageId) {
550
            $containsId = !!strpos($mediaObject->{'@id'}, $imageId);
551
            return !$containsId;
552
        };
553
554
        // Remove any media objects that match the image.
555
        $filteredMediaObjects = array_filter(
556
            $offerLd->mediaObject,
557
            $shouldNotBeRemoved
558
        );
559
560
        // Unset the main image if it matches the removed image
561
        if (isset($offerLd->image) && strpos($offerLd->{'image'}, $imageId)) {
562
            unset($offerLd->{"image"});
563
        }
564
565
        if (!isset($offerLd->image) && count($filteredMediaObjects) > 0) {
566
            $offerLd->image = array_values($filteredMediaObjects)[0]->contentUrl;
567
        }
568
569
        // If no media objects are left remove the attribute.
570
        if (empty($filteredMediaObjects)) {
571
            unset($offerLd->{"mediaObject"});
572
        } else {
573
            $offerLd->mediaObject = array_values($filteredMediaObjects);
574
        }
575
576
        return $document->withBody($offerLd);
577
    }
578
579
    /**
580
     * @param AbstractMainImageSelected $mainImageSelected
581
     * @return JsonDocument
582
     */
583
    protected function applyMainImageSelected(AbstractMainImageSelected $mainImageSelected)
584
    {
585
        $document = $this->loadDocumentFromRepository($mainImageSelected);
586
        $offerLd = $document->getBody();
587
        $imageId = $mainImageSelected->getImage()->getMediaObjectId();
588
        $mediaObjectMatcher = function ($matchingMediaObject, $currentMediaObject) use ($imageId) {
589
            if (!$matchingMediaObject && $this->mediaObjectMatchesId($currentMediaObject, $imageId)) {
590
                $matchingMediaObject = $currentMediaObject;
591
            }
592
593
            return $matchingMediaObject;
594
        };
595
        $mediaObject = array_reduce(
596
            $offerLd->mediaObject,
597
            $mediaObjectMatcher
598
        );
599
600
        $offerLd->image = $mediaObject->contentUrl;
601
602
        return $document->withBody($offerLd);
603
    }
604
605
    /**
606
     * @param Object $mediaObject
607
     * @param UUID $mediaObjectId
608
     *
609
     * @return bool
610
     */
611
    protected function mediaObjectMatchesId($mediaObject, UUID $mediaObjectId)
612
    {
613
        return strpos($mediaObject->{'@id'}, (string) $mediaObjectId) > 0;
614
    }
615
616
    /**
617
     * @param AbstractTitleTranslated $titleTranslated
618
     * @return JsonDocument
619
     */
620
    protected function applyTitleTranslated(AbstractTitleTranslated $titleTranslated)
621
    {
622
        $document = $this->loadDocumentFromRepository($titleTranslated);
623
624
        $offerLd = $document->getBody();
625
        $offerLd->name->{$titleTranslated->getLanguage()->getCode(
626
        )} = $titleTranslated->getTitle()->toNative();
627
628
        return $document->withBody($offerLd);
629
    }
630
631
    /**
632
     * @param AbstractTitleUpdated $titleUpdated
633
     * @return JsonDocument
634
     */
635
    protected function applyTitleUpdated(AbstractTitleUpdated $titleUpdated)
636
    {
637
        $document = $this->loadDocumentFromRepository($titleUpdated);
638
        $offerLd = $document->getBody();
639
        $mainLanguage = isset($offerLd->mainLanguage) ? $offerLd->mainLanguage : 'nl';
640
641
        $offerLd->name->{$mainLanguage} = $titleUpdated->getTitle()->toNative();
642
643
        return $document->withBody($offerLd);
644
    }
645
646
    /**
647
     * @param AbstractDescriptionTranslated $descriptionTranslated
648
     * @return JsonDocument
649
     */
650
    protected function applyDescriptionTranslated(
651
        AbstractDescriptionTranslated $descriptionTranslated
652
    ) {
653
        $document = $this->loadDocumentFromRepository($descriptionTranslated);
654
655
        $offerLd = $document->getBody();
656
        $languageCode = $descriptionTranslated->getLanguage()->getCode();
657
        $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...
658
        if (empty($offerLd->description)) {
659
            $offerLd->description = new \stdClass();
660
        }
661
        $offerLd->description->{$languageCode} = $description;
662
663
        return $document->withBody($offerLd);
664
    }
665
666
    /**
667
     * @param AbstractCalendarUpdated $calendarUpdated
668
     *
669
     * @return JsonDocument
670
     */
671
    protected function applyCalendarUpdated(AbstractCalendarUpdated $calendarUpdated)
672
    {
673
        $document = $this->loadDocumentFromRepository($calendarUpdated)
674
            ->apply(OfferUpdate::calendar($calendarUpdated->getCalendar()));
675
676
        $offerLd = $document->getBody();
677
678
        $availableTo = AvailableTo::createFromCalendar($calendarUpdated->getCalendar());
679
        $offerLd->availableTo = (string)$availableTo;
680
681
        return $document->withBody($offerLd);
682
    }
683
684
    /**
685
     * Apply the organizer updated event to the offer repository.
686
     * @param AbstractOrganizerUpdated $organizerUpdated
687
     * @return JsonDocument
688
     */
689
    protected function applyOrganizerUpdated(AbstractOrganizerUpdated $organizerUpdated)
690
    {
691
        $document = $this->loadDocumentFromRepository($organizerUpdated);
692
693
        $offerLd = $document->getBody();
694
695
        $offerLd->organizer = array(
696
                '@type' => 'Organizer',
697
            ) + (array)$this->organizerJSONLD($organizerUpdated->getOrganizerId());
698
699
        return $document->withBody($offerLd);
700
    }
701
702
    /**
703
     * Apply the organizer delete event to the offer repository.
704
     * @param AbstractOrganizerDeleted $organizerDeleted
705
     * @return JsonDocument
706
     */
707
    protected function applyOrganizerDeleted(AbstractOrganizerDeleted $organizerDeleted)
708
    {
709
        $document = $this->loadDocumentFromRepository($organizerDeleted);
710
711
        $offerLd = $document->getBody();
712
713
        unset($offerLd->organizer);
714
715
        return $document->withBody($offerLd);
716
    }
717
718
    /**
719
     * Apply the booking info updated event to the offer repository.
720
     * @param AbstractBookingInfoUpdated $bookingInfoUpdated
721
     * @return JsonDocument
722
     */
723
    protected function applyBookingInfoUpdated(AbstractBookingInfoUpdated $bookingInfoUpdated)
724
    {
725
        $document = $this->loadDocumentFromRepository($bookingInfoUpdated);
726
727
        $offerLd = $document->getBody();
728
        $offerLd->bookingInfo = $bookingInfoUpdated->getBookingInfo()->toJsonLd();
729
730
        return $document->withBody($offerLd);
731
    }
732
733
    /**
734
     * @param AbstractPriceInfoUpdated $priceInfoUpdated
735
     * @return JsonDocument
736
     */
737
    protected function applyPriceInfoUpdated(AbstractPriceInfoUpdated $priceInfoUpdated)
738
    {
739
        $document = $this->loadDocumentFromRepository($priceInfoUpdated);
740
741
        $offerLd = $document->getBody();
742
        $offerLd->priceInfo = [];
743
744
        $basePrice = $priceInfoUpdated->getPriceInfo()->getBasePrice();
745
746
        $offerLd->priceInfo[] = [
747
            'category' => 'base',
748
            'name' => 'Basistarief',
749
            'price' => $basePrice->getPrice()->toFloat(),
750
            'priceCurrency' => $basePrice->getCurrency()->getCode()->toNative(),
751
        ];
752
753
        foreach ($priceInfoUpdated->getPriceInfo()->getTariffs() as $tariff) {
754
            $offerLd->priceInfo[] = [
755
                'category' => 'tariff',
756
                'name' => $tariff->getName()->toNative(),
757
                'price' => $tariff->getPrice()->toFloat(),
758
                'priceCurrency' => $tariff->getCurrency()->getCode()->toNative(),
759
            ];
760
        }
761
762
        return $document->withBody($offerLd);
763
    }
764
765
    /**
766
     * Apply the contact point updated event to the offer repository.
767
     * @param AbstractContactPointUpdated $contactPointUpdated
768
     * @return JsonDocument
769
     */
770
    protected function applyContactPointUpdated(AbstractContactPointUpdated $contactPointUpdated)
771
    {
772
        $document = $this->loadDocumentFromRepository($contactPointUpdated);
773
774
        $offerLd = $document->getBody();
775
        $offerLd->contactPoint = $contactPointUpdated->getContactPoint()->toJsonLd();
776
777
        return $document->withBody($offerLd);
778
    }
779
780
    /**
781
     * Apply the description updated event to the offer repository.
782
     * @param AbstractDescriptionUpdated $descriptionUpdated
783
     * @return JsonDocument
784
     */
785
    protected function applyDescriptionUpdated(
786
        AbstractDescriptionUpdated $descriptionUpdated
787
    ) {
788
        $document = $this->loadDocumentFromRepository($descriptionUpdated);
789
790
        $offerLd = $document->getBody();
791
        if (empty($offerLd->description)) {
792
            $offerLd->description = new \stdClass();
793
        }
794
        $offerLd->description->{'nl'} = $descriptionUpdated->getDescription();
795
796
        return $document->withBody($offerLd);
797
    }
798
799
    /**
800
     * Apply the typical age range updated event to the offer repository.
801
     * @param AbstractTypicalAgeRangeUpdated $typicalAgeRangeUpdated
802
     * @return JsonDocument
803
     */
804
    protected function applyTypicalAgeRangeUpdated(
805
        AbstractTypicalAgeRangeUpdated $typicalAgeRangeUpdated
806
    ) {
807
        $document = $this->loadDocumentFromRepository($typicalAgeRangeUpdated);
808
809
        $offerLd = $document->getBody();
810
        $offerLd->typicalAgeRange = (string) $typicalAgeRangeUpdated->getTypicalAgeRange();
811
812
        return $document->withBody($offerLd);
813
    }
814
815
    /**
816
     * Apply the typical age range deleted event to the offer repository.
817
     * @param AbstractTypicalAgeRangeDeleted $typicalAgeRangeDeleted
818
     * @return JsonDocument
819
     */
820
    protected function applyTypicalAgeRangeDeleted(
821
        AbstractTypicalAgeRangeDeleted $typicalAgeRangeDeleted
822
    ) {
823
        $document = $this->loadDocumentFromRepository($typicalAgeRangeDeleted);
824
825
        $offerLd = $document->getBody();
826
827
        unset($offerLd->typicalAgeRange);
828
829
        return $document->withBody($offerLd);
830
    }
831
832
    /**
833
     * @param AbstractPublished $published
834
     * @return JsonDocument
835
     */
836
    protected function applyPublished(AbstractPublished $published)
837
    {
838
        $document = $this->loadDocumentFromRepository($published);
839
840
        $offerLd = $document->getBody();
841
842
        $offerLd->workflowStatus = WorkflowStatus::READY_FOR_VALIDATION()->getName();
843
844
        $publicationDate = $published->getPublicationDate();
845
        $offerLd->availableFrom = $publicationDate->format(\DateTime::ATOM);
846
847
        return $document->withBody($offerLd);
848
    }
849
850
    /**
851
     * @param AbstractApproved $approved
852
     * @return JsonDocument
853
     */
854
    protected function applyApproved(AbstractApproved $approved)
855
    {
856
        $document = $this->loadDocumentFromRepository($approved);
857
        $offerLd = $document->getBody();
858
        $offerLd->workflowStatus = WorkflowStatus::APPROVED()->getName();
859
        return $document->withBody($offerLd);
860
    }
861
862
    /**
863
     * @param AbstractRejected $rejected
864
     * @return JsonDocument
865
     */
866 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...
867
    {
868
        $document = $this->loadDocumentFromRepository($rejected);
869
        $offerLd = $document->getBody();
870
        $offerLd->workflowStatus = WorkflowStatus::REJECTED()->getName();
871
        return $document->withBody($offerLd);
872
    }
873
874
    /**
875
     * @param AbstractFlaggedAsDuplicate $flaggedAsDuplicate
876
     * @return JsonDocument
877
     */
878 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...
879
        AbstractFlaggedAsDuplicate $flaggedAsDuplicate
880
    ) {
881
        $document = $this->loadDocumentFromRepository($flaggedAsDuplicate);
882
        $offerLd = $document->getBody();
883
        $offerLd->workflowStatus = WorkflowStatus::REJECTED()->getName();
884
        return $document->withBody($offerLd);
885
    }
886
887
    /**
888
     * @param AbstractFlaggedAsInappropriate $flaggedAsInappropriate
889
     * @return JsonDocument
890
     */
891 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...
892
        AbstractFlaggedAsInappropriate $flaggedAsInappropriate
893
    ) {
894
        $document = $this->loadDocumentFromRepository($flaggedAsInappropriate);
895
        $offerLd = $document->getBody();
896
        $offerLd->workflowStatus = WorkflowStatus::REJECTED()->getName();
897
        return $document->withBody($offerLd);
898
    }
899
900
    /**
901
     * @param AbstractImagesImportedFromUDB2 $imagesImportedFromUDB2
902
     * @return JsonDocument
903
     */
904 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...
905
    {
906
        $document = $this->loadDocumentFromRepository($imagesImportedFromUDB2);
907
        $offerLd = $document->getBody();
908
        $this->applyUdb2ImagesEvent($offerLd, $imagesImportedFromUDB2);
909
        return $document->withBody($offerLd);
910
    }
911
912
    /**
913
     * @param AbstractImagesUpdatedFromUDB2 $imagesUpdatedFromUDB2
914
     * @return JsonDocument
915
     */
916 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...
917
    {
918
        $document = $this->loadDocumentFromRepository($imagesUpdatedFromUDB2);
919
        $offerLd = $document->getBody();
920
        $this->applyUdb2ImagesEvent($offerLd, $imagesUpdatedFromUDB2);
921
        return $document->withBody($offerLd);
922
    }
923
924
    /**
925
     * This indirect apply method can be called internally to deal with images coming from UDB2.
926
     * Imports from UDB2 only contain the native Dutch content.
927
     * @see https://github.com/cultuurnet/udb3-udb2-bridge/blob/db0a7ab2444f55bb3faae3d59b82b39aaeba253b/test/Media/ImageCollectionFactoryTest.php#L79-L103
928
     * Because of this we have to make sure translated images are left in place.
929
     *
930
     * @param \stdClass $offerLd
931
     * @param AbstractImagesEvent $imagesEvent
932
     */
933
    private function applyUdb2ImagesEvent(\stdClass $offerLd, AbstractImagesEvent $imagesEvent)
934
    {
935
        $images = $imagesEvent->getImages();
936
        $currentMediaObjects = isset($offerLd->mediaObject) ? $offerLd->mediaObject : [];
937
        $dutchMediaObjects = array_map(
938
            function (Image $image) {
939
                return $this->mediaObjectSerializer->serialize($image, 'json-ld');
940
            },
941
            $images->toArray()
942
        );
943
        $translatedMediaObjects = array_filter(
944
            $currentMediaObjects,
945
            function ($image) {
946
                return $image->inLanguage !== 'nl';
947
            }
948
        );
949
        $mainImage = $images->getMain();
950
951
        unset($offerLd->mediaObject, $offerLd->image);
952
953
        if (!empty($dutchMediaObjects) || !empty($translatedMediaObjects)) {
954
            $offerLd->mediaObject = array_merge($dutchMediaObjects, $translatedMediaObjects);
955
        }
956
957
        if (isset($mainImage)) {
958
            $offerLd->image = (string) $mainImage->getSourceLocation();
959
        }
960
    }
961
962
    /**
963
     * @param string $id
964
     * @return JsonDocument
965
     */
966 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...
967
    {
968
        $document = new JsonDocument($id);
969
970
        $offerLd = $document->getBody();
971
        $offerLd->{'@id'} = $this->iriGenerator->iri($id);
972
973
        return $document->withBody($offerLd);
974
    }
975
976
    /**
977
     * @param AbstractEvent $event
978
     * @return JsonDocument
979
     */
980
    protected function loadDocumentFromRepository(AbstractEvent $event)
981
    {
982
        return $this->loadDocumentFromRepositoryByItemId($event->getItemId());
983
    }
984
985
    /**
986
     * @param string $itemId
987
     * @return JsonDocument
988
     */
989
    protected function loadDocumentFromRepositoryByItemId($itemId)
990
    {
991
        $document = $this->repository->get($itemId);
992
993
        if (!$document) {
994
            return $this->newDocument($itemId);
995
        }
996
997
        return $document;
998
    }
999
1000
    /**
1001
     * @inheritdoc
1002
     */
1003
    public function organizerJSONLD($organizerId)
1004
    {
1005
        try {
1006
            $organizerJSONLD = $this->organizerService->getEntity(
1007
                $organizerId
1008
            );
1009
1010
            return json_decode($organizerJSONLD);
1011
        } catch (EntityNotFoundException $e) {
1012
            // In case the place can not be found at the moment, just add its ID
1013
            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...
1014
                '@id' => $this->organizerService->iri($organizerId),
1015
            );
1016
        }
1017
    }
1018
1019
    /**
1020
     * @param JsonDocument $jsonDocument
1021
     * @param DomainMessage $domainMessage
1022
     * @return JsonDocument
1023
     */
1024
    private function updateModified(JsonDocument $jsonDocument, DomainMessage $domainMessage)
1025
    {
1026
        $body = $jsonDocument->getBody();
1027
1028
        $recordedDateTime = RecordedOn::fromDomainMessage($domainMessage);
1029
        $body->modified = $recordedDateTime->toString();
1030
1031
        return $jsonDocument->withBody($body);
1032
    }
1033
}
1034