Completed
Push — master ( 664c38...4ed7d3 )
by Jonas
19s queued 12s
created

Offer::importLabels()   C

Complexity

Conditions 10
Paths 72

Size

Total Lines 61

Duplication

Lines 8
Ratio 13.11 %

Importance

Changes 0
Metric Value
dl 8
loc 61
rs 6.9842
c 0
b 0
f 0
cc 10
nc 72
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Language;
24
use CultuurNet\UDB3\Offer\Events\AbstractBookingInfoUpdated;
25
use CultuurNet\UDB3\Offer\Events\AbstractCalendarUpdated;
26
use CultuurNet\UDB3\Offer\Events\AbstractContactPointUpdated;
27
use CultuurNet\UDB3\Offer\Events\AbstractFacilitiesUpdated;
28
use CultuurNet\UDB3\Offer\Events\AbstractGeoCoordinatesUpdated;
29
use CultuurNet\UDB3\Offer\Events\AbstractLabelsImported;
30
use CultuurNet\UDB3\Offer\Events\AbstractThemeUpdated;
31
use CultuurNet\UDB3\Offer\Events\AbstractTitleTranslated;
32
use CultuurNet\UDB3\Offer\Events\AbstractTitleUpdated;
33
use CultuurNet\UDB3\Offer\Events\AbstractDescriptionTranslated;
34
use CultuurNet\UDB3\Offer\Events\AbstractDescriptionUpdated;
35
use CultuurNet\UDB3\Offer\Events\AbstractLabelAdded;
36
use CultuurNet\UDB3\Offer\Events\AbstractLabelRemoved;
37
use CultuurNet\UDB3\Offer\Events\AbstractOfferDeleted;
38
use CultuurNet\UDB3\Offer\Events\AbstractOrganizerDeleted;
39
use CultuurNet\UDB3\Offer\Events\AbstractOrganizerUpdated;
40
use CultuurNet\UDB3\Offer\Events\AbstractPriceInfoUpdated;
41
use CultuurNet\UDB3\Offer\Events\AbstractTypeUpdated;
42
use CultuurNet\UDB3\Offer\Events\AbstractTypicalAgeRangeDeleted;
43
use CultuurNet\UDB3\Offer\Events\AbstractTypicalAgeRangeUpdated;
44
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageAdded;
45
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageRemoved;
46
use CultuurNet\UDB3\Offer\Events\Image\AbstractImagesEvent;
47
use CultuurNet\UDB3\Offer\Events\Image\AbstractImagesImportedFromUDB2;
48
use CultuurNet\UDB3\Offer\Events\Image\AbstractImagesUpdatedFromUDB2;
49
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageUpdated;
50
use CultuurNet\UDB3\Offer\Events\Image\AbstractMainImageSelected;
51
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractApproved;
52
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractFlaggedAsDuplicate;
53
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractFlaggedAsInappropriate;
54
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractPublished;
55
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractRejected;
56
use CultuurNet\UDB3\PriceInfo\PriceInfo;
57
use CultuurNet\UDB3\Theme;
58
use CultuurNet\UDB3\Title;
59
use Exception;
60
use TwoDotsTwice\Collection\Exception\CollectionItemNotFoundException;
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
     * @var bool
153
     */
154
    protected $isDeleted = false;
155
156
    /**
157
     * Offer constructor.
158
     */
159
    public function __construct()
160
    {
161
        $this->titles = [];
162
        $this->descriptions = [];
163
        $this->labels = new LabelCollection();
164
        $this->images = new ImageCollection();
165
        $this->facilities = [];
166
        $this->contactPoint = null;
167
        $this->calendar = null;
168
        $this->typicalAgeRange = null;
169
        $this->bookingInfo = null;
170
    }
171
172
    /**
173
     * @param EventType $type
174
     */
175
    public function updateType(EventType $type)
176
    {
177
        if (!$this->typeId || $this->typeId !== $type->getId()) {
178
            $this->apply($this->createTypeUpdatedEvent($type));
179
        }
180
    }
181
182
    /**
183
     * @param Theme $theme
184
     */
185
    public function updateTheme(Theme $theme)
186
    {
187
        if (!$this->themeId || $this->themeId !== $theme->getId()) {
188
            $this->apply($this->createThemeUpdatedEvent($theme));
189
        }
190
    }
191
192
    /**
193
     * @param array $facilities
194
     */
195
    public function updateFacilities(array $facilities)
196
    {
197
        if (empty($this->facilities) || !$this->sameFacilities($this->facilities, $facilities)) {
198
            $this->apply($this->createFacilitiesUpdatedEvent($facilities));
199
        }
200
    }
201
202
    /**
203
     * @param AbstractFacilitiesUpdated $facilitiesUpdated
204
     */
205
    protected function applyFacilitiesUpdated(AbstractFacilitiesUpdated $facilitiesUpdated)
206
    {
207
        $this->facilities = $facilitiesUpdated->getFacilities();
208
    }
209
210
    /**
211
     * @param array $facilities1
212
     * @param array $facilities2
213
     * @return bool
214
     */
215
    private function sameFacilities($facilities1, $facilities2)
216
    {
217
        if (count($facilities1) !== count($facilities2)) {
218
            return false;
219
        }
220
221
        $sameFacilities = array_uintersect(
222
            $facilities1,
223
            $facilities2,
224
            function (Facility $facility1, Facility $facility2) {
225
                return strcmp($facility1->getId(), $facility2->getId());
226
            }
227
        );
228
229
        return count($sameFacilities) === count($facilities2);
230
    }
231
232
    /**
233
     * Get the id of the main image if one is selected for this offer.
234
     *
235
     * @return UUID|null
236
     */
237
    protected function getMainImageId()
238
    {
239
        $mainImage = $this->images->getMain();
240
        return isset($mainImage) ? $mainImage->getMediaObjectId() : null;
241
    }
242
243
    /**
244
     * @inheritdoc
245
     */
246
    public function addLabel(Label $label)
247
    {
248
        if (!$this->labels->contains($label)) {
249
            $this->apply(
250
                $this->createLabelAddedEvent($label)
251
            );
252
        }
253
    }
254
255
    /**
256
     * @inheritdoc
257
     */
258
    public function removeLabel(Label $label)
259
    {
260
        if ($this->labels->contains($label)) {
261
            $this->apply(
262
                $this->createLabelRemovedEvent($label)
263
            );
264
        }
265
    }
266
267
    /**
268
     * @param Labels $labels
269
     * @param Labels $labelsToKeepIfAlreadyOnOffer
270
     */
271
    public function importLabels(Labels $labels, Labels $labelsToKeepIfAlreadyOnOffer, Labels $labelsToRemoveWhenOnOffer)
272
    {
273
        $convertLabelClass = function (\CultuurNet\UDB3\Model\ValueObject\Taxonomy\Label\Label $label) {
274
            return new Label(
275
                $label->getName()->toString(),
276
                $label->isVisible()
277
            );
278
        };
279
280
        // Convert the imported labels to label collection.
281
        $importLabelsCollection = new LabelCollection(
282
            array_map($convertLabelClass, $labels->toArray())
283
        );
284
285
        // Convert the labels to keep if already applied.
286
        $keepLabelsCollection = new LabelCollection(
287
            array_map($convertLabelClass, $labelsToKeepIfAlreadyOnOffer->toArray())
288
        );
289
290
        // Convert the labels to remove when on offer.
291
        $removeLabelsCollection = new LabelCollection(
292
            array_map($convertLabelClass, $labelsToRemoveWhenOnOffer->toArray())
293
        );
294
295
        // What are the added labels?
296
        // Labels which are not inside the internal state but inside the imported labels
297
        $addedLabels = new LabelCollection();
298
        foreach ($importLabelsCollection->asArray() as $label) {
299
            if (!$this->labels->contains($label)) {
300
                $addedLabels = $addedLabels->with($label);
301
            }
302
        }
303
304
        // Fire a LabelsImported for all new labels.
305
        $importLabels = new Labels();
306 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...
307
            $importLabels = $importLabels->with(
308
                new \CultuurNet\UDB3\Model\ValueObject\Taxonomy\Label\Label(
309
                    new LabelName((string) $addedLabel),
310
                    $addedLabel->isVisible()
311
                )
312
            );
313
        }
314
        if ($importLabels->count() > 0) {
315
            $this->apply($this->createLabelsImportedEvent($importLabels));
316
        }
317
318
        // For each added label fire a LabelAdded event.
319
        foreach ($addedLabels->asArray() as $label) {
320
            $this->apply($this->createLabelAddedEvent($label));
321
        }
322
323
        // What are the deleted labels?
324
        // Labels which are inside the internal state but not inside imported labels or labels to keep.
325
        // For each deleted label fire a LabelDeleted event.
326
        foreach ($this->labels->asArray() as $label) {
327
            if (!$importLabelsCollection->contains($label) && !$keepLabelsCollection->contains($label) && $removeLabelsCollection->contains($label)) {
328
                $this->apply($this->createLabelRemovedEvent($label));
329
            }
330
        }
331
    }
332
333
    /**
334
     * @param Language $language
335
     * @param Title $title
336
     */
337
    public function updateTitle(Language $language, Title $title)
338
    {
339
        if ($this->isTitleChanged($title, $language)) {
340
            if ($language->getCode() !== $this->mainLanguage->getCode()) {
341
                $event = $this->createTitleTranslatedEvent($language, $title);
342
            } else {
343
                $event = $this->createTitleUpdatedEvent($title);
344
            }
345
346
            $this->apply($event);
347
        }
348
    }
349
350
    /**
351
     * @param AbstractTitleTranslated $titleTranslated
352
     */
353
    public function applyTitleTranslated(AbstractTitleTranslated $titleTranslated)
354
    {
355
        $this->titles[$titleTranslated->getLanguage()->getCode()] = $titleTranslated->getTitle();
356
    }
357
358
359
    /**
360
     * @param AbstractTitleUpdated $titleUpdated
361
     */
362
    public function applyTitleUpdated(AbstractTitleUpdated $titleUpdated)
363
    {
364
        $this->titles[$this->mainLanguage->getCode()] = $titleUpdated->getTitle();
365
    }
366
367
    /**
368
     * @param Description $description
369
     * @param Language $language
370
     */
371
    public function updateDescription(Description $description, Language $language)
372
    {
373
        if ($this->isDescriptionChanged($description, $language)) {
374
            if ($language->getCode() !== $this->mainLanguage->getCode()) {
375
                $event = $this->createDescriptionTranslatedEvent($language, $description);
376
            } else {
377
                $event = $this->createDescriptionUpdatedEvent($description);
378
            }
379
380
            $this->apply($event);
381
        }
382
    }
383
384
    /**
385
     * @param Calendar $calendar
386
     */
387
    public function updateCalendar(Calendar $calendar)
388
    {
389
        if (is_null($this->calendar) || !$this->calendar->sameAs($calendar)) {
390
            $this->apply(
391
                $this->createCalendarUpdatedEvent($calendar)
392
            );
393
        }
394
    }
395
396
    /**
397
     * @param AbstractCalendarUpdated $calendarUpdated
398
     */
399
    protected function applyCalendarUpdated(AbstractCalendarUpdated $calendarUpdated)
400
    {
401
        $this->calendar = $calendarUpdated->getCalendar();
402
    }
403
404
    /**
405
     * @param AgeRange $typicalAgeRange
406
     */
407
    public function updateTypicalAgeRange(AgeRange $typicalAgeRange)
408
    {
409
        $typicalAgeRangeUpdatedEvent = $this->createTypicalAgeRangeUpdatedEvent($typicalAgeRange);
410
411
        if (empty($this->typicalAgeRange) || !$this->typicalAgeRange->sameAs($typicalAgeRangeUpdatedEvent->getTypicalAgeRange())) {
412
            $this->apply($typicalAgeRangeUpdatedEvent);
413
        }
414
    }
415
416
    /**
417
     * @param AbstractTypicalAgeRangeUpdated $typicalAgeRangeUpdated
418
     */
419
    protected function applyTypicalAgeRangeUpdated(AbstractTypicalAgeRangeUpdated $typicalAgeRangeUpdated)
420
    {
421
        $this->typicalAgeRange = $typicalAgeRangeUpdated->getTypicalAgeRange();
422
    }
423
424
    public function deleteTypicalAgeRange()
425
    {
426
        if (!is_null($this->typicalAgeRange)) {
427
            $this->apply(
428
                $this->createTypicalAgeRangeDeletedEvent()
429
            );
430
        }
431
    }
432
433
    /**
434
     * @param AbstractTypicalAgeRangeDeleted $typicalAgeRangeDeleted
435
     */
436
    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...
437
    {
438
        $this->typicalAgeRange = null;
439
    }
440
441
    /**
442
     * @param string $organizerId
443
     */
444
    public function updateOrganizer($organizerId)
445
    {
446
        if ($this->organizerId !== $organizerId) {
447
            $this->apply(
448
                $this->createOrganizerUpdatedEvent($organizerId)
449
            );
450
        }
451
    }
452
453
    /**
454
     * Delete the given organizer.
455
     *
456
     * @param string $organizerId
457
     */
458
    public function deleteOrganizer($organizerId)
459
    {
460
        if ($this->organizerId === $organizerId) {
461
            $this->apply(
462
                $this->createOrganizerDeletedEvent($organizerId)
463
            );
464
        }
465
    }
466
467
    /**
468
     * Delete the current organizer regardless of the id.
469
     */
470
    public function deleteCurrentOrganizer()
471
    {
472
        if (!is_null($this->organizerId)) {
473
            $this->apply(
474
                $this->createOrganizerDeletedEvent($this->organizerId)
475
            );
476
        }
477
    }
478
479
    /**
480
     * Updated the contact info.
481
     * @param ContactPoint $contactPoint
482
     */
483
    public function updateContactPoint(ContactPoint $contactPoint)
484
    {
485
        if (is_null($this->contactPoint) || !$this->contactPoint->sameAs($contactPoint)) {
486
            $this->apply(
487
                $this->createContactPointUpdatedEvent($contactPoint)
488
            );
489
        }
490
    }
491
492
    /**
493
     * @param AbstractContactPointUpdated $contactPointUpdated
494
     */
495
    protected function applyContactPointUpdated(AbstractContactPointUpdated $contactPointUpdated)
496
    {
497
        $this->contactPoint = $contactPointUpdated->getContactPoint();
498
    }
499
500
    /**
501
     * @param Coordinates $coordinates
502
     */
503
    public function updateGeoCoordinates(Coordinates $coordinates)
504
    {
505
        // Note: DON'T compare to previous coordinates and apply only on
506
        // changes. Various projectors expect GeoCoordinatesUpdated after
507
        // MajorInfoUpdated and PlaceUpdatedFromUDB2, even if the address
508
        // and thus the coordinates haven't actually changed.
509
        $this->apply(
510
            $this->createGeoCoordinatesUpdatedEvent($coordinates)
511
        );
512
    }
513
514
    /**
515
     * Updated the booking info.
516
     *
517
     * @param BookingInfo $bookingInfo
518
     */
519
    public function updateBookingInfo(BookingInfo $bookingInfo)
520
    {
521
        if (is_null($this->bookingInfo) || !$this->bookingInfo->sameAs($bookingInfo)) {
522
            $this->apply(
523
                $this->createBookingInfoUpdatedEvent($bookingInfo)
524
            );
525
        }
526
    }
527
528
    /**
529
     * @param AbstractBookingInfoUpdated $bookingInfoUpdated
530
     */
531
    public function applyBookingInfoUpdated(AbstractBookingInfoUpdated $bookingInfoUpdated)
532
    {
533
        $this->bookingInfo = $bookingInfoUpdated->getBookingInfo();
534
    }
535
536
    /**
537
     * @param PriceInfo $priceInfo
538
     */
539
    public function updatePriceInfo(PriceInfo $priceInfo)
540
    {
541
        if (is_null($this->priceInfo) || $priceInfo->serialize() !== $this->priceInfo->serialize()) {
542
            $this->apply(
543
                $this->createPriceInfoUpdatedEvent($priceInfo)
544
            );
545
        }
546
    }
547
548
    /**
549
     * @param AbstractPriceInfoUpdated $priceInfoUpdated
550
     */
551
    protected function applyPriceInfoUpdated(AbstractPriceInfoUpdated $priceInfoUpdated)
552
    {
553
        $this->priceInfo = $priceInfoUpdated->getPriceInfo();
554
    }
555
556
    /**
557
     * @param AbstractLabelAdded $labelAdded
558
     */
559
    protected function applyLabelAdded(AbstractLabelAdded $labelAdded)
560
    {
561
        $this->labels = $this->labels->with($labelAdded->getLabel());
562
    }
563
564
    /**
565
     * @param AbstractLabelRemoved $labelRemoved
566
     */
567
    protected function applyLabelRemoved(AbstractLabelRemoved $labelRemoved)
568
    {
569
        $this->labels = $this->labels->without($labelRemoved->getLabel());
570
    }
571
572
    /**
573
     * @param AbstractThemeUpdated $themeUpdated
574
     */
575
    protected function applyThemeUpdated(AbstractThemeUpdated $themeUpdated)
576
    {
577
        $this->themeId = $themeUpdated->getTheme()->getId();
578
    }
579
580
    /**
581
     * @param AbstractTypeUpdated $themeUpdated
582
     */
583
    protected function applyTypeUpdated(AbstractTypeUpdated $themeUpdated)
584
    {
585
        $this->typeId = $themeUpdated->getType()->getId();
586
    }
587
588
    protected function applyDescriptionUpdated(AbstractDescriptionUpdated $descriptionUpdated)
589
    {
590
        $mainLanguageCode = $this->mainLanguage->getCode();
591
        $this->descriptions[$mainLanguageCode] = $descriptionUpdated->getDescription();
592
    }
593
594
    protected function applyDescriptionTranslated(AbstractDescriptionTranslated $descriptionTranslated)
595
    {
596
        $languageCode = $descriptionTranslated->getLanguage()->getCode();
597
        $this->descriptions[$languageCode] = $descriptionTranslated->getDescription();
598
    }
599
600
    /**
601
     * Add a new image.
602
     *
603
     * @param Image $image
604
     */
605
    public function addImage(Image $image)
606
    {
607
        // Find the image based on UUID inside the internal state.
608
        $existingImage = $this->images->findImageByUUID($image->getMediaObjectId());
609
610
        if ($existingImage === null) {
611
            $this->apply(
612
                $this->createImageAddedEvent($image)
613
            );
614
        }
615
    }
616
617
    /**
618
     * @param UUID $mediaObjectId
619
     * @param StringLiteral $description
620
     * @param StringLiteral $copyrightHolder
621
     */
622
    public function updateImage(
623
        UUID $mediaObjectId,
624
        StringLiteral $description,
625
        StringLiteral $copyrightHolder
626
    ) {
627
        if ($this->updateImageAllowed($mediaObjectId, $description, $copyrightHolder)) {
628
            $this->apply(
629
                $this->createImageUpdatedEvent(
630
                    $mediaObjectId,
631
                    $description,
632
                    $copyrightHolder
633
                )
634
            );
635
        }
636
    }
637
638
    /**
639
     * @param UUID $mediaObjectId
640
     * @param StringLiteral $description
641
     * @param StringLiteral $copyrightHolder
642
     * @return bool
643
     */
644
    private function updateImageAllowed(
645
        UUID $mediaObjectId,
646
        StringLiteral $description,
647
        StringLiteral $copyrightHolder
648
    ) {
649
        $image = $this->images->findImageByUUID($mediaObjectId);
650
651
        // Don't update if the image is not found based on UUID.
652
        if (!$image) {
653
            return false;
654
        }
655
656
        // Update when copyright or description is changed.
657
        return !$copyrightHolder->sameValueAs($image->getCopyrightHolder()) ||
658
            !$description->sameValueAs($image->getDescription());
659
    }
660
661
    /**
662
     * Remove an image.
663
     *
664
     * @param Image $image
665
     */
666
    public function removeImage(Image $image)
667
    {
668
        // Find the image based on UUID inside the internal state.
669
        // Use the image from the internal state.
670
        $existingImage = $this->images->findImageByUUID($image->getMediaObjectId());
671
672
        if ($existingImage) {
673
            $this->apply(
674
                $this->createImageRemovedEvent($existingImage)
675
            );
676
        }
677
    }
678
679
    /**
680
     * Make an existing image of the item the main image.
681
     *
682
     * @param Image $image
683
     */
684
    public function selectMainImage(Image $image)
685
    {
686
        if (!$this->images->findImageByUUID($image->getMediaObjectId())) {
687
            throw new \InvalidArgumentException('You can not select a random image to be main, it has to be added to the item.');
688
        }
689
690
        $oldMainImage = $this->images->getMain();
691
692
        if (!isset($oldMainImage) || $oldMainImage->getMediaObjectId() !== $image->getMediaObjectId()) {
693
            $this->apply(
694
                $this->createMainImageSelectedEvent($image)
695
            );
696
        }
697
    }
698
699
    /**
700
     * @param ImageCollection $imageCollection
701
     */
702
    public function importImages(ImageCollection $imageCollection)
703
    {
704
        $currentImageCollection = $this->images;
705
        $newMainImage = $imageCollection->getMain();
706
707
        $importImages = $imageCollection->toArray();
708
        $currentImages = $currentImageCollection->toArray();
709
710
        $compareImages = function (Image $a, Image $b) {
711
            $idA = $a->getMediaObjectId()->toNative();
712
            $idB = $b->getMediaObjectId()->toNative();
713
            return strcmp($idA, $idB);
714
        };
715
716
        /* @var Image[] $addedImages */
717
        $addedImages = array_udiff($importImages, $currentImages, $compareImages);
718
719
        /* @var Image[] $updatedImages */
720
        $updatedImages = array_uintersect($importImages, $currentImages, $compareImages);
721
722
        /* @var Image[] $removedImages */
723
        $removedImages = array_udiff($currentImages, $importImages, $compareImages);
724
725
        foreach ($addedImages as $addedImage) {
726
            $this->apply($this->createImageAddedEvent($addedImage));
727
        }
728
729
        foreach ($updatedImages as $updatedImage) {
730
            $this->apply(
731
                $this->createImageUpdatedEvent(
732
                    $updatedImage->getMediaObjectId(),
733
                    $updatedImage->getDescription(),
734
                    $updatedImage->getCopyrightHolder()
735
                )
736
            );
737
        }
738
739
        foreach ($removedImages as $removedImage) {
740
            $this->apply($this->createImageRemovedEvent($removedImage));
741
        }
742
743
        if ($newMainImage) {
744
            $this->apply($this->createMainImageSelectedEvent($newMainImage));
745
        }
746
    }
747
748
    /**
749
     * Delete the offer.
750
     */
751
    public function delete()
752
    {
753
        $this->apply(
754
            $this->createOfferDeletedEvent()
755
        );
756
    }
757
758
    /**
759
     * @param CultureFeed_Cdb_Item_Base $cdbItem
760
     */
761
    protected function importWorkflowStatus(CultureFeed_Cdb_Item_Base $cdbItem)
762
    {
763
        try {
764
            $workflowStatus = WorkflowStatus::fromNative($cdbItem->getWfStatus());
765
        } catch (\InvalidArgumentException $exception) {
766
            $workflowStatus = WorkflowStatus::READY_FOR_VALIDATION();
767
        }
768
        $this->workflowStatus = $workflowStatus;
769
    }
770
771
    /**
772
     * Publish the offer when it has workflowstatus draft.
773
     * @param \DateTimeInterface $publicationDate
774
     */
775
    public function publish(\DateTimeInterface $publicationDate)
776
    {
777
        $this->guardPublish() ?: $this->apply(
778
            $this->createPublishedEvent($publicationDate)
779
        );
780
    }
781
782
    /**
783
     * @return bool
784
     * @throws Exception
785
     */
786 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...
787
    {
788
        if ($this->workflowStatus === WorkflowStatus::READY_FOR_VALIDATION()) {
789
            return true; // nothing left to do if the offer has already been published
790
        }
791
792
        if ($this->workflowStatus !== WorkflowStatus::DRAFT()) {
793
            throw new Exception('You can not publish an offer that is not draft');
794
        }
795
796
        return false;
797
    }
798
799
    /**
800
     * Approve the offer when it's waiting for validation.
801
     */
802
    public function approve()
803
    {
804
        $this->guardApprove() ?: $this->apply($this->createApprovedEvent());
805
    }
806
807
    /**
808
     * @return bool
809
     * @throws Exception
810
     */
811 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...
812
    {
813
        if ($this->workflowStatus === WorkflowStatus::APPROVED()) {
814
            return true; // nothing left to do if the offer has already been approved
815
        }
816
817
        if ($this->workflowStatus !== WorkflowStatus::READY_FOR_VALIDATION()) {
818
            throw new Exception('You can not approve an offer that is not ready for validation');
819
        }
820
821
        return false;
822
    }
823
824
    /**
825
     * Reject an offer that is waiting for validation with a given reason.
826
     * @param StringLiteral $reason
827
     */
828
    public function reject(StringLiteral $reason)
829
    {
830
        $this->guardRejection($reason) ?: $this->apply($this->createRejectedEvent($reason));
831
    }
832
833
    public function flagAsDuplicate()
834
    {
835
        $reason = new StringLiteral(self::DUPLICATE_REASON);
836
        $this->guardRejection($reason) ?: $this->apply($this->createFlaggedAsDuplicate());
837
    }
838
839
    public function flagAsInappropriate()
840
    {
841
        $reason = new StringLiteral(self::INAPPROPRIATE_REASON);
842
        $this->guardRejection($reason) ?: $this->apply($this->createFlaggedAsInappropriate());
843
    }
844
845
    /**
846
     * @param StringLiteral $reason
847
     * @return bool
848
     *  false when the offer can still be rejected, true when the offer is already rejected for the same reason
849
     * @throws Exception
850
     */
851
    private function guardRejection(StringLiteral $reason)
852
    {
853
        if ($this->workflowStatus === WorkflowStatus::REJECTED()) {
854
            if ($this->rejectedReason && $reason->sameValueAs($this->rejectedReason)) {
855
                return true; // nothing left to do if the offer has already been rejected for the same reason
856
            } else {
857
                throw new Exception('The offer has already been rejected for another reason: ' . $this->rejectedReason);
858
            }
859
        }
860
861
        if ($this->workflowStatus !== WorkflowStatus::READY_FOR_VALIDATION()) {
862
            throw new Exception('You can not reject an offer that is not ready for validation');
863
        }
864
865
        return false;
866
    }
867
868
    /**
869
     * @param Title $title
870
     * @param Language $language
871
     * @return bool
872
     */
873 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...
874
    {
875
        $languageCode = $language->getCode();
876
877
        return !isset($this->titles[$languageCode]) ||
878
            !$title->sameValueAs($this->titles[$languageCode]);
879
    }
880
881
    /**
882
     * @param Description $description
883
     * @param Language $language
884
     * @return bool
885
     */
886 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...
887
    {
888
        $languageCode = $language->getCode();
889
890
        return !isset($this->descriptions[$languageCode]) ||
891
            !$description->sameValueAs($this->descriptions[$languageCode]);
892
    }
893
894
    /**
895
     * Overwrites or resets the main image and all media objects
896
     * by importing a new collection of images from UDB2.
897
     *
898
     * @param ImageCollection $images
899
     */
900
    public function importImagesFromUDB2(ImageCollection $images)
901
    {
902
        $this->apply($this->createImagesImportedFromUDB2($images));
903
    }
904
905
    /**
906
     * Overwrites or resets the main image and all media objects
907
     * by updating with a new collection of images from UDB2.
908
     *
909
     * @param ImageCollection $images
910
     */
911
    public function updateImagesFromUDB2(ImageCollection $images)
912
    {
913
        $this->apply($this->createImagesUpdatedFromUDB2($images));
914
    }
915
916
    /**
917
     * @param AbstractPublished $published
918
     */
919
    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...
920
    {
921
        $this->workflowStatus = WorkflowStatus::READY_FOR_VALIDATION();
922
    }
923
924
    /**
925
     * @param AbstractApproved $approved
926
     */
927
    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...
928
    {
929
        $this->workflowStatus = WorkflowStatus::APPROVED();
930
    }
931
932
    /**
933
     * @param AbstractRejected $rejected
934
     */
935
    protected function applyRejected(AbstractRejected $rejected)
936
    {
937
        $this->rejectedReason = $rejected->getReason();
938
        $this->workflowStatus = WorkflowStatus::REJECTED();
939
    }
940
941
    /**
942
     * @param AbstractFlaggedAsDuplicate $flaggedAsDuplicate
943
     */
944
    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...
945
    {
946
        $this->rejectedReason = new StringLiteral(self::DUPLICATE_REASON);
947
        $this->workflowStatus = WorkflowStatus::REJECTED();
948
    }
949
950
    /**
951
     * @param AbstractFlaggedAsInappropriate $flaggedAsInappropriate
952
     */
953
    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...
954
    {
955
        $this->rejectedReason = new StringLiteral(self::INAPPROPRIATE_REASON);
956
        $this->workflowStatus = WorkflowStatus::REJECTED();
957
    }
958
959
    protected function applyImageAdded(AbstractImageAdded $imageAdded)
960
    {
961
        $this->images = $this->images->with($imageAdded->getImage());
962
    }
963
964
    protected function applyImageUpdated(AbstractImageUpdated $imageUpdated)
965
    {
966
        $image = $this->images->findImageByUUID($imageUpdated->getMediaObjectId());
967
968
        $updatedImage = new Image(
969
            $image->getMediaObjectId(),
970
            $image->getMimeType(),
971
            new ImageDescription($imageUpdated->getDescription()->toNative()),
972
            new CopyrightHolder($imageUpdated->getCopyrightHolder()->toNative()),
973
            $image->getSourceLocation(),
974
            $image->getLanguage()
975
        );
976
977
        // Currently no other option to update an item inside a collection.
978
        $this->images = $this->images->without($image);
979
        $this->images = $this->images->with($updatedImage);
980
    }
981
982
    protected function applyImageRemoved(AbstractImageRemoved $imageRemoved)
983
    {
984
        try {
985
            $this->images = $this->images->without($imageRemoved->getImage());
986
        } catch (CollectionItemNotFoundException $exception) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
987
        }
988
    }
989
990
    protected function applyMainImageSelected(AbstractMainImageSelected $mainImageSelected)
991
    {
992
        $this->images = $this->images->withMain($mainImageSelected->getImage());
993
    }
994
995
    protected function applyOrganizerUpdated(AbstractOrganizerUpdated $organizerUpdated)
996
    {
997
        $this->organizerId = $organizerUpdated->getOrganizerId();
998
    }
999
1000
    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...
1001
    {
1002
        $this->organizerId = null;
1003
    }
1004
1005
    /**
1006
     * @param AbstractImagesImportedFromUDB2 $imagesImportedFromUDB2
1007
     */
1008
    protected function applyImagesImportedFromUDB2(AbstractImagesImportedFromUDB2 $imagesImportedFromUDB2)
1009
    {
1010
        $this->applyUdb2ImagesEvent($imagesImportedFromUDB2);
1011
    }
1012
1013
    /**
1014
     * @param AbstractImagesUpdatedFromUDB2 $imagesUpdatedFromUDB2
1015
     */
1016
    protected function applyImagesUpdatedFromUDB2(AbstractImagesUpdatedFromUDB2 $imagesUpdatedFromUDB2)
1017
    {
1018
        $this->applyUdb2ImagesEvent($imagesUpdatedFromUDB2);
1019
    }
1020
1021
    /**
1022
     * This indirect apply method can be called internally to deal with images coming from UDB2.
1023
     * Imports from UDB2 only contain the native Dutch content.
1024
     * @see https://github.com/cultuurnet/udb3-udb2-bridge/blob/db0a7ab2444f55bb3faae3d59b82b39aaeba253b/test/Media/ImageCollectionFactoryTest.php#L79-L103
1025
     * Because of this we have to make sure translated images are left in place.
1026
     *
1027
     * @param AbstractImagesEvent $imagesEvent
1028
     */
1029
    protected function applyUdb2ImagesEvent(AbstractImagesEvent $imagesEvent)
1030
    {
1031
        $newMainImage = $imagesEvent->getImages()->getMain();
1032
        $dutchImagesList = $imagesEvent->getImages()->toArray();
1033
        $translatedImagesList = array_filter(
1034
            $this->images->toArray(),
1035
            function (Image $image) {
1036
                return $image->getLanguage()->getCode() !== 'nl';
1037
            }
1038
        );
1039
1040
        $imagesList = array_merge($dutchImagesList, $translatedImagesList);
1041
        $images = ImageCollection::fromArray($imagesList);
1042
1043
        $this->images = isset($newMainImage) ? $images->withMain($newMainImage) : $images;
1044
    }
1045
1046
    /**
1047
     * @param Label $label
1048
     * @return AbstractLabelAdded
1049
     */
1050
    abstract protected function createLabelAddedEvent(Label $label);
1051
1052
    /**
1053
     * @param Label $label
1054
     * @return AbstractLabelRemoved
1055
     */
1056
    abstract protected function createLabelRemovedEvent(Label $label);
1057
1058
    /**
1059
     * @param Labels $labels
1060
     * @return AbstractLabelsImported
1061
     */
1062
    abstract protected function createLabelsImportedEvent(Labels $labels);
1063
1064
    /**
1065
     * @param Language $language
1066
     * @param Title $title
1067
     * @return AbstractTitleTranslated
1068
     */
1069
    abstract protected function createTitleTranslatedEvent(Language $language, Title $title);
1070
1071
    /**
1072
     * @param Language $language
1073
     * @param Description $description
1074
     * @return AbstractDescriptionTranslated
1075
     */
1076
    abstract protected function createDescriptionTranslatedEvent(Language $language, Description $description);
1077
1078
    /**
1079
     * @param Image $image
1080
     * @return AbstractImageAdded
1081
     */
1082
    abstract protected function createImageAddedEvent(Image $image);
1083
1084
    /**
1085
     * @param Image $image
1086
     * @return AbstractImageRemoved
1087
     */
1088
    abstract protected function createImageRemovedEvent(Image $image);
1089
1090
    /**
1091
     * @param UUID $uuid
1092
     * @param StringLiteral $description
1093
     * @param StringLiteral $copyrightHolder
1094
     * @return AbstractImageUpdated
1095
     */
1096
    abstract protected function createImageUpdatedEvent(
1097
        UUID $uuid,
1098
        StringLiteral $description,
1099
        StringLiteral $copyrightHolder
1100
    );
1101
1102
    /**
1103
     * @param Image $image
1104
     * @return AbstractMainImageSelected
1105
     */
1106
    abstract protected function createMainImageSelectedEvent(Image $image);
1107
1108
    /**
1109
     * @return AbstractOfferDeleted
1110
     */
1111
    abstract protected function createOfferDeletedEvent();
1112
1113
    /**
1114
     * @param Title $title
1115
     * @return AbstractTitleUpdated
1116
     */
1117
    abstract protected function createTitleUpdatedEvent(Title $title);
1118
1119
    /**
1120
     * @param Description $description
1121
     * @return AbstractDescriptionUpdated
1122
     */
1123
    abstract protected function createDescriptionUpdatedEvent(Description $description);
1124
1125
    /**
1126
     * @param Calendar $calendar
1127
     * @return AbstractCalendarUpdated
1128
     */
1129
    abstract protected function createCalendarUpdatedEvent(Calendar $calendar);
1130
1131
    /**
1132
     * @param string $typicalAgeRange
1133
     * @return AbstractTypicalAgeRangeUpdated
1134
     */
1135
    abstract protected function createTypicalAgeRangeUpdatedEvent($typicalAgeRange);
1136
1137
    /**
1138
     * @return AbstractTypicalAgeRangeDeleted
1139
     */
1140
    abstract protected function createTypicalAgeRangeDeletedEvent();
1141
1142
    /**
1143
     * @param string $organizerId
1144
     * @return AbstractOrganizerUpdated
1145
     */
1146
    abstract protected function createOrganizerUpdatedEvent($organizerId);
1147
1148
    /**
1149
     * @param string $organizerId
1150
     * @return AbstractOrganizerDeleted
1151
     */
1152
    abstract protected function createOrganizerDeletedEvent($organizerId);
1153
1154
    /**
1155
     * @param ContactPoint $contactPoint
1156
     * @return AbstractContactPointUpdated
1157
     */
1158
    abstract protected function createContactPointUpdatedEvent(ContactPoint $contactPoint);
1159
1160
    /**
1161
     * @param Coordinates $coordinates
1162
     * @return AbstractGeoCoordinatesUpdated
1163
     */
1164
    abstract protected function createGeoCoordinatesUpdatedEvent(Coordinates $coordinates);
1165
1166
    /**
1167
     * @param BookingInfo $bookingInfo
1168
     * @return AbstractBookingInfoUpdated
1169
     */
1170
    abstract protected function createBookingInfoUpdatedEvent(BookingInfo $bookingInfo);
1171
1172
    /**
1173
     * @param PriceInfo $priceInfo
1174
     * @return AbstractPriceInfoUpdated
1175
     */
1176
    abstract protected function createPriceInfoUpdatedEvent(PriceInfo $priceInfo);
1177
1178
    /**
1179
     * @param \DateTimeInterface $publicationDate
1180
     * @return AbstractPublished
1181
     */
1182
    abstract protected function createPublishedEvent(\DateTimeInterface $publicationDate);
1183
1184
    /**
1185
     * @return AbstractApproved
1186
     */
1187
    abstract protected function createApprovedEvent();
1188
1189
    /**
1190
     * @param StringLiteral $reason
1191
     * @return AbstractRejected
1192
     */
1193
    abstract protected function createRejectedEvent(StringLiteral $reason);
1194
1195
    /**
1196
     * @return AbstractFlaggedAsDuplicate
1197
     */
1198
    abstract protected function createFlaggedAsDuplicate();
1199
1200
    /**
1201
     * @return AbstractFlaggedAsInappropriate
1202
     */
1203
    abstract protected function createFlaggedAsInappropriate();
1204
1205
    /**
1206
     * @param ImageCollection $images
1207
     * @return AbstractImagesImportedFromUDB2
1208
     */
1209
    abstract protected function createImagesImportedFromUDB2(ImageCollection $images);
1210
1211
    /**
1212
     * @param ImageCollection $images
1213
     * @return AbstractImagesUpdatedFromUDB2
1214
     */
1215
    abstract protected function createImagesUpdatedFromUDB2(ImageCollection $images);
1216
1217
    /**
1218
     * @param EventType $type
1219
     * @return AbstractTypeUpdated
1220
     */
1221
    abstract protected function createTypeUpdatedEvent(EventType $type);
1222
1223
    /**
1224
     * @param Theme $theme
1225
     * @return AbstractThemeUpdated
1226
     */
1227
    abstract protected function createThemeUpdatedEvent(Theme $theme);
1228
1229
    /**
1230
     * @param array $facilities
1231
     * @return AbstractFacilitiesUpdated
1232
     */
1233
    abstract protected function createFacilitiesUpdatedEvent(array $facilities);
1234
}
1235