Passed
Push — master ( 3a427b...e8ca6d )
by MusikAnimal
05:17
created

AdminStatsController::normalizeProject()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

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