Completed
Pull Request — master (#338)
by Luc
05:44
created

OfferLDProjector::getTitleTranslatedClassName()

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