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

AdminStatsController::defaultDays()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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