Passed
Push — develop ( ce2c28...5aaf0d )
by Torben
60:56 queued 15:54
created

EventController::getCurrentLanguageCode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Extension "sf_event_mgt" for TYPO3 CMS.
7
 *
8
 * For the full copyright and license information, please read the
9
 * LICENSE.txt file that was distributed with this source code.
10
 */
11
12
namespace DERHANSEN\SfEventMgt\Controller;
13
14
use DateInterval;
15
use DateTime;
16
use DERHANSEN\SfEventMgt\Domain\Model\Dto\CategoryDemand;
17
use DERHANSEN\SfEventMgt\Domain\Model\Dto\EventDemand;
18
use DERHANSEN\SfEventMgt\Domain\Model\Dto\ForeignRecordDemand;
19
use DERHANSEN\SfEventMgt\Domain\Model\Dto\SearchDemand;
20
use DERHANSEN\SfEventMgt\Domain\Model\Event;
21
use DERHANSEN\SfEventMgt\Domain\Model\Registration;
22
use DERHANSEN\SfEventMgt\Event\AfterRegistrationCancelledEvent;
23
use DERHANSEN\SfEventMgt\Event\AfterRegistrationConfirmedEvent;
24
use DERHANSEN\SfEventMgt\Event\AfterRegistrationSavedEvent;
25
use DERHANSEN\SfEventMgt\Event\EventPidCheckFailedEvent;
26
use DERHANSEN\SfEventMgt\Event\ModifyCalendarViewVariablesEvent;
27
use DERHANSEN\SfEventMgt\Event\ModifyCancelRegistrationViewVariablesEvent;
28
use DERHANSEN\SfEventMgt\Event\ModifyConfirmRegistrationViewVariablesEvent;
29
use DERHANSEN\SfEventMgt\Event\ModifyCreateDependingRegistrationsEvent;
30
use DERHANSEN\SfEventMgt\Event\ModifyDetailViewVariablesEvent;
31
use DERHANSEN\SfEventMgt\Event\ModifyListViewVariablesEvent;
32
use DERHANSEN\SfEventMgt\Event\ModifyRegistrationViewVariablesEvent;
33
use DERHANSEN\SfEventMgt\Event\ModifySearchViewVariablesEvent;
34
use DERHANSEN\SfEventMgt\Event\ProcessRedirectToPaymentEvent;
35
use DERHANSEN\SfEventMgt\Event\WaitlistMoveUpEvent;
36
use DERHANSEN\SfEventMgt\Exception;
37
use DERHANSEN\SfEventMgt\Service\EventCacheService;
38
use DERHANSEN\SfEventMgt\Utility\MessageType;
39
use DERHANSEN\SfEventMgt\Utility\PageUtility;
40
use DERHANSEN\SfEventMgt\Utility\RegistrationResult;
41
use Psr\Http\Message\ResponseInterface;
42
use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
43
use TYPO3\CMS\Core\Http\PropagateResponseException;
44
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
45
use TYPO3\CMS\Core\Utility\ArrayUtility;
0 ignored issues
show
Bug introduced by
The type TYPO3\CMS\Core\Utility\ArrayUtility was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
46
use TYPO3\CMS\Core\Utility\GeneralUtility;
0 ignored issues
show
Bug introduced by
The type TYPO3\CMS\Core\Utility\GeneralUtility was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
47
use TYPO3\CMS\Extbase\Annotation as Extbase;
48
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
49
use TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter;
50
use TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter;
51
use TYPO3\CMS\Fluid\View\StandaloneView;
52
use TYPO3\CMS\Frontend\Controller\ErrorController;
53
54
class EventController extends AbstractController
55
{
56
    protected EventCacheService $eventCacheService;
57
58
    public function injectEventCacheService(EventCacheService $cacheService): void
59
    {
60
        $this->eventCacheService = $cacheService;
61
    }
62
63
    /**
64
     * Assign contentObjectData and pageData view
65
     */
66
    protected function initializeView(): void
67
    {
68
        $this->view->assign('contentObjectData', $this->request->getAttribute('currentContentObject')->data);
69
        if ($this->getTypoScriptFrontendController()) {
70
            $this->view->assign('pageData', $this->getTypoScriptFrontendController()->page);
71
        }
72
    }
73
74
    /**
75
     * Initializes the current action
76
     */
77
    public function initializeAction(): void
78
    {
79
        $typoScriptFrontendController = $this->getTypoScriptFrontendController();
80
        if ($typoScriptFrontendController !== null) {
81
            static $cacheTagsSet = false;
82
83
            if (!$cacheTagsSet) {
84
                $typoScriptFrontendController->addCacheTags(['tx_sfeventmgt']);
85
                $cacheTagsSet = true;
86
            }
87
        }
88
    }
89
90
    /**
91
     * Initialize list action and set format
92
     */
93
    public function initializeListAction(): void
94
    {
95
        if (isset($this->settings['list']['format'])) {
96
            $this->request = $this->request->withFormat($this->settings['list']['format']);
97
        }
98
    }
99
100
    /**
101
     * List view
102
     */
103
    public function listAction(array $overwriteDemand = []): ResponseInterface
104
    {
105
        $eventDemand = EventDemand::createFromSettings($this->settings);
106
        $foreignRecordDemand = ForeignRecordDemand::createFromSettings($this->settings);
107
        $categoryDemand = CategoryDemand::createFromSettings($this->settings);
108
        if ($this->isOverwriteDemand($overwriteDemand)) {
109
            $eventDemand = $this->overwriteEventDemandObject($eventDemand, $overwriteDemand);
110
        }
111
        $events = $this->eventRepository->findDemanded($eventDemand);
112
        $categories = $this->categoryRepository->findDemanded($categoryDemand);
113
        $locations = $this->locationRepository->findDemanded($foreignRecordDemand);
114
        $organisators = $this->organisatorRepository->findDemanded($foreignRecordDemand);
115
        $speakers = $this->speakerRepository->findDemanded($foreignRecordDemand);
116
117
        $modifyListViewVariablesEvent = new ModifyListViewVariablesEvent(
118
            [
119
                'events' => $events,
120
                'pagination' => $this->getPagination($events, $this->settings['pagination'] ?? []),
121
                'categories' => $categories,
122
                'locations' => $locations,
123
                'organisators' => $organisators,
124
                'speakers' => $speakers,
125
                'overwriteDemand' => $overwriteDemand,
126
                'eventDemand' => $eventDemand,
127
                'settings' => $this->settings,
128
            ],
129
            $this
130
        );
131
        $this->eventDispatcher->dispatch($modifyListViewVariablesEvent);
132
        $variables = $modifyListViewVariablesEvent->getVariables();
133
        $this->view->assignMultiple($variables);
134
135
        $this->eventCacheService->addPageCacheTagsByEventDemandObject($eventDemand);
136
137
        return $this->htmlResponse();
138
    }
139
140
    /**
141
     * Calendar view
142
     */
143
    public function calendarAction(array $overwriteDemand = []): ResponseInterface
144
    {
145
        $eventDemand = EventDemand::createFromSettings($this->settings);
146
        $foreignRecordDemand = ForeignRecordDemand::createFromSettings($this->settings);
147
        $categoryDemand = CategoryDemand::createFromSettings($this->settings);
148
        if ($this->isOverwriteDemand($overwriteDemand)) {
149
            $eventDemand = $this->overwriteEventDemandObject($eventDemand, $overwriteDemand);
150
        }
151
152
        // Set month/year to demand if not given
153
        if (!$eventDemand->getMonth()) {
154
            $currentMonth = (int)date('n');
155
            $eventDemand->setMonth($currentMonth);
156
        } else {
157
            $currentMonth = $eventDemand->getMonth();
158
        }
159
        if (!$eventDemand->getYear()) {
160
            $currentYear = (int)date('Y');
161
            $eventDemand->setYear($currentYear);
162
        } else {
163
            $currentYear = $eventDemand->getYear();
164
        }
165
166
        // If a weeknumber is given in overwriteDemand['week'], we overwrite the current month
167
        if ($overwriteDemand['week'] ?? false) {
168
            $firstDayOfWeek = (new DateTime())->setISODate($currentYear, (int)$overwriteDemand['week']);
169
            $currentMonth = (int)$firstDayOfWeek->format('m');
170
            $eventDemand->setMonth($currentMonth);
171
        } else {
172
            $firstDayOfWeek = (new DateTime())->setISODate($currentYear, (int)date('W'));
173
        }
174
175
        // Set demand from calendar date range instead of month / year
176
        if ((bool)($this->settings['calendar']['includeEventsForEveryDayOfAllCalendarWeeks'] ?? false)) {
177
            $eventDemand = $this->changeEventDemandToFullMonthDateRange($eventDemand);
178
        }
179
180
        $events = $this->eventRepository->findDemanded($eventDemand);
181
        $weeks = $this->calendarService->getCalendarArray(
182
            $currentMonth,
183
            $currentYear,
184
            strtotime('today midnight'),
185
            (int)($this->settings['calendar']['firstDayOfWeek'] ?? 1),
186
            $events
187
        );
188
189
        $modifyCalendarViewVariablesEvent = new ModifyCalendarViewVariablesEvent(
190
            [
191
                'events' => $events,
192
                'weeks' => $weeks,
193
                'categories' => $this->categoryRepository->findDemanded($categoryDemand),
194
                'locations' => $this->locationRepository->findDemanded($foreignRecordDemand),
195
                'organisators' => $this->organisatorRepository->findDemanded($foreignRecordDemand),
196
                'eventDemand' => $eventDemand,
197
                'overwriteDemand' => $overwriteDemand,
198
                'currentPageId' => $this->getTypoScriptFrontendController()->id,
199
                'firstDayOfMonth' => DateTime::createFromFormat(
200
                    'd.m.Y',
201
                    sprintf('1.%s.%s', $currentMonth, $currentYear)
202
                ),
203
                'previousMonthConfig' => $this->calendarService->getDateConfig($currentMonth, $currentYear, '-1 month'),
204
                'nextMonthConfig' => $this->calendarService->getDateConfig($currentMonth, $currentYear, '+1 month'),
205
                'weekConfig' => $this->calendarService->getWeekConfig($firstDayOfWeek),
206
                'settings' => $this->settings,
207
            ],
208
            $this
209
        );
210
        $this->eventDispatcher->dispatch($modifyCalendarViewVariablesEvent);
211
        $variables = $modifyCalendarViewVariablesEvent->getVariables();
212
213
        $this->view->assignMultiple($variables);
214
        return $this->htmlResponse();
215
    }
216
217
    /**
218
     * Changes the given event demand object to select a date range for a calendar month including days of the previous
219
     * month for the first week and they days for the next month for the last week
220
     */
221
    protected function changeEventDemandToFullMonthDateRange(EventDemand $eventDemand): EventDemand
222
    {
223
        $calendarDateRange = $this->calendarService->getCalendarDateRange(
224
            $eventDemand->getMonth(),
225
            $eventDemand->getYear(),
226
            (int)($this->settings['calendar']['firstDayOfWeek'] ?? 0)
227
        );
228
229
        $eventDemand->setMonth(0);
230
        $eventDemand->setYear(0);
231
232
        $startDate = new DateTime();
233
        $startDate->setTimestamp($calendarDateRange['firstDayOfCalendar']);
234
        $endDate = new DateTime();
235
        $endDate->setTimestamp($calendarDateRange['lastDayOfCalendar']);
236
        $endDate->setTime(23, 59, 59);
237
238
        $searchDemand = GeneralUtility::makeInstance(SearchDemand::class);
239
        $searchDemand->setStartDate($startDate);
240
        $searchDemand->setEndDate($endDate);
241
        $eventDemand->setSearchDemand($searchDemand);
242
243
        return $eventDemand;
244
    }
245
246
    /**
247
     * Detail view for an event
248
     *
249
     * @return mixed
250
     */
251
    public function detailAction(?Event $event = null)
252
    {
253
        $event = $this->evaluateSingleEventSetting($event);
254
        $event = $this->evaluateIsShortcutSetting($event);
255
        if (is_a($event, Event::class) && ($this->settings['detail']['checkPidOfEventRecord'] ?? false)) {
256
            $event = $this->checkPidOfEventRecord($event);
257
        }
258
259
        if (is_null($event) && isset($this->settings['event']['errorHandling'])) {
260
            return $this->handleEventNotFoundError($this->settings);
261
        }
262
263
        $modifyDetailViewVariablesEvent = new ModifyDetailViewVariablesEvent(['event' => $event, 'settings' => $this->settings], $this);
264
        $this->eventDispatcher->dispatch($modifyDetailViewVariablesEvent);
265
        $variables = $modifyDetailViewVariablesEvent->getVariables();
266
267
        $this->view->assignMultiple($variables);
268
        if ($event !== null) {
269
            $this->eventCacheService->addCacheTagsByEventRecords([$event]);
270
        }
271
272
        return $this->htmlResponse();
273
    }
274
275
    /**
276
     * Error handling if event is not found
277
     *
278
     * @param array $settings
279
     * @return ResponseInterface
280
     * @throws Exception
281
     * @throws PropagateResponseException
282
     * @throws PageNotFoundException
283
     */
284
    protected function handleEventNotFoundError(array $settings): ResponseInterface
285
    {
286
        if (empty($settings['event']['errorHandling'])) {
287
            throw new Exception('Event errorHandling not configured. Please check settings.event.errorHandling', 1671205677);
288
        }
289
290
        $configuration = GeneralUtility::trimExplode(',', $settings['event']['errorHandling'], true);
291
292
        switch ($configuration[0]) {
293
            case 'redirectToListView':
294
                $listPid = (int)($settings['listPid'] ?? 0) > 0 ? (int)$settings['listPid'] : 1;
295
                return $this->redirect('list', null, null, null, $listPid);
296
            case 'pageNotFoundHandler':
297
                $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
298
                    $this->request,
299
                    'Event not found.'
300
                );
301
                throw new PropagateResponseException($response, 1631261423);
302
            case 'showStandaloneTemplate':
303
            default:
304
                $status = (int)($configuration[2] ?? 200);
305
                $standaloneTemplate = GeneralUtility::makeInstance(StandaloneView::class);
306
                $standaloneTemplate->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($configuration[1]));
307
308
                $response = $this->responseFactory->createResponse()
309
                    ->withStatus($status)
310
                    ->withHeader('Content-Type', 'text/html; charset=utf-8');
311
                $response->getBody()->write($standaloneTemplate->render());
312
                return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response returns the type Psr\Http\Message\MessageInterface which includes types incompatible with the type-hinted return Psr\Http\Message\ResponseInterface.
Loading history...
313
        }
314
    }
315
316
    /**
317
     * Initiates the iCalendar download for the given event
318
     *
319
     * @return mixed
320
     */
321
    public function icalDownloadAction(?Event $event = null)
322
    {
323
        if (is_a($event, Event::class) && ($this->settings['detail']['checkPidOfEventRecord'] ?? false)) {
324
            $event = $this->checkPidOfEventRecord($event);
325
        }
326
        if (is_null($event) && isset($this->settings['event']['errorHandling'])) {
327
            return $this->handleEventNotFoundError($this->settings);
328
        }
329
        $this->icalendarService->downloadiCalendarFile($event);
0 ignored issues
show
Bug introduced by
It seems like $event can also be of type null; however, parameter $event of DERHANSEN\SfEventMgt\Ser...downloadiCalendarFile() does only seem to accept DERHANSEN\SfEventMgt\Domain\Model\Event, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

329
        $this->icalendarService->downloadiCalendarFile(/** @scrutinizer ignore-type */ $event);
Loading history...
330
        exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
331
    }
332
333
    /**
334
     * Registration view for an event
335
     */
336
    public function registrationAction(?Event $event = null): ResponseInterface
337
    {
338
        $event = $this->evaluateSingleEventSetting($event);
339
        if (is_a($event, Event::class) && ($this->settings['registration']['checkPidOfEventRecord'] ?? false)) {
340
            $event = $this->checkPidOfEventRecord($event);
341
        }
342
        if (is_null($event) && isset($this->settings['event']['errorHandling'])) {
343
            return $this->handleEventNotFoundError($this->settings);
344
        }
345
        if ($event->getRestrictPaymentMethods()) {
346
            $paymentMethods = $this->paymentService->getRestrictedPaymentMethods($event);
347
        } else {
348
            $paymentMethods = $this->paymentService->getPaymentMethods();
349
        }
350
351
        $modifyRegistrationViewVariablesEvent = new ModifyRegistrationViewVariablesEvent(
352
            [
353
                'event' => $event,
354
                'paymentMethods' => $paymentMethods,
355
                'settings' => $this->settings,
356
            ],
357
            $this
358
        );
359
        $this->eventDispatcher->dispatch($modifyRegistrationViewVariablesEvent);
360
        $variables = $modifyRegistrationViewVariablesEvent->getVariables();
361
        $this->view->assignMultiple($variables);
362
363
        return $this->htmlResponse();
364
    }
365
366
    /**
367
     * Removes all possible spamcheck fields (which do not belong to the domain model) from arguments.
368
     */
369
    protected function removePossibleSpamCheckFieldsFromArguments(): void
370
    {
371
        $arguments = $this->request->getArguments();
372
        if (!isset($arguments['event'])) {
373
            return;
374
        }
375
376
        // Remove a possible honeypot field
377
        $honeypotField = 'hp' . (int)$arguments['event'];
378
        if (isset($arguments['registration'][$honeypotField])) {
379
            unset($arguments['registration'][$honeypotField]);
380
        }
381
382
        // Remove a possible challenge/response field
383
        if (isset($arguments['registration']['cr-response'])) {
384
            unset($arguments['registration']['cr-response']);
385
        }
386
387
        $this->request = $this->request->withArguments($arguments);
388
    }
389
390
    /**
391
     * Processes incoming registrations fields and adds field values to arguments
392
     */
393
    protected function setRegistrationFieldValuesToArguments(): void
394
    {
395
        $arguments = $this->request->getArguments();
396
        if (!isset($arguments['event'])) {
397
            return;
398
        }
399
400
        /** @var Event $event */
401
        $event = $this->eventRepository->findByUid((int)$this->request->getArgument('event'));
402
        if (!is_a($event, Event::class)) {
403
            return;
404
        }
405
406
        $registrationMvcArgument = $this->arguments->getArgument('registration');
407
        $propertyMapping = $registrationMvcArgument->getPropertyMappingConfiguration();
408
        $propertyMapping->allowProperties('fieldValues');
409
        $propertyMapping->allowCreationForSubProperty('fieldValues');
410
        $propertyMapping->allowModificationForSubProperty('fieldValues');
411
412
        // Set event to registration (required for validation)
413
        $propertyMapping->allowProperties('event');
414
        $propertyMapping->allowCreationForSubProperty('event');
415
        $propertyMapping->allowModificationForSubProperty('event');
416
        $arguments['registration']['event'] = (int)$this->request->getArgument('event');
417
418
        if (count($event->getRegistrationFieldsUids()) === 0) {
419
            // Set arguments to request, so event is set for event
420
            $this->request = $this->request->withArguments($arguments);
421
            return;
422
        }
423
424
        // allow creation of new objects (for validation)
425
        $propertyMapping->setTypeConverterOptions(
426
            PersistentObjectConverter::class,
427
            [
428
                PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED => true,
429
                PersistentObjectConverter::CONFIGURATION_MODIFICATION_ALLOWED => true,
430
            ]
431
        );
432
433
        $index = 0;
434
        foreach ((array)($arguments['registration']['fields'] ?? []) as $fieldUid => $value) {
435
            // Only accept registration fields of the current event
436
            if (!in_array((int)$fieldUid, $event->getRegistrationFieldsUids(), true)) {
437
                continue;
438
            }
439
440
            // allow subvalues in new property mapper
441
            $propertyMapping->forProperty('fieldValues')->allowProperties($index);
442
            $propertyMapping->forProperty('fieldValues.' . $index)->allowAllProperties();
443
            $propertyMapping->allowCreationForSubProperty('fieldValues.' . $index);
444
            $propertyMapping->allowModificationForSubProperty('fieldValues.' . $index);
445
446
            if (is_array($value)) {
447
                if (empty($value)) {
448
                    $value = '';
449
                } else {
450
                    $value = json_encode($value);
451
                }
452
            }
453
454
            /** @var Registration\Field $field */
455
            $field = $this->fieldRepository->findByUid((int)$fieldUid);
456
457
            $arguments['registration']['fieldValues'][$index] = [
458
                'pid' => $field->getPid(),
459
                'value' => $value,
460
                'field' => (string)$fieldUid,
461
                'valueType' => $field->getValueType(),
462
            ];
463
464
            $index++;
465
        }
466
467
        // Remove temporary "fields" field
468
        if (isset($arguments['registration']['fields'])) {
469
            $arguments = ArrayUtility::removeByPath($arguments, 'registration/fields');
470
        }
471
        $this->request = $this->request->withArguments($arguments);
472
    }
473
474
    /**
475
     * Set date format for field dateOfBirth
476
     */
477
    public function initializeSaveRegistrationAction(): void
478
    {
479
        $this->arguments->getArgument('registration')
480
            ->getPropertyMappingConfiguration()->forProperty('dateOfBirth')
481
            ->setTypeConverterOption(
482
                DateTimeConverter::class,
483
                DateTimeConverter::CONFIGURATION_DATE_FORMAT,
484
                $this->settings['registration']['formatDateOfBirth'] ?? 'd.m.Y'
485
            );
486
        $this->removePossibleSpamCheckFieldsFromArguments();
487
        $this->setRegistrationFieldValuesToArguments();
488
    }
489
490
    /**
491
     * Saves the registration
492
     *
493
     * @Extbase\Validate("DERHANSEN\SfEventMgt\Validation\Validator\RegistrationFieldValidator", param="registration")
494
     * @Extbase\Validate("DERHANSEN\SfEventMgt\Validation\Validator\RegistrationValidator", param="registration")
495
     */
496
    public function saveRegistrationAction(Registration $registration, Event $event): ResponseInterface
497
    {
498
        if (is_a($event, Event::class) && ($this->settings['registration']['checkPidOfEventRecord'] ?? false)) {
499
            $event = $this->checkPidOfEventRecord($event);
500
        }
501
        if (is_null($event) && isset($this->settings['event']['errorHandling'])) {
502
            return $this->handleEventNotFoundError($this->settings);
503
        }
504
        $autoConfirmation = (bool)($this->settings['registration']['autoConfirmation'] ?? false) ||
505
            $event->getEnableAutoconfirm();
506
        $result = RegistrationResult::REGISTRATION_SUCCESSFUL;
507
        list($success, $result) = $this->registrationService->checkRegistrationSuccess($event, $registration, $result);
508
509
        // Save registration if no errors
510
        if ($success) {
511
            $isWaitlistRegistration = $this->registrationService->isWaitlistRegistration(
512
                $event,
513
                $registration->getAmountOfRegistrations()
514
            );
515
            $linkValidity = (int)($this->settings['confirmation']['linkValidity'] ?? 3600);
516
            if ($linkValidity === 0) {
517
                // Use 3600 seconds as default value if not set or zero
518
                $linkValidity = 3600;
519
            }
520
            $confirmationUntil = new DateTime();
521
            $confirmationUntil->add(new DateInterval('PT' . $linkValidity . 'S'));
522
523
            $registration->setEvent($event);
524
            $registration->setPid($event->getPid());
525
            $registration->setRegistrationDate(new DateTime());
526
            $registration->setConfirmationUntil($confirmationUntil);
527
            $registration->setLanguage($this->getCurrentLanguageCode());
528
            $registration->setFeUser($this->registrationService->getCurrentFeUserObject());
529
            $registration->setWaitlist($isWaitlistRegistration);
530
            $this->registrationRepository->add($registration);
531
532
            // Persist registration, so we have an UID
533
            $this->persistAll();
534
535
            if ($isWaitlistRegistration) {
536
                $messageType = MessageType::REGISTRATION_WAITLIST_NEW;
537
            } else {
538
                $messageType = MessageType::REGISTRATION_NEW;
539
            }
540
541
            $this->eventDispatcher->dispatch(new AfterRegistrationSavedEvent($registration, $this));
542
543
            // Send notifications to user and admin if confirmation link should be sent
544
            if (!$autoConfirmation) {
545
                $this->notificationService->sendUserMessage(
546
                    $event,
547
                    $registration,
548
                    $this->settings,
549
                    $messageType
550
                );
551
                $this->notificationService->sendAdminMessage(
552
                    $event,
553
                    $registration,
554
                    $this->settings,
555
                    $messageType
556
                );
557
            }
558
559
            // Create given amount of registrations if necessary
560
            $modifyCreateDependingRegistrationsEvent = new ModifyCreateDependingRegistrationsEvent(
561
                $registration,
562
                ($registration->getAmountOfRegistrations() > 1),
563
                $this
564
            );
565
            $this->eventDispatcher->dispatch($modifyCreateDependingRegistrationsEvent);
566
            $createDependingRegistrations = $modifyCreateDependingRegistrationsEvent->getCreateDependingRegistrations();
567
            if ($createDependingRegistrations) {
568
                $this->registrationService->createDependingRegistrations($registration);
569
            }
570
571
            // Flush page cache for event, since new registration has been added
572
            $this->eventCacheService->flushEventCache($event->getUid(), $event->getPid());
573
        }
574
575
        if ($autoConfirmation && $success) {
576
            return $this->redirect(
577
                'confirmRegistration',
578
                null,
579
                null,
580
                [
581
                    'reguid' => $registration->getUid(),
582
                    'hmac' => $this->hashService->generateHmac('reg-' . $registration->getUid()),
583
                ]
584
            );
585
        }
586
587
        return $this->redirect(
588
            'saveRegistrationResult',
589
            null,
590
            null,
591
            [
592
                'result' => $result,
593
                'eventuid' => $event->getUid(),
594
                'hmac' => $this->hashService->generateHmac('event-' . $event->getUid()),
595
            ]
596
        );
597
    }
598
599
    /**
600
     * Shows the result of the saveRegistrationAction
601
     */
602
    public function saveRegistrationResultAction(int $result, int $eventuid, string $hmac): ResponseInterface
603
    {
604
        $event = null;
605
606
        switch ($result) {
607
            case RegistrationResult::REGISTRATION_SUCCESSFUL:
608
                $messageKey = 'event.message.registrationsuccessful';
609
                $titleKey = 'registrationResult.title.successful';
610
                break;
611
            case RegistrationResult::REGISTRATION_SUCCESSFUL_WAITLIST:
612
                $messageKey = 'event.message.registrationwaitlistsuccessful';
613
                $titleKey = 'registrationWaitlistResult.title.successful';
614
                break;
615
            case RegistrationResult::REGISTRATION_FAILED_EVENT_EXPIRED:
616
                $messageKey = 'event.message.registrationfailedeventexpired';
617
                $titleKey = 'registrationResult.title.failed';
618
                break;
619
            case RegistrationResult::REGISTRATION_FAILED_MAX_PARTICIPANTS:
620
                $messageKey = 'event.message.registrationfailedmaxparticipants';
621
                $titleKey = 'registrationResult.title.failed';
622
                break;
623
            case RegistrationResult::REGISTRATION_NOT_ENABLED:
624
                $messageKey = 'event.message.registrationfailednotenabled';
625
                $titleKey = 'registrationResult.title.failed';
626
                break;
627
            case RegistrationResult::REGISTRATION_FAILED_DEADLINE_EXPIRED:
628
                $messageKey = 'event.message.registrationfaileddeadlineexpired';
629
                $titleKey = 'registrationResult.title.failed';
630
                break;
631
            case RegistrationResult::REGISTRATION_FAILED_NOT_ENOUGH_FREE_PLACES:
632
                $messageKey = 'event.message.registrationfailednotenoughfreeplaces';
633
                $titleKey = 'registrationResult.title.failed';
634
                break;
635
            case RegistrationResult::REGISTRATION_FAILED_MAX_AMOUNT_REGISTRATIONS_EXCEEDED:
636
                $messageKey = 'event.message.registrationfailedmaxamountregistrationsexceeded';
637
                $titleKey = 'registrationResult.title.failed';
638
                break;
639
            case RegistrationResult::REGISTRATION_FAILED_EMAIL_NOT_UNIQUE:
640
                $messageKey = 'event.message.registrationfailedemailnotunique';
641
                $titleKey = 'registrationResult.title.failed';
642
                break;
643
            default:
644
                $messageKey = '';
645
                $titleKey = '';
646
        }
647
648
        if (!$this->hashService->validateHmac('event-' . $eventuid, $hmac)) {
649
            $messageKey = 'event.message.registrationsuccessfulwrongeventhmac';
650
            $titleKey = 'registrationResult.title.failed';
651
        } else {
652
            $event = $this->eventRepository->findByUid((int)$eventuid);
653
        }
654
655
        $this->view->assignMultiple([
656
            'messageKey' => $messageKey,
657
            'titleKey' => $titleKey,
658
            'event' => $event,
659
        ]);
660
661
        return $this->htmlResponse();
662
    }
663
664
    /**
665
     * Confirms the registration if possible and sends emails to admin and user
666
     */
667
    public function confirmRegistrationAction(int $reguid, string $hmac): ResponseInterface
668
    {
669
        $event = null;
670
671
        /* @var $registration Registration */
672
        list($failed, $registration, $messageKey, $titleKey) = $this->registrationService->checkConfirmRegistration(
673
            $reguid,
674
            $hmac
675
        );
676
677
        if ($failed === false) {
678
            $registration->setConfirmed(true);
679
            $event = $registration->getEvent();
680
            $this->registrationRepository->update($registration);
681
682
            $this->eventDispatcher->dispatch(new AfterRegistrationConfirmedEvent($registration, $this));
683
684
            $messageType = MessageType::REGISTRATION_CONFIRMED;
685
            if ($registration->getWaitlist()) {
686
                $messageType = MessageType::REGISTRATION_WAITLIST_CONFIRMED;
687
            }
688
689
            // Send notifications to user and admin
690
            $this->notificationService->sendUserMessage(
691
                $registration->getEvent(),
0 ignored issues
show
Bug introduced by
It seems like $registration->getEvent() can also be of type null; however, parameter $event of DERHANSEN\SfEventMgt\Ser...vice::sendUserMessage() does only seem to accept DERHANSEN\SfEventMgt\Domain\Model\Event, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

691
                /** @scrutinizer ignore-type */ $registration->getEvent(),
Loading history...
692
                $registration,
693
                $this->settings,
694
                $messageType
695
            );
696
            $this->notificationService->sendAdminMessage(
697
                $registration->getEvent(),
0 ignored issues
show
Bug introduced by
It seems like $registration->getEvent() can also be of type null; however, parameter $event of DERHANSEN\SfEventMgt\Ser...ice::sendAdminMessage() does only seem to accept DERHANSEN\SfEventMgt\Domain\Model\Event, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

697
                /** @scrutinizer ignore-type */ $registration->getEvent(),
Loading history...
698
                $registration,
699
                $this->settings,
700
                $messageType
701
            );
702
703
            // Confirm registrations depending on main registration if necessary
704
            if ($registration->getAmountOfRegistrations() > 1) {
705
                $this->registrationService->confirmDependingRegistrations($registration);
706
            }
707
        }
708
709
        // Redirect to payment provider if payment/redirect is enabled.
710
        // Skip if the registration is a waitlist registration, since it is not sure, if the user will participate.
711
        $paymentPid = (int)($this->settings['paymentPid'] ?? 0);
712
        $paymentRedirectResponse = null;
713
        $processRedirect = !$failed &&
714
            $paymentPid > 0 &&
715
            $registration &&
716
            !$registration->getWaitlist() &&
717
            $this->registrationService->redirectPaymentEnabled($registration);
718
        if ($processRedirect) {
719
            $paymentRedirectResponse = $this->getRedirectToPaymentResponse($paymentPid, $registration);
720
        }
721
722
        if ($paymentRedirectResponse instanceof ResponseInterface) {
723
            return $paymentRedirectResponse;
724
        }
725
726
        $modifyConfirmRegistrationViewVariablesEvent = new ModifyConfirmRegistrationViewVariablesEvent(
727
            [
728
                'failed' => $failed,
729
                'messageKey' => $messageKey,
730
                'titleKey' => $titleKey,
731
                'event' => $event,
732
                'registration' => $registration,
733
                'settings' => $this->settings,
734
            ],
735
            $this
736
        );
737
        $this->eventDispatcher->dispatch($modifyConfirmRegistrationViewVariablesEvent);
738
        $variables = $modifyConfirmRegistrationViewVariablesEvent->getVariables();
739
        $this->view->assignMultiple($variables);
740
741
        return $this->htmlResponse();
742
    }
743
744
    /**
745
     * Returns a response object to the given payment PID. Extension authors can use ProcessRedirectToPaymentEvent
746
     * PSR-14 event to intercept the redirect response.
747
     */
748
    private function getRedirectToPaymentResponse(int $paymentPid, Registration $registration): ?ResponseInterface
749
    {
750
        $processRedirectToPaymentEvent = new ProcessRedirectToPaymentEvent($registration, $this);
751
        if ($processRedirectToPaymentEvent->getProcessRedirect()) {
752
            $this->uriBuilder->reset()
753
                ->setTargetPageUid($paymentPid);
754
            $uri = $this->uriBuilder->uriFor(
755
                'redirect',
756
                [
757
                    'registration' => $registration,
758
                    'hmac' => $this->hashService->generateHmac('redirectAction-' . $registration->getUid()),
759
                ],
760
                'Payment',
761
                'sfeventmgt',
762
                'Pipayment'
763
            );
764
            return $this->redirectToUri($uri);
765
        }
766
767
        return null;
768
    }
769
770
    /**
771
     * Cancels the registration if possible and sends emails to admin and user
772
     */
773
    public function cancelRegistrationAction(int $reguid, string $hmac): ResponseInterface
774
    {
775
        $event = null;
776
777
        /* @var $registration Registration */
778
        list($failed, $registration, $messageKey, $titleKey) =
779
            $this->registrationService->checkCancelRegistration($reguid, $hmac);
780
781
        if ($failed === false) {
782
            $event = $registration->getEvent();
783
784
            // Send notifications (must run before cancelling the registration)
785
            $this->notificationService->sendUserMessage(
786
                $registration->getEvent(),
0 ignored issues
show
Bug introduced by
It seems like $registration->getEvent() can also be of type null; however, parameter $event of DERHANSEN\SfEventMgt\Ser...vice::sendUserMessage() does only seem to accept DERHANSEN\SfEventMgt\Domain\Model\Event, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

786
                /** @scrutinizer ignore-type */ $registration->getEvent(),
Loading history...
787
                $registration,
788
                $this->settings,
789
                MessageType::REGISTRATION_CANCELLED
790
            );
791
            $this->notificationService->sendAdminMessage(
792
                $registration->getEvent(),
0 ignored issues
show
Bug introduced by
It seems like $registration->getEvent() can also be of type null; however, parameter $event of DERHANSEN\SfEventMgt\Ser...ice::sendAdminMessage() does only seem to accept DERHANSEN\SfEventMgt\Domain\Model\Event, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

792
                /** @scrutinizer ignore-type */ $registration->getEvent(),
Loading history...
793
                $registration,
794
                $this->settings,
795
                MessageType::REGISTRATION_CANCELLED
796
            );
797
798
            // First cancel depending registrations
799
            if ($registration->getAmountOfRegistrations() > 1) {
800
                $this->registrationService->cancelDependingRegistrations($registration);
801
            }
802
803
            // Finally cancel registration
804
            $this->registrationRepository->remove($registration);
805
806
            // Persist changes, so following functions can work with $event properties (e.g. amount of registrations)
807
            $this->persistAll();
808
809
            $afterRegistrationCancelledEvent = new AfterRegistrationCancelledEvent($registration, $this);
810
            $this->eventDispatcher->dispatch($afterRegistrationCancelledEvent);
811
812
            // Dispatch event, so waitlist registrations can be moved up and default move up process can be stopped
813
            $waitlistMoveUpEvent = new WaitlistMoveUpEvent($event, $this, true);
0 ignored issues
show
Bug introduced by
It seems like $event can also be of type null; however, parameter $event of DERHANSEN\SfEventMgt\Eve...eUpEvent::__construct() does only seem to accept DERHANSEN\SfEventMgt\Domain\Model\Event, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

813
            $waitlistMoveUpEvent = new WaitlistMoveUpEvent(/** @scrutinizer ignore-type */ $event, $this, true);
Loading history...
814
            $this->eventDispatcher->dispatch($waitlistMoveUpEvent);
815
816
            // Move up waitlist registrations if configured on event basis and if not disabled by $waitlistMoveUpEvent
817
            if ($waitlistMoveUpEvent->getProcessDefaultMoveUp()) {
818
                $this->registrationService->moveUpWaitlistRegistrations($event, $this->settings);
0 ignored issues
show
Bug introduced by
It seems like $event can also be of type null; however, parameter $event of DERHANSEN\SfEventMgt\Ser...WaitlistRegistrations() does only seem to accept DERHANSEN\SfEventMgt\Domain\Model\Event, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

818
                $this->registrationService->moveUpWaitlistRegistrations(/** @scrutinizer ignore-type */ $event, $this->settings);
Loading history...
819
            }
820
821
            // Flush page cache for event, since amount of registrations has changed
822
            $this->eventCacheService->flushEventCache($event->getUid(), $event->getPid());
0 ignored issues
show
Bug introduced by
It seems like $event->getUid() can also be of type null; however, parameter $eventUid of DERHANSEN\SfEventMgt\Ser...vice::flushEventCache() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

822
            $this->eventCacheService->flushEventCache(/** @scrutinizer ignore-type */ $event->getUid(), $event->getPid());
Loading history...
Bug introduced by
It seems like $event->getPid() can also be of type null; however, parameter $eventPid of DERHANSEN\SfEventMgt\Ser...vice::flushEventCache() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

822
            $this->eventCacheService->flushEventCache($event->getUid(), /** @scrutinizer ignore-type */ $event->getPid());
Loading history...
823
        }
824
825
        $modifyCancelRegistrationViewVariablesEvent = new ModifyCancelRegistrationViewVariablesEvent(
826
            [
827
                'failed' => $failed,
828
                'messageKey' => $messageKey,
829
                'titleKey' => $titleKey,
830
                'event' => $event,
831
                'settings' => $this->settings,
832
            ],
833
            $this
834
        );
835
        $this->eventDispatcher->dispatch($modifyCancelRegistrationViewVariablesEvent);
836
        $variables = $modifyCancelRegistrationViewVariablesEvent->getVariables();
837
        $this->view->assignMultiple($variables);
838
839
        return $this->htmlResponse();
840
    }
841
842
    /**
843
     * Set date format for field startDate and endDate
844
     */
845
    public function initializeSearchAction(): void
846
    {
847
        if ($this->settings !== null && ($this->settings['search']['dateFormat'] ?? false)) {
848
            $this->arguments->getArgument('searchDemand')
849
                ->getPropertyMappingConfiguration()->forProperty('startDate')
850
                ->setTypeConverterOption(
851
                    DateTimeConverter::class,
852
                    DateTimeConverter::CONFIGURATION_DATE_FORMAT,
853
                    $this->settings['search']['dateFormat']
854
                );
855
            $this->arguments->getArgument('searchDemand')
856
                ->getPropertyMappingConfiguration()->forProperty('endDate')
857
                ->setTypeConverterOption(
858
                    DateTimeConverter::class,
859
                    DateTimeConverter::CONFIGURATION_DATE_FORMAT,
860
                    $this->settings['search']['dateFormat']
861
                );
862
        }
863
        if ($this->arguments->hasArgument('searchDemand')) {
864
            $propertyMappingConfiguration = $this->arguments->getArgument('searchDemand')
865
                ->getPropertyMappingConfiguration();
866
            $propertyMappingConfiguration->allowAllProperties();
867
            $propertyMappingConfiguration->setTypeConverterOption(
868
                PersistentObjectConverter::class,
869
                PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED,
870
                true
871
            );
872
        }
873
    }
874
875
    /**
876
     * Search view
877
     */
878
    public function searchAction(SearchDemand $searchDemand = null, array $overwriteDemand = []): ResponseInterface
879
    {
880
        $eventDemand = EventDemand::createFromSettings($this->settings);
881
        $eventDemand->setSearchDemand($searchDemand);
882
        $foreignRecordDemand = ForeignRecordDemand::createFromSettings($this->settings);
883
        $categoryDemand = CategoryDemand::createFromSettings($this->settings);
884
885
        if ($searchDemand !== null) {
886
            $searchDemand->setFields($this->settings['search']['fields'] ?? '');
887
888
            $adjustTime = (bool)($this->settings['search']['adjustTime'] ?? false);
889
            if ($adjustTime && $searchDemand->getStartDate() !== null) {
890
                $searchDemand->getStartDate()->setTime(0, 0);
891
            }
892
893
            if ($adjustTime && $searchDemand->getEndDate() !== null) {
894
                $searchDemand->getEndDate()->setTime(23, 59, 59);
895
            }
896
        }
897
898
        if ($this->isOverwriteDemand($overwriteDemand)) {
899
            $eventDemand = $this->overwriteEventDemandObject($eventDemand, $overwriteDemand);
900
        }
901
902
        $categories = $this->categoryRepository->findDemanded($categoryDemand);
903
        $locations = $this->locationRepository->findDemanded($foreignRecordDemand);
904
        $organisators = $this->organisatorRepository->findDemanded($foreignRecordDemand);
905
        $speakers = $this->speakerRepository->findDemanded($foreignRecordDemand);
906
        $events = $this->eventRepository->findDemanded($eventDemand);
907
908
        $modifySearchViewVariablesEvent = new ModifySearchViewVariablesEvent(
909
            [
910
                'events' => $events,
911
                'categories' => $categories,
912
                'locations' => $locations,
913
                'organisators' => $organisators,
914
                'speakers' => $speakers,
915
                'searchDemand' => $searchDemand,
916
                'overwriteDemand' => $overwriteDemand,
917
                'settings' => $this->settings,
918
            ],
919
            $this
920
        );
921
        $this->eventDispatcher->dispatch($modifySearchViewVariablesEvent);
922
        $variables = $modifySearchViewVariablesEvent->getVariables();
923
        $this->view->assignMultiple($variables);
924
925
        return $this->htmlResponse();
926
    }
927
928
    /**
929
     * Returns if a demand object can be overwritten with the given overwriteDemand array
930
     *
931
     * @param array $overwriteDemand
932
     * @return bool
933
     */
934
    protected function isOverwriteDemand(array $overwriteDemand): bool
935
    {
936
        return ($this->settings['disableOverrideDemand'] ?? 0) !== 1 && $overwriteDemand !== [];
937
    }
938
939
    /**
940
     * If no event is given and the singleEvent setting is set, the configured single event is returned
941
     */
942
    protected function evaluateSingleEventSetting(?Event $event): ?Event
943
    {
944
        if ($event === null && (int)($this->settings['singleEvent'] ?? 0) > 0) {
945
            $event = $this->eventRepository->findByUid((int)$this->settings['singleEvent']);
946
        }
947
948
        return $event;
949
    }
950
951
    /**
952
     * If no event is given and the isShortcut setting is set, the event is displayed using the "Insert Record"
953
     * content element and should be loaded from contect object data
954
     */
955
    protected function evaluateIsShortcutSetting(?Event $event): ?Event
956
    {
957
        if ($event === null && (bool)($this->settings['detail']['isShortcut'] ?? false)) {
958
            $eventRawData = $this->request->getAttribute('currentContentObject')->data;
959
            $event = $this->eventRepository->findByUid($eventRawData['uid']);
960
        }
961
962
        return $event;
963
    }
964
965
    /**
966
     * Checks if the event pid could be found in the storagePage settings of the detail plugin and
967
     * if the pid could not be found it return null instead of the event object.
968
     */
969
    protected function checkPidOfEventRecord(Event $event): ?Event
970
    {
971
        $allowedStoragePages = GeneralUtility::trimExplode(
972
            ',',
973
            PageUtility::extendPidListByChildren(
974
                $this->settings['storagePage'] ?? '',
975
                (int)($this->settings['recursive'] ?? 0)
976
            ),
977
            true
978
        );
979
        if (count($allowedStoragePages) > 0 && !in_array($event->getPid(), $allowedStoragePages)) {
980
            $this->eventDispatcher->dispatch(new EventPidCheckFailedEvent($event, $this));
981
            $event = null;
982
        }
983
984
        return $event;
985
    }
986
987
    /**
988
     * Calls persistAll() of the persistenceManager
989
     */
990
    protected function persistAll(): void
991
    {
992
        GeneralUtility::makeInstance(PersistenceManager::class)->persistAll();
993
    }
994
995
    /**
996
     * Returns the language code of the current language
997
     */
998
    protected function getCurrentLanguageCode(): string
999
    {
1000
        if ($this->request->getAttribute('language') instanceof SiteLanguage) {
1001
            /** @var SiteLanguage $siteLanguage */
1002
            $siteLanguage = $this->request->getAttribute('language');
1003
            return $siteLanguage->getLocale()->getLanguageCode();
1004
        }
1005
1006
        return '';
1007
    }
1008
}
1009