Passed
Pull Request — main (#443)
by MusikAnimal
08:13 queued 04:05
created

EditCounterController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 10
dl 0
loc 15
rs 10
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\Controller;
6
7
use App\Model\EditCounter;
8
use App\Model\GlobalContribs;
9
use App\Model\UserRights;
10
use App\Repository\EditCounterRepository;
11
use App\Repository\EditRepository;
12
use App\Repository\GlobalContribsRepository;
13
use App\Repository\UserRightsRepository;
14
use Symfony\Component\HttpFoundation\Cookie;
15
use Symfony\Component\HttpFoundation\JsonResponse;
16
use Symfony\Component\HttpFoundation\RedirectResponse;
17
use Symfony\Component\HttpFoundation\Response;
18
use Symfony\Component\Routing\Annotation\Route;
19
20
/**
21
 * Class EditCounterController
22
 */
23
class EditCounterController extends XtoolsController
24
{
25
    /**
26
     * Available statistic sections. These can be hand-picked on the index form so that you only get the data you
27
     * want and hence speed up the tool. Keys are the i18n messages (and DOM IDs), values are the action names.
28
     */
29
    private const AVAILABLE_SECTIONS = [
30
        'general-stats' => 'EditCounterGeneralStats',
31
        'namespace-totals' => 'EditCounterNamespaceTotals',
32
        'year-counts' => 'EditCounterYearCounts',
33
        'month-counts' => 'EditCounterMonthCounts',
34
        'timecard' => 'EditCounterTimecard',
35
        'top-edited-pages' => 'TopEditsResultNamespace',
36
        'rights-changes' => 'EditCounterRightsChanges',
37
    ];
38
39
    protected EditCounter $editCounter;
40
    protected UserRights $userRights;
41
42
    /** @var string[] Which sections to show. */
43
    protected array $sections;
44
45
    /**
46
     * @inheritDoc
47
     * @codeCoverageIgnore
48
     */
49
    public function getIndexRoute(): string
50
    {
51
        return 'EditCounter';
52
    }
53
54
    /**
55
     * Causes the tool to redirect to the Simple Edit Counter if the user has too high of an edit count.
56
     * @inheritDoc
57
     * @codeCoverageIgnore
58
     */
59
    public function tooHighEditCountRoute(): string
60
    {
61
        return 'SimpleEditCounterResult';
62
    }
63
64
    /**
65
     * @inheritDoc
66
     * @codeCoverageIgnore
67
     */
68
    public function tooHighEditCountActionAllowlist(): array
69
    {
70
        return ['rightsChanges'];
71
    }
72
73
    /**
74
     * @inheritDoc
75
     * @codeCoverageIgnore
76
     */
77
    public function restrictedApiActions(): array
78
    {
79
        return ['monthCountsApi', 'timecardApi'];
80
    }
81
82
    /**
83
     * Every action in this controller (other than 'index') calls this first.
84
     * If a response is returned, the calling action is expected to return it.
85
     * @param EditCounterRepository $editCounterRepo
86
     * @param UserRightsRepository $userRightsRepo
87
     * @codeCoverageIgnore
88
     */
89
    protected function setUpEditCounter(
90
        EditCounterRepository $editCounterRepo,
91
        UserRightsRepository $userRightsRepo
92
    ): void {
93
        // Whether we're making a subrequest (the view makes a request to another action).
94
        // Subrequests to the same controller do not re-instantiate a new controller, and hence
95
        // this flag would not be set in XtoolsController::__construct(), so we must do it here as well.
96
        $this->isSubRequest = $this->request->get('htmlonly')
97
            || null !== $this->get('request_stack')->getParentRequest();
98
99
        // Return the EditCounter if we already have one.
100
        if (isset($this->editCounter)) {
101
            return;
102
        }
103
104
        // Will redirect to Simple Edit Counter if they have too many edits, as defined self::construct.
105
        $this->validateUser($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

105
        $this->validateUser($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...
106
107
        // Store which sections of the Edit Counter they requested.
108
        $this->sections = $this->getRequestedSections();
109
110
        $this->userRights = new UserRights($userRightsRepo, $this->project, $this->user, $this->i18n);
0 ignored issues
show
Bug introduced by
It seems like $this->user can also be of type null; however, parameter $user of App\Model\UserRights::__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

110
        $this->userRights = new UserRights($userRightsRepo, $this->project, /** @scrutinizer ignore-type */ $this->user, $this->i18n);
Loading history...
111
112
        // Instantiate EditCounter.
113
        $this->editCounter = new EditCounter(
114
            $editCounterRepo,
115
            $this->i18n,
116
            $this->userRights,
117
            $this->project,
118
            $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\EditCounter::__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

118
            /** @scrutinizer ignore-type */ $this->user
Loading history...
119
        );
120
    }
121
122
    /**
123
     * The initial GET request that displays the search form.
124
     * @Route("/ec", name="EditCounter")
125
     * @Route("/ec/index.php", name="EditCounterIndexPhp")
126
     * @Route("/ec/{project}", name="EditCounterProject")
127
     * @return RedirectResponse|Response
128
     */
129
    public function indexAction()
130
    {
131
        if (isset($this->params['project']) && isset($this->params['username'])) {
132
            return $this->redirectFromSections();
133
        }
134
135
        $this->sections = $this->getRequestedSections(true);
136
137
        // Otherwise fall through.
138
        return $this->render('editCounter/index.html.twig', [
139
            'xtPageTitle' => 'tool-editcounter',
140
            'xtSubtitle' => 'tool-editcounter-desc',
141
            'xtPage' => 'EditCounter',
142
            'project' => $this->project,
143
            'sections' => $this->sections,
144
            'availableSections' => $this->getSectionNames(),
145
            'isAllSections' => $this->sections === $this->getSectionNames(),
146
        ]);
147
    }
148
149
    /**
150
     * Get the requested sections either from the URL, cookie, or the defaults (all sections).
151
     * @param bool $useCookies Whether or not to check cookies for the preferred sections.
152
     *   This option should not be true except on the index form.
153
     * @return array|string[]
154
     * @codeCoverageIgnore
155
     */
156
    private function getRequestedSections(bool $useCookies = false): array
157
    {
158
        // Happens from sub-tool index pages, e.g. see self::generalStatsIndexAction().
159
        if (isset($this->sections)) {
160
            return $this->sections;
161
        }
162
163
        // Query param for sections gets priority.
164
        $sectionsQuery = $this->request->get('sections', '');
165
166
        // If not present, try the cookie, and finally the defaults (all sections).
167
        if ($useCookies && '' == $sectionsQuery) {
168
            $sectionsQuery = $this->request->cookies->get('XtoolsEditCounterOptions', '');
169
        }
170
171
        // Either a pipe-separated string or an array.
172
        $sections = is_array($sectionsQuery) ? $sectionsQuery : explode('|', $sectionsQuery);
173
174
        // Filter out any invalid section IDs.
175
        $sections = array_filter($sections, function ($section) {
176
            return in_array($section, $this->getSectionNames());
177
        });
178
179
        // Fallback for when no valid sections were requested or provided by the cookie.
180
        if (0 === count($sections)) {
181
            $sections = $this->getSectionNames();
182
        }
183
184
        return $sections;
185
    }
186
187
    /**
188
     * Get the names of the available sections.
189
     * @return string[]
190
     * @codeCoverageIgnore
191
     */
192
    private function getSectionNames(): array
193
    {
194
        return array_keys(self::AVAILABLE_SECTIONS);
195
    }
196
197
    /**
198
     * Redirect to the appropriate action based on what sections are being requested.
199
     * @return RedirectResponse
200
     * @codeCoverageIgnore
201
     */
202
    private function redirectFromSections(): RedirectResponse
203
    {
204
        $this->sections = $this->getRequestedSections();
205
206
        if (1 === count($this->sections)) {
207
            // Redirect to dedicated route.
208
            $response = $this->redirectToRoute(self::AVAILABLE_SECTIONS[$this->sections[0]], $this->params);
209
        } elseif ($this->sections === $this->getSectionNames()) {
210
            $response = $this->redirectToRoute('EditCounterResult', $this->params);
211
        } else {
212
            // Add sections to the params, which $this->generalUrl() will append to the URL.
213
            $this->params['sections'] = implode('|', $this->sections);
214
215
            // We want a pretty URL, with pipes | instead of the encoded value %7C
216
            $url = str_replace('%7C', '|', $this->generateUrl('EditCounterResult', $this->params));
217
218
            $response = $this->redirect($url);
219
        }
220
221
        // Save the preferred sections in a cookie.
222
        $response->headers->setCookie(
223
            new Cookie('XtoolsEditCounterOptions', implode('|', $this->sections))
224
        );
225
226
        return $response;
227
    }
228
229
    /**
230
     * Display all results.
231
     * @Route(
232
     *     "/ec/{project}/{username}",
233
     *     name="EditCounterResult",
234
     *     requirements={
235
     *         "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
236
     *     }
237
     * )
238
     * @param EditCounterRepository $editCounterRepo
239
     * @param UserRightsRepository $userRightsRepo
240
     * @return Response|RedirectResponse
241
     * @codeCoverageIgnore
242
     */
243
    public function resultAction(EditCounterRepository $editCounterRepo, UserRightsRepository $userRightsRepo)
244
    {
245
        $this->setUpEditCounter($editCounterRepo, $userRightsRepo);
246
247
        if (1 === count($this->sections)) {
248
            // Redirect to dedicated route.
249
            return $this->redirectToRoute(self::AVAILABLE_SECTIONS[$this->sections[0]], $this->params);
250
        }
251
252
        $ret = [
253
            'xtTitle' => $this->user->getUsername() . ' - ' . $this->project->getTitle(),
254
            'xtPage' => 'EditCounter',
255
            'user' => $this->user,
256
            'project' => $this->project,
257
            'ec' => $this->editCounter,
258
            'sections' => $this->sections,
259
            'isAllSections' => $this->sections === $this->getSectionNames(),
260
        ];
261
262
        // Used when querying for global rights changes.
263
        if ($this->isWMF) {
264
            $ret['metaProject'] = $this->projectRepo->getProject('metawiki');
265
        }
266
267
        return $this->getFormattedResponse('editCounter/result', $ret);
268
    }
269
270
    /**
271
     * Display the general statistics section.
272
     * @Route(
273
     *     "/ec-generalstats/{project}/{username}",
274
     *     name="EditCounterGeneralStats",
275
     *     requirements={
276
     *         "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
277
     *     }
278
     * )
279
     * @param EditCounterRepository $editCounterRepo
280
     * @param UserRightsRepository $userRightsRepo
281
     * @param GlobalContribsRepository $globalContribsRepo
282
     * @param EditRepository $editRepo
283
     * @return Response
284
     * @codeCoverageIgnore
285
     */
286
    public function generalStatsAction(
287
        EditCounterRepository $editCounterRepo,
288
        UserRightsRepository $userRightsRepo,
289
        GlobalContribsRepository $globalContribsRepo,
290
        EditRepository $editRepo
291
    ): Response {
292
        $this->setUpEditCounter($editCounterRepo, $userRightsRepo);
293
294
        $globalContribs = new GlobalContribs(
295
            $globalContribsRepo,
296
            $this->pageRepo,
297
            $this->userRepo,
298
            $editRepo,
299
            $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\GlobalContribs::__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

299
            /** @scrutinizer ignore-type */ $this->user
Loading history...
300
        );
301
        $ret = [
302
            'xtTitle' => $this->user->getUsername(),
303
            'xtPage' => 'EditCounter',
304
            'subtool_msg_key' => 'general-stats',
305
            'is_sub_request' => $this->isSubRequest,
306
            'user' => $this->user,
307
            'project' => $this->project,
308
            'ec' => $this->editCounter,
309
            'gc' => $globalContribs,
310
        ];
311
312
        // Output the relevant format template.
313
        return $this->getFormattedResponse('editCounter/general_stats', $ret);
314
    }
315
316
    /**
317
     * Search form for general stats.
318
     * @Route(
319
     *     "/ec-generalstats",
320
     *     name="EditCounterGeneralStatsIndex",
321
     *     requirements={
322
     *         "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
323
     *     }
324
     * )
325
     * @return Response
326
     */
327
    public function generalStatsIndexAction(): Response
328
    {
329
        $this->sections = ['general-stats'];
330
        return $this->indexAction();
331
    }
332
333
    /**
334
     * Display the namespace totals section.
335
     * @Route(
336
     *     "/ec-namespacetotals/{project}/{username}",
337
     *     name="EditCounterNamespaceTotals",
338
     *     requirements={
339
     *         "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
340
     *     }
341
     * )
342
     * @param EditCounterRepository $editCounterRepo
343
     * @param UserRightsRepository $userRightsRepo
344
     * @return Response
345
     * @codeCoverageIgnore
346
     */
347
    public function namespaceTotalsAction(
348
        EditCounterRepository $editCounterRepo,
349
        UserRightsRepository $userRightsRepo
350
    ): Response {
351
        $this->setUpEditCounter($editCounterRepo, $userRightsRepo);
352
353
        $ret = [
354
            'xtTitle' => $this->user->getUsername(),
355
            'xtPage' => 'EditCounter',
356
            'subtool_msg_key' => 'namespace-totals',
357
            'is_sub_request' => $this->isSubRequest,
358
            'user' => $this->user,
359
            'project' => $this->project,
360
            'ec' => $this->editCounter,
361
        ];
362
363
        // Output the relevant format template.
364
        return $this->getFormattedResponse('editCounter/namespace_totals', $ret);
365
    }
366
367
    /**
368
     * Search form for namespace totals.
369
     * @Route("/ec-namespacetotals", name="EditCounterNamespaceTotalsIndex")
370
     * @return Response
371
     */
372
    public function namespaceTotalsIndexAction(): Response
373
    {
374
        $this->sections = ['namespace-totals'];
375
        return $this->indexAction();
376
    }
377
378
    /**
379
     * Display the timecard section.
380
     * @Route(
381
     *     "/ec-timecard/{project}/{username}",
382
     *     name="EditCounterTimecard",
383
     *     requirements={
384
     *         "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
385
     *     }
386
     * )
387
     * @param EditCounterRepository $editCounterRepo
388
     * @param UserRightsRepository $userRightsRepo
389
     * @return Response
390
     * @codeCoverageIgnore
391
     */
392
    public function timecardAction(
393
        EditCounterRepository $editCounterRepo,
394
        UserRightsRepository $userRightsRepo
395
    ): Response {
396
        $this->setUpEditCounter($editCounterRepo, $userRightsRepo);
397
398
        $ret = [
399
            'xtTitle' => $this->user->getUsername(),
400
            'xtPage' => 'EditCounter',
401
            'subtool_msg_key' => 'timecard',
402
            'is_sub_request' => $this->isSubRequest,
403
            'user' => $this->user,
404
            'project' => $this->project,
405
            'ec' => $this->editCounter,
406
            'opted_in_page' => $this->getOptedInPage(),
407
        ];
408
409
        // Output the relevant format template.
410
        return $this->getFormattedResponse('editCounter/timecard', $ret);
411
    }
412
413
    /**
414
     * Search form for timecard.
415
     * @Route("/ec-timecard", name="EditCounterTimecardIndex")
416
     * @return Response
417
     */
418
    public function timecardIndexAction(): Response
419
    {
420
        $this->sections = ['timecard'];
421
        return $this->indexAction();
422
    }
423
424
    /**
425
     * Display the year counts section.
426
     * @Route(
427
     *     "/ec-yearcounts/{project}/{username}",
428
     *     name="EditCounterYearCounts",
429
     *     requirements={
430
     *         "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
431
     *     }
432
     * )
433
     * @param EditCounterRepository $editCounterRepo
434
     * @param UserRightsRepository $userRightsRepo
435
     * @return Response
436
     * @codeCoverageIgnore
437
     */
438
    public function yearCountsAction(
439
        EditCounterRepository $editCounterRepo,
440
        UserRightsRepository $userRightsRepo
441
    ): Response {
442
        $this->setUpEditCounter($editCounterRepo, $userRightsRepo);
443
444
        $ret = [
445
            'xtTitle' => $this->user->getUsername(),
446
            'xtPage' => 'EditCounter',
447
            'subtool_msg_key' => 'year-counts',
448
            'is_sub_request' => $this->isSubRequest,
449
            'user' => $this->user,
450
            'project' => $this->project,
451
            'ec' => $this->editCounter,
452
        ];
453
454
        // Output the relevant format template.
455
        return $this->getFormattedResponse('editCounter/yearcounts', $ret);
456
    }
457
458
    /**
459
     * Search form for year counts.
460
     * @Route("/ec-yearcounts", name="EditCounterYearCountsIndex")
461
     * @return Response
462
     */
463
    public function yearCountsIndexAction(): Response
464
    {
465
        $this->sections = ['year-counts'];
466
        return $this->indexAction();
467
    }
468
469
    /**
470
     * Display the month counts section.
471
     * @Route(
472
     *     "/ec-monthcounts/{project}/{username}",
473
     *     name="EditCounterMonthCounts",
474
     *     requirements={
475
     *         "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
476
     *     }
477
     * )
478
     * @param EditCounterRepository $editCounterRepo
479
     * @param UserRightsRepository $userRightsRepo
480
     * @return Response
481
     * @codeCoverageIgnore
482
     */
483
    public function monthCountsAction(
484
        EditCounterRepository $editCounterRepo,
485
        UserRightsRepository $userRightsRepo
486
    ): Response {
487
        $this->setUpEditCounter($editCounterRepo, $userRightsRepo);
488
489
        $ret = [
490
            'xtTitle' => $this->user->getUsername(),
491
            'xtPage' => 'EditCounter',
492
            'subtool_msg_key' => 'month-counts',
493
            'is_sub_request' => $this->isSubRequest,
494
            'user' => $this->user,
495
            'project' => $this->project,
496
            'ec' => $this->editCounter,
497
            'opted_in_page' => $this->getOptedInPage(),
498
        ];
499
500
        // Output the relevant format template.
501
        return $this->getFormattedResponse('editCounter/monthcounts', $ret);
502
    }
503
504
    /**
505
     * Search form for month counts.
506
     * @Route("/ec-monthcounts", name="EditCounterMonthCountsIndex")
507
     * @return Response
508
     */
509
    public function monthCountsIndexAction(): Response
510
    {
511
        $this->sections = ['month-counts'];
512
        return $this->indexAction();
513
    }
514
515
    /**
516
     * Display the user rights changes section.
517
     * @Route(
518
     *     "/ec-rightschanges/{project}/{username}",
519
     *     name="EditCounterRightsChanges",
520
     *     requirements={
521
     *         "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
522
     *     }
523
     * )
524
     * @param EditCounterRepository $editCounterRepo
525
     * @param UserRightsRepository $userRightsRepo
526
     * @return Response
527
     * @codeCoverageIgnore
528
     */
529
    public function rightsChangesAction(
530
        EditCounterRepository $editCounterRepo,
531
        UserRightsRepository $userRightsRepo
532
    ): Response {
533
        $this->setUpEditCounter($editCounterRepo, $userRightsRepo);
534
535
        $ret = [
536
            'xtTitle' => $this->user->getUsername(),
537
            'xtPage' => 'EditCounter',
538
            'is_sub_request' => $this->isSubRequest,
539
            'user' => $this->user,
540
            'project' => $this->project,
541
            'ec' => $this->editCounter,
542
        ];
543
544
        if ($this->isWMF) {
545
            $ret['metaProject'] = $this->projectRepo->getProject('metawiki');
546
        }
547
548
        // Output the relevant format template.
549
        return $this->getFormattedResponse('editCounter/rights_changes', $ret);
550
    }
551
552
    /**
553
     * Search form for rights changes.
554
     * @Route("/ec-rightschanges", name="EditCounterRightsChangesIndex")
555
     * @return Response
556
     */
557
    public function rightsChangesIndexAction(): Response
558
    {
559
        $this->sections = ['rights-changes'];
560
        return $this->indexAction();
561
    }
562
563
    /************************ API endpoints ************************/
564
565
    /**
566
     * Get various log counts for the user as JSON.
567
     * @Route(
568
     *     "/api/user/log_counts/{project}/{username}",
569
     *     name="UserApiLogCounts",
570
     *     requirements={
571
     *         "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
572
     *     }
573
     * )
574
     * @param EditCounterRepository $editCounterRepo
575
     * @param UserRightsRepository $userRightsRepo
576
     * @return JsonResponse
577
     * @codeCoverageIgnore
578
     */
579
    public function logCountsApiAction(
580
        EditCounterRepository $editCounterRepo,
581
        UserRightsRepository $userRightsRepo
582
    ): JsonResponse {
583
        $this->setUpEditCounter($editCounterRepo, $userRightsRepo);
584
585
        return $this->getFormattedApiResponse([
586
            'log_counts' => $this->editCounter->getLogCounts(),
587
        ]);
588
    }
589
590
    /**
591
     * Get the namespace totals for the user as JSON.
592
     * @Route(
593
     *     "/api/user/namespace_totals/{project}/{username}",
594
     *     name="UserApiNamespaceTotals",
595
     *     requirements={
596
     *         "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
597
     *     }
598
     * )
599
     * @param EditCounterRepository $editCounterRepo
600
     * @param UserRightsRepository $userRightsRepo
601
     * @return JsonResponse
602
     * @codeCoverageIgnore
603
     */
604
    public function namespaceTotalsApiAction(
605
        EditCounterRepository $editCounterRepo,
606
        UserRightsRepository $userRightsRepo
607
    ): JsonResponse {
608
        $this->setUpEditCounter($editCounterRepo, $userRightsRepo);
609
610
        return $this->getFormattedApiResponse([
611
            'namespace_totals' => (object)$this->editCounter->namespaceTotals(),
612
        ]);
613
    }
614
615
    /**
616
     * Get the month counts for the user as JSON.
617
     * @Route(
618
     *     "/api/user/month_counts/{project}/{username}",
619
     *     name="UserApiMonthCounts",
620
     *     requirements={
621
     *         "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
622
     *     }
623
     * )
624
     * @param EditCounterRepository $editCounterRepo
625
     * @param UserRightsRepository $userRightsRepo
626
     * @return JsonResponse
627
     * @codeCoverageIgnore
628
     */
629
    public function monthCountsApiAction(
630
        EditCounterRepository $editCounterRepo,
631
        UserRightsRepository $userRightsRepo
632
    ): JsonResponse {
633
        $this->setUpEditCounter($editCounterRepo, $userRightsRepo);
634
635
        $ret = $this->editCounter->monthCounts();
636
637
        // Remove labels that are only needed by Twig views, and not consumers of the API.
638
        unset($ret['yearLabels']);
639
        unset($ret['monthLabels']);
640
641
        return $this->getFormattedApiResponse($ret);
642
    }
643
644
    /**
645
     * Get the timecard data as JSON.
646
     * @Route(
647
     *     "/api/user/timecard/{project}/{username}",
648
     *     name="UserApiTimeCard",
649
     *     requirements={
650
     *         "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
651
     *     }
652
     * )
653
     * @param EditCounterRepository $editCounterRepo
654
     * @param UserRightsRepository $userRightsRepo
655
     * @return JsonResponse
656
     * @codeCoverageIgnore
657
     */
658
    public function timecardApiAction(
659
        EditCounterRepository $editCounterRepo,
660
        UserRightsRepository $userRightsRepo
661
    ): JsonResponse {
662
        $this->setUpEditCounter($editCounterRepo, $userRightsRepo);
663
664
        return $this->getFormattedApiResponse([
665
            'timecard' => $this->editCounter->timeCard(),
666
        ]);
667
    }
668
}
669