Completed
Pull Request — master (#349)
by Luc
08:07 queued 01:13
created

Offer::importLabels()   C

Complexity

Conditions 7
Paths 36

Size

Total Lines 50
Code Lines 24

Duplication

Lines 8
Ratio 16 %

Importance

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