Passed
Push — master ( 36a02f...f7da57 )
by MusikAnimal
07:36
created

AdminStatsRepository::getUserGroupByLocality()   A

Complexity

Conditions 6
Paths 10

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 8
nc 10
nop 3
dl 0
loc 16
rs 9.2222
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains only the AdminStatsRepository class.
4
 */
5
6
declare(strict_types = 1);
7
8
namespace AppBundle\Repository;
9
10
use AppBundle\Model\Project;
11
use Mediawiki\Api\SimpleRequest;
12
13
/**
14
 * AdminStatsRepository is responsible for retrieving data from the database
15
 * about users with administrative rights on a given wiki.
16
 * @codeCoverageIgnore
17
 */
18
class AdminStatsRepository extends Repository
19
{
20
    /**
21
     * Get the URLs of the icons for the user groups.
22
     * @return string[] System user group name as the key, URL to image as the value.
23
     */
24
    public function getUserGroupIcons(): array
25
    {
26
        return $this->container->getParameter('user_group_icons');
27
    }
28
29
    /**
30
     * Core function to get statistics about users who have admin/patroller/steward-like permissions.
31
     * @param Project $project
32
     * @param int $start UTC timestamp.
33
     * @param int $end UTC timestamp.
34
     * @param string $type Which 'type' we're querying for, as configured in admin_stats.yml
35
     * @param string[] $actions Which log actions to query for.
36
     * @return string[][] with key for each action type (specified in admin_stats.yml), including 'total'.
37
     */
38
    public function getStats(Project $project, int $start, int $end, string $type, array $actions = []): array
39
    {
40
        $cacheKey = $this->getCacheKey(func_get_args(), 'adminstats');
41
        if ($this->cache->hasItem($cacheKey)) {
42
            return $this->cache->getItem($cacheKey)->get();
43
        }
44
45
        $userTable = $project->getTableName('user');
46
        $loggingTable = $project->getTableName('logging', 'logindex');
47
        [$countSql, $types, $actions] = $this->getLogSqlParts($project, $type, $actions);
48
        $dateConditions = $this->getDateConditions($start, $end, "logging_logindex.", 'log_timestamp');
49
50
        $sql = "SELECT user_name AS `username`,
51
                    $countSql
52
                    SUM(IF(log_type != '' AND log_action != '', 1, 0)) AS `total`
53
                FROM $loggingTable
54
                JOIN $userTable ON user_id = log_user
55
                WHERE log_type IN ($types)
56
                    AND log_action IN ($actions)
57
                    $dateConditions
58
                GROUP BY user_name
59
                HAVING `total` > 0";
60
61
        $results = $this->executeProjectsQuery($sql)->fetchAll();
62
63
        // Cache and return.
64
        return $this->setCache($cacheKey, $results);
65
    }
66
67
    /**
68
     * Get the SQL to query for the given type and actions.
69
     * @param Project $project
70
     * @param string $type
71
     * @param string[] $requestedActions
72
     * @return string[]
73
     */
74
    private function getLogSqlParts(Project $project, string $type, array $requestedActions = []): array
75
    {
76
        $config = $this->getConfig($project)[$type];
77
78
        $countSql = '';
79
        $logTypes = [];
80
        $logActions = [];
81
82
        foreach ($config['actions'] as $key => $logTypeActions) {
83
            if (is_array($requestedActions) && !in_array($key, $requestedActions)) {
84
                continue;
85
            }
86
87
            $keyTypes = [];
88
            $keyActions = [];
89
90
            /** @var string|array $entry String matching 'log_type/log_action' or a configuration array. */
91
            foreach ($logTypeActions as $entry) {
92
                // admin_stats.yml gives us the log type and action as a string in the format of "type/action".
93
                [$logType, $logAction] = explode('/', $entry);
94
95
                $logTypes[] = $keyTypes[] = $this->getProjectsConnection()->quote($logType, \PDO::PARAM_STR);
96
                $logActions[] = $keyActions[] = $this->getProjectsConnection()->quote($logAction, \PDO::PARAM_STR);
97
            }
98
99
            $keyTypes = implode(',', array_unique($keyTypes));
100
            $keyActions = implode(',', array_unique($keyActions));
101
102
            $countSql .= "SUM(IF((log_type IN ($keyTypes) AND log_action IN ($keyActions)), 1, 0)) AS `$key`,\n";
103
        }
104
105
        return [$countSql, implode(',', array_unique($logTypes)), implode(',', array_unique($logActions))];
106
    }
107
108
    /**
109
     * Get the configuration for the given Project. This respects extensions installed on the wiki.
110
     * @param Project $project
111
     * @return array
112
     */
113
    public function getConfig(Project $project): array
114
    {
115
        $config = $this->container->getParameter('admin_stats');
116
        $extensions = $project->getInstalledExtensions();
117
118
        foreach ($config as $type => $values) {
119
            foreach ($values['actions'] as $permission => $actions) {
120
                $requiredExtension = $actions[0]['extension'] ?? '';
121
                if ('' !== $requiredExtension) {
122
                    unset($config[$type]['actions'][$permission][0]);
123
124
                    if (!in_array($requiredExtension, $extensions)) {
125
                        unset($config[$type]['actions'][$permission]);
126
                        continue;
127
                    }
128
                }
129
            }
130
        }
131
132
        return $config;
133
    }
134
135
    /**
136
     * Get the user_group from the config given the 'type'.
137
     * @param string $type
138
     * @return string
139
     */
140
    public function getRelevantUserGroup(string $type): string
141
    {
142
        return $this->container->getParameter('admin_stats')[$type]['user_group'];
143
    }
144
145
    /**
146
     * Get all user groups with permissions applicable to the given 'group'.
147
     * @param Project $project
148
     * @param string $type Which 'type' we're querying for, as configured in admin_stats.yml
149
     * @return array Keys are 'local' and 'global', each an array of user groups with keys 'name' and 'rights'.
150
     */
151
    public function getUserGroups(Project $project, string $type): array
152
    {
153
        $cacheKey = $this->getCacheKey(func_get_args(), 'admingroups');
154
        if ($this->cache->hasItem($cacheKey)) {
155
            return $this->cache->getItem($cacheKey)->get();
156
        }
157
158
        $permissions = $this->container->getParameter('admin_stats')[$type]['permissions'];
159
160
        $api = $this->getMediawikiApi($project);
161
        $res = $api->getRequest(new SimpleRequest('query', [
162
            'meta' => 'siteinfo',
163
            'siprop' => 'usergroups',
164
            'list' => 'globalgroups',
165
            'ggpprop' => 'rights',
166
        ]))['query'];
167
168
        $userGroups = [
169
            'local' => $this->getUserGroupByLocality($res, $permissions),
170
            'global' => $this->getUserGroupByLocality($res, $permissions, true),
171
        ];
172
173
        // Cache for a week and return.
174
        return $this->setCache($cacheKey, $userGroups, 'P7D');
175
    }
176
177
    /**
178
     * Parse user groups API response, returning groups that have any of the given permissions.
179
     * @param string[][] $res API response.
180
     * @param string[] $permissions Permissions to look for.
181
     * @param bool $global Return global user groups instead of local.
182
     */
183
    private function getUserGroupByLocality(array $res, array $permissions, bool $global = false): array
184
    {
185
        $userGroups = [];
186
187
        foreach (($global ? $res['globalgroups'] : $res['usergroups']) as $userGroup) {
188
            // If they are able to add and remove user groups, we'll treat them as having the 'userrights' permission.
189
            if (isset($userGroup['add']) || isset($userGroup['remove'])) {
190
                array_push($userGroup['rights'], 'userrights');
191
                $userGroup['rights'][] = 'userrights';
192
            }
193
            if (count(array_intersect($userGroup['rights'], $permissions)) > 0) {
194
                $userGroups[] = $userGroup['name'];
195
            }
196
        }
197
198
        return $userGroups;
199
    }
200
}
201