Completed
Pull Request — master (#239)
by Luc
04:59
created

Offer::getLabels()   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 0
1
<?php
2
3
namespace CultuurNet\UDB3\Offer;
4
5
use Broadway\EventSourcing\EventSourcedAggregateRoot;
6
use CultureFeed_Cdb_Item_Base;
7
use CultuurNet\UDB3\BookingInfo;
8
use CultuurNet\UDB3\ContactPoint;
9
use CultuurNet\UDB3\Label;
10
use CultuurNet\UDB3\LabelCollection;
11
use CultuurNet\UDB3\Media\Image;
12
use CultuurNet\UDB3\Offer\Commands\Image\AbstractUpdateImage;
13
use CultuurNet\UDB3\Language;
14
use CultuurNet\UDB3\Offer\Events\AbstractBookingInfoUpdated;
15
use CultuurNet\UDB3\Offer\Events\AbstractContactPointUpdated;
16
use CultuurNet\UDB3\Offer\Events\AbstractDescriptionTranslated;
17
use CultuurNet\UDB3\Offer\Events\AbstractDescriptionUpdated;
18
use CultuurNet\UDB3\Offer\Events\AbstractLabelAdded;
19
use CultuurNet\UDB3\Offer\Events\AbstractLabelDeleted;
20
use CultuurNet\UDB3\Offer\Events\AbstractOfferDeleted;
21
use CultuurNet\UDB3\Offer\Events\AbstractOrganizerDeleted;
22
use CultuurNet\UDB3\Offer\Events\AbstractOrganizerUpdated;
23
use CultuurNet\UDB3\Offer\Events\AbstractPriceInfoUpdated;
24
use CultuurNet\UDB3\Offer\Events\AbstractTypicalAgeRangeDeleted;
25
use CultuurNet\UDB3\Offer\Events\AbstractTypicalAgeRangeUpdated;
26
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageAdded;
27
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageRemoved;
28
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageUpdated;
29
use CultuurNet\UDB3\Offer\Events\Image\AbstractMainImageSelected;
30
use CultuurNet\UDB3\Offer\Events\AbstractTitleTranslated;
31
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractApproved;
32
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractFlaggedAsDuplicate;
33
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractFlaggedAsInappropriate;
34
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractPublished;
35
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractRejected;
36
use CultuurNet\UDB3\PriceInfo\PriceInfo;
37
use Exception;
38
use ValueObjects\Identity\UUID;
39
use ValueObjects\String\String as StringLiteral;
40
41
abstract class Offer extends EventSourcedAggregateRoot
42
{
43
    const DUPLICATE_REASON = 'duplicate';
44
    const INAPPROPRIATE_REASON = 'inappropriate';
45
46
    /**
47
     * @var LabelCollection
48
     */
49
    protected $labels;
50
51
    /**
52
     * @var UUID[]
53
     */
54
    protected $mediaObjects = [];
55
56
    /**
57
     * @var UUID
58
     */
59
    protected $mainImageId;
60
61
    /**
62
     * @var string
63
     *
64
     * Organizer ids can come from UDB2 which does not strictly use UUIDs.
65
     */
66
    protected $organizerId;
67
68
    /**
69
     * @var WorkflowStatus
70
     */
71
    protected $workflowStatus;
72
73
    /**
74
     * @var StringLiteral|null
75
     */
76
    protected $rejectedReason;
77
78
    /**
79
     * @var PriceInfo
80
     */
81
    protected $priceInfo;
82
83
    /**
84
     * Offer constructor.
85
     */
86
    public function __construct()
87
    {
88
        $this->resetLabels();
89
    }
90
91
    /**
92
     * @return LabelCollection
93
     */
94
    public function getLabels()
95
    {
96
        return $this->labels;
97
    }
98
99
    /**
100
     * @param \CultureFeed_Cdb_Item_Base $udb2Item
101
     */
102 View Code Duplication
    protected function setLabelsFromUDB2Item(\CultureFeed_Cdb_Item_Base $udb2Item)
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...
103
    {
104
        $this->resetLabels();
105
106
        /** @var \CultureFeed_Cdb_Data_Keyword $udb2Keyword */
107
        foreach (array_values($udb2Item->getKeywords(true)) as $udb2Keyword) {
108
            $keyword = trim($udb2Keyword->getValue());
109
            if ($keyword) {
110
                $this->labels = $this->labels->with(
111
                    new Label($keyword, $udb2Keyword->isVisible())
112
                );
113
            }
114
        }
115
    }
116
117
    /**
118
     * Get the id of the main image if one is selected for this offer.
119
     *
120
     * @return UUID|null
121
     */
122
    protected function getMainImageId()
123
    {
124
        return $this->mainImageId;
125
    }
126
127
    /**
128
     * @param Label $label
129
     */
130
    public function addLabel(Label $label)
131
    {
132
        if (!$this->labels->contains($label)) {
133
            $this->apply(
134
                $this->createLabelAddedEvent($label)
135
            );
136
        }
137
    }
138
139
    /**
140
     * @param Label $label
141
     */
142
    public function deleteLabel(Label $label)
143
    {
144
        if ($this->labels->contains($label)) {
145
            $this->apply(
146
                $this->createLabelDeletedEvent($label)
147
            );
148
        }
149
    }
150
151
    /**
152
     * @param Language $language
153
     * @param StringLiteral $title
154
     */
155
    public function translateTitle(Language $language, StringLiteral $title)
156
    {
157
        $this->apply(
158
            $this->createTitleTranslatedEvent($language, $title)
159
        );
160
    }
161
162
    /**
163
     * @param Language $language
164
     * @param StringLiteral $description
165
     */
166
    public function translateDescription(Language $language, StringLiteral $description)
167
    {
168
        $this->apply(
169
            $this->createDescriptionTranslatedEvent($language, $description)
170
        );
171
    }
172
173
174
    /**
175
     * @param string $description
176
     */
177
    public function updateDescription($description)
178
    {
179
        $this->apply(
180
            $this->createDescriptionUpdatedEvent($description)
181
        );
182
    }
183
184
    /**
185
     * @param string $typicalAgeRange
186
     */
187
    public function updateTypicalAgeRange($typicalAgeRange)
188
    {
189
        $this->apply(
190
            $this->createTypicalAgeRangeUpdatedEvent($typicalAgeRange)
191
        );
192
    }
193
194
    public function deleteTypicalAgeRange()
195
    {
196
        $this->apply(
197
            $this->createTypicalAgeRangeDeletedEvent()
198
        );
199
    }
200
201
    /**
202
     * @param string $organizerId
203
     */
204
    public function updateOrganizer($organizerId)
205
    {
206
        if ($this->organizerId !== $organizerId) {
207
            $this->apply(
208
                $this->createOrganizerUpdatedEvent($organizerId)
209
            );
210
        }
211
    }
212
213
    /**
214
     * Delete the given organizer.
215
     *
216
     * @param string $organizerId
217
     */
218
    public function deleteOrganizer($organizerId)
219
    {
220
        if ($this->organizerId === $organizerId) {
221
            $this->apply(
222
                $this->createOrganizerDeletedEvent($organizerId)
223
            );
224
        }
225
    }
226
227
    /**
228
     * Updated the contact info.
229
     * @param ContactPoint $contactPoint
230
     */
231
    public function updateContactPoint(ContactPoint $contactPoint)
232
    {
233
        $this->apply(
234
            $this->createContactPointUpdatedEvent($contactPoint)
235
        );
236
    }
237
238
    /**
239
     * Updated the booking info.
240
     *
241
     * @param BookingInfo $bookingInfo
242
     */
243
    public function updateBookingInfo(BookingInfo $bookingInfo)
244
    {
245
        $this->apply(
246
            $this->createBookingInfoUpdatedEvent($bookingInfo)
247
        );
248
    }
249
250
    /**
251
     * @param PriceInfo $priceInfo
252
     */
253
    public function updatePriceInfo(PriceInfo $priceInfo)
254
    {
255
        if (is_null($this->priceInfo) || $priceInfo->serialize() !== $this->priceInfo->serialize()) {
256
            $this->apply(
257
                $this->createPriceInfoUpdatedEvent($priceInfo)
258
            );
259
        }
260
    }
261
262
    /**
263
     * @param AbstractPriceInfoUpdated $priceInfoUpdated
264
     */
265
    protected function applyPriceInfoUpdated(AbstractPriceInfoUpdated $priceInfoUpdated)
266
    {
267
        $this->priceInfo = $priceInfoUpdated->getPriceInfo();
268
    }
269
270
    /**
271
     * @param AbstractLabelAdded $labelAdded
272
     */
273
    protected function applyLabelAdded(AbstractLabelAdded $labelAdded)
274
    {
275
        $newLabel = $labelAdded->getLabel();
276
277
        if (!$this->labels->contains($newLabel)) {
278
            $this->labels = $this->labels->with($newLabel);
279
        }
280
    }
281
282
    /**
283
     * @param AbstractLabelDeleted $labelDeleted
284
     */
285
    protected function applyLabelDeleted(AbstractLabelDeleted $labelDeleted)
286
    {
287
        $this->labels = $this->labels->without(
288
            $labelDeleted->getLabel()
289
        );
290
    }
291
292
    protected function resetLabels()
293
    {
294
        $this->labels = new LabelCollection();
295
    }
296
297
    /**
298
     * @param Image $image
299
     * @return boolean
300
     */
301
    private function containsImage(Image $image)
302
    {
303
        $equalImages = array_filter(
304
            $this->mediaObjects,
305
            function ($existingMediaObjectId) use ($image) {
306
                return $image
307
                    ->getMediaObjectId()
308
                    ->sameValueAs($existingMediaObjectId);
309
            }
310
        );
311
312
        return !empty($equalImages);
313
    }
314
315
    /**
316
     * Add a new image.
317
     *
318
     * @param Image $image
319
     */
320
    public function addImage(Image $image)
321
    {
322
        if (!$this->containsImage($image)) {
323
            $this->apply(
324
                $this->createImageAddedEvent($image)
325
            );
326
        }
327
    }
328
329
    /**
330
     * @param AbstractUpdateImage $updateImageCommand
331
     */
332
    public function updateImage(AbstractUpdateImage $updateImageCommand)
333
    {
334
        $this->apply(
335
            $this->createImageUpdatedEvent($updateImageCommand)
336
        );
337
    }
338
339
    /**
340
     * Remove an image.
341
     *
342
     * @param Image $image
343
     */
344
    public function removeImage(Image $image)
345
    {
346
        if ($this->containsImage($image)) {
347
            $this->apply(
348
                $this->createImageRemovedEvent($image)
349
            );
350
        }
351
    }
352
353
    /**
354
     * Make an existing image of the item the main image.
355
     *
356
     * @param Image $image
357
     */
358
    public function selectMainImage(Image $image)
359
    {
360
        if (!$this->containsImage($image)) {
361
            throw new \InvalidArgumentException('You can not select a random image to be main, it has to be added to the item.');
362
        }
363
364
        if ($this->mainImageId !== $image->getMediaObjectId()) {
365
            $this->apply(
366
                $this->createMainImageSelectedEvent($image)
367
            );
368
        }
369
    }
370
371
    /**
372
     * Delete the offer.
373
     */
374
    public function delete()
375
    {
376
        $this->apply(
377
            $this->createOfferDeletedEvent()
378
        );
379
    }
380
381
    /**
382
     * @param CultureFeed_Cdb_Item_Base $cdbItem
383
     */
384
    protected function importWorkflowStatus(CultureFeed_Cdb_Item_Base $cdbItem)
385
    {
386
        try {
387
            $workflowStatus = WorkflowStatus::fromNative($cdbItem->getWfStatus());
388
        } catch (\InvalidArgumentException $exception) {
389
            $workflowStatus = WorkflowStatus::READY_FOR_VALIDATION();
390
        }
391
        $this->workflowStatus = $workflowStatus;
392
    }
393
394
    /**
395
     * Publish the offer when it has workflowstatus draft.
396
     * @param \DateTimeInterface $publicationDate
397
     */
398
    public function publish(\DateTimeInterface $publicationDate)
399
    {
400
        $this->guardPublish() ?: $this->apply(
401
            $this->createPublishedEvent($publicationDate)
402
        );
403
    }
404
405
    /**
406
     * @return bool
407
     * @throws Exception
408
     */
409 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...
410
    {
411
        if ($this->workflowStatus === WorkflowStatus::READY_FOR_VALIDATION()) {
412
            return true; // nothing left to do if the offer has already been published
413
        }
414
415
        if ($this->workflowStatus !== WorkflowStatus::DRAFT()) {
416
            throw new Exception('You can not publish an offer that is not draft');
417
        }
418
419
        return false;
420
    }
421
422
    /**
423
     * Approve the offer when it's waiting for validation.
424
     */
425
    public function approve()
426
    {
427
        $this->guardApprove() ?: $this->apply($this->createApprovedEvent());
428
    }
429
430
    /**
431
     * @return bool
432
     * @throws Exception
433
     */
434 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...
435
    {
436
        if ($this->workflowStatus === WorkflowStatus::APPROVED()) {
437
            return true; // nothing left to do if the offer has already been approved
438
        }
439
440
        if ($this->workflowStatus !== WorkflowStatus::READY_FOR_VALIDATION()) {
441
            throw new Exception('You can not approve an offer that is not ready for validation');
442
        }
443
444
        return false;
445
    }
446
447
    /**
448
     * Reject an offer that is waiting for validation with a given reason.
449
     * @param StringLiteral $reason
450
     */
451
    public function reject(StringLiteral $reason)
452
    {
453
        $this->guardRejection($reason) ?: $this->apply($this->createRejectedEvent($reason));
454
    }
455
456
    public function flagAsDuplicate()
457
    {
458
        $reason = new StringLiteral(self::DUPLICATE_REASON);
459
        $this->guardRejection($reason) ?: $this->apply($this->createFlaggedAsDuplicate());
460
    }
461
462
    public function flagAsInappropriate()
463
    {
464
        $reason = new StringLiteral(self::INAPPROPRIATE_REASON);
465
        $this->guardRejection($reason) ?: $this->apply($this->createFlaggedAsInappropriate());
466
    }
467
468
    /**
469
     * @param StringLiteral $reason
470
     * @return bool
471
     *  false when the offer can still be rejected, true when the offer is already rejected for the same reason
472
     * @throws Exception
473
     */
474
    private function guardRejection(StringLiteral $reason)
475
    {
476
        if ($this->workflowStatus === WorkflowStatus::REJECTED()) {
477
            if ($this->rejectedReason && $reason->sameValueAs($this->rejectedReason)) {
478
                return true; // nothing left to do if the offer has already been rejected for the same reason
479
            } else {
480
                throw new Exception('The offer has already been rejected for another reason: ' . $this->rejectedReason);
481
            }
482
        }
483
484
        if ($this->workflowStatus !== WorkflowStatus::READY_FOR_VALIDATION()) {
485
            throw new Exception('You can not reject an offer that is not ready for validation');
486
        }
487
488
        return false;
489
    }
490
491
    /**
492
     * @param AbstractPublished $published
493
     */
494
    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...
495
    {
496
        $this->workflowStatus = WorkflowStatus::READY_FOR_VALIDATION();
497
    }
498
499
    /**
500
     * @param AbstractApproved $approved
501
     */
502
    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...
503
    {
504
        $this->workflowStatus = WorkflowStatus::APPROVED();
505
    }
506
507
    /**
508
     * @param AbstractRejected $rejected
509
     */
510
    protected function applyRejected(AbstractRejected $rejected)
511
    {
512
        $this->rejectedReason = $rejected->getReason();
513
        $this->workflowStatus = WorkflowStatus::REJECTED();
514
    }
515
516
    /**
517
     * @param AbstractFlaggedAsDuplicate $flaggedAsDuplicate
518
     */
519
    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...
520
    {
521
        $this->rejectedReason = new StringLiteral(self::DUPLICATE_REASON);
522
        $this->workflowStatus = WorkflowStatus::REJECTED();
523
    }
524
525
    /**
526
     * @param AbstractFlaggedAsInappropriate $flaggedAsInappropriate
527
     */
528
    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...
529
    {
530
        $this->rejectedReason = new StringLiteral(self::INAPPROPRIATE_REASON);
531
        $this->workflowStatus = WorkflowStatus::REJECTED();
532
    }
533
534 View Code Duplication
    protected function applyImageAdded(AbstractImageAdded $imageAdded)
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...
535
    {
536
        $imageId = $imageAdded->getImage()->getMediaObjectId();
537
        $this->mediaObjects[] = $imageId;
538
539
        if (count($this->mediaObjects) === 1) {
540
            $this->mainImageId = $imageId;
541
        }
542
    }
543
544 View Code Duplication
    protected function applyImageRemoved(AbstractImageRemoved $imageRemoved)
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...
545
    {
546
        $this->mediaObjects = array_diff(
547
            $this->mediaObjects,
548
            [$imageRemoved->getImage()->getMediaObjectId()]
549
        );
550
551
        $oldestImageId = reset($this->mediaObjects);
552
        if ($oldestImageId) {
553
            $this->mainImageId = $oldestImageId;
554
        }
555
    }
556
557
    protected function applyMainImageSelected(AbstractMainImageSelected $mainImageSelected)
558
    {
559
        $this->mainImageId = $mainImageSelected->getImage()->getMediaObjectId();
560
    }
561
562
    protected function applyOrganizerUpdated(AbstractOrganizerUpdated $organizerUpdated)
563
    {
564
        $this->organizerId = $organizerUpdated->getOrganizerId();
565
    }
566
567
    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...
568
    {
569
        $this->organizerId = null;
570
    }
571
572
    /**
573
     * @param Label $label
574
     * @return AbstractLabelAdded
575
     */
576
    abstract protected function createLabelAddedEvent(Label $label);
577
578
    /**
579
     * @param Label $label
580
     * @return AbstractLabelDeleted
581
     */
582
    abstract protected function createLabelDeletedEvent(Label $label);
583
584
    /**
585
     * @param Language $language
586
     * @param StringLiteral $title
587
     * @return AbstractTitleTranslated
588
     */
589
    abstract protected function createTitleTranslatedEvent(Language $language, StringLiteral $title);
590
591
    /**
592
     * @param Language $language
593
     * @param StringLiteral $description
594
     * @return AbstractDescriptionTranslated
595
     */
596
    abstract protected function createDescriptionTranslatedEvent(Language $language, StringLiteral $description);
597
598
    /**
599
     * @param Image $image
600
     * @return AbstractImageAdded
601
     */
602
    abstract protected function createImageAddedEvent(Image $image);
603
604
    /**
605
     * @param Image $image
606
     * @return AbstractImageRemoved
607
     */
608
    abstract protected function createImageRemovedEvent(Image $image);
609
610
    /**
611
     * @param AbstractUpdateImage $updateImageCommand
612
     * @return AbstractImageUpdated
613
     */
614
    abstract protected function createImageUpdatedEvent(
615
        AbstractUpdateImage $updateImageCommand
616
    );
617
618
    /**
619
     * @param Image $image
620
     * @return AbstractMainImageSelected
621
     */
622
    abstract protected function createMainImageSelectedEvent(Image $image);
623
624
    /**
625
     * @return AbstractOfferDeleted
626
     */
627
    abstract protected function createOfferDeletedEvent();
628
629
    /**
630
     * @param string $description
631
     * @return AbstractDescriptionUpdated
632
     */
633
    abstract protected function createDescriptionUpdatedEvent($description);
634
635
    /**
636
     * @param string $typicalAgeRange
637
     * @return AbstractTypicalAgeRangeUpdated
638
     */
639
    abstract protected function createTypicalAgeRangeUpdatedEvent($typicalAgeRange);
640
641
    /**
642
     * @return AbstractTypicalAgeRangeDeleted
643
     */
644
    abstract protected function createTypicalAgeRangeDeletedEvent();
645
646
    /**
647
     * @param string $organizerId
648
     * @return AbstractOrganizerUpdated
649
     */
650
    abstract protected function createOrganizerUpdatedEvent($organizerId);
651
652
    /**
653
     * @param string $organizerId
654
     * @return AbstractOrganizerDeleted
655
     */
656
    abstract protected function createOrganizerDeletedEvent($organizerId);
657
658
    /**
659
     * @param ContactPoint $contactPoint
660
     * @return AbstractContactPointUpdated
661
     */
662
    abstract protected function createContactPointUpdatedEvent(ContactPoint $contactPoint);
663
664
    /**
665
     * @param BookingInfo $bookingInfo
666
     * @return AbstractBookingInfoUpdated
667
     */
668
    abstract protected function createBookingInfoUpdatedEvent(BookingInfo $bookingInfo);
669
670
    /**
671
     * @param PriceInfo $priceInfo
672
     * @return AbstractPriceInfoUpdated
673
     */
674
    abstract protected function createPriceInfoUpdatedEvent(PriceInfo $priceInfo);
675
676
    /**
677
     * @param \DateTimeInterface $publicationDate
678
     * @return AbstractPublished
679
     */
680
    abstract protected function createPublishedEvent(\DateTimeInterface $publicationDate);
681
682
    /**
683
     * @return AbstractApproved
684
     */
685
    abstract protected function createApprovedEvent();
686
687
    /**
688
     * @param StringLiteral $reason
689
     * @return AbstractRejected
690
     */
691
    abstract protected function createRejectedEvent(StringLiteral $reason);
692
693
    /**
694
     * @return AbstractFlaggedAsDuplicate
695
     */
696
    abstract protected function createFlaggedAsDuplicate();
697
698
    /**
699
     * @return AbstractFlaggedAsInappropriate
700
     */
701
    abstract protected function createFlaggedAsInappropriate();
702
}
703