Passed
Push — master ( dcfba0...0e0426 )
by MusikAnimal
04:50
created

nonAutomatedEditsApiAction()   B

Complexity

Conditions 3
Paths 2

Size

Total Lines 41
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 21
nc 2
nop 5
dl 0
loc 41
rs 8.8571
c 0
b 0
f 0
ccs 0
cts 0
cp 0
crap 12
1
<?php
2
/**
3
 * This file contains only the AutomatedEditsController class.
4
 */
5
6
namespace AppBundle\Controller;
7
8
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
9
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
10
use Symfony\Component\HttpFoundation\Request;
11
use Symfony\Component\HttpFoundation\Response;
12
use Symfony\Component\HttpFoundation\RedirectResponse;
13
use Symfony\Component\HttpFoundation\JsonResponse;
14
use Xtools\AutoEdits;
15
use Xtools\AutoEditsRepository;
16
use Xtools\Project;
17
use Xtools\ProjectRepository;
18
use Xtools\User;
19
use Xtools\UserRepository;
20
use Xtools\Edit;
21
22
/**
23
 * This controller serves the AutomatedEdits tool.
24
 */
25
class AutomatedEditsController extends XtoolsController
26
{
27
    /** @var AutoEdits The AutoEdits instance. */
28
    protected $autoEdits;
29
30
    /** @var Project The project. */
31
    protected $project;
32
33
    /** @var User The user. */
34
    protected $user;
35
36
    /** @var string The start date. */
37
    protected $start;
38
39
    /** @var string The end date. */
40
    protected $end;
41
42
    /** @var int|string The namespace ID or 'all' for all namespaces. */
43
    protected $namespace;
44
45
    /** @var bool Whether or not there is a start date. */
46
    protected $hasStartDate;
47
48
    /** @var bool Whether or not this is a subrequest. */
49
    protected $isSubRequest;
50
51
    /** @var array Data that is passed to the view. */
52
    private $output;
53
54
    /**
55
     * Get the tool's shortname.
56
     * @return string
57
     * @codeCoverageIgnore
58
     */
59
    public function getToolShortname()
60
    {
61
        return 'autoedits';
62
    }
63
64
    /**
65
     * Display the search form.
66
     * @Route("/autoedits", name="autoedits")
67
     * @Route("/autoedits/", name="autoeditsSlash")
68
     * @Route("/automatededits", name="autoeditsLong")
69
     * @Route("/automatededits/", name="autoeditsLongSlash")
70
     * @Route("/autoedits/index.php", name="autoeditsIndexPhp")
71
     * @Route("/automatededits/index.php", name="autoeditsLongIndexPhp")
72
     * @Route("/autoedits/{project}", name="autoeditsProject")
73
     * @param Request $request The HTTP request.
74
     * @return Response
75
     */
76 1
    public function indexAction(Request $request)
77
    {
78 1
        $params = $this->parseQueryParams($request);
79
80
        // Redirect if at minimum project and username are provided.
81 1
        if (isset($params['project']) && isset($params['username'])) {
82
            return $this->redirectToRoute('AutoEditsResult', $params);
83
        }
84
85
        // Convert the given project (or default project) into a Project instance.
86 1
        $params['project'] = $this->getProjectFromQuery($params);
87
88 1
        return $this->render('autoEdits/index.html.twig', array_merge([
89 1
            'xtPageTitle' => 'tool-autoedits',
90
            'xtSubtitle' => 'tool-autoedits-desc',
91
            'xtPage' => 'autoedits',
92
93
            // Defaults that will get overriden if in $params.
94
            'namespace' => 0,
95
            'start' => '',
96
            'end' => '',
97 1
        ], $params));
98
    }
99
100
    /**
101
     * Set defaults, and instantiate the AutoEdits model. This is called at
102
     * the top of every view action.
103
     * @param Request $request The HTTP request.
104
     * @param int|string $namespace
105
     * @param null|string $start
106
     * @param null|string $end
107
     * @param int $offset
108
     * @codeCoverageIgnore
109
     */
110
    private function setupAutoEdits(Request $request, $namespace, $start, $end, $offset = 0)
111
    {
112
        // Will redirect back to index if the user has too high of an edit count.
113
        $ret = $this->validateProjectAndUser($request, 'autoedits');
114
        if ($ret instanceof RedirectResponse) {
0 ignored issues
show
introduced by
The condition $ret instanceof Symfony\...dation\RedirectResponse can never be false since $ret is always a sub-type of Symfony\Component\HttpFoundation\RedirectResponse.
Loading history...
115
            return $ret;
116
        } else {
117
            list($this->project, $this->user) = $ret;
118
        }
119
120
        // 'false' means the dates are optional and returned as 'false' if empty.
121
        list($this->start, $this->end) = $this->getUTCFromDateParams($start, $end, false);
122
123
        // We'll want to conditionally show some things in the view if there is a start date.
124
        $this->hasStartDate = $this->start > 0;
125
126
        // Format dates as needed by User model, if the date is present.
127
        if ($this->start !== false) {
128
            $this->start = date('Y-m-d', $this->start);
129
        }
130
        if ($this->end !== false) {
131
            $this->end = date('Y-m-d', $this->end);
132
        }
133
134
        // Normalize default namespace.
135
        if ($namespace == '') {
136
            $this->namespace = 0;
137
        } else {
138
            $this->namespace = $namespace;
139
        }
140
141
        // Check query param for the tool name.
142
        $tool = $request->query->get('tool', null);
143
144
        $this->autoEdits = new AutoEdits(
145
            $this->project,
146
            $this->user,
147
            $this->namespace,
148
            $this->start,
149
            $this->end,
150
            $tool,
151
            $offset
152
        );
153
        $autoEditsRepo = new AutoEditsRepository();
154
        $autoEditsRepo->setContainer($this->container);
155
        $this->autoEdits->setRepository($autoEditsRepo);
156
157
        $this->isSubRequest = $request->get('htmlonly')
158
            || $this->get('request_stack')->getParentRequest() !== null;
159
160
        $this->output = [
161
            'xtPage' => 'autoedits',
162
            'xtTitle' => $this->user->getUsername(),
163
            'project' => $this->project,
164
            'user' => $this->user,
165
            'ae' => $this->autoEdits,
166
            'hasStartDate' => $this->hasStartDate,
167
            'is_sub_request' => $this->isSubRequest,
168
        ];
169
    }
170
171
    /**
172
     * Display the results.
173
     * @Route(
174
     *     "/autoedits/{project}/{username}/{namespace}/{start}/{end}", name="AutoEditsResult",
175
     *     requirements={
176
     *         "namespace" = "|all|\d+",
177
     *         "start" = "|\d{4}-\d{2}-\d{2}",
178
     *         "end" = "|\d{4}-\d{2}-\d{2}",
179
     *         "namespace" = "|all|\d+"
180
     *     }
181
     * )
182
     * @param Request $request The HTTP request.
183
     * @param int|string $namespace
184
     * @param null|string $start
185
     * @param null|string $end
186
     * @return RedirectResponse|Response
187
     * @codeCoverageIgnore
188
     */
189
    public function resultAction(Request $request, $namespace = 0, $start = null, $end = null)
190
    {
191
        // Will redirect back to index if the user has too high of an edit count.
192
        $ret = $this->setupAutoEdits($request, $namespace, $start, $end);
193
        if ($ret instanceof RedirectResponse) {
0 ignored issues
show
introduced by
The condition $ret instanceof Symfony\...dation\RedirectResponse can never be false since $ret is always a sub-type of Symfony\Component\HttpFoundation\RedirectResponse.
Loading history...
194
            return $ret;
195
        }
196
197
        // Render the view with all variables set.
198
        return $this->render('autoEdits/result.html.twig', $this->output);
199
    }
200
201
    /**
202
     * Get non-automated edits for the given user.
203
     * @Route(
204
     *   "/nonautoedits-contributions/{project}/{username}/{namespace}/{start}/{end}/{offset}",
205
     *   name="NonAutoEditsContributionsResult",
206
     *   requirements={
207
     *       "namespace" = "|all|\d+",
208
     *       "start" = "|\d{4}-\d{2}-\d{2}",
209
     *       "end" = "|\d{4}-\d{2}-\d{2}",
210
     *       "offset" = "\d*"
211
     *   }
212
     * )
213
     * @param Request $request The HTTP request.
214
     * @param int|string $namespace ID of the namespace, or 'all' for all namespaces.
215
     * @param string $start In the format YYYY-MM-DD.
216
     * @param string $end In the format YYYY-MM-DD.
217
     * @param int $offset For pagination, offset results by N edits.
218
     * @return Response|RedirectResponse
219
     * @codeCoverageIgnore
220
     */
221
    public function nonAutomatedEditsAction(
222
        Request $request,
223
        $namespace = 0,
224
        $start = '',
225
        $end = '',
226
        $offset = 0
227
    ) {
228
        // Will redirect back to index if the user has too high of an edit count.
229
        $ret = $this->setupAutoEdits($request, $namespace, $start, $end, $offset);
230
        if ($ret instanceof RedirectResponse) {
0 ignored issues
show
introduced by
The condition $ret instanceof Symfony\...dation\RedirectResponse can never be false since $ret is always a sub-type of Symfony\Component\HttpFoundation\RedirectResponse.
Loading history...
231
            return $ret;
232
        }
233
234
        return $this->render('autoedits/nonautomated_edits.html.twig', $this->output);
235
    }
236
237
    /**
238
     * Get automated edits for the given user using the given tool.
239
     * @Route(
240
     *   "/autoedits-contributions/{project}/{username}/{namespace}/{start}/{end}/{offset}",
241
     *   name="AutoEditsContributionsResult",
242
     *   requirements={
243
     *       "namespace" = "|all|\d+",
244
     *       "start" = "|\d{4}-\d{2}-\d{2}",
245
     *       "end" = "|\d{4}-\d{2}-\d{2}",
246
     *       "offset" = "\d*"
247
     *   }
248
     * )
249
     * @param Request $request The HTTP request.
250
     * @param int|string $namespace ID of the namespace, or 'all' for all namespaces
251
     * @param string $start In the format YYYY-MM-DD
252
     * @param string $end In the format YYYY-MM-DD
253
     * @param int $offset For pagination, offset results by N edits
254
     * @return Response|RedirectResponse
255
     * @codeCoverageIgnore
256
     */
257
    public function automatedEditsAction(
258
        Request $request,
259
        $namespace = 0,
260
        $start = '',
261
        $end = '',
262
        $offset = 0
263
    ) {
264
        // Will redirect back to index if the user has too high of an edit count.
265
        $ret = $this->setupAutoEdits($request, $namespace, $start, $end, $offset);
266
        if ($ret instanceof RedirectResponse) {
0 ignored issues
show
introduced by
The condition $ret instanceof Symfony\...dation\RedirectResponse can never be false since $ret is always a sub-type of Symfony\Component\HttpFoundation\RedirectResponse.
Loading history...
267
            return $ret;
268
        }
269
270
        return $this->render('autoedits/automated_edits.html.twig', $this->output);
271
    }
272
273
    /************************ API endpoints ************************/
274
275
    /**
276
     * Get a list of the automated tools and their regex/tags/etc.
277
     * @Route("/api/user/automated_tools/{project}")
278
     * @param string $project The project domain or database name.
279
     * @return JsonResponse
280
     * @codeCoverageIgnore
281
     */
282
    public function automatedToolsApiAction($project)
283
    {
284
        $this->recordApiUsage('user/automated_tools');
285
        $projectData = $this->validateProject($project);
286
287
        if ($projectData instanceof RedirectResponse) {
0 ignored issues
show
introduced by
The condition $projectData instanceof ...dation\RedirectResponse can never be true since $projectData is never a sub-type of Symfony\Component\HttpFoundation\RedirectResponse.
Loading history...
288
            return new JsonResponse(
289
                [
290
                    'error' => "$project is not a valid project",
291
                ],
292
                Response::HTTP_NOT_FOUND
293
            );
294
        }
295
296
        $aeh = $this->container->get('app.automated_edits_helper');
297
        return new JsonResponse($aeh->getTools($projectData));
298
    }
299
300
    /**
301
     * Count the number of automated edits the given user has made.
302
     * @Route(
303
     *   "/api/user/automated_editcount/{project}/{username}/{namespace}/{start}/{end}/{tools}",
304
     *   requirements={
305
     *       "namespace" = "|all|\d+",
306
     *       "start" = "|\d{4}-\d{2}-\d{2}",
307
     *       "end" = "|\d{4}-\d{2}-\d{2}"
308
     *   }
309
     * )
310
     * @param Request $request The HTTP request.
311
     * @param int|string $namespace ID of the namespace, or 'all' for all namespaces
312
     * @param string $start In the format YYYY-MM-DD
313
     * @param string $end In the format YYYY-MM-DD
314
     * @param string $tools Non-blank to show which tools were used and how many times.
315
     * @return Response
316
     * @codeCoverageIgnore
317
     */
318
    public function automatedEditCountApiAction(
319
        Request $request,
320
        $namespace = 'all',
321
        $start = '',
322
        $end = '',
323
        $tools = ''
324
    ) {
325
        $this->recordApiUsage('user/automated_editcount');
326
327
        $ret = $this->setupAutoEdits($request, $namespace, $start, $end);
328
        if ($ret instanceof RedirectResponse) {
0 ignored issues
show
introduced by
The condition $ret instanceof Symfony\...dation\RedirectResponse can never be false since $ret is always a sub-type of Symfony\Component\HttpFoundation\RedirectResponse.
Loading history...
329
            // FIXME: Refactor JSON errors/responses, use Intuition as a service.
330
            return new JsonResponse(
331
                [
332
                    'error' => $this->getFlashMessage(),
333
                ],
334
                Response::HTTP_NOT_FOUND
335
            );
336
        }
337
338
        $res = $this->getJsonData();
339
        $res['total_editcount'] = $this->autoEdits->getEditCount();
340
341
        $response = new JsonResponse();
342
        $response->setEncodingOptions(JSON_NUMERIC_CHECK);
343
344
        if ($tools != '') {
345
            $tools = $this->autoEdits->getToolCounts();
346
            $res['automated_tools'] = $tools;
347
        }
348
349
        $res['automated_editcount'] = $this->autoEdits->getAutomatedCount();
350
        $res['nonautomated_editcount'] = $res['total_editcount'] - $res['automated_editcount'];
351
352
        $response->setData($res);
353
        return $response;
354
    }
355
356
    /**
357
     * Get non-automated edits for the given user.
358
     * @Route(
359
     *   "/api/user/nonautomated_edits/{project}/{username}/{namespace}/{start}/{end}/{offset}",
360
     *   requirements={
361
     *       "namespace" = "|all|\d+",
362
     *       "start" = "|\d{4}-\d{2}-\d{2}",
363
     *       "end" = "|\d{4}-\d{2}-\d{2}",
364
     *       "offset" = "\d*"
365
     *   }
366
     * )
367
     * @param Request $request The HTTP request.
368
     * @param int|string $namespace ID of the namespace, or 'all' for all namespaces
369
     * @param string $start In the format YYYY-MM-DD
370
     * @param string $end In the format YYYY-MM-DD
371
     * @param int $offset For pagination, offset results by N edits
372
     * @return Response
373
     * @codeCoverageIgnore
374
     */
375
    public function nonAutomatedEditsApiAction(
376
        Request $request,
377
        $namespace = 0,
378
        $start = '',
379
        $end = '',
380
        $offset = 0
381
    ) {
382
        $this->recordApiUsage('user/nonautomated_edits');
383
384
        $ret = $this->setupAutoEdits($request, $namespace, $start, $end, $offset);
385
        if ($ret instanceof RedirectResponse) {
0 ignored issues
show
introduced by
The condition $ret instanceof Symfony\...dation\RedirectResponse can never be false since $ret is always a sub-type of Symfony\Component\HttpFoundation\RedirectResponse.
Loading history...
386
            // FIXME: Refactor JSON errors/responses, use Intuition as a service.
387
            return new JsonResponse(
388
                [
389
                    'error' => $this->getFlashMessage(),
390
                ],
391
                Response::HTTP_INTERNAL_SERVER_ERROR
392
            );
393
        }
394
395
        $ret = $this->getJsonData();
396
        $ret['nonautomated_edits'] = $this->autoEdits->getNonAutomatedEdits(true);
397
398
        $namespaces = $this->project->getNamespaces();
399
400
        $ret['nonautomated_edits'] = array_map(function ($rev) use ($namespaces) {
401
            $pageTitle = $rev['page_title'];
402
            if ((int)$rev['page_namespace'] === 0) {
403
                $fullPageTitle = $pageTitle;
404
            } else {
405
                $fullPageTitle = $namespaces[$rev['page_namespace']].":$pageTitle";
406
            }
407
408
            return array_merge(['full_page_title' => $fullPageTitle], $rev);
409
        }, $ret['nonautomated_edits']);
410
411
        $response = new JsonResponse();
412
        $response->setEncodingOptions(JSON_NUMERIC_CHECK);
413
414
        $response->setData($ret);
415
        return $response;
416
    }
417
418
    /**
419
     * Get (semi-)automated edits for the given user, optionally using the given tool.
420
     * @Route(
421
     *   "/api/user/automated_edits/{project}/{username}/{namespace}/{start}/{end}/{offset}",
422
     *   requirements={
423
     *       "namespace" = "|all|\d+",
424
     *       "start" = "|\d{4}-\d{2}-\d{2}",
425
     *       "end" = "|\d{4}-\d{2}-\d{2}",
426
     *       "offset" = "\d*"
427
     *   }
428
     * )
429
     * @param Request $request The HTTP request.
430
     * @param int|string $namespace ID of the namespace, or 'all' for all namespaces
431
     * @param string $start In the format YYYY-MM-DD
432
     * @param string $end In the format YYYY-MM-DD
433
     * @param int $offset For pagination, offset results by N edits
434
     * @return Response
435
     * @codeCoverageIgnore
436
     */
437
    public function automatedEditsApiAction(
438
        Request $request,
439
        $namespace = 0,
440
        $start = '',
441
        $end = '',
442
        $offset = 0
443
    ) {
444
        $this->recordApiUsage('user/automated_edits');
445
446
        $ret = $this->setupAutoEdits($request, $namespace, $start, $end, $offset);
447
        if ($ret instanceof RedirectResponse) {
0 ignored issues
show
introduced by
The condition $ret instanceof Symfony\...dation\RedirectResponse can never be false since $ret is always a sub-type of Symfony\Component\HttpFoundation\RedirectResponse.
Loading history...
448
            // FIXME: Refactor JSON errors/responses, use Intuition as a service.
449
            return new JsonResponse(
450
                [
451
                    'error' => $this->getFlashMessage(),
452
                ],
453
                Response::HTTP_INTERNAL_SERVER_ERROR
454
            );
455
        }
456
457
        $ret = $this->getJsonData();
458
        $ret['nonautomated_edits'] = $this->autoEdits->getAutomatedEdits(true);
459
460
        $namespaces = $this->project->getNamespaces();
461
462
        $ret['nonautomated_edits'] = array_map(function ($rev) use ($namespaces) {
463
            $pageTitle = $rev['page_title'];
464
            if ((int)$rev['page_namespace'] === 0) {
465
                $fullPageTitle = $pageTitle;
466
            } else {
467
                $fullPageTitle = $namespaces[$rev['page_namespace']].":$pageTitle";
468
            }
469
470
            return array_merge(['full_page_title' => $fullPageTitle], $rev);
471
        }, $ret['nonautomated_edits']);
472
473
        $response = new JsonResponse();
474
        $response->setEncodingOptions(JSON_NUMERIC_CHECK);
475
476
        $response->setData($ret);
477
        return $response;
478
    }
479
480
    /**
481
     * Get data that will be used in API responses.
482
     * @return array
483
     * @codeCoverageIgnore
484
     */
485
    private function getJsonData()
486
    {
487
        $ret = [
488
            'project' => $this->project->getDomain(),
489
            'username' => $this->user->getUsername(),
490
        ];
491
492
        foreach (['namespace', 'start', 'end', 'offset'] as $param) {
493
            if (isset($this->{$param}) && $this->{$param} != '') {
494
                $ret[$param] = $this->{$param};
495
            }
496
        }
497
498
        return $ret;
499
    }
500
}
501