Issues (186)

Branch: master

includes/Pages/RequestAction/PageCustomClose.php (5 issues)

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\RequestAction;
11
12
use Exception;
13
use Waca\Background\Task\BotCreationTask;
14
use Waca\Background\Task\UserCreationTask;
15
use Waca\DataObjects\EmailTemplate;
16
use Waca\DataObjects\JobQueue;
17
use Waca\DataObjects\Request;
18
use Waca\DataObjects\RequestQueue;
19
use Waca\DataObjects\User;
20
use Waca\Exceptions\AccessDeniedException;
21
use Waca\Exceptions\ApplicationLogicException;
22
use Waca\Exceptions\OptimisticLockFailedException;
23
use Waca\Fragments\RequestData;
24
use Waca\Helpers\Logger;
25
use Waca\Helpers\OAuthUserHelper;
26
use Waca\Helpers\PreferenceManager;
27
use Waca\PdoDatabase;
28
use Waca\RequestStatus;
29
use Waca\SessionAlert;
30
use Waca\WebRequest;
31
32
class PageCustomClose extends PageCloseRequest
33
{
34
    use RequestData;
35
36
    public const CREATE_OAUTH = 'created-oauth';
37
    public const CREATE_BOT = 'created-bot';
38
39
    protected function main()
40
    {
41
        $database = $this->getDatabase();
42
43
        $request = $this->getRequest($database);
44
        $currentUser = User::getCurrent($this->getDatabase());
45
46
        if ($request->getStatus() === RequestStatus::CLOSED) {
47
            throw new ApplicationLogicException('Request is already closed');
48
        }
49
50
        // Dual-mode page
51
        if (WebRequest::wasPosted()) {
52
            $this->validateCSRFToken();
53
            $success = $this->doCustomClose($currentUser, $request, $database);
54
55
            if ($success) {
56
                $this->redirect();
57
            }
58
        }
59
        else {
60
            $this->assignCSRFToken();
61
            $this->showCustomCloseForm($database, $request);
62
        }
63
    }
64
65
    /**
66
     * @param $database
67
     *
68
     * @return Request
69
     * @throws ApplicationLogicException
70
     */
71
    protected function getRequest(PdoDatabase $database)
72
    {
73
        $requestId = WebRequest::getInt('request');
74
        if ($requestId === null) {
75
            throw new ApplicationLogicException('Request ID not found');
76
        }
77
78
        /** @var Request $request */
79
        $request = Request::getById($requestId, $database);
80
81
        if ($request === false) {
0 ignored issues
show
The condition $request === false is always false.
Loading history...
82
            throw new ApplicationLogicException('Request not found');
83
        }
84
85
        return $request;
86
    }
87
88
    /**
89
     * @param PdoDatabase $database
90
     *
91
     * @return EmailTemplate|null
92
     */
93
    protected function getTemplate(PdoDatabase $database)
94
    {
95
        $templateId = WebRequest::getInt('template');
96
        if ($templateId === null) {
97
            return null;
98
        }
99
100
        /** @var EmailTemplate $template */
101
        $template = EmailTemplate::getById($templateId, $database);
102
        if ($template === false || !$template->getActive()) {
103
            return null;
104
        }
105
106
        return $template;
107
    }
108
109
    /**
110
     * @param $database
111
     * @param $request
112
     *
113
     * @throws Exception
114
     */
115
    protected function showCustomCloseForm(PdoDatabase $database, Request $request)
116
    {
117
        $this->setHtmlTitle("Custom close");
118
119
        $currentUser = User::getCurrent($database);
120
        $preferencesManager = PreferenceManager::getForCurrent($database);
121
        $config = $this->getSiteConfiguration();
122
123
        $allowedPrivateData = $this->isAllowedPrivateData($request, $currentUser);
124
        if (!$allowedPrivateData) {
125
            // we probably shouldn't be showing the user this form if they're not allowed to access private data...
126
            throw new AccessDeniedException($this->getSecurityManager(), $this->getDomainAccessManager());
127
        }
128
129
        $template = $this->getTemplate($database);
130
131
        // Preload data
132
        $this->assign('defaultAction', '');
133
        $this->assign('defaultQueue', null);
134
        $this->assign('preloadText', '');
135
        $this->assign('preloadTitle', '');
136
137
        if ($template !== null) {
138
            $this->assign('defaultAction', $template->getDefaultAction());
139
            $this->assign('defaultQueue', $template->getQueue());
140
            $this->assign('preloadText', $template->getText());
141
            $this->assign('preloadTitle', $template->getName());
142
        }
143
144
        // Static data
145
        $this->assign('requestQueues', $requestQueues = RequestQueue::getEnabledQueues($database));
146
147
        // request data
148
        $this->assign('requestId', $request->getIp());
149
        $this->assign('updateVersion', $request->getUpdateVersion());
150
        $this->setupBasicData($request, $config);
151
        $this->setupReservationDetails($request->getReserved(), $database, $currentUser);
152
        $this->setupPrivateData($request, $config);
153
        $this->setupRelatedRequests($request, $config, $database);
154
155
        // IP location
156
        $trustedIp = $this->getXffTrustProvider()->getTrustedClientIp($request->getIp(), $request->getForwardedIp());
157
        $this->assign('iplocation', $this->getLocationProvider()->getIpLocation($trustedIp));
158
159
        // Confirmations
160
        $this->assign('confirmEmailAlreadySent', $this->checkEmailAlreadySent($request));
161
162
        $this->assign('canSkipCcMailingList', $this->barrierTest('skipCcMailingList', $currentUser));
163
164
        $this->assign('allowWelcomeSkip', false);
165
        $this->assign('forceWelcomeSkip', false);
166
167
        $canOauthCreate = $this->barrierTest(PreferenceManager::CREATION_OAUTH, $currentUser, 'RequestCreation');
168
        $canBotCreate = $this->barrierTest(PreferenceManager::CREATION_BOT, $currentUser, 'RequestCreation');
169
170
        $oauth = new OAuthUserHelper($currentUser, $this->getDatabase(), $this->getOAuthProtocolHelper(), $config);
171
172
        $welcomeTemplate = $preferencesManager->getPreference(PreferenceManager::PREF_WELCOMETEMPLATE);
173
        if ($welcomeTemplate != 0) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $welcomeTemplate of type mixed|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
174
            $this->assign('allowWelcomeSkip', true);
175
176
            if (!$oauth->canWelcome()) {
177
                $this->assign('forceWelcomeSkip', true);
178
            }
179
        }
180
181
        $this->assign('preferredCreationMode', (int)$preferencesManager->getPreference(PreferenceManager::PREF_CREATION_MODE));
182
183
        // disable options if there's a misconfiguration.
184
        $canOauthCreate &= $oauth->canCreateAccount();
185
        $canBotCreate &= $this->getSiteConfiguration()->getCreationBotPassword() !== null;
186
187
        $this->assign('canOauthCreate', $canOauthCreate);
188
        $this->assign('canBotCreate', $canBotCreate);
189
190
        // template
191
        $this->setTemplate('custom-close.tpl');
192
    }
193
194
    /**
195
     * @param User        $currentUser
196
     * @param Request     $request
197
     * @param PdoDatabase $database
198
     *
199
     * @throws ApplicationLogicException
200
     */
201
    protected function doCustomClose(User $currentUser, Request $request, PdoDatabase $database) : bool
202
    {
203
        $messageBody = WebRequest::postString('msgbody');
204
        if ($messageBody === null || trim($messageBody) === '') {
205
            throw new ApplicationLogicException('Message body cannot be blank');
206
        }
207
208
        $ccMailingList = true;
209
        if ($this->barrierTest('skipCcMailingList', $currentUser)) {
210
            $ccMailingList = WebRequest::postBoolean('ccMailingList');
211
        }
212
213
        if ($request->getStatus() === RequestStatus::CLOSED) {
214
            throw new ApplicationLogicException('Request is already closed');
215
        }
216
217
        if (!(WebRequest::postBoolean('confirmEmailAlreadySent'))
218
        ) {
219
            throw new ApplicationLogicException('Not all confirmations checked');
220
        }
221
222
        $action = WebRequest::postString('action');
223
224
        if ($action === EmailTemplate::ACTION_CREATED || $action === EmailTemplate::ACTION_NOT_CREATED) {
225
226
            if ($action === EmailTemplate::ACTION_CREATED) {
227
                if ($this->checkAccountCreated($request)) {
228
                    $this->assignCSRFToken();
229
                    $this->showCustomCloseForm($database, $request);
230
231
                    $this->assign("preloadText", $messageBody);
232
                    $this->assign('preloadAction', $action);
233
                    $this->assign('ccMailingList', $ccMailingList);
234
                    $this->assign('showNonExistentAccountWarning', true);
235
                    $this->assign('skipAutoWelcome', WebRequest::postBoolean('skipAutoWelcome'));
236
237
                    return false;
238
                }
239
            }
240
241
            // Close request
242
            $this->closeRequest($request, $database, $action, $messageBody);
243
244
            $this->processWelcome($action, null);
245
246
            // Send the mail after the save, since save can be rolled back
247
            $this->sendMail($request, $messageBody, $currentUser, $ccMailingList);
248
249
            return true;
250
        }
251
252
        if ($action === self::CREATE_OAUTH || $action === self::CREATE_BOT) {
253
            $this->processAutoCreation($currentUser, $action, $request, $messageBody, $ccMailingList);
254
255
            return true;
256
        }
257
258
        if ($action === 'mail') {
259
            $request->setReserved(null);
260
            $request->setUpdateVersion(WebRequest::postInt('updateversion'));
261
            $request->save();
262
263
            // Perform the notifications and stuff *after* we've successfully saved, since the save can throw an OLE
264
            // and be rolled back.
265
266
            // Send mail
267
            $this->sendMail($request, $messageBody, $currentUser, $ccMailingList);
268
269
            Logger::sentMail($database, $request, $messageBody);
270
            Logger::unreserve($database, $request);
271
272
            $this->getNotificationHelper()->sentMail($request);
273
            SessionAlert::success("Sent mail to Request {$request->getId()}");
274
275
            return true;
276
        }
277
278
        $targetQueue = RequestQueue::getByApiName($database, $action, 1);
0 ignored issues
show
It seems like $action can also be of type null; however, parameter $apiName of Waca\DataObjects\RequestQueue::getByApiName() 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

278
        $targetQueue = RequestQueue::getByApiName($database, /** @scrutinizer ignore-type */ $action, 1);
Loading history...
279
        if ($targetQueue !== false) {
280
            // Defer to other state
281
            $this->deferRequest($request, $database, $targetQueue, $messageBody);
282
283
            // Send the mail after the save, since save can be rolled back
284
            $this->sendMail($request, $messageBody, $currentUser, $ccMailingList);
285
286
            return true;
287
        }
288
289
        throw new ApplicationLogicException('Unknown action for custom close.');
290
    }
291
292
    /**
293
     * @param Request     $request
294
     * @param PdoDatabase $database
295
     * @param string      $action
296
     * @param string      $messageBody
297
     *
298
     * @throws Exception
299
     * @throws OptimisticLockFailedException
300
     */
301
    protected function closeRequest(Request $request, PdoDatabase $database, $action, $messageBody)
302
    {
303
        $request->setStatus(RequestStatus::CLOSED);
304
        $request->setQueue(null);
305
        $request->setReserved(null);
306
        $request->setUpdateVersion(WebRequest::postInt('updateversion'));
307
        $request->save();
308
309
        // Perform the notifications and stuff *after* we've successfully saved, since the save can throw an OLE and
310
        // be rolled back.
311
312
        if ($action == EmailTemplate::ACTION_CREATED) {
313
            $logCloseType = 'custom-y';
314
            $notificationCloseType = "Custom, Created";
315
        }
316
        else {
317
            $logCloseType = 'custom-n';
318
            $notificationCloseType = "Custom, Not Created";
319
        }
320
321
        Logger::closeRequest($database, $request, $logCloseType, $messageBody);
0 ignored issues
show
$logCloseType of type string is incompatible with the type integer expected by parameter $target of Waca\Helpers\Logger::closeRequest(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

321
        Logger::closeRequest($database, $request, /** @scrutinizer ignore-type */ $logCloseType, $messageBody);
Loading history...
322
        $this->getNotificationHelper()->requestClosed($request, $notificationCloseType);
323
324
        $requestName = htmlentities($request->getName(), ENT_COMPAT, 'UTF-8');
325
        SessionAlert::success("Request {$request->getId()} ({$requestName}) closed as {$notificationCloseType}.");
326
    }
327
328
    /**
329
     * @param Request      $request
330
     * @param PdoDatabase  $database
331
     * @param RequestQueue $targetQueue
332
     * @param string       $messageBody
333
     *
334
     * @throws OptimisticLockFailedException
335
     */
336
    protected function deferRequest(
337
        Request $request,
338
        PdoDatabase $database,
339
        RequestQueue $targetQueue,
340
        $messageBody
341
    ) {
342
        $request->setStatus(RequestStatus::OPEN);
343
        $request->setQueue($targetQueue->getId());
344
        $request->setReserved(null);
345
        $request->setUpdateVersion(WebRequest::postInt('updateversion'));
346
        $request->save();
347
348
        // Perform the notifications and stuff *after* we've successfully saved, since the save can throw an OLE
349
        // and be rolled back.
350
351
        $deferToLog = $targetQueue->getLogName();
0 ignored issues
show
Deprecated Code introduced by
The function Waca\DataObjects\RequestQueue::getLogName() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

351
        $deferToLog = /** @scrutinizer ignore-deprecated */ $targetQueue->getLogName();
Loading history...
352
        Logger::sentMail($database, $request, $messageBody);
353
        Logger::deferRequest($database, $request, $deferToLog);
354
355
        $this->getNotificationHelper()->requestDeferredWithMail($request);
356
357
        $deferTo = htmlentities($targetQueue->getDisplayName(), ENT_COMPAT, 'UTF-8');
358
        SessionAlert::success("Request {$request->getId()} deferred to $deferTo, sending an email.");
359
    }
360
361
    /**
362
     * @param User    $currentUser
363
     * @param string  $action
364
     * @param Request $request
365
     * @param string  $messageBody
366
     * @param bool    $ccMailingList
367
     *
368
     * @throws AccessDeniedException
369
     * @throws ApplicationLogicException
370
     * @throws OptimisticLockFailedException
371
     */
372
    protected function processAutoCreation(User $currentUser, string $action, Request $request, string $messageBody, bool $ccMailingList): void
373
    {
374
        $db = $this->getDatabase();
375
        $oauth = new OAuthUserHelper($currentUser, $db, $this->getOAuthProtocolHelper(), $this->getSiteConfiguration());
376
        $canOauthCreate = $this->barrierTest(PreferenceManager::CREATION_OAUTH, $currentUser, 'RequestCreation');
377
        $canBotCreate = $this->barrierTest(PreferenceManager::CREATION_BOT, $currentUser, 'RequestCreation');
378
        $canOauthCreate &= $oauth->canCreateAccount();
379
        $canBotCreate &= $this->getSiteConfiguration()->getCreationBotPassword() !== null;
380
381
        $creationTaskClass = null;
382
383
        if ($action === self::CREATE_OAUTH) {
384
            if (!$canOauthCreate) {
385
                throw new AccessDeniedException($this->getSecurityManager(), $this->getDomainAccessManager());
386
            }
387
388
            $creationTaskClass = UserCreationTask::class;
389
        }
390
391
        if ($action === self::CREATE_BOT) {
392
            if (!$canBotCreate) {
393
                throw new AccessDeniedException($this->getSecurityManager(), $this->getDomainAccessManager());
394
            }
395
396
            $creationTaskClass = BotCreationTask::class;
397
        }
398
399
        if ($creationTaskClass === null) {
400
            throw new ApplicationLogicException('Cannot determine creation mode');
401
        }
402
403
        $request->setStatus(RequestStatus::JOBQUEUE);
404
        $request->setReserved(null);
405
        $request->save();
406
407
        $parameters = [
408
            'emailText' => $messageBody,
409
            'ccMailingList' => $ccMailingList
410
        ];
411
412
        $creationTask = new JobQueue();
413
        $creationTask->setDomain(1); // FIXME: domains!
414
        $creationTask->setTask($creationTaskClass);
415
        $creationTask->setRequest($request->getId());
416
        $creationTask->setTriggerUserId($currentUser->getId());
417
        $creationTask->setParameters(json_encode($parameters));
418
        $creationTask->setDatabase($db);
419
        $creationTask->save();
420
421
        $creationTaskId = $creationTask->getId();
422
423
        Logger::enqueuedJobQueue($db, $request);
424
        $this->getNotificationHelper()->requestCloseQueued($request, 'Custom, Created');
425
426
        SessionAlert::success("Request {$request->getId()} has been queued for autocreation");
427
428
        // forge this since it is actually a creation.
429
        $this->processWelcome(EmailTemplate::ACTION_CREATED, $creationTaskId);
430
    }
431
}
432