Passed
Push — master ( 07416b...441bb4 )
by MusikAnimal
04:55
created

EditCounterController::redirectFromSections()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 11
ccs 0
cts 6
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains only the EditCounterController class.
4
 */
5
6
namespace AppBundle\Controller;
7
8
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
9
use Symfony\Component\DependencyInjection\ContainerInterface;
10
use Symfony\Component\HttpFoundation\JsonResponse;
11
use Symfony\Component\HttpFoundation\RedirectResponse;
12
use Symfony\Component\HttpFoundation\RequestStack;
13
use Symfony\Component\HttpFoundation\Response;
14
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
15
use Xtools\EditCounter;
16
use Xtools\EditCounterRepository;
17
use Xtools\ProjectRepository;
18
19
/**
20
 * Class EditCounterController
21
 */
22
class EditCounterController extends XtoolsController
23
{
24
    /**
25
     * Available statistic sections. These can be hand-picked on the index form so that you only get the data you
26
     * want and hence speed up the tool. Keys are the i18n messages (and DOM IDs), values are the action names.
27
     */
28
    const AVAILABLE_SECTIONS = [
29
        'general-stats' => 'EditCounterGeneralStats',
30
        'namespace-totals' => 'EditCounterNamespaceTotals',
31
        'year-counts' => 'EditCounterYearCounts',
32
        'month-counts' => 'EditCounterMonthCounts',
33
        'timecard' => 'EditCounterRightsChanges',
34
        'top-edited-pages' => 'TopEditsResult',
35
        'rights-changes' => 'EditCounterRightsChanges',
36
        'latest-global-edits' => 'EditCounterLatestGlobalContribs',
37
    ];
38
39
    /** @var EditCounter The edit-counter, that does all the work. */
40
    protected $editCounter;
41
42
    /** @var string[] Which sections to show. */
43
    protected $sections;
44
45
    /**
46
     * Get the name of the tool's index route. This is also the name of the associated model.
47
     * @return string
48
     * @codeCoverageIgnore
49
     */
50
    public function getIndexRoute()
51
    {
52
        return 'EditCounter';
53
    }
54
55
    /**
56
     * EditCounterController constructor.
57
     * @param RequestStack $requestStack
58
     * @param ContainerInterface $container
59
     */
60 1
    public function __construct(RequestStack $requestStack, ContainerInterface $container)
61
    {
62
        // Causes the tool to redirect to the Simple Edit Counter if the user has too high of an edit count.
63 1
        $this->tooHighEditCountAction = 'SimpleEditCounterResult';
64
65
        // The rightsChanges action is exempt from the edit count limitation.
66 1
        $this->tooHighEditCountActionBlacklist = ['rightsChanges'];
67
68 1
        parent::__construct($requestStack, $container);
69
70
        // Now that we have the request object, parse out the requested sections from the URL.
71
        // This could be a pipe-separated string, or an array.
72 1
        $sectionsQuery = $this->request->get('sections', array_keys(self::AVAILABLE_SECTIONS));
73 1
        $this->sections = is_array($sectionsQuery) ? $sectionsQuery : explode('|', $sectionsQuery);
74 1
    }
75
76
    /**
77
     * Every action in this controller (other than 'index') calls this first.
78
     * If a response is returned, the calling action is expected to return it.
79
     * @param string $key API key, as given in the request. Omit this for actions
80
     *   that are public (only /api/ec actions should pass this in).
81
     * @return RedirectResponse|null
82
     * @throws AccessDeniedException If attempting to access internal endpoint.
83
     * @codeCoverageIgnore
84
     */
85
    protected function setUpEditCounter($key = null)
86
    {
87
        // Whether we're making a subrequest (the view makes a request to another action).
88
        // Subrequests to the same controller do not re-instantiate a new controller, and hence
89
        // this flag would not be set in XtoolsController::__construct(), so we must do it here as well.
90
        $this->isSubRequest = $this->request->get('htmlonly')
91
            || $this->get('request_stack')->getParentRequest() !== null;
92
93
        // Return the EditCounter if we already have one.
94
        if ($this->editCounter instanceof EditCounter) {
0 ignored issues
show
introduced by
$this->editCounter is always a sub-type of Xtools\EditCounter. If $this->editCounter can have other possible types, add them to src/AppBundle/Controller/EditCounterController.php:39.
Loading history...
95
            return null;
96
        }
97
98
        // Validate key if attempted to make internal API request.
99
        if ($key && (string)$key !== (string)$this->container->getParameter('secret')) {
100
            throw $this->createAccessDeniedException('This endpoint is for internal use only.');
101
        }
102
103
        // Will redirect to Simple Edit Counter if they have too many edits, as defined self::construct.
104
        $this->validateUser($this->user->getUsername());
105
106
        // Instantiate EditCounter.
107
        $editCounterRepo = new EditCounterRepository();
108
        $editCounterRepo->setContainer($this->container);
109
        $this->editCounter = new EditCounter(
110
            $this->project,
111
            $this->user,
112
            $this->container->get('app.i18n_helper')
113
        );
114
        $this->editCounter->setRepository($editCounterRepo);
115
    }
116
117
    /**
118
     * The initial GET request that displays the search form.
119
     * @Route("/ec", name="EditCounter")
120
     * @Route("/ec/", name="EditCounterSlash")
121
     * @Route("/ec/index.php", name="EditCounterIndexPhp")
122
     * @Route("/ec/{project}", name="EditCounterProject")
123
     * @return RedirectResponse|Response
124
     */
125 1
    public function indexAction()
126
    {
127 1
        if (isset($this->params['project']) && isset($this->params['username'])) {
128
            return $this->redirectFromSections();
129
        }
130
131
        // Otherwise fall through.
132 1
        return $this->render('editCounter/index.html.twig', [
133 1
            'xtPageTitle' => 'tool-editcounter',
134 1
            'xtSubtitle' => 'tool-editcounter-desc',
135 1
            'xtPage' => 'editcounter',
136 1
            'project' => $this->project,
137 1
            'sections' => $this->sections,
138 1
            'availableSections' => array_keys(self::AVAILABLE_SECTIONS),
139
        ]);
140
    }
141
142
    /**
143
     * Redirect to the appropriate action based on what sections are being requested.
144
     * @return RedirectResponse
145
     */
146
    private function redirectFromSections()
147
    {
148
        if (count($this->sections) === 1) {
149
            // Redirect to dedicated route.
150
            return $this->redirectToRoute(self::AVAILABLE_SECTIONS[$this->sections[0]], $this->params);
151
        }
152
153
        // Here we want a pretty URL.
154
        $sectionsParam = implode('|', $this->sections);
155
        return $this->redirect(
156
            $this->generateUrl('EditCounterResult', $this->params).'?sections='.$sectionsParam
157
        );
158
    }
159
160
    /**
161
     * Display all results.
162
     * @Route("/ec/{project}/{username}", name="EditCounterResult")
163
     * @return Response
164
     * @codeCoverageIgnore
165
     */
166
    public function resultAction()
167
    {
168
        $this->setUpEditCounter();
169
170
        $ret = [
171
            'xtTitle' => $this->user->getUsername() . ' - ' . $this->project->getTitle(),
172
            'xtPage' => 'editcounter',
173
            'user' => $this->user,
174
            'project' => $this->project,
175
            'ec' => $this->editCounter,
176
            'sections' => $this->sections,
177
        ];
178
179
        // Used when querying for global rights changes.
180
        if ((bool)$this->container->hasParameter('app.is_labs')) {
181
            $ret['metaProject'] = ProjectRepository::getProject('metawiki', $this->container);
182
        }
183
184
        // Output the relevant format template.
185
        return $this->getFormattedResponse($this->request, 'editCounter/result', $ret);
186
    }
187
188
    /**
189
     * Display the general statistics section.
190
     * @Route("/ec-generalstats/{project}/{username}", name="EditCounterGeneralStats")
191
     * @return Response
192
     * @codeCoverageIgnore
193
     */
194
    public function generalStatsAction()
195
    {
196
        $this->setUpEditCounter();
197
198
        $ret = [
199
            'xtTitle' => $this->user->getUsername(),
200
            'xtPage' => 'editcounter',
201
            'is_sub_request' => $this->isSubRequest,
202
            'user' => $this->user,
203
            'project' => $this->project,
204
            'ec' => $this->editCounter,
205
        ];
206
207
        // Output the relevant format template.
208
        return $this->getFormattedResponse($this->request, 'editCounter/general_stats', $ret);
209
    }
210
211
    /**
212
     * Display the namespace totals section.
213
     * @Route("/ec-namespacetotals/{project}/{username}", name="EditCounterNamespaceTotals")
214
     * @return Response
215
     * @codeCoverageIgnore
216
     */
217
    public function namespaceTotalsAction()
218
    {
219
        $this->setUpEditCounter();
220
221
        $ret = [
222
            'xtTitle' => $this->user->getUsername(),
223
            'xtPage' => 'editcounter',
224
            'is_sub_request' => $this->isSubRequest,
225
            'user' => $this->user,
226
            'project' => $this->project,
227
            'ec' => $this->editCounter,
228
        ];
229
230
        // Output the relevant format template.
231
        return $this->getFormattedResponse($this->request, 'editCounter/namespace_totals', $ret);
232
    }
233
234
    /**
235
     * Display the timecard section.
236
     * @Route("/ec-timecard/{project}/{username}", name="EditCounterTimecard")
237
     * @return Response
238
     * @codeCoverageIgnore
239
     */
240
    public function timecardAction()
241
    {
242
        $this->setUpEditCounter();
243
244
        $optedInPage = $this->project
245
            ->getRepository()
246
            ->getPage($this->project, $this->project->userOptInPage($this->user));
247
248
        $ret = [
249
            'xtTitle' => $this->user->getUsername(),
250
            'xtPage' => 'editcounter',
251
            'is_sub_request' => $this->isSubRequest,
252
            'user' => $this->user,
253
            'project' => $this->project,
254
            'ec' => $this->editCounter,
255
            'opted_in_page' => $optedInPage,
256
        ];
257
258
        // Output the relevant format template.
259
        return $this->getFormattedResponse($this->request, 'editCounter/timecard', $ret);
260
    }
261
262
    /**
263
     * Display the year counts section.
264
     * @Route("/ec-yearcounts/{project}/{username}", name="EditCounterYearCounts")
265
     * @return Response
266
     * @codeCoverageIgnore
267
     */
268
    public function yearCountsAction()
269
    {
270
        $this->setUpEditCounter();
271
272
        $ret = [
273
            'xtTitle' => $this->user->getUsername(),
274
            'xtPage' => 'editcounter',
275
            'is_sub_request' => $this->isSubRequest,
276
            'user' => $this->user,
277
            'project' => $this->project,
278
            'ec' => $this->editCounter,
279
        ];
280
281
        // Output the relevant format template.
282
        return $this->getFormattedResponse($this->request, 'editCounter/yearcounts', $ret);
283
    }
284
285
    /**
286
     * Display the month counts section.
287
     * @Route("/ec-monthcounts/{project}/{username}", name="EditCounterMonthCounts")
288
     * @return Response
289
     * @codeCoverageIgnore
290
     */
291
    public function monthCountsAction()
292
    {
293
        $this->setUpEditCounter();
294
295
        $optedInPage = $this->project
296
            ->getRepository()
297
            ->getPage($this->project, $this->project->userOptInPage($this->user));
298
        $ret = [
299
            'xtTitle' => $this->user->getUsername(),
300
            'xtPage' => 'editcounter',
301
            'is_sub_request' => $this->isSubRequest,
302
            'user' => $this->user,
303
            'project' => $this->project,
304
            'ec' => $this->editCounter,
305
            'opted_in_page' => $optedInPage,
306
        ];
307
308
        // Output the relevant format template.
309
        return $this->getFormattedResponse($this->request, 'editCounter/monthcounts', $ret);
310
    }
311
312
    /**
313
     * Display the user rights changes section.
314
     * @Route("/ec-rightschanges/{project}/{username}", name="EditCounterRightsChanges")
315
     * @return Response
316
     * @codeCoverageIgnore
317
     */
318
    public function rightsChangesAction()
319
    {
320
        $this->setUpEditCounter();
321
322
        $ret = [
323
            'xtTitle' => $this->user->getUsername(),
324
            'xtPage' => 'editcounter',
325
            'is_sub_request' => $this->isSubRequest,
326
            'user' => $this->user,
327
            'project' => $this->project,
328
            'ec' => $this->editCounter,
329
        ];
330
331
        if ((bool)$this->container->hasParameter('app.is_labs')) {
332
            $ret['metaProject'] = ProjectRepository::getProject('metawiki', $this->container);
333
        }
334
335
        // Output the relevant format template.
336
        return $this->getFormattedResponse($this->request, 'editCounter/rights_changes', $ret);
337
    }
338
339
    /**
340
     * Display the latest global edits section.
341
     * @Route(
342
     *     "/ec-latestglobal-contributions/{project}/{username}/{offset}",
343
     *     name="EditCounterLatestGlobalContribs",
344
     *     requirements={"offset" = "|\d*"},
345
     *     defaults={"offset" = 0}
346
     * )
347
     * @Route(
348
     *     "/ec-latestglobal/{project}/{username}/{offset}",
349
     *     name="EditCounterLatestGlobal",
350
     *     requirements={"offset" = "|\d*"},
351
     *     defaults={"offset" = 0}
352
     * ),
353
     * @return Response
354
     * @codeCoverageIgnore
355
     */
356
    public function latestGlobalAction()
357
    {
358
        $this->setUpEditCounter();
359
360
        return $this->render('editCounter/latest_global.html.twig', [
361
            'xtTitle' => $this->user->getUsername(),
362
            'xtPage' => 'editcounter',
363
            'is_sub_request' => $this->isSubRequest,
364
            'user' => $this->user,
365
            'project' => $this->project,
366
            'ec' => $this->editCounter,
367
            'offset' => $this->request->get('offset'),
368
            'pageSize' => $this->request->get('pagesize'),
369
        ]);
370
    }
371
372
373
    /**
374
     * Below are internal API endpoints for the Edit Counter.
375
     * All only respond with JSON and only to requests passing in the value
376
     * of the 'secret' parameter. This should not be used in JavaScript or clientside
377
     * applications, rather only used internally.
378
     */
379
380
    /**
381
     * Get (most) of the general statistics as JSON.
382
     * @Route("/api/ec/pairdata/{project}/{username}/{key}", name="EditCounterApiPairData")
383
     * @param string $key API key.
384
     * @return JsonResponse|RedirectResponse
385
     * @codeCoverageIgnore
386
     */
387
    public function pairDataApiAction($key)
388
    {
389
        $this->setUpEditCounter($key);
390
391
        return new JsonResponse(
392
            $this->editCounter->getPairData(),
393
            Response::HTTP_OK
394
        );
395
    }
396
397
    /**
398
     * Get various log counts for the user as JSON.
399
     * @Route("/api/ec/logcounts/{project}/{username}/{key}", name="EditCounterApiLogCounts")
400
     * @param string $key API key.
401
     * @return JsonResponse|RedirectResponse
402
     * @codeCoverageIgnore
403
     */
404
    public function logCountsApiAction($key)
405
    {
406
        $this->setUpEditCounter($key);
407
408
        return new JsonResponse(
409
            $this->editCounter->getLogCounts(),
410
            Response::HTTP_OK
411
        );
412
    }
413
414
    /**
415
     * Get edit sizes for the user as JSON.
416
     * @Route("/api/ec/editsizes/{project}/{username}/{key}", name="EditCounterApiEditSizes")
417
     * @param string $key API key.
418
     * @return JsonResponse|RedirectResponse
419
     * @codeCoverageIgnore
420
     */
421
    public function editSizesApiAction($key)
422
    {
423
        $this->setUpEditCounter($key);
424
425
        return new JsonResponse(
426
            $this->editCounter->getEditSizeData(),
427
            Response::HTTP_OK
428
        );
429
    }
430
431
    /**
432
     * Get the namespace totals for the user as JSON.
433
     * @Route("/api/ec/namespacetotals/{project}/{username}/{key}", name="EditCounterApiNamespaceTotals")
434
     * @param string $key API key.
435
     * @return Response|RedirectResponse
436
     * @codeCoverageIgnore
437
     */
438
    public function namespaceTotalsApiAction($key)
439
    {
440
        $this->setUpEditCounter($key);
441
442
        return new JsonResponse(
443
            $this->editCounter->namespaceTotals(),
444
            Response::HTTP_OK
445
        );
446
    }
447
448
    /**
449
     * Display or fetch the month counts for the user.
450
     * @Route("/api/ec/monthcounts/{project}/{username}/{key}", name="EditCounterApiMonthCounts")
451
     * @param string $key API key.
452
     * @return Response
453
     * @codeCoverageIgnore
454
     */
455
    public function monthCountsApiAction($key)
456
    {
457
        $this->setUpEditCounter($key);
458
459
        return new JsonResponse(
460
            $this->editCounter->monthCounts(),
461
            Response::HTTP_OK
462
        );
463
    }
464
}
465