Passed
Push — master ( 571304...3519d0 )
by MusikAnimal
07:14 queued 19s
created

AdminStatsController::adminStatsApiAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 13
nc 1
nop 1
dl 0
loc 23
rs 9.8333
c 0
b 0
f 0
ccs 0
cts 0
cp 0
crap 2
1
<?php
2
/**
3
 * This file contains the code that powers the AdminStats page of XTools.
4
 */
5
6
declare(strict_types=1);
7
8
namespace AppBundle\Controller;
9
10
use AppBundle\Helper\I18nHelper;
11
use AppBundle\Model\AdminStats;
12
use AppBundle\Model\Project;
13
use AppBundle\Repository\AdminStatsRepository;
14
use AppBundle\Repository\ProjectRepository;
15
use AppBundle\Repository\UserRightsRepository;
16
use Symfony\Component\DependencyInjection\ContainerInterface;
17
use Symfony\Component\HttpFoundation\JsonResponse;
18
use Symfony\Component\HttpFoundation\RequestStack;
19
use Symfony\Component\HttpFoundation\Response;
20
use Symfony\Component\Routing\Annotation\Route;
21
22
/**
23
 * The AdminStatsController serves the search form and results page of the AdminStats tool.
24
 */
25
class AdminStatsController extends XtoolsController
26
{
27
    /** @var AdminStats The admin stats instance that does all the work. */
28
    protected $adminStats;
29
30
    public const DEFAULT_DAYS = 31;
31
    public const MAX_DAYS_UI = 365;
32
    public const MAX_DAYS_API = 31;
33
34
    /**
35
     * Get the name of the tool's index route. This is also the name of the associated model.
36
     * @return string
37
     * @codeCoverageIgnore
38
     */
39
    public function getIndexRoute(): string
40
    {
41
        return 'AdminStats';
42
    }
43
44
    /**
45
     * AdminStatsController constructor.
46
     * @param RequestStack $requestStack
47
     * @param ContainerInterface $container
48
     * @param I18nHelper $i18n
49
     */
50
    public function __construct(RequestStack $requestStack, ContainerInterface $container, I18nHelper $i18n)
51
    {
52
        // Set the max length for the date range. Value is smaller for API requests.
53
        $isApi = '/api/' === substr($requestStack->getCurrentRequest()->getPathInfo(), 0, 5);
54
        $this->maxDays = $isApi ? self::MAX_DAYS_API : self::MAX_DAYS_UI;
55
        $this->defaultDays = self::DEFAULT_DAYS;
56
57
        parent::__construct($requestStack, $container, $i18n);
58
    }
59
60
    /**
61
     * Method for rendering the AdminStats Main Form.
62
     * This method redirects if valid parameters are found, making it a valid form endpoint as well.
63
     * @Route(
64
     *     "/{group}stats", name="AdminStats",
65
     *     requirements={"group"="admin|patroller|steward"},
66
     *     defaults={"group"="admin"}
67
     * )
68
     * @return Response
69
     */
70
    public function indexAction(): Response
71
    {
72
        $this->getAndSetRequestedActions();
73
74
        // Redirect if we have a project.
75
        if (isset($this->params['project'])) {
76
            // We want pretty URLs.
77
            $route = $this->generateUrl('AdminStatsResult', $this->params);
78
            $url = str_replace('%7C', '|', $route);
79
            return $this->redirect($url);
80
        }
81
82
        $adminStatsRepo = new AdminStatsRepository();
83
        $adminStatsRepo->setContainer($this->container);
84
        $actionsConfig = $adminStatsRepo->getConfig($this->project);
85
        $group = $this->params['group'];
86
        $xtPage = lcfirst($group).'Stats';
87
88
        $params = array_merge([
89
            'xtPage' => $xtPage,
90
            'xtPageTitle' => "tool-{$group}stats",
91
            'xtSubtitle' => "tool-{$group}stats-desc",
92
            'actionsConfig' => $actionsConfig,
93
94
            // Defaults that will get overridden if in $params.
95
            'start' => '',
96
            'end' => '',
97
            'group' => 'admin',
98
        ], $this->params);
99
        $params['project'] = $this->normalizeProject($params['group']);
100
101
        $params['isAllActions'] = $params['actions'] === implode('|', $this->getActionNames($params['group']));
102
103
        // Otherwise render form.
104
        return $this->render('adminStats/index.html.twig', $params);
105
    }
106
107
    /**
108
     * Normalize the Project to be Meta if viewing Steward Stats.
109
     * @param string $group
110
     * @return Project
111
     */
112
    private function normalizeProject(string $group): Project
113
    {
114
        if ('meta.wikimedia.org' !== $this->project->getDomain() &&
115
            'steward' === $group &&
116
            $this->getParameter('app.is_labs')
117
        ) {
118
            $this->project = ProjectRepository::getProject('meta.wikimedia.org', $this->container);
119
        }
120
121
        return $this->project;
122
    }
123
124
    /**
125
     * Get the requested actions and set the class property.
126
     * @return string[]
127
     * @codeCoverageIgnore
128
     */
129
    private function getAndSetRequestedActions(): array
130
    {
131
        /** @var string $group The requested 'group'. See keys at admin_stats.yml for possible values. */
132
        $group = $this->params['group'] = $this->params['group'] ?? 'admin';
133
134
        // Query param for sections gets priority.
135
        $actionsQuery = $this->request->get('actions', '');
136
137
        // Either a pipe-separated string or an array.
138
        $actions = is_array($actionsQuery) ? $actionsQuery : explode('|', $actionsQuery);
139
140
        // Filter out any invalid section IDs.
141
        $actions = array_filter($actions, function ($action) use ($group) {
142
            return in_array($action, $this->getActionNames($group));
143
        });
144
145
        // Fallback for when no valid sections were requested.
146
        if (0 === count($actions)) {
147
            $actions = $this->getActionNames($group);
148
        }
149
150
        // Store as pipe-separated string for prettier URLs.
151
        $this->params['actions'] = str_replace('%7C', '|', implode('|', $actions));
152
153
        return $actions;
154
    }
155
156
    /**
157
     * Get the names of the available sections.
158
     * @param string $group Corresponds to the groups specified in admin_stats.yml
159
     * @return string[]
160
     * @codeCoverageIgnore
161
     */
162
    private function getActionNames(string $group): array
163
    {
164
        $actionsConfig = $this->container->getParameter('admin_stats');
165
        return array_keys($actionsConfig[$group]['actions']);
166
    }
167
168
    /**
169
     * Every action in this controller (other than 'index') calls this first.
170
     * @return AdminStats
171
     * @codeCoverageIgnore
172
     */
173
    public function setUpAdminStats(): AdminStats
174
    {
175
        $adminStatsRepo = new AdminStatsRepository();
176
        $adminStatsRepo->setContainer($this->container);
177
        $group = $this->params['group'] ?? 'admin';
178
179
        $this->adminStats = new AdminStats(
180
            $this->normalizeProject($group),
181
            (int)$this->start,
182
            (int)$this->end,
183
            $group ?? 'admin',
184
            $this->getAndSetRequestedActions()
185
        );
186
        $this->adminStats->setRepository($adminStatsRepo);
187
188
        // For testing purposes.
189
        return $this->adminStats;
190
    }
191
192
    /**
193
     * Method for rendering the AdminStats results.
194
     * @Route(
195
     *     "/{group}stats/{project}/{start}/{end}", name="AdminStatsResult",
196
     *     requirements={"start"="|\d{4}-\d{2}-\d{2}", "end"="|\d{4}-\d{2}-\d{2}", "group"="admin|patroller|steward"},
197
     *     defaults={"start"=false, "end"=false, "group"="admin"}
198
     * )
199
     * @param I18nHelper $i18n
200
     * @return Response
201
     * @codeCoverageIgnore
202
     */
203
    public function resultAction(I18nHelper $i18n): Response
204
    {
205
        $this->setUpAdminStats();
206
207
        $this->adminStats->prepareStats();
208
209
        // For the HTML view, we want the localized name of the user groups.
210
        // These are in the 'title' attribute of the icons for each user group.
211
        $userRightsRepo = new UserRightsRepository();
212
        $userRightsRepo->setContainer($this->container);
213
        $rightsNames = $userRightsRepo->getRightsNames($this->project, $i18n->getLang());
214
215
        return $this->getFormattedResponse('adminStats/result', [
216
            'xtPage' => lcfirst($this->params['group']).'Stats',
217
            'xtTitle' => $this->project->getDomain(),
218
            'as' => $this->adminStats,
219
            'rightsNames' => $rightsNames,
220
        ]);
221
    }
222
223
    /************************ API endpoints ************************/
224
225
    /**
226
     * Get users of the project that are capable of making 'admin actions',
227
     * keyed by user name with a list of the relevant user groups as the values.
228
     * @Route("/api/project/admins_groups/{project}", name="ProjectApiAdminsGroups")
229
     * @Route("/api/project/users_groups/{project}/{group}")
230
     * @return JsonResponse
231
     * @codeCoverageIgnore
232
     */
233
    public function adminsGroupsApiAction(): JsonResponse
234
    {
235
        $this->recordApiUsage('project/admins_groups');
236
237
        $this->setUpAdminStats();
238
239
        unset($this->params['actions']);
240
        unset($this->params['start']);
241
        unset($this->params['end']);
242
243
        return $this->getFormattedApiResponse([
244
            'users_and_groups' => $this->adminStats->getUsersAndGroups(),
245
        ]);
246
    }
247
248
    /**
249
     * Same as adminStatsApiAction except it accepts dates.
250
     * @Route(
251
     *     "/api/project/{group}_stats/{project}/{start}/{end}",
252
     *     name="ProjectApiAdminStatsDates",
253
     *     requirements={"start"="|\d{4}-\d{2}-\d{2}", "end"="|\d{4}-\d{2}-\d{2}", "group"="admin|patroller|steward"},
254
     *     defaults={"start"=false, "end"=false, "group"="admin"}
255
     * )
256
     * @return JsonResponse
257
     * @codeCoverageIgnore
258
     */
259
    public function adminStatsDatesApiAction(): JsonResponse
260
    {
261
        $this->recordApiUsage('project/adminstats');
262
263
        $this->setUpAdminStats();
264
        $this->adminStats->prepareStats();
265
266
        return $this->getFormattedApiResponse([
267
            'users' => $this->adminStats->getStats(),
268
        ]);
269
    }
270
}
271