Completed
Push — master ( 5ace45...cd9bd4 )
by
unknown
14s queued 10s
created

Offer::updateImagesFromUDB2()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
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($updateImageCommand)
590
            );
591
        }
592
    }
593
594
    /**
595
     * @param AbstractUpdateImage $updateImageCommand
596
     * @return bool
597
     */
598
    private function updateImageAllowed(AbstractUpdateImage $updateImageCommand)
599
    {
600
        $image = $this->images->findImageByUUID($updateImageCommand->getMediaObjectId());
601
602
        // Don't update if the image is not found based on UUID.
603
        if (!$image) {
604
            return false;
605
        }
606
607
        // Update when copyright or description is changed.
608
        return !$updateImageCommand->getCopyrightHolder()->sameValueAs($image->getCopyrightHolder()) ||
609
            !$updateImageCommand->getDescription()->sameValueAs($image->getDescription());
610
    }
611
612
    /**
613
     * Remove an image.
614
     *
615
     * @param Image $image
616
     */
617
    public function removeImage(Image $image)
618
    {
619
        if ($this->images->contains($image)) {
620
            $this->apply(
621
                $this->createImageRemovedEvent($image)
622
            );
623
        }
624
    }
625
626
    /**
627
     * Make an existing image of the item the main image.
628
     *
629
     * @param Image $image
630
     */
631
    public function selectMainImage(Image $image)
632
    {
633
        if (!$this->images->contains($image)) {
634
            throw new \InvalidArgumentException('You can not select a random image to be main, it has to be added to the item.');
635
        }
636
637
        $oldMainImage = $this->images->getMain();
638
639
        if (!isset($oldMainImage) || $oldMainImage->getMediaObjectId() !== $image->getMediaObjectId()) {
640
            $this->apply(
641
                $this->createMainImageSelectedEvent($image)
642
            );
643
        }
644
    }
645
646
    /**
647
     * Delete the offer.
648
     */
649
    public function delete()
650
    {
651
        $this->apply(
652
            $this->createOfferDeletedEvent()
653
        );
654
    }
655
656
    /**
657
     * @param CultureFeed_Cdb_Item_Base $cdbItem
658
     */
659
    protected function importWorkflowStatus(CultureFeed_Cdb_Item_Base $cdbItem)
660
    {
661
        try {
662
            $workflowStatus = WorkflowStatus::fromNative($cdbItem->getWfStatus());
663
        } catch (\InvalidArgumentException $exception) {
664
            $workflowStatus = WorkflowStatus::READY_FOR_VALIDATION();
665
        }
666
        $this->workflowStatus = $workflowStatus;
667
    }
668
669
    /**
670
     * Publish the offer when it has workflowstatus draft.
671
     * @param \DateTimeInterface $publicationDate
672
     */
673
    public function publish(\DateTimeInterface $publicationDate)
674
    {
675
        $this->guardPublish() ?: $this->apply(
676
            $this->createPublishedEvent($publicationDate)
677
        );
678
    }
679
680
    /**
681
     * @return bool
682
     * @throws Exception
683
     */
684 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...
685
    {
686
        if ($this->workflowStatus === WorkflowStatus::READY_FOR_VALIDATION()) {
687
            return true; // nothing left to do if the offer has already been published
688
        }
689
690
        if ($this->workflowStatus !== WorkflowStatus::DRAFT()) {
691
            throw new Exception('You can not publish an offer that is not draft');
692
        }
693
694
        return false;
695
    }
696
697
    /**
698
     * Approve the offer when it's waiting for validation.
699
     */
700
    public function approve()
701
    {
702
        $this->guardApprove() ?: $this->apply($this->createApprovedEvent());
703
    }
704
705
    /**
706
     * @return bool
707
     * @throws Exception
708
     */
709 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...
710
    {
711
        if ($this->workflowStatus === WorkflowStatus::APPROVED()) {
712
            return true; // nothing left to do if the offer has already been approved
713
        }
714
715
        if ($this->workflowStatus !== WorkflowStatus::READY_FOR_VALIDATION()) {
716
            throw new Exception('You can not approve an offer that is not ready for validation');
717
        }
718
719
        return false;
720
    }
721
722
    /**
723
     * Reject an offer that is waiting for validation with a given reason.
724
     * @param StringLiteral $reason
725
     */
726
    public function reject(StringLiteral $reason)
727
    {
728
        $this->guardRejection($reason) ?: $this->apply($this->createRejectedEvent($reason));
729
    }
730
731
    public function flagAsDuplicate()
732
    {
733
        $reason = new StringLiteral(self::DUPLICATE_REASON);
734
        $this->guardRejection($reason) ?: $this->apply($this->createFlaggedAsDuplicate());
735
    }
736
737
    public function flagAsInappropriate()
738
    {
739
        $reason = new StringLiteral(self::INAPPROPRIATE_REASON);
740
        $this->guardRejection($reason) ?: $this->apply($this->createFlaggedAsInappropriate());
741
    }
742
743
    /**
744
     * @param StringLiteral $reason
745
     * @return bool
746
     *  false when the offer can still be rejected, true when the offer is already rejected for the same reason
747
     * @throws Exception
748
     */
749
    private function guardRejection(StringLiteral $reason)
750
    {
751
        if ($this->workflowStatus === WorkflowStatus::REJECTED()) {
752
            if ($this->rejectedReason && $reason->sameValueAs($this->rejectedReason)) {
753
                return true; // nothing left to do if the offer has already been rejected for the same reason
754
            } else {
755
                throw new Exception('The offer has already been rejected for another reason: ' . $this->rejectedReason);
756
            }
757
        }
758
759
        if ($this->workflowStatus !== WorkflowStatus::READY_FOR_VALIDATION()) {
760
            throw new Exception('You can not reject an offer that is not ready for validation');
761
        }
762
763
        return false;
764
    }
765
766
    /**
767
     * @param Title $title
768
     * @param Language $language
769
     * @return bool
770
     */
771 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...
772
    {
773
        $languageCode = $language->getCode();
774
775
        return !isset($this->titles[$languageCode]) ||
776
            !$title->sameValueAs($this->titles[$languageCode]);
777
    }
778
779
    /**
780
     * @param Description $description
781
     * @param Language $language
782
     * @return bool
783
     */
784 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...
785
    {
786
        $languageCode = $language->getCode();
787
788
        return !isset($this->descriptions[$languageCode]) ||
789
            !$description->sameValueAs($this->descriptions[$languageCode]);
790
    }
791
792
    /**
793
     * Overwrites or resets the main image and all media objects
794
     * by importing a new collection of images from UDB2.
795
     *
796
     * @param ImageCollection $images
797
     */
798
    public function importImagesFromUDB2(ImageCollection $images)
799
    {
800
        $this->apply($this->createImagesImportedFromUDB2($images));
801
    }
802
803
    /**
804
     * Overwrites or resets the main image and all media objects
805
     * by updating with a new collection of images from UDB2.
806
     *
807
     * @param ImageCollection $images
808
     */
809
    public function updateImagesFromUDB2(ImageCollection $images)
810
    {
811
        $this->apply($this->createImagesUpdatedFromUDB2($images));
812
    }
813
814
    /**
815
     * @param AbstractPublished $published
816
     */
817
    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...
818
    {
819
        $this->workflowStatus = WorkflowStatus::READY_FOR_VALIDATION();
820
    }
821
822
    /**
823
     * @param AbstractApproved $approved
824
     */
825
    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...
826
    {
827
        $this->workflowStatus = WorkflowStatus::APPROVED();
828
    }
829
830
    /**
831
     * @param AbstractRejected $rejected
832
     */
833
    protected function applyRejected(AbstractRejected $rejected)
834
    {
835
        $this->rejectedReason = $rejected->getReason();
836
        $this->workflowStatus = WorkflowStatus::REJECTED();
837
    }
838
839
    /**
840
     * @param AbstractFlaggedAsDuplicate $flaggedAsDuplicate
841
     */
842
    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...
843
    {
844
        $this->rejectedReason = new StringLiteral(self::DUPLICATE_REASON);
845
        $this->workflowStatus = WorkflowStatus::REJECTED();
846
    }
847
848
    /**
849
     * @param AbstractFlaggedAsInappropriate $flaggedAsInappropriate
850
     */
851
    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...
852
    {
853
        $this->rejectedReason = new StringLiteral(self::INAPPROPRIATE_REASON);
854
        $this->workflowStatus = WorkflowStatus::REJECTED();
855
    }
856
857
    protected function applyImageAdded(AbstractImageAdded $imageAdded)
858
    {
859
        $this->images = $this->images->with($imageAdded->getImage());
860
    }
861
862
    protected function applyImageUpdated(AbstractImageUpdated $imageUpdated)
863
    {
864
        $image = $this->images->findImageByUUID($imageUpdated->getMediaObjectId());
865
866
        $updatedImage = new Image(
867
            $image->getMediaObjectId(),
868
            $image->getMimeType(),
869
            new ImageDescription($imageUpdated->getDescription()->toNative()),
870
            new CopyrightHolder($imageUpdated->getCopyrightHolder()->toNative()),
871
            $image->getSourceLocation(),
872
            $image->getLanguage()
873
        );
874
875
        // Currently no other option to update an item inside a collection.
876
        $this->images = $this->images->without($image);
877
        $this->images = $this->images->with($updatedImage);
878
    }
879
880
    protected function applyImageRemoved(AbstractImageRemoved $imageRemoved)
881
    {
882
        $this->images = $this->images->without($imageRemoved->getImage());
883
    }
884
885
    protected function applyMainImageSelected(AbstractMainImageSelected $mainImageSelected)
886
    {
887
        $this->images = $this->images->withMain($mainImageSelected->getImage());
888
    }
889
890
    protected function applyOrganizerUpdated(AbstractOrganizerUpdated $organizerUpdated)
891
    {
892
        $this->organizerId = $organizerUpdated->getOrganizerId();
893
    }
894
895
    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...
896
    {
897
        $this->organizerId = null;
898
    }
899
900
    /**
901
     * @param AbstractImagesImportedFromUDB2 $imagesImportedFromUDB2
902
     */
903
    protected function applyImagesImportedFromUDB2(AbstractImagesImportedFromUDB2 $imagesImportedFromUDB2)
904
    {
905
        $this->applyUdb2ImagesEvent($imagesImportedFromUDB2);
906
    }
907
908
    /**
909
     * @param AbstractImagesUpdatedFromUDB2 $imagesUpdatedFromUDB2
910
     */
911
    protected function applyImagesUpdatedFromUDB2(AbstractImagesUpdatedFromUDB2 $imagesUpdatedFromUDB2)
912
    {
913
        $this->applyUdb2ImagesEvent($imagesUpdatedFromUDB2);
914
    }
915
916
    /**
917
     * This indirect apply method can be called internally to deal with images coming from UDB2.
918
     * Imports from UDB2 only contain the native Dutch content.
919
     * @see https://github.com/cultuurnet/udb3-udb2-bridge/blob/db0a7ab2444f55bb3faae3d59b82b39aaeba253b/test/Media/ImageCollectionFactoryTest.php#L79-L103
920
     * Because of this we have to make sure translated images are left in place.
921
     *
922
     * @param AbstractImagesEvent $imagesEvent
923
     */
924
    protected function applyUdb2ImagesEvent(AbstractImagesEvent $imagesEvent)
925
    {
926
        $newMainImage = $imagesEvent->getImages()->getMain();
927
        $dutchImagesList = $imagesEvent->getImages()->toArray();
928
        $translatedImagesList = array_filter(
929
            $this->images->toArray(),
930
            function (Image $image) {
931
                return $image->getLanguage()->getCode() !== 'nl';
932
            }
933
        );
934
935
        $imagesList = array_merge($dutchImagesList, $translatedImagesList);
936
        $images = ImageCollection::fromArray($imagesList);
937
938
        $this->images = isset($newMainImage) ? $images->withMain($newMainImage) : $images;
939
    }
940
941
    /**
942
     * @param Label $label
943
     * @return AbstractLabelAdded
944
     */
945
    abstract protected function createLabelAddedEvent(Label $label);
946
947
    /**
948
     * @param Label $label
949
     * @return AbstractLabelRemoved
950
     */
951
    abstract protected function createLabelRemovedEvent(Label $label);
952
953
    /**
954
     * @param Labels $labels
955
     * @return AbstractLabelsImported
956
     */
957
    abstract protected function createLabelsImportedEvent(Labels $labels);
958
959
    /**
960
     * @param Language $language
961
     * @param StringLiteral $title
962
     * @return AbstractTitleTranslated
963
     */
964
    abstract protected function createTitleTranslatedEvent(Language $language, StringLiteral $title);
965
966
    /**
967
     * @param Language $language
968
     * @param StringLiteral $description
969
     * @return AbstractDescriptionTranslated
970
     */
971
    abstract protected function createDescriptionTranslatedEvent(Language $language, StringLiteral $description);
972
973
    /**
974
     * @param Image $image
975
     * @return AbstractImageAdded
976
     */
977
    abstract protected function createImageAddedEvent(Image $image);
978
979
    /**
980
     * @param Image $image
981
     * @return AbstractImageRemoved
982
     */
983
    abstract protected function createImageRemovedEvent(Image $image);
984
985
    /**
986
     * @param AbstractUpdateImage $updateImageCommand
987
     * @return AbstractImageUpdated
988
     */
989
    abstract protected function createImageUpdatedEvent(
990
        AbstractUpdateImage $updateImageCommand
991
    );
992
993
    /**
994
     * @param Image $image
995
     * @return AbstractMainImageSelected
996
     */
997
    abstract protected function createMainImageSelectedEvent(Image $image);
998
999
    /**
1000
     * @return AbstractOfferDeleted
1001
     */
1002
    abstract protected function createOfferDeletedEvent();
1003
1004
    /**
1005
     * @param Title $title
1006
     * @return AbstractTitleUpdated
1007
     */
1008
    abstract protected function createTitleUpdatedEvent(Title $title);
1009
1010
    /**
1011
     * @param string $description
1012
     * @return AbstractDescriptionUpdated
1013
     */
1014
    abstract protected function createDescriptionUpdatedEvent($description);
1015
1016
    /**
1017
     * @param Calendar $calendar
1018
     * @return AbstractCalendarUpdated
1019
     */
1020
    abstract protected function createCalendarUpdatedEvent(Calendar $calendar);
1021
1022
    /**
1023
     * @param string $typicalAgeRange
1024
     * @return AbstractTypicalAgeRangeUpdated
1025
     */
1026
    abstract protected function createTypicalAgeRangeUpdatedEvent($typicalAgeRange);
1027
1028
    /**
1029
     * @return AbstractTypicalAgeRangeDeleted
1030
     */
1031
    abstract protected function createTypicalAgeRangeDeletedEvent();
1032
1033
    /**
1034
     * @param string $organizerId
1035
     * @return AbstractOrganizerUpdated
1036
     */
1037
    abstract protected function createOrganizerUpdatedEvent($organizerId);
1038
1039
    /**
1040
     * @param string $organizerId
1041
     * @return AbstractOrganizerDeleted
1042
     */
1043
    abstract protected function createOrganizerDeletedEvent($organizerId);
1044
1045
    /**
1046
     * @param ContactPoint $contactPoint
1047
     * @return AbstractContactPointUpdated
1048
     */
1049
    abstract protected function createContactPointUpdatedEvent(ContactPoint $contactPoint);
1050
1051
    /**
1052
     * @param Coordinates $coordinates
1053
     * @return AbstractGeoCoordinatesUpdated
1054
     */
1055
    abstract protected function createGeoCoordinatesUpdatedEvent(Coordinates $coordinates);
1056
1057
    /**
1058
     * @param BookingInfo $bookingInfo
1059
     * @return AbstractBookingInfoUpdated
1060
     */
1061
    abstract protected function createBookingInfoUpdatedEvent(BookingInfo $bookingInfo);
1062
1063
    /**
1064
     * @param PriceInfo $priceInfo
1065
     * @return AbstractPriceInfoUpdated
1066
     */
1067
    abstract protected function createPriceInfoUpdatedEvent(PriceInfo $priceInfo);
1068
1069
    /**
1070
     * @param \DateTimeInterface $publicationDate
1071
     * @return AbstractPublished
1072
     */
1073
    abstract protected function createPublishedEvent(\DateTimeInterface $publicationDate);
1074
1075
    /**
1076
     * @return AbstractApproved
1077
     */
1078
    abstract protected function createApprovedEvent();
1079
1080
    /**
1081
     * @param StringLiteral $reason
1082
     * @return AbstractRejected
1083
     */
1084
    abstract protected function createRejectedEvent(StringLiteral $reason);
1085
1086
    /**
1087
     * @return AbstractFlaggedAsDuplicate
1088
     */
1089
    abstract protected function createFlaggedAsDuplicate();
1090
1091
    /**
1092
     * @return AbstractFlaggedAsInappropriate
1093
     */
1094
    abstract protected function createFlaggedAsInappropriate();
1095
1096
    /**
1097
     * @param ImageCollection $images
1098
     * @return AbstractImagesImportedFromUDB2
1099
     */
1100
    abstract protected function createImagesImportedFromUDB2(ImageCollection $images);
1101
1102
    /**
1103
     * @param ImageCollection $images
1104
     * @return AbstractImagesUpdatedFromUDB2
1105
     */
1106
    abstract protected function createImagesUpdatedFromUDB2(ImageCollection $images);
1107
1108
    /**
1109
     * @param EventType $type
1110
     * @return AbstractTypeUpdated
1111
     */
1112
    abstract protected function createTypeUpdatedEvent(EventType $type);
1113
1114
    /**
1115
     * @param Theme $theme
1116
     * @return AbstractThemeUpdated
1117
     */
1118
    abstract protected function createThemeUpdatedEvent(Theme $theme);
1119
1120
    /**
1121
     * @param array $facilities
1122
     * @return AbstractFacilitiesUpdated
1123
     */
1124
    abstract protected function createFacilitiesUpdatedEvent(array $facilities);
1125
}
1126