Completed
Pull Request — master (#352)
by
unknown
04:54
created

Offer::importImages()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 45
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 45
rs 8.439
cc 5
eloc 24
nc 16
nop 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 Description $description
337
     * @param Language $language
338
     */
339
    public function updateDescription(Description $description, Language $language)
340
    {
341
        if ($this->isDescriptionChanged($description, $language)) {
342
            if ($language->getCode() !== $this->mainLanguage->getCode()) {
343
                $event = $this->createDescriptionTranslatedEvent($language, $description);
344
            } else {
345
                $event = $this->createDescriptionUpdatedEvent((string) $description);
346
            }
347
348
            $this->apply($event);
349
        }
350
    }
351
352
    /**
353
     * @param Calendar $calendar
354
     */
355
    public function updateCalendar(Calendar $calendar)
356
    {
357
        if (is_null($this->calendar) || !$this->calendar->sameAs($calendar)) {
358
            $this->apply(
359
                $this->createCalendarUpdatedEvent($calendar)
360
            );
361
        }
362
    }
363
364
    /**
365
     * @param AbstractCalendarUpdated $calendarUpdated
366
     */
367
    protected function applyCalendarUpdated(AbstractCalendarUpdated $calendarUpdated)
368
    {
369
        $this->calendar = $calendarUpdated->getCalendar();
370
    }
371
372
    /**
373
     * @param string $typicalAgeRange
374
     */
375
    public function updateTypicalAgeRange($typicalAgeRange)
376
    {
377
        $typicalAgeRangeUpdatedEvent = $this->createTypicalAgeRangeUpdatedEvent($typicalAgeRange);
378
379
        if (empty($this->typicalAgeRange) || !$this->typicalAgeRange->sameAs($typicalAgeRangeUpdatedEvent->getTypicalAgeRange())) {
380
            $this->apply($typicalAgeRangeUpdatedEvent);
381
        }
382
    }
383
384
    /**
385
     * @param AbstractTypicalAgeRangeUpdated $typicalAgeRangeUpdated
386
     */
387
    protected function applyTypicalAgeRangeUpdated(AbstractTypicalAgeRangeUpdated $typicalAgeRangeUpdated)
388
    {
389
        $this->typicalAgeRange = $typicalAgeRangeUpdated->getTypicalAgeRange();
390
    }
391
392
    public function deleteTypicalAgeRange()
393
    {
394
        if (!is_null($this->typicalAgeRange)) {
395
            $this->apply(
396
                $this->createTypicalAgeRangeDeletedEvent()
397
            );
398
        }
399
    }
400
401
    /**
402
     * @param AbstractTypicalAgeRangeDeleted $typicalAgeRangeDeleted
403
     */
404
    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...
405
    {
406
        $this->typicalAgeRange = null;
407
    }
408
409
    /**
410
     * @param string $organizerId
411
     */
412
    public function updateOrganizer($organizerId)
413
    {
414
        if ($this->organizerId !== $organizerId) {
415
            $this->apply(
416
                $this->createOrganizerUpdatedEvent($organizerId)
417
            );
418
        }
419
    }
420
421
    /**
422
     * Delete the given organizer.
423
     *
424
     * @param string $organizerId
425
     */
426
    public function deleteOrganizer($organizerId)
427
    {
428
        if ($this->organizerId === $organizerId) {
429
            $this->apply(
430
                $this->createOrganizerDeletedEvent($organizerId)
431
            );
432
        }
433
    }
434
435
    /**
436
     * Delete the current organizer regardless of the id.
437
     */
438
    public function deleteCurrentOrganizer()
439
    {
440
        if (!is_null($this->organizerId)) {
441
            $this->apply(
442
                $this->createOrganizerDeletedEvent($this->organizerId)
443
            );
444
        }
445
    }
446
447
    /**
448
     * Updated the contact info.
449
     * @param ContactPoint $contactPoint
450
     */
451
    public function updateContactPoint(ContactPoint $contactPoint)
452
    {
453
        if (is_null($this->contactPoint) || !$this->contactPoint->sameAs($contactPoint)) {
454
            $this->apply(
455
                $this->createContactPointUpdatedEvent($contactPoint)
456
            );
457
        }
458
    }
459
460
    /**
461
     * @param AbstractContactPointUpdated $contactPointUpdated
462
     */
463
    protected function applyContactPointUpdated(AbstractContactPointUpdated $contactPointUpdated)
464
    {
465
        $this->contactPoint = $contactPointUpdated->getContactPoint();
466
    }
467
468
    /**
469
     * @param Coordinates $coordinates
470
     */
471
    public function updateGeoCoordinates(Coordinates $coordinates)
472
    {
473
        // Note: DON'T compare to previous coordinates and apply only on
474
        // changes. Various projectors expect GeoCoordinatesUpdated after
475
        // MajorInfoUpdated and PlaceUpdatedFromUDB2, even if the address
476
        // and thus the coordinates haven't actually changed.
477
        $this->apply(
478
            $this->createGeoCoordinatesUpdatedEvent($coordinates)
479
        );
480
    }
481
482
    /**
483
     * Updated the booking info.
484
     *
485
     * @param BookingInfo $bookingInfo
486
     */
487
    public function updateBookingInfo(BookingInfo $bookingInfo)
488
    {
489
        if (is_null($this->bookingInfo) || !$this->bookingInfo->sameAs($bookingInfo)) {
490
            $this->apply(
491
                $this->createBookingInfoUpdatedEvent($bookingInfo)
492
            );
493
        }
494
    }
495
496
    /**
497
     * @param AbstractBookingInfoUpdated $bookingInfoUpdated
498
     */
499
    public function applyBookingInfoUpdated(AbstractBookingInfoUpdated $bookingInfoUpdated)
500
    {
501
        $this->bookingInfo = $bookingInfoUpdated->getBookingInfo();
502
    }
503
504
    /**
505
     * @param PriceInfo $priceInfo
506
     */
507
    public function updatePriceInfo(PriceInfo $priceInfo)
508
    {
509
        if (is_null($this->priceInfo) || $priceInfo->serialize() !== $this->priceInfo->serialize()) {
510
            $this->apply(
511
                $this->createPriceInfoUpdatedEvent($priceInfo)
512
            );
513
        }
514
    }
515
516
    /**
517
     * @param AbstractPriceInfoUpdated $priceInfoUpdated
518
     */
519
    protected function applyPriceInfoUpdated(AbstractPriceInfoUpdated $priceInfoUpdated)
520
    {
521
        $this->priceInfo = $priceInfoUpdated->getPriceInfo();
522
    }
523
524
    /**
525
     * @param AbstractLabelAdded $labelAdded
526
     */
527
    protected function applyLabelAdded(AbstractLabelAdded $labelAdded)
528
    {
529
        $this->labels = $this->labels->with($labelAdded->getLabel());
530
    }
531
532
    /**
533
     * @param AbstractLabelRemoved $labelRemoved
534
     */
535
    protected function applyLabelRemoved(AbstractLabelRemoved $labelRemoved)
536
    {
537
        $this->labels = $this->labels->without($labelRemoved->getLabel());
538
    }
539
540
    /**
541
     * @param AbstractThemeUpdated $themeUpdated
542
     */
543
    protected function applyThemeUpdated(AbstractThemeUpdated $themeUpdated)
544
    {
545
        $this->themeId = $themeUpdated->getTheme()->getId();
546
    }
547
548
    /**
549
     * @param AbstractTypeUpdated $themeUpdated
550
     */
551
    protected function applyTypeUpdated(AbstractTypeUpdated $themeUpdated)
552
    {
553
        $this->typeId = $themeUpdated->getType()->getId();
554
    }
555
556
    protected function applyDescriptionUpdated(AbstractDescriptionUpdated $descriptionUpdated)
557
    {
558
        $mainLanguageCode = $this->mainLanguage->getCode();
559
        $this->descriptions[$mainLanguageCode] = new Description($descriptionUpdated->getDescription());
560
    }
561
562
    protected function applyDescriptionTranslated(AbstractDescriptionTranslated $descriptionTranslated)
563
    {
564
        $languageCode = $descriptionTranslated->getLanguage()->getCode();
565
        $this->descriptions[$languageCode] = $descriptionTranslated->getDescription();
566
    }
567
568
    /**
569
     * Add a new image.
570
     *
571
     * @param Image $image
572
     */
573
    public function addImage(Image $image)
574
    {
575
        if (!$this->images->contains($image)) {
576
            $this->apply(
577
                $this->createImageAddedEvent($image)
578
            );
579
        }
580
    }
581
582
    /**
583
     * @param AbstractUpdateImage $updateImageCommand
584
     */
585
    public function updateImage(AbstractUpdateImage $updateImageCommand)
586
    {
587
        if ($this->updateImageAllowed($updateImageCommand)) {
588
            $this->apply(
589
                $this->createImageUpdatedEvent(
590
                    $updateImageCommand->getMediaObjectId(),
591
                    $updateImageCommand->getDescription(),
592
                    $updateImageCommand->getCopyrightHolder()
593
                )
594
            );
595
        }
596
    }
597
598
    /**
599
     * @param AbstractUpdateImage $updateImageCommand
600
     * @return bool
601
     */
602
    private function updateImageAllowed(AbstractUpdateImage $updateImageCommand)
603
    {
604
        $image = $this->images->findImageByUUID($updateImageCommand->getMediaObjectId());
605
606
        // Don't update if the image is not found based on UUID.
607
        if (!$image) {
608
            return false;
609
        }
610
611
        // Update when copyright or description is changed.
612
        return !$updateImageCommand->getCopyrightHolder()->sameValueAs($image->getCopyrightHolder()) ||
613
            !$updateImageCommand->getDescription()->sameValueAs($image->getDescription());
614
    }
615
616
    /**
617
     * Remove an image.
618
     *
619
     * @param Image $image
620
     */
621
    public function removeImage(Image $image)
622
    {
623
        if ($this->images->contains($image)) {
624
            $this->apply(
625
                $this->createImageRemovedEvent($image)
626
            );
627
        }
628
    }
629
630
    /**
631
     * Make an existing image of the item the main image.
632
     *
633
     * @param Image $image
634
     */
635
    public function selectMainImage(Image $image)
636
    {
637
        if (!$this->images->contains($image)) {
638
            throw new \InvalidArgumentException('You can not select a random image to be main, it has to be added to the item.');
639
        }
640
641
        $oldMainImage = $this->images->getMain();
642
643
        if (!isset($oldMainImage) || $oldMainImage->getMediaObjectId() !== $image->getMediaObjectId()) {
644
            $this->apply(
645
                $this->createMainImageSelectedEvent($image)
646
            );
647
        }
648
    }
649
650
    /**
651
     * @param ImageCollection $imageCollection
652
     */
653
    public function importImages(ImageCollection $imageCollection)
654
    {
655
        $currentImageCollection = $this->images;
656
        $newMainImage = $imageCollection->getMain();
657
658
        $newImages = $imageCollection->toArray();
659
        $currentImages = $currentImageCollection->toArray();
660
661
        $compareImages = function (Image $a, Image $b) {
662
            $idA = $a->getMediaObjectId()->toNative();
663
            $idB = $b->getMediaObjectId()->toNative();
664
            return strcmp($idA, $idB);
665
        };
666
667
        /* @var Image[] $addedImages */
668
        $addedImages = array_udiff($newImages, $currentImages, $compareImages);
669
670
        /* @var Image[] $updatedImages */
671
        $updatedImages = array_uintersect($newImages, $currentImages, $compareImages);
672
673
        /* @var Image[] $removedImages */
674
        $removedImages = array_udiff($currentImages, $newImages, $compareImages);
675
676
        foreach ($addedImages as $addedImage) {
677
            $this->apply($this->createImageAddedEvent($addedImage));
678
        }
679
680
        foreach ($updatedImages as $updatedImage) {
681
            $this->apply(
682
                $this->createImageUpdatedEvent(
683
                    $updatedImage->getMediaObjectId(),
684
                    $updatedImage->getDescription(),
685
                    $updatedImage->getCopyrightHolder()
686
                )
687
            );
688
        }
689
690
        foreach ($removedImages as $removedImage) {
691
            $this->apply($this->createImageRemovedEvent($removedImage));
692
        }
693
694
        if ($newMainImage) {
695
            $this->apply($this->createMainImageSelectedEvent($newMainImage));
696
        }
697
    }
698
699
    /**
700
     * Delete the offer.
701
     */
702
    public function delete()
703
    {
704
        $this->apply(
705
            $this->createOfferDeletedEvent()
706
        );
707
    }
708
709
    /**
710
     * @param CultureFeed_Cdb_Item_Base $cdbItem
711
     */
712
    protected function importWorkflowStatus(CultureFeed_Cdb_Item_Base $cdbItem)
713
    {
714
        try {
715
            $workflowStatus = WorkflowStatus::fromNative($cdbItem->getWfStatus());
716
        } catch (\InvalidArgumentException $exception) {
717
            $workflowStatus = WorkflowStatus::READY_FOR_VALIDATION();
718
        }
719
        $this->workflowStatus = $workflowStatus;
720
    }
721
722
    /**
723
     * Publish the offer when it has workflowstatus draft.
724
     * @param \DateTimeInterface $publicationDate
725
     */
726
    public function publish(\DateTimeInterface $publicationDate)
727
    {
728
        $this->guardPublish() ?: $this->apply(
729
            $this->createPublishedEvent($publicationDate)
730
        );
731
    }
732
733
    /**
734
     * @return bool
735
     * @throws Exception
736
     */
737 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...
738
    {
739
        if ($this->workflowStatus === WorkflowStatus::READY_FOR_VALIDATION()) {
740
            return true; // nothing left to do if the offer has already been published
741
        }
742
743
        if ($this->workflowStatus !== WorkflowStatus::DRAFT()) {
744
            throw new Exception('You can not publish an offer that is not draft');
745
        }
746
747
        return false;
748
    }
749
750
    /**
751
     * Approve the offer when it's waiting for validation.
752
     */
753
    public function approve()
754
    {
755
        $this->guardApprove() ?: $this->apply($this->createApprovedEvent());
756
    }
757
758
    /**
759
     * @return bool
760
     * @throws Exception
761
     */
762 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...
763
    {
764
        if ($this->workflowStatus === WorkflowStatus::APPROVED()) {
765
            return true; // nothing left to do if the offer has already been approved
766
        }
767
768
        if ($this->workflowStatus !== WorkflowStatus::READY_FOR_VALIDATION()) {
769
            throw new Exception('You can not approve an offer that is not ready for validation');
770
        }
771
772
        return false;
773
    }
774
775
    /**
776
     * Reject an offer that is waiting for validation with a given reason.
777
     * @param StringLiteral $reason
778
     */
779
    public function reject(StringLiteral $reason)
780
    {
781
        $this->guardRejection($reason) ?: $this->apply($this->createRejectedEvent($reason));
782
    }
783
784
    public function flagAsDuplicate()
785
    {
786
        $reason = new StringLiteral(self::DUPLICATE_REASON);
787
        $this->guardRejection($reason) ?: $this->apply($this->createFlaggedAsDuplicate());
788
    }
789
790
    public function flagAsInappropriate()
791
    {
792
        $reason = new StringLiteral(self::INAPPROPRIATE_REASON);
793
        $this->guardRejection($reason) ?: $this->apply($this->createFlaggedAsInappropriate());
794
    }
795
796
    /**
797
     * @param StringLiteral $reason
798
     * @return bool
799
     *  false when the offer can still be rejected, true when the offer is already rejected for the same reason
800
     * @throws Exception
801
     */
802
    private function guardRejection(StringLiteral $reason)
803
    {
804
        if ($this->workflowStatus === WorkflowStatus::REJECTED()) {
805
            if ($this->rejectedReason && $reason->sameValueAs($this->rejectedReason)) {
806
                return true; // nothing left to do if the offer has already been rejected for the same reason
807
            } else {
808
                throw new Exception('The offer has already been rejected for another reason: ' . $this->rejectedReason);
809
            }
810
        }
811
812
        if ($this->workflowStatus !== WorkflowStatus::READY_FOR_VALIDATION()) {
813
            throw new Exception('You can not reject an offer that is not ready for validation');
814
        }
815
816
        return false;
817
    }
818
819
    /**
820
     * @param Title $title
821
     * @param Language $language
822
     * @return bool
823
     */
824 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...
825
    {
826
        $languageCode = $language->getCode();
827
828
        return !isset($this->titles[$languageCode]) ||
829
            !$title->sameValueAs($this->titles[$languageCode]);
830
    }
831
832
    /**
833
     * @param Description $description
834
     * @param Language $language
835
     * @return bool
836
     */
837 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...
838
    {
839
        $languageCode = $language->getCode();
840
841
        return !isset($this->descriptions[$languageCode]) ||
842
            !$description->sameValueAs($this->descriptions[$languageCode]);
843
    }
844
845
    /**
846
     * Overwrites or resets the main image and all media objects
847
     * by importing a new collection of images from UDB2.
848
     *
849
     * @param ImageCollection $images
850
     */
851
    public function importImagesFromUDB2(ImageCollection $images)
852
    {
853
        $this->apply($this->createImagesImportedFromUDB2($images));
854
    }
855
856
    /**
857
     * Overwrites or resets the main image and all media objects
858
     * by updating with a new collection of images from UDB2.
859
     *
860
     * @param ImageCollection $images
861
     */
862
    public function updateImagesFromUDB2(ImageCollection $images)
863
    {
864
        $this->apply($this->createImagesUpdatedFromUDB2($images));
865
    }
866
867
    /**
868
     * @param AbstractPublished $published
869
     */
870
    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...
871
    {
872
        $this->workflowStatus = WorkflowStatus::READY_FOR_VALIDATION();
873
    }
874
875
    /**
876
     * @param AbstractApproved $approved
877
     */
878
    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...
879
    {
880
        $this->workflowStatus = WorkflowStatus::APPROVED();
881
    }
882
883
    /**
884
     * @param AbstractRejected $rejected
885
     */
886
    protected function applyRejected(AbstractRejected $rejected)
887
    {
888
        $this->rejectedReason = $rejected->getReason();
889
        $this->workflowStatus = WorkflowStatus::REJECTED();
890
    }
891
892
    /**
893
     * @param AbstractFlaggedAsDuplicate $flaggedAsDuplicate
894
     */
895
    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...
896
    {
897
        $this->rejectedReason = new StringLiteral(self::DUPLICATE_REASON);
898
        $this->workflowStatus = WorkflowStatus::REJECTED();
899
    }
900
901
    /**
902
     * @param AbstractFlaggedAsInappropriate $flaggedAsInappropriate
903
     */
904
    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...
905
    {
906
        $this->rejectedReason = new StringLiteral(self::INAPPROPRIATE_REASON);
907
        $this->workflowStatus = WorkflowStatus::REJECTED();
908
    }
909
910
    protected function applyImageAdded(AbstractImageAdded $imageAdded)
911
    {
912
        $this->images = $this->images->with($imageAdded->getImage());
913
    }
914
915
    protected function applyImageUpdated(AbstractImageUpdated $imageUpdated)
916
    {
917
        $image = $this->images->findImageByUUID($imageUpdated->getMediaObjectId());
918
919
        $updatedImage = new Image(
920
            $image->getMediaObjectId(),
921
            $image->getMimeType(),
922
            new ImageDescription($imageUpdated->getDescription()->toNative()),
923
            new CopyrightHolder($imageUpdated->getCopyrightHolder()->toNative()),
924
            $image->getSourceLocation(),
925
            $image->getLanguage()
926
        );
927
928
        // Currently no other option to update an item inside a collection.
929
        $this->images = $this->images->without($image);
930
        $this->images = $this->images->with($updatedImage);
931
    }
932
933
    protected function applyImageRemoved(AbstractImageRemoved $imageRemoved)
934
    {
935
        $this->images = $this->images->without($imageRemoved->getImage());
936
    }
937
938
    protected function applyMainImageSelected(AbstractMainImageSelected $mainImageSelected)
939
    {
940
        $this->images = $this->images->withMain($mainImageSelected->getImage());
941
    }
942
943
    protected function applyOrganizerUpdated(AbstractOrganizerUpdated $organizerUpdated)
944
    {
945
        $this->organizerId = $organizerUpdated->getOrganizerId();
946
    }
947
948
    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...
949
    {
950
        $this->organizerId = null;
951
    }
952
953
    /**
954
     * @param AbstractImagesImportedFromUDB2 $imagesImportedFromUDB2
955
     */
956
    protected function applyImagesImportedFromUDB2(AbstractImagesImportedFromUDB2 $imagesImportedFromUDB2)
957
    {
958
        $this->applyUdb2ImagesEvent($imagesImportedFromUDB2);
959
    }
960
961
    /**
962
     * @param AbstractImagesUpdatedFromUDB2 $imagesUpdatedFromUDB2
963
     */
964
    protected function applyImagesUpdatedFromUDB2(AbstractImagesUpdatedFromUDB2 $imagesUpdatedFromUDB2)
965
    {
966
        $this->applyUdb2ImagesEvent($imagesUpdatedFromUDB2);
967
    }
968
969
    /**
970
     * This indirect apply method can be called internally to deal with images coming from UDB2.
971
     * Imports from UDB2 only contain the native Dutch content.
972
     * @see https://github.com/cultuurnet/udb3-udb2-bridge/blob/db0a7ab2444f55bb3faae3d59b82b39aaeba253b/test/Media/ImageCollectionFactoryTest.php#L79-L103
973
     * Because of this we have to make sure translated images are left in place.
974
     *
975
     * @param AbstractImagesEvent $imagesEvent
976
     */
977
    protected function applyUdb2ImagesEvent(AbstractImagesEvent $imagesEvent)
978
    {
979
        $newMainImage = $imagesEvent->getImages()->getMain();
980
        $dutchImagesList = $imagesEvent->getImages()->toArray();
981
        $translatedImagesList = array_filter(
982
            $this->images->toArray(),
983
            function (Image $image) {
984
                return $image->getLanguage()->getCode() !== 'nl';
985
            }
986
        );
987
988
        $imagesList = array_merge($dutchImagesList, $translatedImagesList);
989
        $images = ImageCollection::fromArray($imagesList);
990
991
        $this->images = isset($newMainImage) ? $images->withMain($newMainImage) : $images;
992
    }
993
994
    /**
995
     * @param Label $label
996
     * @return AbstractLabelAdded
997
     */
998
    abstract protected function createLabelAddedEvent(Label $label);
999
1000
    /**
1001
     * @param Label $label
1002
     * @return AbstractLabelRemoved
1003
     */
1004
    abstract protected function createLabelRemovedEvent(Label $label);
1005
1006
    /**
1007
     * @param Labels $labels
1008
     * @return AbstractLabelsImported
1009
     */
1010
    abstract protected function createLabelsImportedEvent(Labels $labels);
1011
1012
    /**
1013
     * @param Language $language
1014
     * @param StringLiteral $title
1015
     * @return AbstractTitleTranslated
1016
     */
1017
    abstract protected function createTitleTranslatedEvent(Language $language, StringLiteral $title);
1018
1019
    /**
1020
     * @param Language $language
1021
     * @param StringLiteral $description
1022
     * @return AbstractDescriptionTranslated
1023
     */
1024
    abstract protected function createDescriptionTranslatedEvent(Language $language, StringLiteral $description);
1025
1026
    /**
1027
     * @param Image $image
1028
     * @return AbstractImageAdded
1029
     */
1030
    abstract protected function createImageAddedEvent(Image $image);
1031
1032
    /**
1033
     * @param Image $image
1034
     * @return AbstractImageRemoved
1035
     */
1036
    abstract protected function createImageRemovedEvent(Image $image);
1037
1038
    /**
1039
     * @param UUID $uuid
1040
     * @param StringLiteral $description
1041
     * @param StringLiteral $copyrightHolder
1042
     * @return AbstractImageUpdated
1043
     */
1044
    abstract protected function createImageUpdatedEvent(
1045
        UUID $uuid,
1046
        StringLiteral $description,
1047
        StringLiteral $copyrightHolder
1048
    );
1049
1050
    /**
1051
     * @param Image $image
1052
     * @return AbstractMainImageSelected
1053
     */
1054
    abstract protected function createMainImageSelectedEvent(Image $image);
1055
1056
    /**
1057
     * @return AbstractOfferDeleted
1058
     */
1059
    abstract protected function createOfferDeletedEvent();
1060
1061
    /**
1062
     * @param Title $title
1063
     * @return AbstractTitleUpdated
1064
     */
1065
    abstract protected function createTitleUpdatedEvent(Title $title);
1066
1067
    /**
1068
     * @param string $description
1069
     * @return AbstractDescriptionUpdated
1070
     */
1071
    abstract protected function createDescriptionUpdatedEvent($description);
1072
1073
    /**
1074
     * @param Calendar $calendar
1075
     * @return AbstractCalendarUpdated
1076
     */
1077
    abstract protected function createCalendarUpdatedEvent(Calendar $calendar);
1078
1079
    /**
1080
     * @param string $typicalAgeRange
1081
     * @return AbstractTypicalAgeRangeUpdated
1082
     */
1083
    abstract protected function createTypicalAgeRangeUpdatedEvent($typicalAgeRange);
1084
1085
    /**
1086
     * @return AbstractTypicalAgeRangeDeleted
1087
     */
1088
    abstract protected function createTypicalAgeRangeDeletedEvent();
1089
1090
    /**
1091
     * @param string $organizerId
1092
     * @return AbstractOrganizerUpdated
1093
     */
1094
    abstract protected function createOrganizerUpdatedEvent($organizerId);
1095
1096
    /**
1097
     * @param string $organizerId
1098
     * @return AbstractOrganizerDeleted
1099
     */
1100
    abstract protected function createOrganizerDeletedEvent($organizerId);
1101
1102
    /**
1103
     * @param ContactPoint $contactPoint
1104
     * @return AbstractContactPointUpdated
1105
     */
1106
    abstract protected function createContactPointUpdatedEvent(ContactPoint $contactPoint);
1107
1108
    /**
1109
     * @param Coordinates $coordinates
1110
     * @return AbstractGeoCoordinatesUpdated
1111
     */
1112
    abstract protected function createGeoCoordinatesUpdatedEvent(Coordinates $coordinates);
1113
1114
    /**
1115
     * @param BookingInfo $bookingInfo
1116
     * @return AbstractBookingInfoUpdated
1117
     */
1118
    abstract protected function createBookingInfoUpdatedEvent(BookingInfo $bookingInfo);
1119
1120
    /**
1121
     * @param PriceInfo $priceInfo
1122
     * @return AbstractPriceInfoUpdated
1123
     */
1124
    abstract protected function createPriceInfoUpdatedEvent(PriceInfo $priceInfo);
1125
1126
    /**
1127
     * @param \DateTimeInterface $publicationDate
1128
     * @return AbstractPublished
1129
     */
1130
    abstract protected function createPublishedEvent(\DateTimeInterface $publicationDate);
1131
1132
    /**
1133
     * @return AbstractApproved
1134
     */
1135
    abstract protected function createApprovedEvent();
1136
1137
    /**
1138
     * @param StringLiteral $reason
1139
     * @return AbstractRejected
1140
     */
1141
    abstract protected function createRejectedEvent(StringLiteral $reason);
1142
1143
    /**
1144
     * @return AbstractFlaggedAsDuplicate
1145
     */
1146
    abstract protected function createFlaggedAsDuplicate();
1147
1148
    /**
1149
     * @return AbstractFlaggedAsInappropriate
1150
     */
1151
    abstract protected function createFlaggedAsInappropriate();
1152
1153
    /**
1154
     * @param ImageCollection $images
1155
     * @return AbstractImagesImportedFromUDB2
1156
     */
1157
    abstract protected function createImagesImportedFromUDB2(ImageCollection $images);
1158
1159
    /**
1160
     * @param ImageCollection $images
1161
     * @return AbstractImagesUpdatedFromUDB2
1162
     */
1163
    abstract protected function createImagesUpdatedFromUDB2(ImageCollection $images);
1164
1165
    /**
1166
     * @param EventType $type
1167
     * @return AbstractTypeUpdated
1168
     */
1169
    abstract protected function createTypeUpdatedEvent(EventType $type);
1170
1171
    /**
1172
     * @param Theme $theme
1173
     * @return AbstractThemeUpdated
1174
     */
1175
    abstract protected function createThemeUpdatedEvent(Theme $theme);
1176
1177
    /**
1178
     * @param array $facilities
1179
     * @return AbstractFacilitiesUpdated
1180
     */
1181
    abstract protected function createFacilitiesUpdatedEvent(array $facilities);
1182
}
1183