Completed
Push — master ( 486773...bc0257 )
by
unknown
12s
created

Offer::createImagesUpdatedFromUDB2()

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;
4
5
use Broadway\EventSourcing\EventSourcedAggregateRoot;
6
use CultureFeed_Cdb_Item_Base;
7
use CultuurNet\Geocoding\Coordinate\Coordinates;
8
use CultuurNet\UDB3\BookingInfo;
9
use CultuurNet\UDB3\Calendar;
10
use CultuurNet\UDB3\ContactPoint;
11
use CultuurNet\UDB3\Description;
12
use CultuurNet\UDB3\Event\EventType;
13
use CultuurNet\UDB3\Facility;
14
use CultuurNet\UDB3\Label;
15
use CultuurNet\UDB3\LabelAwareAggregateRoot;
16
use CultuurNet\UDB3\LabelCollection;
17
use CultuurNet\UDB3\Media\Image;
18
use CultuurNet\UDB3\Media\ImageCollection;
19
use CultuurNet\UDB3\Media\Properties\CopyrightHolder;
20
use \CultuurNet\UDB3\Media\Properties\Description as ImageDescription;
21
use CultuurNet\UDB3\Model\ValueObject\Taxonomy\Label\LabelName;
22
use CultuurNet\UDB3\Model\ValueObject\Taxonomy\Label\Labels;
23
use CultuurNet\UDB3\Offer\Commands\Image\AbstractUpdateImage;
24
use CultuurNet\UDB3\Language;
25
use CultuurNet\UDB3\Offer\Events\AbstractBookingInfoUpdated;
26
use CultuurNet\UDB3\Offer\Events\AbstractCalendarUpdated;
27
use CultuurNet\UDB3\Offer\Events\AbstractContactPointUpdated;
28
use CultuurNet\UDB3\Offer\Events\AbstractFacilitiesUpdated;
29
use CultuurNet\UDB3\Offer\Events\AbstractGeoCoordinatesUpdated;
30
use CultuurNet\UDB3\Offer\Events\AbstractLabelsImported;
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\AbstractDescriptionTranslated;
35
use CultuurNet\UDB3\Offer\Events\AbstractDescriptionUpdated;
36
use CultuurNet\UDB3\Offer\Events\AbstractLabelAdded;
37
use CultuurNet\UDB3\Offer\Events\AbstractLabelRemoved;
38
use CultuurNet\UDB3\Offer\Events\AbstractOfferDeleted;
39
use CultuurNet\UDB3\Offer\Events\AbstractOrganizerDeleted;
40
use CultuurNet\UDB3\Offer\Events\AbstractOrganizerUpdated;
41
use CultuurNet\UDB3\Offer\Events\AbstractPriceInfoUpdated;
42
use CultuurNet\UDB3\Offer\Events\AbstractTypeUpdated;
43
use CultuurNet\UDB3\Offer\Events\AbstractTypicalAgeRangeDeleted;
44
use CultuurNet\UDB3\Offer\Events\AbstractTypicalAgeRangeUpdated;
45
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageAdded;
46
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageRemoved;
47
use CultuurNet\UDB3\Offer\Events\Image\AbstractImagesEvent;
48
use CultuurNet\UDB3\Offer\Events\Image\AbstractImagesImportedFromUDB2;
49
use CultuurNet\UDB3\Offer\Events\Image\AbstractImagesUpdatedFromUDB2;
50
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageUpdated;
51
use CultuurNet\UDB3\Offer\Events\Image\AbstractMainImageSelected;
52
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractApproved;
53
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractFlaggedAsDuplicate;
54
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractFlaggedAsInappropriate;
55
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractPublished;
56
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractRejected;
57
use CultuurNet\UDB3\PriceInfo\PriceInfo;
58
use CultuurNet\UDB3\Theme;
59
use CultuurNet\UDB3\Title;
60
use Exception;
61
use ValueObjects\Identity\UUID;
62
use ValueObjects\StringLiteral\StringLiteral;
63
64
abstract class Offer extends EventSourcedAggregateRoot implements LabelAwareAggregateRoot
65
{
66
    const DUPLICATE_REASON = 'duplicate';
67
    const INAPPROPRIATE_REASON = 'inappropriate';
68
69
    /**
70
     * @var LabelCollection
71
     */
72
    protected $labels;
73
74
    /**
75
     * @var ImageCollection
76
     */
77
    protected $images;
78
79
    /**
80
     * @var string
81
     *
82
     * Organizer ids can come from UDB2 which does not strictly use UUIDs.
83
     */
84
    protected $organizerId;
85
86
    /**
87
     * @var WorkflowStatus
88
     */
89
    protected $workflowStatus;
90
91
    /**
92
     * @var StringLiteral|null
93
     */
94
    protected $rejectedReason;
95
96
    /**
97
     * @var PriceInfo
98
     */
99
    protected $priceInfo;
100
101
    /**
102
     * @var StringLiteral[]
103
     */
104
    protected $titles;
105
106
    /**
107
     * @var Description[]
108
     */
109
    protected $descriptions;
110
111
    /**
112
     * @var Language
113
     */
114
    protected $mainLanguage;
115
116
    /**
117
     * @var string;
118
     */
119
    protected $typeId;
120
121
    /**
122
     * @var string;
123
     */
124
    protected $themeId;
125
126
    /**
127
     * @var array
128
     */
129
    protected $facilities;
130
131
    /**
132
     * @var ContactPoint
133
     */
134
    protected $contactPoint;
135
136
    /**
137
     * @var Calendar
138
     */
139
    protected $calendar;
140
141
    /**
142
     * @var AgeRange
143
     */
144
    protected $typicalAgeRange;
145
146
    /**
147
     * @var BookingInfo
148
     */
149
    protected $bookingInfo;
150
151
    /**
152
     * Offer constructor.
153
     */
154
    public function __construct()
155
    {
156
        $this->titles = [];
157
        $this->descriptions = [];
158
        $this->labels = new LabelCollection();
159
        $this->images = new ImageCollection();
160
        $this->facilities = [];
161
        $this->contactPoint = null;
162
        $this->calendar = null;
163
        $this->typicalAgeRange = null;
164
        $this->bookingInfo = null;
165
    }
166
167
    /**
168
     * @param EventType $type
169
     */
170
    public function updateType(EventType $type)
171
    {
172
        if (!$this->typeId || $this->typeId !== $type->getId()) {
173
            $this->apply($this->createTypeUpdatedEvent($type));
174
        }
175
    }
176
177
    /**
178
     * @param Theme $theme
179
     */
180
    public function updateTheme(Theme $theme)
181
    {
182
        if (!$this->themeId || $this->themeId !== $theme->getId()) {
183
            $this->apply($this->createThemeUpdatedEvent($theme));
184
        }
185
    }
186
187
    /**
188
     * @param array $facilities
189
     */
190
    public function updateFacilities(array $facilities)
191
    {
192
        if (empty($this->facilities) || !$this->sameFacilities($this->facilities, $facilities)) {
193
            $this->apply($this->createFacilitiesUpdatedEvent($facilities));
194
        }
195
    }
196
197
    /**
198
     * @param AbstractFacilitiesUpdated $facilitiesUpdated
199
     */
200
    protected function applyFacilitiesUpdated(AbstractFacilitiesUpdated $facilitiesUpdated)
201
    {
202
        $this->facilities = $facilitiesUpdated->getFacilities();
203
    }
204
205
    /**
206
     * @param array $facilities1
207
     * @param array $facilities2
208
     * @return bool
209
     */
210
    private function sameFacilities($facilities1, $facilities2)
211
    {
212
        if (count($facilities1) !== count($facilities2)) {
213
            return false;
214
        }
215
216
        $sameFacilities = array_uintersect(
217
            $facilities1,
218
            $facilities2,
219
            function (Facility $facility1, Facility $facility2) {
220
                return strcmp($facility1->getId(), $facility2->getId());
221
            }
222
        );
223
224
        return count($sameFacilities) === count($facilities2);
225
    }
226
227
    /**
228
     * Get the id of the main image if one is selected for this offer.
229
     *
230
     * @return UUID|null
231
     */
232
    protected function getMainImageId()
233
    {
234
        $mainImage = $this->images->getMain();
235
        return isset($mainImage) ? $mainImage->getMediaObjectId() : null;
236
    }
237
238
    /**
239
     * @inheritdoc
240
     */
241
    public function addLabel(Label $label)
242
    {
243
        if (!$this->labels->contains($label)) {
244
            $this->apply(
245
                $this->createLabelAddedEvent($label)
246
            );
247
        }
248
    }
249
250
    /**
251
     * @inheritdoc
252
     */
253
    public function removeLabel(Label $label)
254
    {
255
        if ($this->labels->contains($label)) {
256
            $this->apply(
257
                $this->createLabelRemovedEvent($label)
258
            );
259
        }
260
    }
261
262
    /**
263
     * @param Labels $labels
264
     */
265
    public function importLabels(Labels $labels)
266
    {
267
        // Convert the imported labels to label collection.
268
        $importLabelsCollection = new LabelCollection(
269
            array_map(
270
                function (\CultuurNet\UDB3\Model\ValueObject\Taxonomy\Label\Label $label) {
271
                    return new Label(
272
                        $label->getName()->toString(),
273
                        $label->isVisible()
274
                    );
275
                },
276
                $labels->toArray()
277
            )
278
        );
279
280
        // What are the added labels?
281
        // Labels which are not inside the internal state but inside the imported labels
282
        $addedLabels = new LabelCollection();
283
        foreach ($importLabelsCollection->asArray() as $label) {
284
            if (!$this->labels->contains($label)) {
285
                $addedLabels = $addedLabels->with($label);
286
            }
287
        }
288
289
        // Fire a LabelsImported for all new labels.
290
        $importLabels = new Labels();
291 View Code Duplication
        foreach ($addedLabels->asArray() as $addedLabel) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
292
            $importLabels = $importLabels->with(
293
                new \CultuurNet\UDB3\Model\ValueObject\Taxonomy\Label\Label(
294
                    new LabelName((string) $addedLabel),
295
                    $addedLabel->isVisible()
296
                )
297
            );
298
        }
299
        if ($importLabels->count() > 0) {
300
            $this->apply($this->createLabelsImportedEvent($importLabels));
301
        }
302
303
        // For each added label fire a LabelAdded event.
304
        foreach ($addedLabels->asArray() as $label) {
305
            $this->apply($this->createLabelAddedEvent($label));
306
        }
307
308
        // What are the deleted labels?
309
        // Labels which are inside the internal state but not inside imported labels.
310
        // For each deleted label fire a LabelDeleted event.
311
        foreach ($this->labels->asArray() as $label) {
312
            if (!$importLabelsCollection->contains($label)) {
313
                $this->apply($this->createLabelRemovedEvent($label));
314
            }
315
        }
316
    }
317
318
    /**
319
     * @param Language $language
320
     * @param Title $title
321
     */
322
    public function updateTitle(Language $language, Title $title)
323
    {
324
        if ($this->isTitleChanged($title, $language)) {
325
            if ($language->getCode() !== $this->mainLanguage->getCode()) {
326
                $event = $this->createTitleTranslatedEvent($language, $title);
327
            } else {
328
                $event = $this->createTitleUpdatedEvent($title);
329
            }
330
331
            $this->apply($event);
332
        }
333
    }
334
335
    /**
336
     * @param AbstractTitleTranslated $titleTranslated
337
     */
338
    public function applyTitleTranslated(AbstractTitleTranslated $titleTranslated)
339
    {
340
        $this->titles[$titleTranslated->getLanguage()->getCode()] = $titleTranslated->getTitle();
341
    }
342
343
344
    /**
345
     * @param AbstractTitleUpdated $titleUpdated
346
     */
347
    public function applyTitleUpdated(AbstractTitleUpdated $titleUpdated)
348
    {
349
        $this->titles[$this->mainLanguage->getCode()] = $titleUpdated->getTitle();
350
    }
351
352
    /**
353
     * @param Description $description
354
     * @param Language $language
355
     */
356
    public function updateDescription(Description $description, Language $language)
357
    {
358
        if ($this->isDescriptionChanged($description, $language)) {
359
            if ($language->getCode() !== $this->mainLanguage->getCode()) {
360
                $event = $this->createDescriptionTranslatedEvent($language, $description);
361
            } else {
362
                $event = $this->createDescriptionUpdatedEvent((string) $description);
363
            }
364
365
            $this->apply($event);
366
        }
367
    }
368
369
    /**
370
     * @param Calendar $calendar
371
     */
372
    public function updateCalendar(Calendar $calendar)
373
    {
374
        if (is_null($this->calendar) || !$this->calendar->sameAs($calendar)) {
375
            $this->apply(
376
                $this->createCalendarUpdatedEvent($calendar)
377
            );
378
        }
379
    }
380
381
    /**
382
     * @param AbstractCalendarUpdated $calendarUpdated
383
     */
384
    protected function applyCalendarUpdated(AbstractCalendarUpdated $calendarUpdated)
385
    {
386
        $this->calendar = $calendarUpdated->getCalendar();
387
    }
388
389
    /**
390
     * @param string $typicalAgeRange
391
     */
392
    public function updateTypicalAgeRange($typicalAgeRange)
393
    {
394
        $typicalAgeRangeUpdatedEvent = $this->createTypicalAgeRangeUpdatedEvent($typicalAgeRange);
395
396
        if (empty($this->typicalAgeRange) || !$this->typicalAgeRange->sameAs($typicalAgeRangeUpdatedEvent->getTypicalAgeRange())) {
397
            $this->apply($typicalAgeRangeUpdatedEvent);
398
        }
399
    }
400
401
    /**
402
     * @param AbstractTypicalAgeRangeUpdated $typicalAgeRangeUpdated
403
     */
404
    protected function applyTypicalAgeRangeUpdated(AbstractTypicalAgeRangeUpdated $typicalAgeRangeUpdated)
405
    {
406
        $this->typicalAgeRange = $typicalAgeRangeUpdated->getTypicalAgeRange();
407
    }
408
409
    public function deleteTypicalAgeRange()
410
    {
411
        if (!is_null($this->typicalAgeRange)) {
412
            $this->apply(
413
                $this->createTypicalAgeRangeDeletedEvent()
414
            );
415
        }
416
    }
417
418
    /**
419
     * @param AbstractTypicalAgeRangeDeleted $typicalAgeRangeDeleted
420
     */
421
    public function applyTypicalAgeRangeDeleted(AbstractTypicalAgeRangeDeleted $typicalAgeRangeDeleted)
0 ignored issues
show
Unused Code introduced by
The parameter $typicalAgeRangeDeleted is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
422
    {
423
        $this->typicalAgeRange = null;
424
    }
425
426
    /**
427
     * @param string $organizerId
428
     */
429
    public function updateOrganizer($organizerId)
430
    {
431
        if ($this->organizerId !== $organizerId) {
432
            $this->apply(
433
                $this->createOrganizerUpdatedEvent($organizerId)
434
            );
435
        }
436
    }
437
438
    /**
439
     * Delete the given organizer.
440
     *
441
     * @param string $organizerId
442
     */
443
    public function deleteOrganizer($organizerId)
444
    {
445
        if ($this->organizerId === $organizerId) {
446
            $this->apply(
447
                $this->createOrganizerDeletedEvent($organizerId)
448
            );
449
        }
450
    }
451
452
    /**
453
     * Delete the current organizer regardless of the id.
454
     */
455
    public function deleteCurrentOrganizer()
456
    {
457
        if (!is_null($this->organizerId)) {
458
            $this->apply(
459
                $this->createOrganizerDeletedEvent($this->organizerId)
460
            );
461
        }
462
    }
463
464
    /**
465
     * Updated the contact info.
466
     * @param ContactPoint $contactPoint
467
     */
468
    public function updateContactPoint(ContactPoint $contactPoint)
469
    {
470
        if (is_null($this->contactPoint) || !$this->contactPoint->sameAs($contactPoint)) {
471
            $this->apply(
472
                $this->createContactPointUpdatedEvent($contactPoint)
473
            );
474
        }
475
    }
476
477
    /**
478
     * @param AbstractContactPointUpdated $contactPointUpdated
479
     */
480
    protected function applyContactPointUpdated(AbstractContactPointUpdated $contactPointUpdated)
481
    {
482
        $this->contactPoint = $contactPointUpdated->getContactPoint();
483
    }
484
485
    /**
486
     * @param Coordinates $coordinates
487
     */
488
    public function updateGeoCoordinates(Coordinates $coordinates)
489
    {
490
        // Note: DON'T compare to previous coordinates and apply only on
491
        // changes. Various projectors expect GeoCoordinatesUpdated after
492
        // MajorInfoUpdated and PlaceUpdatedFromUDB2, even if the address
493
        // and thus the coordinates haven't actually changed.
494
        $this->apply(
495
            $this->createGeoCoordinatesUpdatedEvent($coordinates)
496
        );
497
    }
498
499
    /**
500
     * Updated the booking info.
501
     *
502
     * @param BookingInfo $bookingInfo
503
     */
504
    public function updateBookingInfo(BookingInfo $bookingInfo)
505
    {
506
        if (is_null($this->bookingInfo) || !$this->bookingInfo->sameAs($bookingInfo)) {
507
            $this->apply(
508
                $this->createBookingInfoUpdatedEvent($bookingInfo)
509
            );
510
        }
511
    }
512
513
    /**
514
     * @param AbstractBookingInfoUpdated $bookingInfoUpdated
515
     */
516
    public function applyBookingInfoUpdated(AbstractBookingInfoUpdated $bookingInfoUpdated)
517
    {
518
        $this->bookingInfo = $bookingInfoUpdated->getBookingInfo();
519
    }
520
521
    /**
522
     * @param PriceInfo $priceInfo
523
     */
524
    public function updatePriceInfo(PriceInfo $priceInfo)
525
    {
526
        if (is_null($this->priceInfo) || $priceInfo->serialize() !== $this->priceInfo->serialize()) {
527
            $this->apply(
528
                $this->createPriceInfoUpdatedEvent($priceInfo)
529
            );
530
        }
531
    }
532
533
    /**
534
     * @param AbstractPriceInfoUpdated $priceInfoUpdated
535
     */
536
    protected function applyPriceInfoUpdated(AbstractPriceInfoUpdated $priceInfoUpdated)
537
    {
538
        $this->priceInfo = $priceInfoUpdated->getPriceInfo();
539
    }
540
541
    /**
542
     * @param AbstractLabelAdded $labelAdded
543
     */
544
    protected function applyLabelAdded(AbstractLabelAdded $labelAdded)
545
    {
546
        $this->labels = $this->labels->with($labelAdded->getLabel());
547
    }
548
549
    /**
550
     * @param AbstractLabelRemoved $labelRemoved
551
     */
552
    protected function applyLabelRemoved(AbstractLabelRemoved $labelRemoved)
553
    {
554
        $this->labels = $this->labels->without($labelRemoved->getLabel());
555
    }
556
557
    /**
558
     * @param AbstractThemeUpdated $themeUpdated
559
     */
560
    protected function applyThemeUpdated(AbstractThemeUpdated $themeUpdated)
561
    {
562
        $this->themeId = $themeUpdated->getTheme()->getId();
563
    }
564
565
    /**
566
     * @param AbstractTypeUpdated $themeUpdated
567
     */
568
    protected function applyTypeUpdated(AbstractTypeUpdated $themeUpdated)
569
    {
570
        $this->typeId = $themeUpdated->getType()->getId();
571
    }
572
573
    protected function applyDescriptionUpdated(AbstractDescriptionUpdated $descriptionUpdated)
574
    {
575
        $mainLanguageCode = $this->mainLanguage->getCode();
576
        $this->descriptions[$mainLanguageCode] = new Description($descriptionUpdated->getDescription());
577
    }
578
579
    protected function applyDescriptionTranslated(AbstractDescriptionTranslated $descriptionTranslated)
580
    {
581
        $languageCode = $descriptionTranslated->getLanguage()->getCode();
582
        $this->descriptions[$languageCode] = $descriptionTranslated->getDescription();
583
    }
584
585
    /**
586
     * Add a new image.
587
     *
588
     * @param Image $image
589
     */
590
    public function addImage(Image $image)
591
    {
592
        if (!$this->images->contains($image)) {
593
            $this->apply(
594
                $this->createImageAddedEvent($image)
595
            );
596
        }
597
    }
598
599
    /**
600
     * @param AbstractUpdateImage $updateImageCommand
601
     */
602
    public function updateImage(AbstractUpdateImage $updateImageCommand)
603
    {
604
        if ($this->updateImageAllowed($updateImageCommand)) {
605
            $this->apply(
606
                $this->createImageUpdatedEvent(
607
                    $updateImageCommand->getMediaObjectId(),
608
                    $updateImageCommand->getDescription(),
609
                    $updateImageCommand->getCopyrightHolder()
610
                )
611
            );
612
        }
613
    }
614
615
    /**
616
     * @param AbstractUpdateImage $updateImageCommand
617
     * @return bool
618
     */
619
    private function updateImageAllowed(AbstractUpdateImage $updateImageCommand)
620
    {
621
        $image = $this->images->findImageByUUID($updateImageCommand->getMediaObjectId());
622
623
        // Don't update if the image is not found based on UUID.
624
        if (!$image) {
625
            return false;
626
        }
627
628
        // Update when copyright or description is changed.
629
        return !$updateImageCommand->getCopyrightHolder()->sameValueAs($image->getCopyrightHolder()) ||
630
            !$updateImageCommand->getDescription()->sameValueAs($image->getDescription());
631
    }
632
633
    /**
634
     * Remove an image.
635
     *
636
     * @param Image $image
637
     */
638
    public function removeImage(Image $image)
639
    {
640
        if ($this->images->contains($image)) {
641
            $this->apply(
642
                $this->createImageRemovedEvent($image)
643
            );
644
        }
645
    }
646
647
    /**
648
     * Make an existing image of the item the main image.
649
     *
650
     * @param Image $image
651
     */
652
    public function selectMainImage(Image $image)
653
    {
654
        if (!$this->images->contains($image)) {
655
            throw new \InvalidArgumentException('You can not select a random image to be main, it has to be added to the item.');
656
        }
657
658
        $oldMainImage = $this->images->getMain();
659
660
        if (!isset($oldMainImage) || $oldMainImage->getMediaObjectId() !== $image->getMediaObjectId()) {
661
            $this->apply(
662
                $this->createMainImageSelectedEvent($image)
663
            );
664
        }
665
    }
666
667
    /**
668
     * @param ImageCollection $imageCollection
669
     */
670
    public function importImages(ImageCollection $imageCollection)
671
    {
672
        $currentImageCollection = $this->images;
673
        $newMainImage = $imageCollection->getMain();
674
675
        $importImages = $imageCollection->toArray();
676
        $currentImages = $currentImageCollection->toArray();
677
678
        $compareImages = function (Image $a, Image $b) {
679
            $idA = $a->getMediaObjectId()->toNative();
680
            $idB = $b->getMediaObjectId()->toNative();
681
            return strcmp($idA, $idB);
682
        };
683
684
        /* @var Image[] $addedImages */
685
        $addedImages = array_udiff($importImages, $currentImages, $compareImages);
686
687
        /* @var Image[] $updatedImages */
688
        $updatedImages = array_uintersect($importImages, $currentImages, $compareImages);
689
690
        /* @var Image[] $removedImages */
691
        $removedImages = array_udiff($currentImages, $importImages, $compareImages);
692
693
        foreach ($addedImages as $addedImage) {
694
            $this->apply($this->createImageAddedEvent($addedImage));
695
        }
696
697
        foreach ($updatedImages as $updatedImage) {
698
            $this->apply(
699
                $this->createImageUpdatedEvent(
700
                    $updatedImage->getMediaObjectId(),
701
                    $updatedImage->getDescription(),
702
                    $updatedImage->getCopyrightHolder()
703
                )
704
            );
705
        }
706
707
        foreach ($removedImages as $removedImage) {
708
            $this->apply($this->createImageRemovedEvent($removedImage));
709
        }
710
711
        if ($newMainImage) {
712
            $this->apply($this->createMainImageSelectedEvent($newMainImage));
713
        }
714
    }
715
716
    /**
717
     * Delete the offer.
718
     */
719
    public function delete()
720
    {
721
        $this->apply(
722
            $this->createOfferDeletedEvent()
723
        );
724
    }
725
726
    /**
727
     * @param CultureFeed_Cdb_Item_Base $cdbItem
728
     */
729
    protected function importWorkflowStatus(CultureFeed_Cdb_Item_Base $cdbItem)
730
    {
731
        try {
732
            $workflowStatus = WorkflowStatus::fromNative($cdbItem->getWfStatus());
733
        } catch (\InvalidArgumentException $exception) {
734
            $workflowStatus = WorkflowStatus::READY_FOR_VALIDATION();
735
        }
736
        $this->workflowStatus = $workflowStatus;
737
    }
738
739
    /**
740
     * Publish the offer when it has workflowstatus draft.
741
     * @param \DateTimeInterface $publicationDate
742
     */
743
    public function publish(\DateTimeInterface $publicationDate)
744
    {
745
        $this->guardPublish() ?: $this->apply(
746
            $this->createPublishedEvent($publicationDate)
747
        );
748
    }
749
750
    /**
751
     * @return bool
752
     * @throws Exception
753
     */
754 View Code Duplication
    private function guardPublish()
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...
755
    {
756
        if ($this->workflowStatus === WorkflowStatus::READY_FOR_VALIDATION()) {
757
            return true; // nothing left to do if the offer has already been published
758
        }
759
760
        if ($this->workflowStatus !== WorkflowStatus::DRAFT()) {
761
            throw new Exception('You can not publish an offer that is not draft');
762
        }
763
764
        return false;
765
    }
766
767
    /**
768
     * Approve the offer when it's waiting for validation.
769
     */
770
    public function approve()
771
    {
772
        $this->guardApprove() ?: $this->apply($this->createApprovedEvent());
773
    }
774
775
    /**
776
     * @return bool
777
     * @throws Exception
778
     */
779 View Code Duplication
    private function guardApprove()
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...
780
    {
781
        if ($this->workflowStatus === WorkflowStatus::APPROVED()) {
782
            return true; // nothing left to do if the offer has already been approved
783
        }
784
785
        if ($this->workflowStatus !== WorkflowStatus::READY_FOR_VALIDATION()) {
786
            throw new Exception('You can not approve an offer that is not ready for validation');
787
        }
788
789
        return false;
790
    }
791
792
    /**
793
     * Reject an offer that is waiting for validation with a given reason.
794
     * @param StringLiteral $reason
795
     */
796
    public function reject(StringLiteral $reason)
797
    {
798
        $this->guardRejection($reason) ?: $this->apply($this->createRejectedEvent($reason));
799
    }
800
801
    public function flagAsDuplicate()
802
    {
803
        $reason = new StringLiteral(self::DUPLICATE_REASON);
804
        $this->guardRejection($reason) ?: $this->apply($this->createFlaggedAsDuplicate());
805
    }
806
807
    public function flagAsInappropriate()
808
    {
809
        $reason = new StringLiteral(self::INAPPROPRIATE_REASON);
810
        $this->guardRejection($reason) ?: $this->apply($this->createFlaggedAsInappropriate());
811
    }
812
813
    /**
814
     * @param StringLiteral $reason
815
     * @return bool
816
     *  false when the offer can still be rejected, true when the offer is already rejected for the same reason
817
     * @throws Exception
818
     */
819
    private function guardRejection(StringLiteral $reason)
820
    {
821
        if ($this->workflowStatus === WorkflowStatus::REJECTED()) {
822
            if ($this->rejectedReason && $reason->sameValueAs($this->rejectedReason)) {
823
                return true; // nothing left to do if the offer has already been rejected for the same reason
824
            } else {
825
                throw new Exception('The offer has already been rejected for another reason: ' . $this->rejectedReason);
826
            }
827
        }
828
829
        if ($this->workflowStatus !== WorkflowStatus::READY_FOR_VALIDATION()) {
830
            throw new Exception('You can not reject an offer that is not ready for validation');
831
        }
832
833
        return false;
834
    }
835
836
    /**
837
     * @param Title $title
838
     * @param Language $language
839
     * @return bool
840
     */
841 View Code Duplication
    private function isTitleChanged(Title $title, Language $language)
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...
842
    {
843
        $languageCode = $language->getCode();
844
845
        return !isset($this->titles[$languageCode]) ||
846
            !$title->sameValueAs($this->titles[$languageCode]);
847
    }
848
849
    /**
850
     * @param Description $description
851
     * @param Language $language
852
     * @return bool
853
     */
854 View Code Duplication
    private function isDescriptionChanged(Description $description, Language $language)
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...
855
    {
856
        $languageCode = $language->getCode();
857
858
        return !isset($this->descriptions[$languageCode]) ||
859
            !$description->sameValueAs($this->descriptions[$languageCode]);
860
    }
861
862
    /**
863
     * Overwrites or resets the main image and all media objects
864
     * by importing a new collection of images from UDB2.
865
     *
866
     * @param ImageCollection $images
867
     */
868
    public function importImagesFromUDB2(ImageCollection $images)
869
    {
870
        $this->apply($this->createImagesImportedFromUDB2($images));
871
    }
872
873
    /**
874
     * Overwrites or resets the main image and all media objects
875
     * by updating with a new collection of images from UDB2.
876
     *
877
     * @param ImageCollection $images
878
     */
879
    public function updateImagesFromUDB2(ImageCollection $images)
880
    {
881
        $this->apply($this->createImagesUpdatedFromUDB2($images));
882
    }
883
884
    /**
885
     * @param AbstractPublished $published
886
     */
887
    protected function applyPublished(AbstractPublished $published)
0 ignored issues
show
Unused Code introduced by
The parameter $published is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
888
    {
889
        $this->workflowStatus = WorkflowStatus::READY_FOR_VALIDATION();
890
    }
891
892
    /**
893
     * @param AbstractApproved $approved
894
     */
895
    protected function applyApproved(AbstractApproved $approved)
0 ignored issues
show
Unused Code introduced by
The parameter $approved is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
896
    {
897
        $this->workflowStatus = WorkflowStatus::APPROVED();
898
    }
899
900
    /**
901
     * @param AbstractRejected $rejected
902
     */
903
    protected function applyRejected(AbstractRejected $rejected)
904
    {
905
        $this->rejectedReason = $rejected->getReason();
906
        $this->workflowStatus = WorkflowStatus::REJECTED();
907
    }
908
909
    /**
910
     * @param AbstractFlaggedAsDuplicate $flaggedAsDuplicate
911
     */
912
    protected function applyFlaggedAsDuplicate(AbstractFlaggedAsDuplicate $flaggedAsDuplicate)
0 ignored issues
show
Unused Code introduced by
The parameter $flaggedAsDuplicate is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
913
    {
914
        $this->rejectedReason = new StringLiteral(self::DUPLICATE_REASON);
915
        $this->workflowStatus = WorkflowStatus::REJECTED();
916
    }
917
918
    /**
919
     * @param AbstractFlaggedAsInappropriate $flaggedAsInappropriate
920
     */
921
    protected function applyFlaggedAsInappropriate(AbstractFlaggedAsInappropriate $flaggedAsInappropriate)
0 ignored issues
show
Unused Code introduced by
The parameter $flaggedAsInappropriate is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
922
    {
923
        $this->rejectedReason = new StringLiteral(self::INAPPROPRIATE_REASON);
924
        $this->workflowStatus = WorkflowStatus::REJECTED();
925
    }
926
927
    protected function applyImageAdded(AbstractImageAdded $imageAdded)
928
    {
929
        $this->images = $this->images->with($imageAdded->getImage());
930
    }
931
932
    protected function applyImageUpdated(AbstractImageUpdated $imageUpdated)
933
    {
934
        $image = $this->images->findImageByUUID($imageUpdated->getMediaObjectId());
935
936
        $updatedImage = new Image(
937
            $image->getMediaObjectId(),
938
            $image->getMimeType(),
939
            new ImageDescription($imageUpdated->getDescription()->toNative()),
940
            new CopyrightHolder($imageUpdated->getCopyrightHolder()->toNative()),
941
            $image->getSourceLocation(),
942
            $image->getLanguage()
943
        );
944
945
        // Currently no other option to update an item inside a collection.
946
        $this->images = $this->images->without($image);
947
        $this->images = $this->images->with($updatedImage);
948
    }
949
950
    protected function applyImageRemoved(AbstractImageRemoved $imageRemoved)
951
    {
952
        $this->images = $this->images->without($imageRemoved->getImage());
953
    }
954
955
    protected function applyMainImageSelected(AbstractMainImageSelected $mainImageSelected)
956
    {
957
        $this->images = $this->images->withMain($mainImageSelected->getImage());
958
    }
959
960
    protected function applyOrganizerUpdated(AbstractOrganizerUpdated $organizerUpdated)
961
    {
962
        $this->organizerId = $organizerUpdated->getOrganizerId();
963
    }
964
965
    protected function applyOrganizerDeleted(AbstractOrganizerDeleted $organizerDeleted)
0 ignored issues
show
Unused Code introduced by
The parameter $organizerDeleted is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
966
    {
967
        $this->organizerId = null;
968
    }
969
970
    /**
971
     * @param AbstractImagesImportedFromUDB2 $imagesImportedFromUDB2
972
     */
973
    protected function applyImagesImportedFromUDB2(AbstractImagesImportedFromUDB2 $imagesImportedFromUDB2)
974
    {
975
        $this->applyUdb2ImagesEvent($imagesImportedFromUDB2);
976
    }
977
978
    /**
979
     * @param AbstractImagesUpdatedFromUDB2 $imagesUpdatedFromUDB2
980
     */
981
    protected function applyImagesUpdatedFromUDB2(AbstractImagesUpdatedFromUDB2 $imagesUpdatedFromUDB2)
982
    {
983
        $this->applyUdb2ImagesEvent($imagesUpdatedFromUDB2);
984
    }
985
986
    /**
987
     * This indirect apply method can be called internally to deal with images coming from UDB2.
988
     * Imports from UDB2 only contain the native Dutch content.
989
     * @see https://github.com/cultuurnet/udb3-udb2-bridge/blob/db0a7ab2444f55bb3faae3d59b82b39aaeba253b/test/Media/ImageCollectionFactoryTest.php#L79-L103
990
     * Because of this we have to make sure translated images are left in place.
991
     *
992
     * @param AbstractImagesEvent $imagesEvent
993
     */
994
    protected function applyUdb2ImagesEvent(AbstractImagesEvent $imagesEvent)
995
    {
996
        $newMainImage = $imagesEvent->getImages()->getMain();
997
        $dutchImagesList = $imagesEvent->getImages()->toArray();
998
        $translatedImagesList = array_filter(
999
            $this->images->toArray(),
1000
            function (Image $image) {
1001
                return $image->getLanguage()->getCode() !== 'nl';
1002
            }
1003
        );
1004
1005
        $imagesList = array_merge($dutchImagesList, $translatedImagesList);
1006
        $images = ImageCollection::fromArray($imagesList);
1007
1008
        $this->images = isset($newMainImage) ? $images->withMain($newMainImage) : $images;
1009
    }
1010
1011
    /**
1012
     * @param Label $label
1013
     * @return AbstractLabelAdded
1014
     */
1015
    abstract protected function createLabelAddedEvent(Label $label);
1016
1017
    /**
1018
     * @param Label $label
1019
     * @return AbstractLabelRemoved
1020
     */
1021
    abstract protected function createLabelRemovedEvent(Label $label);
1022
1023
    /**
1024
     * @param Labels $labels
1025
     * @return AbstractLabelsImported
1026
     */
1027
    abstract protected function createLabelsImportedEvent(Labels $labels);
1028
1029
    /**
1030
     * @param Language $language
1031
     * @param StringLiteral $title
1032
     * @return AbstractTitleTranslated
1033
     */
1034
    abstract protected function createTitleTranslatedEvent(Language $language, StringLiteral $title);
1035
1036
    /**
1037
     * @param Language $language
1038
     * @param StringLiteral $description
1039
     * @return AbstractDescriptionTranslated
1040
     */
1041
    abstract protected function createDescriptionTranslatedEvent(Language $language, StringLiteral $description);
1042
1043
    /**
1044
     * @param Image $image
1045
     * @return AbstractImageAdded
1046
     */
1047
    abstract protected function createImageAddedEvent(Image $image);
1048
1049
    /**
1050
     * @param Image $image
1051
     * @return AbstractImageRemoved
1052
     */
1053
    abstract protected function createImageRemovedEvent(Image $image);
1054
1055
    /**
1056
     * @param UUID $uuid
1057
     * @param StringLiteral $description
1058
     * @param StringLiteral $copyrightHolder
1059
     * @return AbstractImageUpdated
1060
     */
1061
    abstract protected function createImageUpdatedEvent(
1062
        UUID $uuid,
1063
        StringLiteral $description,
1064
        StringLiteral $copyrightHolder
1065
    );
1066
1067
    /**
1068
     * @param Image $image
1069
     * @return AbstractMainImageSelected
1070
     */
1071
    abstract protected function createMainImageSelectedEvent(Image $image);
1072
1073
    /**
1074
     * @return AbstractOfferDeleted
1075
     */
1076
    abstract protected function createOfferDeletedEvent();
1077
1078
    /**
1079
     * @param Title $title
1080
     * @return AbstractTitleUpdated
1081
     */
1082
    abstract protected function createTitleUpdatedEvent(Title $title);
1083
1084
    /**
1085
     * @param string $description
1086
     * @return AbstractDescriptionUpdated
1087
     */
1088
    abstract protected function createDescriptionUpdatedEvent($description);
1089
1090
    /**
1091
     * @param Calendar $calendar
1092
     * @return AbstractCalendarUpdated
1093
     */
1094
    abstract protected function createCalendarUpdatedEvent(Calendar $calendar);
1095
1096
    /**
1097
     * @param string $typicalAgeRange
1098
     * @return AbstractTypicalAgeRangeUpdated
1099
     */
1100
    abstract protected function createTypicalAgeRangeUpdatedEvent($typicalAgeRange);
1101
1102
    /**
1103
     * @return AbstractTypicalAgeRangeDeleted
1104
     */
1105
    abstract protected function createTypicalAgeRangeDeletedEvent();
1106
1107
    /**
1108
     * @param string $organizerId
1109
     * @return AbstractOrganizerUpdated
1110
     */
1111
    abstract protected function createOrganizerUpdatedEvent($organizerId);
1112
1113
    /**
1114
     * @param string $organizerId
1115
     * @return AbstractOrganizerDeleted
1116
     */
1117
    abstract protected function createOrganizerDeletedEvent($organizerId);
1118
1119
    /**
1120
     * @param ContactPoint $contactPoint
1121
     * @return AbstractContactPointUpdated
1122
     */
1123
    abstract protected function createContactPointUpdatedEvent(ContactPoint $contactPoint);
1124
1125
    /**
1126
     * @param Coordinates $coordinates
1127
     * @return AbstractGeoCoordinatesUpdated
1128
     */
1129
    abstract protected function createGeoCoordinatesUpdatedEvent(Coordinates $coordinates);
1130
1131
    /**
1132
     * @param BookingInfo $bookingInfo
1133
     * @return AbstractBookingInfoUpdated
1134
     */
1135
    abstract protected function createBookingInfoUpdatedEvent(BookingInfo $bookingInfo);
1136
1137
    /**
1138
     * @param PriceInfo $priceInfo
1139
     * @return AbstractPriceInfoUpdated
1140
     */
1141
    abstract protected function createPriceInfoUpdatedEvent(PriceInfo $priceInfo);
1142
1143
    /**
1144
     * @param \DateTimeInterface $publicationDate
1145
     * @return AbstractPublished
1146
     */
1147
    abstract protected function createPublishedEvent(\DateTimeInterface $publicationDate);
1148
1149
    /**
1150
     * @return AbstractApproved
1151
     */
1152
    abstract protected function createApprovedEvent();
1153
1154
    /**
1155
     * @param StringLiteral $reason
1156
     * @return AbstractRejected
1157
     */
1158
    abstract protected function createRejectedEvent(StringLiteral $reason);
1159
1160
    /**
1161
     * @return AbstractFlaggedAsDuplicate
1162
     */
1163
    abstract protected function createFlaggedAsDuplicate();
1164
1165
    /**
1166
     * @return AbstractFlaggedAsInappropriate
1167
     */
1168
    abstract protected function createFlaggedAsInappropriate();
1169
1170
    /**
1171
     * @param ImageCollection $images
1172
     * @return AbstractImagesImportedFromUDB2
1173
     */
1174
    abstract protected function createImagesImportedFromUDB2(ImageCollection $images);
1175
1176
    /**
1177
     * @param ImageCollection $images
1178
     * @return AbstractImagesUpdatedFromUDB2
1179
     */
1180
    abstract protected function createImagesUpdatedFromUDB2(ImageCollection $images);
1181
1182
    /**
1183
     * @param EventType $type
1184
     * @return AbstractTypeUpdated
1185
     */
1186
    abstract protected function createTypeUpdatedEvent(EventType $type);
1187
1188
    /**
1189
     * @param Theme $theme
1190
     * @return AbstractThemeUpdated
1191
     */
1192
    abstract protected function createThemeUpdatedEvent(Theme $theme);
1193
1194
    /**
1195
     * @param array $facilities
1196
     * @return AbstractFacilitiesUpdated
1197
     */
1198
    abstract protected function createFacilitiesUpdatedEvent(array $facilities);
1199
}
1200