PageRequestAccount::createComment()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 15
ccs 0
cts 6
cp 0
rs 9.9666
cc 3
nc 2
nop 0
crap 12
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
            }
80
            catch (ApplicationLogicException $e) {
81
                SessionAlert::error('Please try submitting the form again.', 'Oops! Something went wrong');
82
                $this->preserveSessionFormData();
83
                $this->redirect();
84
                return;
85
            }
86
87
            $request = $this->createNewRequest($form);
88
            $this->handleFormPost($request);
89
        }
90
        else {
91
            $this->assignCSRFToken(self::CSRF_TOKEN_CONTEXT);
92
            $this->requestClientHints();
93
            $this->handleFormRefilling();
94
95
            $renderer = new MarkdownRenderingHelper();
96
            $this->assign('formPreamble', $renderer->doRender($form->getFormContent()));
97
            $this->assign('formUsernameHelp', $renderer->doRenderInline($form->getUsernameHelp()));
98
            $this->assign('formEmailHelp', $renderer->doRenderInline($form->getEmailHelp()));
99
            $this->assign('formCommentsHelp', $renderer->doRenderInline($form->getCommentHelp()));
100
101
            $this->setTemplate('request/request-form-dynamic.tpl');
102
        }
103
    }
104
105
    /**
106
     * @param RequestForm|null $form
107
     *
108
     * @return Request
109
     * @throws ApplicationLogicException
110
     */
111
    protected function createNewRequest(?RequestForm $form): Request
112
    {
113
        $database = $this->getDatabase();
114
115
        $request = new Request();
116
117
        if ($form === null) {
118
            $domain = 1;
119
        }
120
        else {
121
            $domain = $form->getDomain();
122
            $request->setOriginForm($form->getId());
123
        }
124
125
        $request->setQueue(RequestQueue::getDefaultQueue($database, $domain)->getId());
126
        $request->setDatabase($database);
127
        $request->setDomain($domain);
128
129
        $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

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