Completed
Pull Request — master (#328)
by Luc
04:54
created

OfferLDProjector::updateTerm()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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