Passed
Push — master ( 94d9d8...f8825d )
by MusikAnimal
07:28
created

AdminStatsRepository::getLogSqlParts()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 17
nc 4
nop 3
dl 0
loc 32
rs 9.3888
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 string $start SQL-ready format.
33
     * @param string $end
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, string $start, string $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
49
        $sql = "SELECT user_name AS `username`,
50
                    $countSql
51
                    SUM(IF(log_type != '' AND log_action != '', 1, 0)) AS `total`
52
                FROM $loggingTable
53
                JOIN $userTable ON user_id = log_user
54
                WHERE log_timestamp BETWEEN :start AND :end
55
                  AND log_type IN ($types)
56
                  AND log_action IN ($actions)
57
                GROUP BY user_name
58
                HAVING `total` > 0
59
                ORDER BY 'total' DESC";
60
61
        $results = $this->executeProjectsQuery($sql, [
62
            'start' => $start,
63
            'end' => $end,
64
        ])->fetchAll();
65
66
        // Cache and return.
67
        return $this->setCache($cacheKey, $results);
68
    }
69
70
    /**
71
     * Get the SQL to query for the given type and actions.
72
     * @param Project $project
73
     * @param string $type
74
     * @param string[] $requestedActions
75
     * @return string[]
76
     */
77
    private function getLogSqlParts(Project $project, string $type, array $requestedActions = []): array
78
    {
79
        $config = $this->getConfig($project)[$type];
80
81
        $countSql = '';
82
        $logTypes = [];
83
        $logActions = [];
84
85
        foreach ($config['actions'] as $key => $logTypeActions) {
86
            if (is_array($requestedActions) && !in_array($key, $requestedActions)) {
87
                continue;
88
            }
89
90
            $keyTypes = [];
91
            $keyActions = [];
92
93
            /** @var string|array $entry String matching 'log_type/log_action' or a configuration array. */
94
            foreach ($logTypeActions as $entry) {
95
                // admin_stats.yml gives us the log type and action as a string in the format of "type/action".
96
                [$logType, $logAction] = explode('/', $entry);
97
98
                $logTypes[] = $keyTypes[] = $this->getProjectsConnection()->quote($logType, \PDO::PARAM_STR);
99
                $logActions[] = $keyActions[] = $this->getProjectsConnection()->quote($logAction, \PDO::PARAM_STR);
100
            }
101
102
            $keyTypes = implode(',', array_unique($keyTypes));
103
            $keyActions = implode(',', array_unique($keyActions));
104
105
            $countSql .= "SUM(IF((log_type IN ($keyTypes) AND log_action IN ($keyActions)), 1, 0)) AS `$key`,\n";
106
        }
107
108
        return [$countSql, implode(',', array_unique($logTypes)), implode(',', array_unique($logActions))];
109
    }
110
111
    /**
112
     * Get the configuration for the given Project. This respects extensions installed on the wiki.
113
     * @param Project $project
114
     * @return array
115
     */
116
    public function getConfig(Project $project): array
117
    {
118
        $config = $this->container->getParameter('admin_stats');
119
        $extensions = $project->getInstalledExtensions();
120
121
        foreach ($config as $type => $values) {
122
            foreach ($values['actions'] as $permission => $actions) {
123
                $requiredExtension = $actions[0]['extension'] ?? '';
124
                if ('' !== $requiredExtension) {
125
                    unset($config[$type]['actions'][$permission][0]);
126
127
                    if (!in_array($requiredExtension, $extensions)) {
128
                        unset($config[$type]['actions'][$permission]);
129
                        continue;
130
                    }
131
                }
132
            }
133
        }
134
135
        return $config;
136
    }
137
138
    /**
139
     * Get the user_group from the config given the 'type'.
140
     * @param string $type
141
     * @return string
142
     */
143
    public function getRelevantUserGroup(string $type): string
144
    {
145
        return $this->container->getParameter('admin_stats')[$type]['user_group'];
146
    }
147
148
    /**
149
     * Get all user groups with permissions applicable to the given 'group'.
150
     * @param Project $project
151
     * @param string $type Which 'type' we're querying for, as configured in admin_stats.yml
152
     * @return array Each entry contains 'name' (user group) and 'rights' (the permissions).
153
     */
154
    public function getUserGroups(Project $project, string $type): array
155
    {
156
        $cacheKey = $this->getCacheKey(func_get_args(), 'admingroups');
157
        if ($this->cache->hasItem($cacheKey)) {
158
            return $this->cache->getItem($cacheKey)->get();
159
        }
160
161
        $permissions = $this->container->getParameter('admin_stats')[$type]['permissions'];
162
        $userGroups = [];
163
164
        $api = $this->getMediawikiApi($project);
165
        $query = new SimpleRequest('query', [
166
            'meta' => 'siteinfo',
167
            'siprop' => 'usergroups',
168
        ]);
169
        $res = $api->getRequest($query);
170
171
        // If there isn't a user groups hash than let it error out... Something else must have gone horribly wrong.
172
        foreach ($res['query']['usergroups'] as $userGroup) {
173
            // If they are able to add and remove user groups,
174
            // we'll treat them as having the 'userrights' permission.
175
            if (isset($userGroup['add']) || isset($userGroup['remove'])) {
176
                array_push($userGroup['rights'], 'userrights');
177
            }
178
179
            if (count(array_intersect($userGroup['rights'], $permissions)) > 0) {
180
                $userGroups[] = $userGroup['name'];
181
            }
182
        }
183
184
        // Cache for a week and return.
185
        return $this->setCache($cacheKey, $userGroups, 'P7D');
186
    }
187
}
188