Completed
Pull Request — master (#349)
by Luc
06:28 queued 01:36
created

Offer   F

Complexity

Total Complexity 124

Size/Duplication

Total Lines 1052
Duplicated Lines 3.61 %

Coupling/Cohesion

Components 1
Dependencies 45

Importance

Changes 0
Metric Value
wmc 124
lcom 1
cbo 45
dl 38
loc 1052
rs 0.7721
c 0
b 0
f 0

98 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 1
A updateType() 0 6 3
A updateTheme() 0 6 3
A updateFacilities() 0 6 3
A applyFacilitiesUpdated() 0 4 1
A sameFacilities() 0 16 2
A getMainImageId() 0 5 2
A addLabel() 0 8 2
A removeLabel() 0 8 2
B importLabels() 0 42 5
A updateTitle() 0 12 3
A updateDescription() 0 12 3
A updateCalendar() 0 8 3
A applyCalendarUpdated() 0 4 1
A updateTypicalAgeRange() 0 8 3
A applyTypicalAgeRangeUpdated() 0 4 1
A deleteTypicalAgeRange() 0 8 2
A applyTypicalAgeRangeDeleted() 0 4 1
A updateOrganizer() 0 8 2
A deleteOrganizer() 0 8 2
A deleteCurrentOrganizer() 0 8 2
A updateContactPoint() 0 8 3
A applyContactPointUpdated() 0 4 1
A updateGeoCoordinates() 0 10 1
A updateBookingInfo() 0 8 3
A applyBookingInfoUpdated() 0 4 1
A updatePriceInfo() 0 8 3
A applyPriceInfoUpdated() 0 4 1
A applyLabelAdded() 0 4 1
A applyLabelRemoved() 0 4 1
A applyThemeUpdated() 0 4 1
A applyTypeUpdated() 0 4 1
A applyDescriptionUpdated() 0 5 1
A applyDescriptionTranslated() 0 5 1
A addImage() 0 8 2
A updateImage() 0 8 2
A updateImageAllowed() 0 13 3
A removeImage() 0 8 2
A selectMainImage() 0 14 4
A delete() 0 6 1
A importWorkflowStatus() 0 9 2
A publish() 0 6 2
A guardPublish() 12 12 3
A approve() 0 4 2
A guardApprove() 12 12 3
A reject() 0 4 2
A flagAsDuplicate() 0 5 2
A flagAsInappropriate() 0 5 2
B guardRejection() 0 16 5
A isTitleChanged() 7 7 2
A isDescriptionChanged() 7 7 2
A importImagesFromUDB2() 0 4 1
A updateImagesFromUDB2() 0 4 1
A applyPublished() 0 4 1
A applyApproved() 0 4 1
A applyRejected() 0 5 1
A applyFlaggedAsDuplicate() 0 5 1
A applyFlaggedAsInappropriate() 0 5 1
A applyImageAdded() 0 4 1
A applyImageUpdated() 0 17 1
A applyImageRemoved() 0 4 1
A applyMainImageSelected() 0 4 1
A applyOrganizerUpdated() 0 4 1
A applyOrganizerDeleted() 0 4 1
A applyImagesImportedFromUDB2() 0 4 1
A applyImagesUpdatedFromUDB2() 0 4 1
A applyUdb2ImagesEvent() 0 16 2
createLabelAddedEvent() 0 1 ?
createLabelRemovedEvent() 0 1 ?
createLabelsImportedEvent() 0 1 ?
createTitleTranslatedEvent() 0 1 ?
createDescriptionTranslatedEvent() 0 1 ?
createImageAddedEvent() 0 1 ?
createImageRemovedEvent() 0 1 ?
createImageUpdatedEvent() 0 3 ?
createMainImageSelectedEvent() 0 1 ?
createOfferDeletedEvent() 0 1 ?
createTitleUpdatedEvent() 0 1 ?
createDescriptionUpdatedEvent() 0 1 ?
createCalendarUpdatedEvent() 0 1 ?
createTypicalAgeRangeUpdatedEvent() 0 1 ?
createTypicalAgeRangeDeletedEvent() 0 1 ?
createOrganizerUpdatedEvent() 0 1 ?
createOrganizerDeletedEvent() 0 1 ?
createContactPointUpdatedEvent() 0 1 ?
createGeoCoordinatesUpdatedEvent() 0 1 ?
createBookingInfoUpdatedEvent() 0 1 ?
createPriceInfoUpdatedEvent() 0 1 ?
createPublishedEvent() 0 1 ?
createApprovedEvent() 0 1 ?
createRejectedEvent() 0 1 ?
createFlaggedAsDuplicate() 0 1 ?
createFlaggedAsInappropriate() 0 1 ?
createImagesImportedFromUDB2() 0 1 ?
createImagesUpdatedFromUDB2() 0 1 ?
createTypeUpdatedEvent() 0 1 ?
createThemeUpdatedEvent() 0 1 ?
createFacilitiesUpdatedEvent() 0 1 ?

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Offer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Offer, and based on these observations, apply Extract Interface, too.

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