Passed
Push — master ( 35c320...7deb12 )
by MusikAnimal
04:42
created

AutomatedEditsController::getToolShortname()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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