Issues (186)

Branch: master

includes/Pages/Request/PageRequestAccount.php (2 issues)

Labels
Severity
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
    /** @var RequestValidationHelper do not use directly. */
32
    private $validationHelper;
33
34
    /**
35
     * Main function for this page, when no specific actions are called.
36
     * @return void
37
     * @throws OptimisticLockFailedException
38
     * @throws Exception
39
     */
40
    protected function main()
41
    {
42
        // dual mode page
43
        if (WebRequest::wasPosted()) {
44
            $request = $this->createNewRequest(null);
45
            $this->handleFormPost($request);
46
        }
47
        else {
48
            $this->requestClientHints();
49
            $this->handleFormRefilling();
50
51
            $this->setTemplate('request/request-form.tpl');
52
        }
53
    }
54
55
    /**
56
     * Handles dynamic request forms.
57
     * @return void
58
     * @throws OptimisticLockFailedException
59
     * @throws Exception
60
     */
61
    protected function dynamic()
62
    {
63
        $database = $this->getDatabase();
64
65
        $pathInfo = WebRequest::pathInfo();
66
        $domain = Domain::getByShortName($pathInfo[1], $database);
67
        if ($domain === false || !$domain->isEnabled()) {
68
            throw new ApplicationLogicException("This form is not available at this time.");
69
        }
70
71
        $form = RequestForm::getByPublicEndpoint($database, $pathInfo[2], $domain->getId());
72
73
        if ($form === false || !$form->isEnabled()) {
74
            throw new ApplicationLogicException("This form is not available at this time.");
75
        }
76
77
        // dual mode page
78
        if (WebRequest::wasPosted()) {
79
            $request = $this->createNewRequest($form);
80
            $this->handleFormPost($request);
81
        }
82
        else {
83
            $this->requestClientHints();
84
            $this->handleFormRefilling();
85
86
            $renderer = new MarkdownRenderingHelper();
87
            $this->assign('formPreamble', $renderer->doRender($form->getFormContent()));
88
            $this->assign('formUsernameHelp', $renderer->doRenderInline($form->getUsernameHelp()));
89
            $this->assign('formEmailHelp', $renderer->doRenderInline($form->getEmailHelp()));
90
            $this->assign('formCommentsHelp', $renderer->doRenderInline($form->getCommentHelp()));
91
92
            $this->setTemplate('request/request-form-dynamic.tpl');
93
        }
94
    }
95
96
    /**
97
     * @param RequestForm|null $form
98
     *
99
     * @return Request
100
     * @throws ApplicationLogicException
101
     */
102
    protected function createNewRequest(?RequestForm $form): Request
103
    {
104
        $database = $this->getDatabase();
105
106
        $request = new Request();
107
108
        if ($form === null) {
109
            $domain = 1;
110
        }
111
        else {
112
            $domain = $form->getDomain();
113
            $request->setOriginForm($form->getId());
114
        }
115
116
        $request->setQueue(RequestQueue::getDefaultQueue($database, $domain)->getId());
117
        $request->setDatabase($database);
118
        $request->setDomain($domain);
119
120
        $request->setName(trim(WebRequest::postString('name')));
0 ignored issues
show
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

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

338
            $d->setValue(/** @scrutinizer ignore-type */ WebRequest::httpHeader($header));
Loading history...
339
340
            $d->save();
341
        }
342
    }
343
}