Passed
Pull Request — master (#1066)
by Simon
04:26 queued 26s
created

PageRequestAccount   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 327
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 33
eloc 140
c 2
b 0
f 0
dl 0
loc 327
ccs 0
cts 142
cp 0
rs 9.76

13 Methods

Rating   Name   Duplication   Size   Complexity  
A saveAsEmailConfirmation() 0 32 3
A createNewRequest() 0 27 2
A savePrivateData() 0 15 3
A main() 0 8 1
A getRequestValidationHelper() 0 16 2
A handleFormPost() 0 31 4
A handleFormRefilling() 0 12 3
A requestClientHints() 0 5 1
B dynamic() 0 44 7
A preserveSessionFormData() 0 8 1
A createComment() 0 15 3
A validateRequest() 0 12 1
A saveWithoutEmailConfirmation() 0 13 2
1
<?php
2
/******************************************************************************
3
 * Wikipedia Account Creation Assistance tool                                 *
4
 * ACC Development Team. Please see team.json for a list of contributors.     *
5
 *                                                                            *
6
 * This is free and unencumbered software released into the public domain.    *
7
 * Please see LICENSE.md for the full licencing statement.                    *
8
 ******************************************************************************/
9
10
namespace Waca\Pages\Request;
11
12
use Exception;
13
use Waca\DataObjects\Comment;
14
use Waca\DataObjects\Domain;
15
use Waca\DataObjects\Request;
16
use Waca\DataObjects\RequestData;
17
use Waca\DataObjects\RequestForm;
18
use Waca\DataObjects\RequestQueue;
19
use Waca\Exceptions\ApplicationLogicException;
20
use Waca\Exceptions\OptimisticLockFailedException;
21
use Waca\Helpers\BanHelper;
22
use Waca\Helpers\MarkdownRenderingHelper;
23
use Waca\SessionAlert;
24
use Waca\Tasks\PublicInterfacePageBase;
25
use Waca\Validation\RequestValidationHelper;
26
use Waca\Validation\ValidationError;
27
use Waca\WebRequest;
28
29
class PageRequestAccount extends PublicInterfacePageBase
30
{
31
    const CSRF_TOKEN_CONTEXT = 'requestFormSubmission';
32
    /** @var RequestValidationHelper do not use directly. */
33
    private $validationHelper;
34
35
    /**
36
     * Main function for this page, when no specific actions are called.
37
     * @return void
38
     * @throws OptimisticLockFailedException
39
     * @throws Exception
40
     */
41
    protected function main()
42
    {
43
        $database = $this->getDatabase();
44
45
        /** @var RequestForm $defaultForm */
46
        $defaultForm = RequestForm::getById($this->getSiteConfiguration()->getDefaultRequestForm(), $database);
47
48
        $this->redirect('r', $defaultForm->getDomainObject()->getShortName() . '/' . $defaultForm->getPublicEndpoint());
49
    }
50
51
    /**
52
     * Handles dynamic request forms.
53
     * @return void
54
     * @throws OptimisticLockFailedException
55
     * @throws Exception
56
     */
57
    protected function dynamic()
58
    {
59
        $database = $this->getDatabase();
60
61
        $pathInfo = WebRequest::pathInfo();
62
        $domain = Domain::getByShortName($pathInfo[1], $database);
63
        if ($domain === false || !$domain->isEnabled()) {
64
            throw new ApplicationLogicException("This form is not available at this time.");
65
        }
66
67
        $form = RequestForm::getByPublicEndpoint($database, $pathInfo[2], $domain->getId());
68
69
        if ($form === false || !$form->isEnabled()) {
70
            throw new ApplicationLogicException("This form is not available at this time.");
71
        }
72
73
        // dual mode page
74
        if (WebRequest::wasPosted()) {
75
            // validate CSRF token
76
            $csrfTokenMinimumLifetime = $this->getSiteConfiguration()->getRequestMinimumTokenAge();
77
            try {
78
                $this->validateCSRFToken(self::CSRF_TOKEN_CONTEXT, $csrfTokenMinimumLifetime);
79
            } catch (ApplicationLogicException $e) {
80
                SessionAlert::error('Please try submitting the form again.', 'Oops! Something went wrong');
81
                $this->preserveSessionFormData();
82
                $this->redirect();
83
                return;
84
            }
85
86
            $request = $this->createNewRequest($form);
87
            $this->handleFormPost($request);
88
        }
89
        else {
90
            $this->assignCSRFToken(self::CSRF_TOKEN_CONTEXT);
91
            $this->requestClientHints();
92
            $this->handleFormRefilling();
93
94
            $renderer = new MarkdownRenderingHelper();
95
            $this->assign('formPreamble', $renderer->doRender($form->getFormContent()));
96
            $this->assign('formUsernameHelp', $renderer->doRenderInline($form->getUsernameHelp()));
97
            $this->assign('formEmailHelp', $renderer->doRenderInline($form->getEmailHelp()));
98
            $this->assign('formCommentsHelp', $renderer->doRenderInline($form->getCommentHelp()));
99
100
            $this->setTemplate('request/request-form-dynamic.tpl');
101
        }
102
    }
103
104
    /**
105
     * @param RequestForm|null $form
106
     *
107
     * @return Request
108
     * @throws ApplicationLogicException
109
     */
110
    protected function createNewRequest(?RequestForm $form): Request
111
    {
112
        $database = $this->getDatabase();
113
114
        $request = new Request();
115
116
        if ($form === null) {
117
            $domain = 1;
118
        }
119
        else {
120
            $domain = $form->getDomain();
121
            $request->setOriginForm($form->getId());
122
        }
123
124
        $request->setQueue(RequestQueue::getDefaultQueue($database, $domain)->getId());
125
        $request->setDatabase($database);
126
        $request->setDomain($domain);
127
128
        $request->setName(trim(WebRequest::postString('name')));
0 ignored issues
show
Bug introduced by
It seems like Waca\WebRequest::postString('name') can also be of type null; however, parameter $string of trim() does only seem to accept string, 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

128
        $request->setName(trim(/** @scrutinizer ignore-type */ WebRequest::postString('name')));
Loading history...
129
        $request->setEmail(WebRequest::postEmail('email'));
130
131
        $request->setIp(WebRequest::remoteAddress());
132
        $request->setForwardedIp(WebRequest::forwardedAddress());
133
134
        $request->setUserAgent(WebRequest::userAgent());
135
136
        return $request;
137
    }
138
139
    /**
140
     * @return Comment|null
141
     */
142
    private function createComment()
143
    {
144
        $commentText = WebRequest::postString('comments');
145
        if ($commentText === null || trim($commentText) === '') {
146
            return null;
147
        }
148
149
        $comment = new Comment();
150
        $comment->setDatabase($this->getDatabase());
151
152
        $comment->setVisibility('requester');
153
        $comment->setUser(null);
154
        $comment->setComment($commentText);
155
156
        return $comment;
157
    }
158
159
    /**
160
     * @param Request $request
161
     *
162
     * @return ValidationError[]
163
     */
164
    protected function validateRequest($request)
165
    {
166
        $validationHelper = $this->getRequestValidationHelper();
167
168
        // These are arrays of ValidationError.
169
        $nameValidation = $validationHelper->validateName($request);
170
        $emailValidation = $validationHelper->validateEmail($request, WebRequest::postEmail('emailconfirm'));
171
        $otherValidation = $validationHelper->validateOther($request);
172
173
        $validationErrors = array_merge($nameValidation, $emailValidation, $otherValidation);
174
175
        return $validationErrors;
176
    }
177
178
    /**
179
     * @param Request      $request
180
     *
181
     * @param Comment|null $comment
182
     *
183
     * @throws OptimisticLockFailedException
184
     * @throws Exception
185
     */
186
    protected function saveAsEmailConfirmation(Request $request, $comment)
187
    {
188
        $request->generateEmailConfirmationHash();
189
        $request->save();
190
191
        if ($comment !== null) {
192
            $comment->setRequest($request->getId());
193
            $comment->save();
194
        }
195
196
        $trustedIp = $this->getXffTrustProvider()->getTrustedClientIp(
197
            $request->getIp(),
198
            $request->getForwardedIp());
199
200
        $this->assign("ip", $trustedIp);
201
        $this->assign("id", $request->getId());
202
        $this->assign("hash", $request->getEmailConfirm());
203
204
        // Sends the confirmation email to the user.
205
        // FIXME: domains
206
        /** @var Domain $domain */
207
        $domain = Domain::getById(1, $this->getDatabase());
208
209
        if ($this->getRequestValidationHelper()->preEmailConfirmationValidations($request)) {
210
            $this->getEmailHelper()->sendMail(
211
                $domain->getEmailReplyAddress(),
212
                $request->getEmail(),
213
                "[ACC #{$request->getId()}] English Wikipedia Account Request",
214
                $this->fetchTemplate('request/confirmation-mail.tpl'));
215
        }
216
217
        $this->redirect('emailConfirmationRequired');
218
    }
219
220
    /**
221
     * @param Request      $request
222
     *
223
     * @param Comment|null $comment
224
     *
225
     * @throws OptimisticLockFailedException
226
     * @throws Exception
227
     */
228
    protected function saveWithoutEmailConfirmation(Request $request, $comment)
229
    {
230
        $request->setEmailConfirm(0); // fixme Since it can't be null
231
        $request->save();
232
233
        if ($comment !== null) {
234
            $comment->setRequest($request->getId());
235
            $comment->save();
236
        }
237
238
        $this->getNotificationHelper()->requestReceived($request);
239
240
        $this->redirect('requestSubmitted');
241
    }
242
243
    /**
244
     * @return RequestValidationHelper
245
     */
246
    protected function getRequestValidationHelper(): RequestValidationHelper
247
    {
248
        $banHelper = new BanHelper($this->getDatabase(), $this->getXffTrustProvider(), null);
249
250
        if ($this->validationHelper === null) {
251
            $this->validationHelper = new RequestValidationHelper(
252
                $banHelper,
253
                $this->getDatabase(),
254
                $this->getAntiSpoofProvider(),
255
                $this->getXffTrustProvider(),
256
                $this->getHttpHelper(),
257
                $this->getTorExitProvider(),
258
                $this->getSiteConfiguration());
259
        }
260
261
        return $this->validationHelper;
262
}
263
264
    /**
265
     * @param Request $request
266
     *
267
     * @return void
268
     * @throws OptimisticLockFailedException
269
     */
270
    protected function handleFormPost(Request $request): void
271
    {
272
        $comment = $this->createComment();
273
274
        $validationErrors = $this->validateRequest($request);
275
276
        if (count($validationErrors) > 0) {
277
            foreach ($validationErrors as $validationError) {
278
                SessionAlert::error($validationError->getErrorMessage());
279
            }
280
281
            // Preserve the data after an error
282
            $this->preserveSessionFormData();
283
284
            // Validation error, bomb out early.
285
            $this->redirect();
286
287
            return;
288
        }
289
290
        // actually save the request to the database
291
        if ($this->getSiteConfiguration()->getEmailConfirmationEnabled()) {
292
            $this->saveAsEmailConfirmation($request, $comment);
293
            $this->savePrivateData($request);
294
        }
295
        else {
296
            $this->saveWithoutEmailConfirmation($request, $comment);
297
            $this->savePrivateData($request);
298
        }
299
300
        $this->getRequestValidationHelper()->postSaveValidations($request);
301
    }
302
303
    /**
304
     * @return void
305
     */
306
    protected function handleFormRefilling(): void
307
    {
308
        // set the form values from the session context
309
        $context = WebRequest::getSessionContext('accountReq');
310
        if ($context !== null && is_array($context)) {
311
            $this->assign('username', $context['username']);
312
            $this->assign('email', $context['email']);
313
            $this->assign('comments', $context['comments']);
314
        }
315
316
        // Clear it for a refresh
317
        WebRequest::setSessionContext('accountReq', null);
318
    }
319
320
    private function requestClientHints()
321
    {
322
        $hints = $this->getSiteConfiguration()->getAcceptClientHints();
323
324
        $this->headerQueue[] = "Accept-CH: " . implode(', ', $hints);
325
    }
326
327
    private function savePrivateData(Request $request)
328
    {
329
        RequestData::saveForRequest(
330
            $request, RequestData::TYPE_USERAGENT, WebRequest::userAgent());
0 ignored issues
show
Bug introduced by
It seems like Waca\WebRequest::userAgent() can also be of type null; however, parameter $value of Waca\DataObjects\RequestData::saveForRequest() does only seem to accept string, 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

330
            $request, RequestData::TYPE_USERAGENT, /** @scrutinizer ignore-type */ WebRequest::userAgent());
Loading history...
331
332
        foreach ($this->getSiteConfiguration()->getAcceptClientHints() as $header)
333
        {
334
            $value = WebRequest::httpHeader($header);
335
336
            if ($value === null) {
337
                continue;
338
            }
339
340
            RequestData::saveForRequest($request,
341
                RequestData::TYPE_CLIENTHINT, $value, $header);
342
        }
343
    }
344
345
    /**
346
     * @return void
347
     */
348
    protected function preserveSessionFormData(): void
349
    {
350
        WebRequest::setSessionContext(
351
            'accountReq',
352
            array(
353
                'username' => WebRequest::postString('name'),
354
                'email'    => WebRequest::postEmail('email'),
355
                'comments' => WebRequest::postString('comments'),
356
            )
357
        );
358
    }
359
}