Passed
Push — typo3-v13 ( 74dcf7...ba73f3 )
by Torben
02:25
created

RegistrationService::checkRegistrationAccess()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 9
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 13
rs 9.9666
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\Service;
13
14
use DateTime;
15
use DERHANSEN\SfEventMgt\Domain\Model\Event;
16
use DERHANSEN\SfEventMgt\Domain\Model\FrontendUser;
17
use DERHANSEN\SfEventMgt\Domain\Model\Registration;
18
use DERHANSEN\SfEventMgt\Domain\Repository\FrontendUserRepository;
19
use DERHANSEN\SfEventMgt\Domain\Repository\RegistrationRepository;
20
use DERHANSEN\SfEventMgt\Event\AfterRegistrationMovedFromWaitlist;
0 ignored issues
show
Bug introduced by
The type DERHANSEN\SfEventMgt\Eve...rationMovedFromWaitlist 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...
21
use DERHANSEN\SfEventMgt\Event\ModifyCheckRegistrationSuccessEvent;
22
use DERHANSEN\SfEventMgt\Payment\AbstractPayment;
23
use DERHANSEN\SfEventMgt\Security\HashScope;
24
use DERHANSEN\SfEventMgt\Utility\MessageType;
25
use DERHANSEN\SfEventMgt\Utility\RegistrationResult;
26
use Psr\EventDispatcher\EventDispatcherInterface;
27
use Psr\Http\Message\ServerRequestInterface;
28
use TYPO3\CMS\Core\Context\Context;
29
use TYPO3\CMS\Core\Crypto\HashService;
30
use TYPO3\CMS\Core\Database\Connection;
31
use TYPO3\CMS\Core\Database\ConnectionPool;
32
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
33
use TYPO3\CMS\Core\Http\PropagateResponseException;
34
use TYPO3\CMS\Core\Utility\GeneralUtility;
35
use TYPO3\CMS\Extbase\Mvc\RequestInterface;
0 ignored issues
show
Bug introduced by
The type TYPO3\CMS\Extbase\Mvc\RequestInterface 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...
36
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
0 ignored issues
show
Bug introduced by
The type TYPO3\CMS\Extbase\Reflection\ObjectAccess 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...
37
use TYPO3\CMS\Frontend\Controller\ErrorController;
0 ignored issues
show
Bug introduced by
The type TYPO3\CMS\Frontend\Controller\ErrorController 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...
38
39
class RegistrationService
40
{
41
    public function __construct(
42
        protected readonly Context $context,
43
        protected readonly EventDispatcherInterface $eventDispatcher,
44
        protected readonly RegistrationRepository $registrationRepository,
45
        protected readonly FrontendUserRepository $frontendUserRepository,
46
        protected readonly HashService $hashService,
47
        protected readonly PaymentService $paymentService,
48
        protected readonly NotificationService $notificationService,
49
    ) {
50
    }
51
52
    /**
53
     * Duplicates the given registration (all public accessible properties) the
54
     * amount of times configured in amountOfRegistrations
55
     */
56
    public function createDependingRegistrations(Registration $registration): void
57
    {
58
        $registrations = $registration->getAmountOfRegistrations();
59
        for ($i = 1; $i <= $registrations - 1; $i++) {
60
            $newReg = GeneralUtility::makeInstance(Registration::class);
61
            $properties = ObjectAccess::getGettableProperties($registration);
62
            foreach ($properties as $propertyName => $propertyValue) {
63
                ObjectAccess::setProperty($newReg, $propertyName, $propertyValue);
64
            }
65
            $newReg->setMainRegistration($registration);
66
            $newReg->setAmountOfRegistrations(1);
67
            $newReg->setIgnoreNotifications(true);
68
            $this->registrationRepository->add($newReg);
69
        }
70
    }
71
72
    /**
73
     * Confirms all depending registrations based on the given main registration
74
     */
75
    public function confirmDependingRegistrations(Registration $registration): void
76
    {
77
        $registrations = $this->registrationRepository->findBy(['mainRegistration' => $registration]);
78
        foreach ($registrations as $foundRegistration) {
79
            /** @var Registration $foundRegistration */
80
            $foundRegistration->setConfirmed(true);
81
            $this->registrationRepository->update($foundRegistration);
82
        }
83
    }
84
85
    /**
86
     * Checks if the registration can be confirmed and returns an array of variables
87
     */
88
    public function checkConfirmRegistration(int $regUid, string $hmac): array
89
    {
90
        /* @var $registration Registration */
91
        $registration = null;
92
        $failed = false;
93
        $messageKey = 'event.message.confirmation_successful';
94
        $titleKey = 'confirmRegistration.title.successful';
95
96
        $isValidHmac = $this->hashService->validateHmac('reg-' . $regUid, HashScope::RegistrationUid->value, $hmac);
97
        if (!$isValidHmac) {
98
            $failed = true;
99
            $messageKey = 'event.message.confirmation_failed_wrong_hmac';
100
            $titleKey = 'confirmRegistration.title.failed';
101
        } else {
102
            $registration = $this->registrationRepository->findByUid($regUid);
103
        }
104
105
        if (!$failed && is_null($registration)) {
106
            $failed = true;
107
            $messageKey = 'event.message.confirmation_failed_registration_not_found';
108
            $titleKey = 'confirmRegistration.title.failed';
109
        }
110
111
        if (!$failed && !$registration->getEvent()) {
112
            $failed = true;
113
            $messageKey = 'event.message.confirmation_failed_registration_event_not_found';
114
            $titleKey = 'confirmRegistration.title.failed';
115
        }
116
117
        if (!$failed && $registration->getConfirmationUntil() < new DateTime()) {
118
            $failed = true;
119
            $messageKey = 'event.message.confirmation_failed_confirmation_until_expired';
120
            $titleKey = 'confirmRegistration.title.failed';
121
        }
122
123
        if (!$failed && $registration->getConfirmed() === true) {
124
            $failed = true;
125
            $messageKey = 'event.message.confirmation_failed_already_confirmed';
126
            $titleKey = 'confirmRegistration.title.failed';
127
        }
128
129
        if (!$failed && $registration->getWaitlist()) {
130
            $messageKey = 'event.message.confirmation_waitlist_successful';
131
            $titleKey = 'confirmRegistrationWaitlist.title.successful';
132
        }
133
134
        return [
135
            $failed,
136
            $registration,
137
            $messageKey,
138
            $titleKey,
139
        ];
140
    }
141
142
    /**
143
     * Cancels all depending registrations based on the given main registration
144
     */
145
    public function cancelDependingRegistrations(Registration $registration): void
146
    {
147
        $registrations = $this->registrationRepository->findBy(['mainRegistration' => $registration]);
148
        foreach ($registrations as $foundRegistration) {
149
            $this->registrationRepository->remove($foundRegistration);
150
        }
151
    }
152
153
    /**
154
     * Checks if the registration can be cancelled and returns an array of variables
155
     */
156
    public function checkCancelRegistration(int $regUid, string $hmac): array
157
    {
158
        /* @var $registration Registration */
159
        $registration = null;
160
        $failed = false;
161
        $messageKey = 'event.message.cancel_successful';
162
        $titleKey = 'cancelRegistration.title.successful';
163
164
        $isValidHmac = $this->hashService->validateHmac('reg-' . $regUid, HashScope::RegistrationUid->value, $hmac);
165
        if (!$isValidHmac) {
166
            $failed = true;
167
            $messageKey = 'event.message.cancel_failed_wrong_hmac';
168
            $titleKey = 'cancelRegistration.title.failed';
169
        } else {
170
            $registration = $this->registrationRepository->findByUid($regUid);
171
        }
172
173
        if (!$failed && is_null($registration)) {
174
            $failed = true;
175
            $messageKey = 'event.message.cancel_failed_registration_not_found_or_cancelled';
176
            $titleKey = 'cancelRegistration.title.failed';
177
        }
178
179
        if (!$failed && !is_a($registration->getEvent(), Event::class)) {
180
            $failed = true;
181
            $messageKey = 'event.message.cancel_failed_event_not_found';
182
            $titleKey = 'cancelRegistration.title.failed';
183
        }
184
185
        if (!$failed && $registration->getEvent()->getEnableCancel() === false) {
186
            $failed = true;
187
            $messageKey = 'event.message.confirmation_failed_cancel_disabled';
188
            $titleKey = 'cancelRegistration.title.failed';
189
        }
190
191
        if (!$failed && $registration->getEvent()->getCancelDeadline() !== null
192
            && $registration->getEvent()->getCancelDeadline() < new DateTime()
193
        ) {
194
            $failed = true;
195
            $messageKey = 'event.message.cancel_failed_deadline_expired';
196
            $titleKey = 'cancelRegistration.title.failed';
197
        }
198
199
        if (!$failed && $registration->getEvent()->getStartdate() < new DateTime()) {
200
            $failed = true;
201
            $messageKey = 'event.message.cancel_failed_event_started';
202
            $titleKey = 'cancelRegistration.title.failed';
203
        }
204
205
        return [
206
            $failed,
207
            $registration,
208
            $messageKey,
209
            $titleKey,
210
        ];
211
    }
212
213
    /**
214
     * Returns the current frontend user object if available
215
     */
216
    public function getCurrentFeUserObject(): ?FrontendUser
217
    {
218
        $user = null;
219
220
        $userUid = $this->context->getPropertyFromAspect('frontend.user', 'id');
221
        if ($userUid > 0) {
222
            /** @var FrontendUser $user */
223
            $user = $this->frontendUserRepository->findByUid($userUid);
224
        }
225
226
        return $user;
227
    }
228
229
    /**
230
     * Checks, if the registration can successfully be created.
231
     */
232
    public function checkRegistrationSuccess(Event $event, Registration $registration, int $result): array
233
    {
234
        $success = true;
235
        if ($event->getEnableRegistration() === false) {
236
            $success = false;
237
            $result = RegistrationResult::REGISTRATION_NOT_ENABLED;
238
        } elseif ($event->getRegistrationDeadline() != null && $event->getRegistrationDeadline() < new DateTime()) {
239
            $success = false;
240
            $result = RegistrationResult::REGISTRATION_FAILED_DEADLINE_EXPIRED;
241
        } elseif (!$event->getAllowRegistrationUntilEnddate() && $event->getStartdate() < new DateTime()) {
242
            $success = false;
243
            $result = RegistrationResult::REGISTRATION_FAILED_EVENT_EXPIRED;
244
        } elseif ($event->getAllowRegistrationUntilEnddate() && $event->getEnddate() && $event->getEnddate() < new DateTime()) {
245
            $success = false;
246
            $result = RegistrationResult::REGISTRATION_FAILED_EVENT_ENDED;
247
        } elseif ($event->getRegistrations()->count() >= $event->getMaxParticipants()
248
            && $event->getMaxParticipants() > 0 && !$event->getEnableWaitlist()
249
        ) {
250
            $success = false;
251
            $result = RegistrationResult::REGISTRATION_FAILED_MAX_PARTICIPANTS;
252
        } elseif ($event->getFreePlaces() < $registration->getAmountOfRegistrations()
253
            && $event->getMaxParticipants() > 0 && !$event->getEnableWaitlist()
254
        ) {
255
            $success = false;
256
            $result = RegistrationResult::REGISTRATION_FAILED_NOT_ENOUGH_FREE_PLACES;
257
        } elseif ($event->getMaxRegistrationsPerUser() < $registration->getAmountOfRegistrations()) {
258
            $success = false;
259
            $result = RegistrationResult::REGISTRATION_FAILED_MAX_AMOUNT_REGISTRATIONS_EXCEEDED;
260
        } elseif ($event->getUniqueEmailCheck() &&
261
            $this->emailNotUnique($event, $registration->getEmail())
262
        ) {
263
            $success = false;
264
            $result = RegistrationResult::REGISTRATION_FAILED_EMAIL_NOT_UNIQUE;
265
        } elseif ($event->getRegistrations()->count() >= $event->getMaxParticipants()
266
            && $event->getMaxParticipants() > 0 && $event->getEnableWaitlist()
267
        ) {
268
            $result = RegistrationResult::REGISTRATION_SUCCESSFUL_WAITLIST;
269
        }
270
271
        $modifyCheckRegistrationSuccessEvent = new ModifyCheckRegistrationSuccessEvent(
272
            $success,
273
            $result,
274
            $registration
275
        );
276
        $this->eventDispatcher->dispatch($modifyCheckRegistrationSuccessEvent);
277
278
        return [$modifyCheckRegistrationSuccessEvent->getSuccess(), $modifyCheckRegistrationSuccessEvent->getResult()];
279
    }
280
281
    /**
282
     * Returns if the given email is registered to the given event
283
     */
284
    protected function emailNotUnique(Event $event, string $email): bool
285
    {
286
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
287
            ->getQueryBuilderForTable('tx_sfeventmgt_domain_model_registration');
288
        $queryBuilder->getRestrictions()->removeByType(HiddenRestriction::class);
289
        $registrations = $queryBuilder->count('email')
290
            ->from('tx_sfeventmgt_domain_model_registration')
291
            ->where(
292
                $queryBuilder->expr()->eq(
293
                    'event',
294
                    $queryBuilder->createNamedParameter($event->getUid(), Connection::PARAM_INT)
295
                ),
296
                $queryBuilder->expr()->eq(
297
                    'email',
298
                    $queryBuilder->createNamedParameter($email, Connection::PARAM_STR)
299
                )
300
            )
301
            ->executeQuery()
302
            ->fetchOne();
303
304
        return $registrations >= 1;
305
    }
306
307
    /**
308
     * Returns, if payment redirect for the payment method is enabled
309
     */
310
    public function redirectPaymentEnabled(Registration $registration): bool
311
    {
312
        if ($registration->getEvent()->getEnablePayment() === false) {
313
            return false;
314
        }
315
316
        /** @var AbstractPayment $paymentInstance */
317
        $paymentInstance = $this->paymentService->getPaymentInstance($registration->getPaymentmethod());
318
319
        return $paymentInstance !== null && $paymentInstance->isRedirectEnabled();
320
    }
321
322
    /**
323
     * Returns if the given amount of registrations for the event will be registrations for the waitlist
324
     * (depending on the total amount of registrations and free places)
325
     */
326
    public function isWaitlistRegistration(Event $event, int $amountOfRegistrations): bool
327
    {
328
        if ($event->getMaxParticipants() === 0 || !$event->getEnableWaitlist()) {
329
            return false;
330
        }
331
332
        $result = false;
333
        if (($event->getFreePlaces() > 0 && $event->getFreePlaces() < $amountOfRegistrations)
334
            || $event->getFreePlaces() <= 0) {
335
            $result = true;
336
        }
337
338
        return $result;
339
    }
340
341
    /**
342
     * Handles the process of moving registration up from the waitlist.
343
     */
344
    public function moveUpWaitlistRegistrations(RequestInterface $request, Event $event, array $settings): void
345
    {
346
        // Early return if move up not enabled, no registrations on waitlist or no free places left
347
        if (!$event->getEnableWaitlistMoveup() || $event->getRegistrationsWaitlist()->count() === 0 ||
348
            $event->getFreePlaces() <= 0
349
        ) {
350
            return;
351
        }
352
353
        $keepMainRegistrationDependency = $settings['waitlist']['moveUp']['keepMainRegistrationDependency'] ?? false;
354
        $freePlaces = $event->getFreePlaces();
355
        $moveupRegistrations = $this->registrationRepository->findWaitlistMoveUpRegistrations($event);
356
357
        /** @var Registration $registration */
358
        foreach ($moveupRegistrations as $registration) {
359
            $registration->setWaitlist(false);
360
            $registration->setIgnoreNotifications(false);
361
362
            if (!(bool)$keepMainRegistrationDependency) {
363
                $registration->_setProperty('mainRegistration', null);
364
            }
365
366
            $this->registrationRepository->update($registration);
367
368
            // Send messages to user and admin
369
            $this->notificationService->sendUserMessage(
370
                $request,
371
                $event,
372
                $registration,
373
                $settings,
374
                MessageType::REGISTRATION_WAITLIST_MOVE_UP
375
            );
376
            $this->notificationService->sendAdminMessage(
377
                $request,
378
                $registration->getEvent(),
379
                $registration,
380
                $settings,
381
                MessageType::REGISTRATION_WAITLIST_MOVE_UP
382
            );
383
384
            $this->eventDispatcher->dispatch(new AfterRegistrationMovedFromWaitlist($registration, $this, $request));
385
386
            $freePlaces--;
387
            if ($freePlaces === 0) {
388
                break;
389
            }
390
        }
391
    }
392
393
    /**
394
     * Checks, if the given registration belongs to the current logged in frontend user. If not, a
395
     * page not found response is thrown.
396
     */
397
    public function checkRegistrationAccess(ServerRequestInterface $request, Registration $registration): void
398
    {
399
        $isLoggedIn = $this->context->getPropertyFromAspect('frontend.user', 'isLoggedIn');
400
        $userUid = $this->context->getPropertyFromAspect('frontend.user', 'id');
401
402
        if (!$isLoggedIn ||
403
            !$registration->getFeUser() ||
404
            $userUid !== (int)$registration->getFeUser()->getUid()) {
405
            $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
406
                $request,
407
                'Registration not found.'
408
            );
409
            throw new PropagateResponseException($response, 1671627320);
410
        }
411
    }
412
}
413