Completed
Push — task/630-registration ( 4ac8a7 )
by Torben
04:27
created

RegistrationService::fixRegistrationEvent()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 2
1
<?php
2
namespace DERHANSEN\SfEventMgt\Service;
3
4
/*
5
 * This file is part of the Extension "sf_event_mgt" for TYPO3 CMS.
6
 *
7
 * For the full copyright and license information, please read the
8
 * LICENSE.txt file that was distributed with this source code.
9
 */
10
11
use DERHANSEN\SfEventMgt\Domain\Model\Event;
12
use DERHANSEN\SfEventMgt\Domain\Model\Registration;
13
use DERHANSEN\SfEventMgt\Payment\AbstractPayment;
14
use DERHANSEN\SfEventMgt\Utility\RegistrationResult;
15
use TYPO3\CMS\Core\Database\Connection;
16
use TYPO3\CMS\Core\Database\ConnectionPool;
17
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
18
use TYPO3\CMS\Core\Utility\GeneralUtility;
19
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
20
21
/**
22
 * RegistrationService
23
 *
24
 * @author Torben Hansen <[email protected]>
25
 */
26
class RegistrationService
27
{
28
    /**
29
     * The object manager
30
     *
31
     * @var \TYPO3\CMS\Extbase\Object\ObjectManager
32
     * */
33
    protected $objectManager;
34
35
    /**
36
     * RegistrationRepository
37
     *
38
     * @var \DERHANSEN\SfEventMgt\Domain\Repository\RegistrationRepository
39
     * */
40
    protected $registrationRepository;
41
42
    /**
43
     * FrontendUserRepository
44
     *
45
     * @var \TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository
46
     * */
47
    protected $frontendUserRepository;
48
49
    /**
50
     * Hash Service
51
     *
52
     * @var \TYPO3\CMS\Extbase\Security\Cryptography\HashService
53
     * */
54
    protected $hashService;
55
56
    /**
57
     * Payment Service
58
     *
59
     * @var \DERHANSEN\SfEventMgt\Service\PaymentService
60
     * */
61
    protected $paymentService;
62
63
    /**
64
     * DI for $frontendUserRepository
65
     *
66
     * @param \TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository $frontendUserRepository
67
     */
68
    public function injectFrontendUserRepository(
69
        \TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository $frontendUserRepository
70
    ) {
71
        $this->frontendUserRepository = $frontendUserRepository;
72
    }
73
74
    /**
75
     * DI for $hashService
76
     *
77
     * @param \TYPO3\CMS\Extbase\Security\Cryptography\HashService $hashService
78
     */
79
    public function injectHashService(\TYPO3\CMS\Extbase\Security\Cryptography\HashService $hashService)
80
    {
81
        $this->hashService = $hashService;
82
    }
83
84
    /**
85
     * DI for $objectManager
86
     *
87
     * @param \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager
88
     */
89
    public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManager $objectManager)
90
    {
91
        $this->objectManager = $objectManager;
92
    }
93
94
    /**
95
     * DI for $paymentService
96
     *
97
     * @param \DERHANSEN\SfEventMgt\Service\PaymentService $paymentService
98
     */
99
    public function injectPaymentService(\DERHANSEN\SfEventMgt\Service\PaymentService $paymentService)
100
    {
101
        $this->paymentService = $paymentService;
102
    }
103
104
    /**
105
     * DI for $registrationRepository
106
     *
107
     * @param \DERHANSEN\SfEventMgt\Domain\Repository\RegistrationRepository $registrationRepository
108
     */
109
    public function injectRegistrationRepository(
110
        \DERHANSEN\SfEventMgt\Domain\Repository\RegistrationRepository $registrationRepository
111
    ) {
112
        $this->registrationRepository = $registrationRepository;
113
    }
114
115
    /**
116
     * Duplicates (all public accessable properties) the given registration the
117
     * amount of times configured in amountOfRegistrations
118
     *
119
     * @param \DERHANSEN\SfEventMgt\Domain\Model\Registration $registration Registration
120
     *
121
     * @return void
122
     */
123
    public function createDependingRegistrations($registration)
124
    {
125
        $registrations = $registration->getAmountOfRegistrations();
126
        for ($i = 1; $i <= $registrations - 1; $i++) {
127
            /** @var \DERHANSEN\SfEventMgt\Domain\Model\Registration $newReg */
128
            $newReg = $this->objectManager->get(Registration::class);
129
            $properties = ObjectAccess::getGettableProperties($registration);
130
            foreach ($properties as $propertyName => $propertyValue) {
131
                ObjectAccess::setProperty($newReg, $propertyName, $propertyValue);
132
            }
133
            $newReg->setMainRegistration($registration);
134
            $newReg->setAmountOfRegistrations(1);
135
            $newReg->setIgnoreNotifications(true);
136
            $this->registrationRepository->add($newReg);
137
        }
138
    }
139
140
    /**
141
     * Confirms all depending registrations based on the given main registration
142
     *
143
     * @param \DERHANSEN\SfEventMgt\Domain\Model\Registration $registration Registration
144
     *
145
     * @return void
146
     */
147
    public function confirmDependingRegistrations($registration)
148
    {
149
        $registrations = $this->registrationRepository->findByMainRegistration($registration);
0 ignored issues
show
Documentation Bug introduced by
The method findByMainRegistration does not exist on object<DERHANSEN\SfEvent...RegistrationRepository>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
150
        foreach ($registrations as $foundRegistration) {
151
            /** @var \DERHANSEN\SfEventMgt\Domain\Model\Registration $foundRegistration */
152
            $foundRegistration->setConfirmed(true);
153
            $this->registrationRepository->update($foundRegistration);
154
        }
155
    }
156
157
    /**
158
     * Checks if the registration can be confirmed and returns an array of variables
159
     *
160
     * @param int $reguid UID of registration
161
     * @param string $hmac HMAC for parameters
162
     *
163
     * @return array
164
     */
165
    public function checkConfirmRegistration($reguid, $hmac)
166
    {
167
        /* @var $registration Registration */
168
        $registration = null;
169
        $failed = false;
170
        $messageKey = 'event.message.confirmation_successful';
171
        $titleKey = 'confirmRegistration.title.successful';
172
173
        if (!$this->hashService->validateHmac('reg-' . $reguid, $hmac)) {
174
            $failed = true;
175
            $messageKey = 'event.message.confirmation_failed_wrong_hmac';
176
            $titleKey = 'confirmRegistration.title.failed';
177
        } else {
178
            $registration = $this->registrationRepository->findByUid($reguid);
179
        }
180
181
        if (!$failed && is_null($registration)) {
182
            $failed = true;
183
            $messageKey = 'event.message.confirmation_failed_registration_not_found';
184
            $titleKey = 'confirmRegistration.title.failed';
185
        }
186
187
        if (!$failed && $registration->getConfirmationUntil() < new \DateTime()) {
188
            $failed = true;
189
            $messageKey = 'event.message.confirmation_failed_confirmation_until_expired';
190
            $titleKey = 'confirmRegistration.title.failed';
191
        }
192
193
        if (!$failed && $registration->getConfirmed() === true) {
194
            $failed = true;
195
            $messageKey = 'event.message.confirmation_failed_already_confirmed';
196
            $titleKey = 'confirmRegistration.title.failed';
197
        }
198
199
        if (!$failed && $registration->getWaitlist()) {
200
            $messageKey = 'event.message.confirmation_waitlist_successful';
201
            $titleKey = 'confirmRegistrationWaitlist.title.successful';
202
        }
203
204
        return [
205
            $failed,
206
            $registration,
207
            $messageKey,
208
            $titleKey
209
        ];
210
    }
211
212
    /**
213
     * Cancels all depending registrations based on the given main registration
214
     *
215
     * @param \DERHANSEN\SfEventMgt\Domain\Model\Registration $registration Registration
216
     *
217
     * @return void
218
     */
219
    public function cancelDependingRegistrations($registration)
220
    {
221
        $registrations = $this->registrationRepository->findByMainRegistration($registration);
0 ignored issues
show
Documentation Bug introduced by
The method findByMainRegistration does not exist on object<DERHANSEN\SfEvent...RegistrationRepository>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
222
        foreach ($registrations as $foundRegistration) {
223
            $this->registrationRepository->remove($foundRegistration);
224
        }
225
    }
226
227
    /**
228
     * Checks if the registration can be cancelled and returns an array of variables
229
     *
230
     * @param int $reguid UID of registration
231
     * @param string $hmac HMAC for parameters
232
     *
233
     * @return array
234
     */
235
    public function checkCancelRegistration($reguid, $hmac)
236
    {
237
        /* @var $registration Registration */
238
        $registration = null;
239
        $failed = false;
240
        $messageKey = 'event.message.cancel_successful';
241
        $titleKey = 'cancelRegistration.title.successful';
242
243
        if (!$this->hashService->validateHmac('reg-' . $reguid, $hmac)) {
244
            $failed = true;
245
            $messageKey = 'event.message.cancel_failed_wrong_hmac';
246
            $titleKey = 'cancelRegistration.title.failed';
247
        } else {
248
            $registration = $this->registrationRepository->findByUid($reguid);
249
        }
250
251
        if (!$failed && is_null($registration)) {
252
            $failed = true;
253
            $messageKey = 'event.message.cancel_failed_registration_not_found_or_cancelled';
254
            $titleKey = 'cancelRegistration.title.failed';
255
        }
256
257
        if (!$failed && $registration->getEvent()->getEnableCancel() === false) {
258
            $failed = true;
259
            $messageKey = 'event.message.confirmation_failed_cancel_disabled';
260
            $titleKey = 'cancelRegistration.title.failed';
261
        }
262
263
        if (!$failed && $registration->getEvent()->getCancelDeadline() > 0
264
            && $registration->getEvent()->getCancelDeadline() < new \DateTime()
265
        ) {
266
            $failed = true;
267
            $messageKey = 'event.message.cancel_failed_deadline_expired';
268
            $titleKey = 'cancelRegistration.title.failed';
269
        }
270
271
        if (!$failed && $registration->getEvent()->getStartdate() < new \DateTime()) {
272
            $failed = true;
273
            $messageKey = 'event.message.cancel_failed_event_started';
274
            $titleKey = 'cancelRegistration.title.failed';
275
        }
276
277
        return [
278
            $failed,
279
            $registration,
280
            $messageKey,
281
            $titleKey
282
        ];
283
    }
284
285
    /**
286
     * Returns the current frontend user object if available
287
     *
288
     * @return mixed \TYPO3\CMS\Extbase\Domain\Model\FrontendUser|null
289
     */
290
    public function getCurrentFeUserObject()
291
    {
292
        if (isset($GLOBALS['TSFE']->fe_user->user['uid'])) {
293
            return $this->frontendUserRepository->findByUid($GLOBALS['TSFE']->fe_user->user['uid']);
294
        }
295
296
        return null;
297
    }
298
299
    /**
300
     * Checks, if the registration can successfully be created. Note, that
301
     * $result is passed by reference!
302
     *
303
     * @param \DERHANSEN\SfEventMgt\Domain\Model\Event $event Event
304
     * @param \DERHANSEN\SfEventMgt\Domain\Model\Registration $registration Registration
305
     * @param int $result Result
306
     *
307
     * @return array
308
     */
309
    public function checkRegistrationSuccess($event, $registration, $result)
310
    {
311
        $success = true;
312
        if ($event->getEnableRegistration() === false) {
313
            $success = false;
314
            $result = RegistrationResult::REGISTRATION_NOT_ENABLED;
315
        } elseif ($event->getRegistrationDeadline() != null && $event->getRegistrationDeadline() < new \DateTime()) {
316
            $success = false;
317
            $result = RegistrationResult::REGISTRATION_FAILED_DEADLINE_EXPIRED;
318
        } elseif ($event->getStartdate() < new \DateTime()) {
319
            $success = false;
320
            $result = RegistrationResult::REGISTRATION_FAILED_EVENT_EXPIRED;
321
        } elseif ($event->getRegistration()->count() >= $event->getMaxParticipants()
322
            && $event->getMaxParticipants() > 0 && !$event->getEnableWaitlist()
323
        ) {
324
            $success = false;
325
            $result = RegistrationResult::REGISTRATION_FAILED_MAX_PARTICIPANTS;
326
        } elseif ($event->getFreePlaces() < $registration->getAmountOfRegistrations()
327
            && $event->getMaxParticipants() > 0 && !$event->getEnableWaitlist()
328
        ) {
329
            $success = false;
330
            $result = RegistrationResult::REGISTRATION_FAILED_NOT_ENOUGH_FREE_PLACES;
331
        } elseif ($event->getMaxRegistrationsPerUser() < $registration->getAmountOfRegistrations()) {
332
            $success = false;
333
            $result = RegistrationResult::REGISTRATION_FAILED_MAX_AMOUNT_REGISTRATIONS_EXCEEDED;
334
        } elseif ($event->getUniqueEmailCheck() &&
335
            $this->emailNotUnique($event, $registration->getEmail())
336
        ) {
337
            $success = false;
338
            $result = RegistrationResult::REGISTRATION_FAILED_EMAIL_NOT_UNIQUE;
339
        } elseif ($event->getRegistration()->count() >= $event->getMaxParticipants()
340
            && $event->getMaxParticipants() > 0 && $event->getEnableWaitlist()
341
        ) {
342
            $result = RegistrationResult::REGISTRATION_SUCCESSFUL_WAITLIST;
343
        }
344
345
        return [$success, $result];
346
    }
347
348
    /**
349
     * Returns if the given e-mail is registered to the given event
350
     *
351
     * @param \DERHANSEN\SfEventMgt\Domain\Model\Event $event
352
     * @param string $email
353
     * @return bool
354
     */
355
    protected function emailNotUnique($event, $email)
356
    {
357
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
358
            ->getQueryBuilderForTable('tx_sfeventmgt_domain_model_registration');
359
        $queryBuilder->getRestrictions()->removeByType(HiddenRestriction::class);
360
        $registrations = $queryBuilder->count('email')
361
            ->from('tx_sfeventmgt_domain_model_registration')
362
            ->where(
363
                $queryBuilder->expr()->eq(
364
                    'event',
365
                    $queryBuilder->createNamedParameter($event->getUid(), Connection::PARAM_INT)
366
                ),
367
                $queryBuilder->expr()->eq(
368
                    'email',
369
                    $queryBuilder->createNamedParameter($email, Connection::PARAM_STR)
370
                )
371
            )
372
            ->execute()
373
            ->fetchColumn();
374
375
        return $registrations >= 1;
376
    }
377
378
    /**
379
     * Returns, if payment redirect for the payment method is enabled
380
     *
381
     * @param Registration $registration
382
     * @return bool
383
     */
384
    public function redirectPaymentEnabled($registration)
385
    {
386
        if ($registration->getEvent()->getEnablePayment() === false) {
387
            return false;
388
        }
389
390
        /** @var AbstractPayment $paymentInstance */
391
        $paymentInstance = $this->paymentService->getPaymentInstance($registration->getPaymentmethod());
392
        if ($paymentInstance !== null && $paymentInstance->isRedirectEnabled()) {
393
            return true;
394
        }
395
396
        return false;
397
    }
398
399
    /**
400
     * Returns if the given amount of registrations for the event will be registrations for the waitlist
401
     * (depending on the total amount of registrations and free places)
402
     *
403
     * @param \DERHANSEN\SfEventMgt\Domain\Model\Event $event
404
     * @param int $amountOfRegistrations
405
     * @return bool
406
     */
407
    public function isWaitlistRegistration($event, $amountOfRegistrations)
408
    {
409
        if ($event->getMaxParticipants() === 0 || !$event->getEnableWaitlist()) {
410
            return false;
411
        }
412
413
        $result = false;
414
        if (($event->getFreePlaces() > 0 && $event->getFreePlaces() < $amountOfRegistrations)
415
            || $event->getFreePlaces() <= 0) {
416
            $result = true;
417
        }
418
419
        return $result;
420
    }
421
422
    /**
423
     * Fixes the event uid of a registration if the event has been saved as a child of a translated event.
424
     *
425
     * Since TYPO3 9.5 (#82363), registrations for events are saved to the translated event record
426
     *
427
     * Example:
428
     *
429
     * When a registration is saved for a translated event, the registration $registration->setEvent($event) will
430
     * now save the UID of the translated event instead of the uid of the event in default language.
431
     *
432
     * This behavior breaks limitations on events (e.g. max participants). Therefore, the registration must always
433
     * be related to the default event language (Extbase behavior before TYPO3 9.5)
434
     *
435
     * @param Registration $registration
436
     * @param Event $event
437
     * @return void
438
     */
439
    public function fixRegistrationEvent(Registration $registration, Event $event)
440
    {
441
        // Early return when event is in default language
442
        if ((int)$event->_getProperty('_languageUid') === 0) {
443
            return;
444
        }
445
        $this->updateRegistrationEventUid($registration, $event);
446
        $this->updateEventRegistrationCounters($event);
447
    }
448
449
    /**
450
     * Sets the "event" field of the given registration to the uid of the given event
451
     *
452
     * @param Registration $registration
453
     * @param Event $event
454
     * @return void
455
     */
456
    protected function updateRegistrationEventUid(Registration $registration, Event $event)
457
    {
458
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
459
            ->getQueryBuilderForTable('tx_sfeventmgt_domain_model_registration');
460
        $queryBuilder->update('tx_sfeventmgt_domain_model_registration')
461
            ->set('event', $event->getUid())
462
            ->where(
463
                $queryBuilder->expr()->eq(
464
                    'uid',
465
                    $queryBuilder->createNamedParameter($registration->getUid(), Connection::PARAM_INT)
466
                )
467
            )
468
            ->execute();
469
    }
470
471
    /**
472
     * Updates registration/waitlist registration counters for the given event
473
     *
474
     * @param Event $event
475
     * @return void
476
     */
477
    protected function updateEventRegistrationCounters(Event $event)
478
    {
479
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
480
            ->getQueryBuilderForTable('tx_sfeventmgt_domain_model_event');
481
482
        $countRegistrations = $this->getEventRegistrationCount($event, 0);
483
        $countRegistrationsWaitlist = $this->getEventRegistrationCount($event, 1);
484
485
        $queryBuilder->update('tx_sfeventmgt_domain_model_event')
486
            ->set('registration', $countRegistrations)
487
            ->set('registration_waitlist', $countRegistrationsWaitlist)
488
            ->where(
489
                $queryBuilder->expr()->eq(
490
                    'uid',
491
                    $queryBuilder->createNamedParameter($event->getUid(), Connection::PARAM_INT)
492
                )
493
            )
494
            ->orWhere(
495
                $queryBuilder->expr()->eq(
496
                    'l10n_parent',
497
                    $queryBuilder->createNamedParameter($event->getUid(), Connection::PARAM_INT)
498
                )
499
            )
500
            ->execute();
501
    }
502
503
    /**
504
     * Returns the total amount of registrations/waitlist registrations for an event
505
     *
506
     * @param Event $event
507
     * @param int $waitlist
508
     * @return mixed
509
     */
510
    protected function getEventRegistrationCount(Event $event, int $waitlist = 0)
511
    {
512
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
513
            ->getQueryBuilderForTable('tx_sfeventmgt_domain_model_registration');
514
        $queryBuilder->getRestrictions()->removeByType(HiddenRestriction::class);
515
        return $queryBuilder->count('uid')
516
            ->from('tx_sfeventmgt_domain_model_registration')
517
            ->where(
518
                $queryBuilder->expr()->eq(
519
                    'event',
520
                    $queryBuilder->createNamedParameter($event->getUid(), Connection::PARAM_INT)
521
                ),
522
                $queryBuilder->expr()->eq(
523
                    'waitlist',
524
                    $queryBuilder->createNamedParameter($waitlist, Connection::PARAM_INT)
525
                )
526
            )
527
            ->execute()
528
            ->fetchColumn();
529
    }
530
}
531