Passed
Pull Request — main (#442)
by MusikAnimal
08:15 queued 04:14
created

AdminStatsController::getActionNames()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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