Passed
Pull Request — main (#442)
by MusikAnimal
08:40 queued 04:21
created

AutomatedEditsController::tooHighEditCountRoute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\Controller;
6
7
use App\Helper\I18nHelper;
8
use App\Model\AutoEdits;
9
use App\Repository\AutoEditsRepository;
10
use App\Repository\EditRepository;
11
use App\Repository\PageRepository;
12
use App\Repository\ProjectRepository;
13
use App\Repository\UserRepository;
14
use GuzzleHttp\Client;
15
use Psr\Cache\CacheItemPoolInterface;
16
use Psr\Container\ContainerInterface;
17
use Symfony\Component\HttpFoundation\JsonResponse;
18
use Symfony\Component\HttpFoundation\RedirectResponse;
19
use Symfony\Component\HttpFoundation\RequestStack;
20
use Symfony\Component\HttpFoundation\Response;
21
use Symfony\Component\Routing\Annotation\Route;
22
23
/**
24
 * This controller serves the AutomatedEdits tool.
25
 */
26
class AutomatedEditsController extends XtoolsController
27
{
28
    protected AutoEdits $autoEdits;
29
    protected AutoEditsRepository $autoEditsRepo;
30
    protected EditRepository $editRepo;
31
    protected PageRepository $pageRepo;
32
33
    /** @var array Data that is passed to the view. */
34
    private array $output;
35
36
    /**
37
     * Get the name of the tool's index route.
38
     * This is also the name of the associated model.
39
     * @return string
40
     * @codeCoverageIgnore
41
     */
42
    public function getIndexRoute(): string
43
    {
44
        return 'AutoEdits';
45
    }
46
47
    public function __construct(
48
        RequestStack $requestStack,
49
        ContainerInterface $container,
50
        CacheItemPoolInterface $cache,
51
        Client $guzzle,
52
        I18nHelper $i18n,
53
        ProjectRepository $projectRepo,
54
        UserRepository $userRepo,
55
        PageRepository $pageRepo,
56
        AutoEditsRepository $autoEditsRepo,
57
        EditRepository $editRepo
58
    ) {
59
        $this->autoEditsRepo = $autoEditsRepo;
60
        $this->editRepo = $editRepo;
61
        $this->pageRepo = $pageRepo;
62
        parent::__construct($requestStack, $container, $cache, $guzzle, $i18n, $projectRepo, $userRepo, $pageRepo);
63
    }
64
65
    /**
66
     * This causes the tool to redirect back to the index page, with an error,
67
     * if the user has too high of an edit count.
68
     * @return string
69
     */
70
    public function tooHighEditCountRoute(): string
71
    {
72
        return $this->getIndexRoute();
73
    }
74
75
    /**
76
     * Display the search form.
77
     * @Route("/autoedits", name="AutoEdits")
78
     * @Route("/automatededits", name="AutoEditsLong")
79
     * @Route("/autoedits/index.php", name="AutoEditsIndexPhp")
80
     * @Route("/automatededits/index.php", name="AutoEditsLongIndexPhp")
81
     * @Route("/autoedits/{project}", name="AutoEditsProject")
82
     * @return Response
83
     */
84
    public function indexAction(): Response
85
    {
86
        // Redirect if at minimum project and username are provided.
87
        if (isset($this->params['project']) && isset($this->params['username'])) {
88
            // If 'tool' param is given, redirect to corresponding action.
89
            $tool = $this->request->query->get('tool');
90
91
            if ('all' === $tool) {
92
                unset($this->params['tool']);
93
                return $this->redirectToRoute('AutoEditsContributionsResult', $this->params);
94
            } elseif ('' != $tool && 'none' !== $tool) {
95
                $this->params['tool'] = $tool;
96
                return $this->redirectToRoute('AutoEditsContributionsResult', $this->params);
97
            } elseif ('none' === $tool) {
98
                unset($this->params['tool']);
99
            }
100
101
            // Otherwise redirect to the normal result action.
102
            return $this->redirectToRoute('AutoEditsResult', $this->params);
103
        }
104
105
        return $this->render('autoEdits/index.html.twig', array_merge([
106
            'xtPageTitle' => 'tool-autoedits',
107
            'xtSubtitle' => 'tool-autoedits-desc',
108
            'xtPage' => 'AutoEdits',
109
110
            // Defaults that will get overridden if in $this->params.
111
            'username' => '',
112
            'namespace' => 0,
113
            'start' => '',
114
            'end' => '',
115
        ], $this->params, ['project' => $this->project]));
116
    }
117
118
    /**
119
     * Set defaults, and instantiate the AutoEdits model. This is called at the top of every view action.
120
     * @codeCoverageIgnore
121
     */
122
    private function setupAutoEdits(): void
123
    {
124
        $tool = $this->request->query->get('tool', null);
125
        $useSandbox = (bool)$this->request->query->get('usesandbox', false);
126
127
        if ($useSandbox && !$this->request->getSession()->get('logged_in_user')) {
128
            $this->addFlashMessage('danger', 'auto-edits-logged-out');
129
            $useSandbox = false;
130
        }
131
        $this->autoEditsRepo->setUseSandbox($useSandbox);
132
133
        $misconfigured = $this->autoEditsRepo->getInvalidTools($this->project);
134
        $helpLink = "https://w.wiki/ppr";
135
        foreach ($misconfigured as $tool) {
136
            $this->addFlashMessage('warning', 'auto-edits-misconfiguration', [$tool, $helpLink]);
137
        }
138
139
        // Validate tool.
140
        // FIXME: instead of redirecting to index page, show result page listing all tools for that project,
141
        //  clickable to show edits by the user, etc.
142
        if ($tool && !isset($this->autoEditsRepo->getTools($this->project)[$tool])) {
143
            $this->throwXtoolsException(
144
                $this->getIndexRoute(),
145
                'auto-edits-unknown-tool',
146
                [$tool],
147
                'tool'
148
            );
149
        }
150
151
        $this->autoEdits = new AutoEdits(
152
            $this->autoEditsRepo,
153
            $this->editRepo,
154
            $this->pageRepo,
155
            $this->userRepo,
156
            $this->project,
157
            $this->user,
0 ignored issues
show
Bug introduced by
It seems like $this->user can also be of type null; however, parameter $user of App\Model\AutoEdits::__construct() does only seem to accept App\Model\User, 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

157
            /** @scrutinizer ignore-type */ $this->user,
Loading history...
158
            $this->namespace,
159
            $this->start,
160
            $this->end,
161
            $tool,
162
            $this->offset,
163
            $this->limit
164
        );
165
166
        $this->output = [
167
            'xtPage' => 'AutoEdits',
168
            'xtTitle' => $this->user->getUsername(),
0 ignored issues
show
Bug introduced by
The method getUsername() does not exist on null. ( Ignorable by Annotation )

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

168
            'xtTitle' => $this->user->/** @scrutinizer ignore-call */ getUsername(),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
169
            'ae' => $this->autoEdits,
170
            'is_sub_request' => $this->isSubRequest,
171
        ];
172
    }
173
174
    /**
175
     * Display the results.
176
     * @Route(
177
     *     "/autoedits/{project}/{username}/{namespace}/{start}/{end}/{offset}", name="AutoEditsResult",
178
     *     requirements={
179
     *         "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
180
     *         "namespace"="|all|\d+",
181
     *         "start"="|\d{4}-\d{2}-\d{2}",
182
     *         "end"="|\d{4}-\d{2}-\d{2}",
183
     *         "offset"="|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}",
184
     *     },
185
     *     defaults={"namespace"=0, "start"=false, "end"=false, "offset"=false}
186
     * )
187
     * @return Response
188
     * @codeCoverageIgnore
189
     */
190
    public function resultAction(): Response
191
    {
192
        // Will redirect back to index if the user has too high of an edit count.
193
        $this->setupAutoEdits();
194
195
        if (in_array('bot', $this->user->getUserRights($this->project))) {
196
            $this->addFlashMessage('warning', 'auto-edits-bot');
197
        }
198
199
        return $this->getFormattedResponse('autoEdits/result', $this->output);
200
    }
201
202
    /**
203
     * Get non-automated edits for the given user.
204
     * @Route(
205
     *   "/nonautoedits-contributions/{project}/{username}/{namespace}/{start}/{end}/{offset}",
206
     *   name="NonAutoEditsContributionsResult",
207
     *   requirements={
208
     *       "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
209
     *       "namespace"="|all|\d+",
210
     *       "start"="|\d{4}-\d{2}-\d{2}",
211
     *       "end"="|\d{4}-\d{2}-\d{2}",
212
     *       "offset"="|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}",
213
     *   },
214
     *   defaults={"namespace"=0, "start"=false, "end"=false, "offset"=false}
215
     * )
216
     * @return Response|RedirectResponse
217
     * @codeCoverageIgnore
218
     */
219
    public function nonAutomatedEditsAction(): Response
220
    {
221
        $this->setupAutoEdits();
222
223
        return $this->getFormattedResponse('autoEdits/nonautomated_edits', $this->output);
224
    }
225
226
    /**
227
     * Get automated edits for the given user using the given tool.
228
     * @Route(
229
     *   "/autoedits-contributions/{project}/{username}/{namespace}/{start}/{end}/{offset}",
230
     *   name="AutoEditsContributionsResult",
231
     *   requirements={
232
     *       "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
233
     *       "namespace"="|all|\d+",
234
     *       "start"="|\d{4}-\d{2}-\d{2}",
235
     *       "end"="|\d{4}-\d{2}-\d{2}",
236
     *       "offset"="|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}",
237
     *   },
238
     *   defaults={"namespace"=0, "start"=false, "end"=false, "offset"=false}
239
     * )
240
     * @return Response
241
     * @codeCoverageIgnore
242
     */
243
    public function automatedEditsAction(): Response
244
    {
245
        $this->setupAutoEdits();
246
247
        return $this->getFormattedResponse('autoEdits/automated_edits', $this->output);
248
    }
249
250
    /************************ API endpoints ************************/
251
252
    /**
253
     * Get a list of the automated tools and their regex/tags/etc.
254
     * @Route("/api/user/automated_tools/{project}", name="UserApiAutoEditsTools")
255
     * @Route("/api/project/automated_tools/{project}", name="ProjectApiAutoEditsTools")
256
     * @return JsonResponse
257
     * @codeCoverageIgnore
258
     */
259
    public function automatedToolsApiAction(): JsonResponse
260
    {
261
        $this->recordApiUsage('user/automated_tools');
262
        return $this->getFormattedApiResponse($this->autoEditsRepo->getTools($this->project));
263
    }
264
265
    /**
266
     * Count the number of automated edits the given user has made.
267
     * @Route(
268
     *   "/api/user/automated_editcount/{project}/{username}/{namespace}/{start}/{end}/{tools}",
269
     *   name="UserApiAutoEditsCount",
270
     *   requirements={
271
     *       "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
272
     *       "namespace"="|all|\d+",
273
     *       "start"="|\d{4}-\d{2}-\d{2}",
274
     *       "end"="|\d{4}-\d{2}-\d{2}"
275
     *   },
276
     *   defaults={"namespace"="all", "start"=false, "end"=false, "tools"=false}
277
     * )
278
     * @return JsonResponse
279
     * @codeCoverageIgnore
280
     */
281
    public function automatedEditCountApiAction(): JsonResponse
282
    {
283
        $this->recordApiUsage('user/automated_editcount');
284
285
        $this->setupAutoEdits();
286
287
        $ret = [
288
            'total_editcount' => $this->autoEdits->getEditCount(),
289
            'automated_editcount' => $this->autoEdits->getAutomatedCount(),
290
        ];
291
        $ret['nonautomated_editcount'] = $ret['total_editcount'] - $ret['automated_editcount'];
292
293
        if (isset($this->params['tools'])) {
294
            $tools = $this->autoEdits->getToolCounts();
295
            $ret['automated_tools'] = $tools;
296
        }
297
298
        return $this->getFormattedApiResponse($ret);
299
    }
300
301
    /**
302
     * Get non-automated edits for the given user.
303
     * @Route(
304
     *   "/api/user/nonautomated_edits/{project}/{username}/{namespace}/{start}/{end}/{offset}",
305
     *   name="UserApiNonAutoEdits",
306
     *   requirements={
307
     *       "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
308
     *       "namespace"="|all|\d+",
309
     *       "start"="|\d{4}-\d{2}-\d{2}",
310
     *       "end"="|\d{4}-\d{2}-\d{2}",
311
     *       "offset"="|\d{4}-?\d{2}-?\d{2}T?\d{2}:?\d{2}:?\d{2}"
312
     *   },
313
     *   defaults={"namespace"=0, "start"=false, "end"=false, "offset"=false, "limit"=50}
314
     * )
315
     * @return JsonResponse
316
     * @codeCoverageIgnore
317
     */
318
    public function nonAutomatedEditsApiAction(): JsonResponse
319
    {
320
        $this->recordApiUsage('user/nonautomated_edits');
321
322
        $this->setupAutoEdits();
323
324
        $out = $this->addFullPageTitlesAndContinue(
325
            'nonautomated_edits',
326
            [],
327
            $this->autoEdits->getNonAutomatedEdits(true)
328
        );
329
330
        return $this->getFormattedApiResponse($out);
331
    }
332
333
    /**
334
     * Get (semi-)automated edits for the given user, optionally using the given tool.
335
     * @Route(
336
     *   "/api/user/automated_edits/{project}/{username}/{namespace}/{start}/{end}/{offset}",
337
     *   name="UserApiAutoEdits",
338
     *   requirements={
339
     *       "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
340
     *       "namespace"="|all|\d+",
341
     *       "start"="|\d{4}-\d{2}-\d{2}",
342
     *       "end"="|\d{4}-\d{2}-\d{2}",
343
     *       "offset"="|\d{4}-?\d{2}-?\d{2}T?\d{2}:?\d{2}:?\d{2}",
344
     *   },
345
     *   defaults={"namespace"=0, "start"=false, "end"=false, "offset"=false, "limit"=50}
346
     * )
347
     * @return Response
348
     * @codeCoverageIgnore
349
     */
350
    public function automatedEditsApiAction(): Response
351
    {
352
        $this->recordApiUsage('user/automated_edits');
353
354
        $this->setupAutoEdits();
355
356
        $extras = $this->autoEdits->getTool()
357
            ? ['tool' => $this->autoEdits->getTool()]
358
            : [];
359
360
        $out = $this->addFullPageTitlesAndContinue(
361
            'automated_edits',
362
            $extras,
363
            $this->autoEdits->getAutomatedEdits(true)
364
        );
365
366
        return $this->getFormattedApiResponse($out);
367
    }
368
}
369