Passed
Push — master ( 619316...3e4776 )
by MusikAnimal
07:30
created

AdminStatsController::getActionNames()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 1
dl 0
loc 6
rs 10
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\Model\AdminStats;
11
use AppBundle\Repository\AdminStatsRepository;
12
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
13
use Symfony\Component\HttpFoundation\JsonResponse;
14
use Symfony\Component\HttpFoundation\Response;
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
    /**
25
     * Get the name of the tool's index route. This is also the name of the associated model.
26
     * @return string
27
     * @codeCoverageIgnore
28
     */
29
    public function getIndexRoute(): string
30
    {
31
        return 'AdminStats';
32
    }
33
34
    /**
35
     * Method for rendering the AdminStats Main Form.
36
     * This method redirects if valid parameters are found, making it a valid form endpoint as well.
37
     * @Route(
38
     *     "/{group}stats", name="AdminStats",
39
     *     requirements={"group"="admin"},
40
     *     defaults={"group"="admin"}
41
     * )
42
     * @Route(
43
     *     "/{group}stats", name="PatrollerStats",
44
     *     requirements={"group"="patroller"},
45
     *     defaults={"group"="patroller"}
46
     * )
47
     * @Route(
48
     *     "/{group}stats", name="StewardStats",
49
     *     requirements={"group"="steward"},
50
     *     defaults={"group"="steward"}
51
     * )
52
     * @return Response
53
     */
54
    public function indexAction(): Response
55
    {
56
        $this->getAndSetRequestedActions();
57
58
        // Redirect if we have a project.
59
        if (isset($this->params['project'])) {
60
            // We want pretty URLs.
61
            $route = $this->generateUrl(ucfirst($this->params['group']).'StatsResult', $this->params);
62
            $url = str_replace('%7C', '|', $route);
63
            return $this->redirect($url);
64
        }
65
66
        $actionsConfig = $this->container->getParameter('admin_stats');
67
        $group = $this->params['group'];
68
        $xtPage = lcfirst($group).'Stats';
69
70
        $params = array_merge([
71
            'xtPage' => $xtPage,
72
            'xtPageTitle' => "tool-{$group}stats",
73
            'xtSubtitle' => "tool-{$group}stats-desc",
74
            'actionsConfig' => $actionsConfig,
75
76
            // Defaults that will get overridden if in $params.
77
            'start' => '',
78
            'end' => '',
79
            'group' => 'admin',
80
        ], $this->params, ['project' => $this->project]);
81
82
        $params['isAllActions'] = $params['actions'] === implode('|', $this->getActionNames($params['group']));
83
84
        // Otherwise render form.
85
        return $this->render('adminStats/index.html.twig', $params);
86
    }
87
88
    /**
89
     * Get the requested actions and set the class property.
90
     * @return string[]
91
     * @codeCoverageIgnore
92
     */
93
    private function getAndSetRequestedActions(): array
94
    {
95
        /** @var string $group The requested 'group'. See keys at admin_stats.yml for possible values. */
96
        $group = $this->params['group'] = $this->params['group'] ?? 'admin';
97
98
        // Query param for sections gets priority.
99
        $actionsQuery = $this->request->get('actions', '');
100
101
        // Either a pipe-separated string or an array.
102
        $actions = is_array($actionsQuery) ? $actionsQuery : explode('|', $actionsQuery);
103
104
        // Filter out any invalid section IDs.
105
        $actions = array_filter($actions, function ($action) use ($group) {
106
            return in_array($action, $this->getActionNames($group));
107
        });
108
109
        // Fallback for when no valid sections were requested.
110
        if (0 === count($actions)) {
111
            $actions = $this->getActionNames($group);
112
        }
113
114
        // Store as pipe-separated string for prettier URLs.
115
        $this->params['actions'] = str_replace('%7C', '|', implode('|', $actions));
116
117
        return $actions;
118
    }
119
120
    /**
121
     * Get the names of the available sections.
122
     * @param string $group Corresponds to the groups specified in admin_stats.yml
123
     * @return string[]
124
     * @codeCoverageIgnore
125
     */
126
    private function getActionNames(string $group): array
127
    {
128
        $actionsConfig = $this->container->getParameter('admin_stats');
129
        unset($actionsConfig[$group]['permissions']);
130
        $actions = array_keys($actionsConfig[$group]);
131
        return $actions;
132
    }
133
134
    /**
135
     * Every action in this controller (other than 'index') calls this first.
136
     * If a response is returned, the calling action is expected to return it.
137
     * @return AdminStats
138
     * @codeCoverageIgnore
139
     */
140
    public function setUpAdminStats(): AdminStats
141
    {
142
        // $this->start and $this->end are already set by the parent XtoolsController, but here we want defaults,
143
        // so we run XtoolsController::getUTCFromDateParams() once more but with the $useDefaults flag set.
144
        [$this->start, $this->end] = $this->getUTCFromDateParams($this->start, $this->end, true);
145
146
        $adminStatsRepo = new AdminStatsRepository();
147
        $adminStatsRepo->setContainer($this->container);
148
        $this->adminStats = new AdminStats(
149
            $this->project,
150
            $this->start,
151
            $this->end,
152
            $this->params['group'] ?? 'admin',
153
            $this->getAndSetRequestedActions()
154
        );
155
        $this->adminStats->setRepository($adminStatsRepo);
156
157
        // For testing purposes.
158
        return $this->adminStats;
159
    }
160
161
    /**
162
     * Method for rendering the AdminStats results.
163
     * @Route(
164
     *     "/{group}stats/{project}/{start}/{end}", name="AdminStatsResult",
165
     *     requirements={"start"="|\d{4}-\d{2}-\d{2}", "end"="|\d{4}-\d{2}-\d{2}", "group"="admin"},
166
     *     defaults={"start"=false, "end"=false, "group"="admin"}
167
     * )
168
     * @Route(
169
     *     "/{group}stats/{project}/{start}/{end}", name="PatrollerStatsResult",
170
     *     requirements={"start"="|\d{4}-\d{2}-\d{2}", "end"="|\d{4}-\d{2}-\d{2}", "group"="patroller"},
171
     *     defaults={"start"=false, "end"=false, "group"="patroller"}
172
     * )
173
     * @Route(
174
     *     "/{group}stats/{project}/{start}/{end}", name="StewardStatsResult",
175
     *     requirements={"start"="|\d{4}-\d{2}-\d{2}", "end"="|\d{4}-\d{2}-\d{2}", "group"="steward"},
176
     *     defaults={"start"=false, "end"=false, "group"="steward"}
177
     * )
178
     * @return Response
179
     * @codeCoverageIgnore
180
     */
181
    public function resultAction(): Response
182
    {
183
        $this->setUpAdminStats();
184
185
        $this->adminStats->prepareStats();
186
187
        return $this->getFormattedResponse('adminStats/result', [
188
            'xtPage' => lcfirst($this->params['group']).'Stats',
189
            'xtTitle' => $this->project->getDomain(),
190
            'as' => $this->adminStats,
191
        ]);
192
    }
193
194
    /************************ API endpoints ************************/
195
196
    /**
197
     * Get users of the project that are capable of making 'admin actions',
198
     * keyed by user name with a list of the relevant user groups as the values.
199
     * @Route("/api/project/admins_groups/{project}", name="ProjectApiAdminsGroups")
200
     * @return JsonResponse
201
     * @codeCoverageIgnore
202
     */
203
    public function adminsGroupsApiAction(): JsonResponse
204
    {
205
        $this->recordApiUsage('project/admins_groups');
206
207
        $this->setUpAdminStats();
208
209
        return new JsonResponse(
210
            $this->adminStats->getAdminsAndGroups(false),
211
            Response::HTTP_OK
212
        );
213
    }
214
215
    /**
216
     * Get users of the project that are capable of making 'admin actions',
217
     * along with various stats about which actions they took. Time period is limited
218
     * to one month.
219
     * @Route(
220
     *     "/api/project/adminstats/{project}/{days}",
221
     *     name="ProjectApiAdminStats",
222
     *     requirements={"days"="\d+"},
223
     *     defaults={"days"=31}
224
     * )
225
     * @Route(
226
     *     "/api/project/admin_stats/{project}/{days}",
227
     *     name="ProjectApiAdminStatsUnderscored",
228
     *     requirements={"days"="\d+"},
229
     *     defaults={"days"=31}
230
     * )
231
     * @param int $days Number of days from present to grab data for. Maximum 31.
232
     * @return JsonResponse
233
     * @codeCoverageIgnore
234
     */
235
    public function adminStatsApiAction(int $days = 31): JsonResponse
236
    {
237
        $this->recordApiUsage('project/adminstats');
238
239
        $this->setUpAdminStats();
240
241
        // Maximum 31 days.
242
        $days = min((int) $days, 31);
243
        $start = date('Y-m-d', strtotime("-$days days"));
244
        $end = date('Y-m-d');
245
246
        $this->adminStats->prepareStats(false);
247
248
        return $this->getFormattedApiResponse([
249
            'start' => $start,
250
            'end' => $end,
251
            'users' => $this->adminStats->getStats(false),
252
        ]);
253
    }
254
}
255