• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eliashaeussler / typo3-form-consent / 26602156121

28 May 2026 09:03PM UTC coverage: 91.598% (-3.1%) from 94.724%
26602156121

Pull #495

github

eliashaeussler
[TASK] Restructure CI and build tools
Pull Request #495: [TASK] Restructure CI and build tools

1 of 1 new or added line in 1 file covered. (100.0%)

36 existing lines in 3 files now uncovered.

774 of 845 relevant lines covered (91.6%)

16.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.89
/Classes/Controller/ConsentController.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the TYPO3 CMS extension "form_consent".
7
 *
8
 * Copyright (C) 2021-2026 Elias Häußler <elias@haeussler.dev>
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, either version 2 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22
 */
23

24
namespace EliasHaeussler\Typo3FormConsent\Controller;
25

26
use EliasHaeussler\Typo3FormConsent\Domain;
27
use EliasHaeussler\Typo3FormConsent\Event;
28
use EliasHaeussler\Typo3FormConsent\Registry;
29
use Psr\Http\Message;
30
use Psr\Log;
31
use TYPO3\CMS\Core;
32
use TYPO3\CMS\Extbase;
33

34
/**
35
 * ConsentController
36
 *
37
 * @author Elias Häußler <elias@haeussler.dev>
38
 * @license GPL-2.0-or-later
39
 */
40
final class ConsentController extends Extbase\Mvc\Controller\ActionController
41
{
42
    public function __construct(
20✔
43
        private readonly Domain\Repository\ConsentRepository $consentRepository,
44
        private readonly Extbase\Persistence\PersistenceManagerInterface $persistenceManager,
45
        private readonly Log\LoggerInterface $logger,
46
    ) {}
20✔
47

48
    public function initializeAction(): void
20✔
49
    {
50
        if ($this->isPreviewRequested()) {
20✔
51
            $this->actionMethodName = 'previewAction';
1✔
52
            $this->request = $this->request->withControllerActionName('preview');
1✔
53
            $this->arguments->removeAll();
1✔
54
        }
55
    }
56

57
    /**
58
     * @throws Extbase\Persistence\Exception\IllegalObjectTypeException
59
     * @throws Core\Http\PropagateResponseException
60
     * @throws Extbase\Persistence\Exception\UnknownObjectException
61
     */
62
    public function approveAction(string $hash, string $email, bool $verify = false): Message\ResponseInterface
14✔
63
    {
64
        $consent = $this->consentRepository->findOneByValidationHash($hash);
14✔
65

66
        // Add template variable
67
        $this->view->assign('consent', $consent);
14✔
68

69
        // Early return if consent could not be found
70
        if ($consent === null) {
14✔
71
            return $this->createErrorResponse('invalidConsent');
1✔
72
        }
73

74
        // Early return if given email does not match registered email
75
        if ($email !== $consent->getEmail()) {
13✔
76
            return $this->createErrorResponse('invalidEmail');
1✔
77
        }
78

79
        // Early return if consent is already approved
80
        if ($consent->isApproved()) {
12✔
81
            return $this->createErrorResponse('alreadyApproved');
1✔
82
        }
83

84
        // Render required user verification button
85
        if ($verify) {
12✔
86
            $this->view->assign('verificationNeeded', true);
1✔
87

88
            return $this->createHtmlResponse();
1✔
89
        }
90

91
        // Register consent state
92
        Registry\ConsentManagerRegistry::registerConsent($consent);
12✔
93

94
        // Approve consent
95
        $consent->setApproved();
12✔
96
        $consent->setValidUntil(null);
12✔
97

98
        // Dispatch approve event
99
        try {
100
            $event = new Event\ApproveConsentEvent($consent);
12✔
101
            $this->eventDispatcher->dispatch($event);
12✔
UNCOV
102
        } catch (\Exception $exception) {
×
UNCOV
103
            $this->logger->error(
×
UNCOV
104
                'Approval failed for consent {consentUid} and form {formPersistenceIdentifier}.',
×
UNCOV
105
                [
×
UNCOV
106
                    'consentUid' => $consent->getUid(),
×
UNCOV
107
                    'formPersistenceIdentifier' => $consent->getFormPersistenceIdentifier(),
×
UNCOV
108
                    'exception' => $exception,
×
UNCOV
109
                ],
×
UNCOV
110
            );
×
111

UNCOV
112
            return $this->createErrorResponse('unexpectedError', $exception);
×
113
        }
114

115
        // Update approved consent
116
        $this->consentRepository->update($consent);
12✔
117
        $this->persistenceManager->persistAll();
12✔
118

119
        return $this->createHtmlResponse($event->getResponse());
12✔
120
    }
121

122
    /**
123
     * @throws Extbase\Persistence\Exception\IllegalObjectTypeException
124
     * @throws Core\Http\PropagateResponseException
125
     * @throws Extbase\Persistence\Exception\UnknownObjectException
126
     */
127
    public function dismissAction(string $hash, string $email, bool $verify = false): Message\ResponseInterface
9✔
128
    {
129
        $consent = $this->consentRepository->findOneByValidationHash($hash);
9✔
130

131
        // Add template variable
132
        $this->view->assign('consent', $consent);
9✔
133

134
        // Early return if consent could not be found
135
        if ($consent === null) {
9✔
136
            return $this->createErrorResponse('invalidConsent');
1✔
137
        }
138

139
        // Early return if given email does not match registered email
140
        if ($consent->getEmail() !== $email) {
9✔
141
            return $this->createErrorResponse('invalidEmail');
1✔
142
        }
143

144
        // Render required user verification button
145
        if ($verify) {
8✔
146
            $this->view->assign('verificationNeeded', true);
1✔
147

148
            return $this->createHtmlResponse();
1✔
149
        }
150

151
        // Register consent state
152
        Registry\ConsentManagerRegistry::registerConsent($consent);
8✔
153

154
        // Un-approve consent
155
        $consent->setDismissed();
8✔
156
        $consent->setValidUntil(null);
8✔
157

158
        // Dispatch dismiss event
159
        try {
160
            $event = new Event\DismissConsentEvent($consent);
8✔
161
            $this->eventDispatcher->dispatch($event);
8✔
UNCOV
162
        } catch (\Exception $exception) {
×
UNCOV
163
            $this->logger->error(
×
UNCOV
164
                'Dismissal failed for consent {consentUid} and form {formPersistenceIdentifier}.',
×
UNCOV
165
                [
×
UNCOV
166
                    'consentUid' => $consent->getUid(),
×
UNCOV
167
                    'formPersistenceIdentifier' => $consent->getFormPersistenceIdentifier(),
×
UNCOV
168
                    'exception' => $exception,
×
UNCOV
169
                ],
×
UNCOV
170
            );
×
171

UNCOV
172
            return $this->createErrorResponse('unexpectedError', $exception);
×
173
        }
174

175
        // Obfuscate submitted data
176
        $consent->setData(null);
8✔
177
        $consent->setOriginalRequestParameters(null);
8✔
178

179
        // Remove dismissed consent
180
        $this->consentRepository->update($consent);
8✔
181
        $this->consentRepository->remove($consent);
8✔
182
        $this->persistenceManager->persistAll();
8✔
183

184
        return $this->createHtmlResponse($event->getResponse());
8✔
185
    }
186

187
    /**
188
     * Dummy preview action for use in backend context.
189
     *
190
     * This action is not part of any frontend plugin. It is used as dummy action for
191
     * preview requests during an active backend session.
192
     *
193
     * NOTE: Method must not be private, otherwise action is not callable by ActionController.
194
     *
195
     * @see ConsentController::initializeAction()
196
     * @see ConsentController::isPreviewRequested()
197
     */
198
    protected function previewAction(): Message\ResponseInterface
1✔
199
    {
200
        return $this->htmlResponse();
1✔
201
    }
202

203
    /**
204
     * @throws Core\Http\PropagateResponseException
205
     */
206
    private function createErrorResponse(string $reason, ?\Throwable $exception = null): Message\ResponseInterface
3✔
207
    {
208
        $this->view->assign('error', true);
3✔
209
        $this->view->assign('reason', $reason);
3✔
210
        $this->view->assign('exception', $exception);
3✔
211

212
        return $this->createHtmlResponse();
3✔
213
    }
214

215
    /**
216
     * @throws Core\Http\PropagateResponseException
217
     */
218
    private function createHtmlResponse(?Message\ResponseInterface $previous = null): Message\ResponseInterface
19✔
219
    {
220
        if ($previous === null) {
19✔
221
            return $this->htmlResponse();
8✔
222
        }
223

224
        if ($previous->getStatusCode() >= 300) {
11✔
225
            throw new Core\Http\PropagateResponseException($previous, 1645646663);
3✔
226
        }
227

228
        $content = (string)$previous->getBody();
8✔
229

230
        if (trim($content) !== '') {
8✔
231
            return $this->htmlResponse($content);
6✔
232
        }
233

234
        return $this->htmlResponse();
2✔
235
    }
236

237
    private function isPreviewRequested(): bool
20✔
238
    {
239
        // Early return if no backend session is active
240
        if ($this->getBackendUser() === null) {
20✔
241
            return false;
20✔
242
        }
243

244
        // Early return if at least one argument is given
245
        if ($this->request->getArguments() !== []) {
1✔
246
            return false;
×
247
        }
248

249
        return true;
1✔
250
    }
251

252
    private function getBackendUser(): ?Core\Authentication\BackendUserAuthentication
20✔
253
    {
254
        $backendUser = $GLOBALS['BE_USER'] ?? null;
20✔
255

256
        if ($backendUser instanceof Core\Authentication\BackendUserAuthentication) {
20✔
257
            return $backendUser;
1✔
258
        }
259

260
        return null;
20✔
261
    }
262
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc