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

AdminStatsRepository::getStats()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 15
nc 2
nop 5
dl 0
loc 29
rs 9.7666
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
     * Core function to get statistics about users who have admin-like permissions.
22
     * @param Project $project
23
     * @param string $start SQL-ready format.
24
     * @param string $end
25
     * @param string $group Which 'group' we're querying for, as configured in admin_stats.yml
26
     * @param string[]|null $actions Which log actions to query for.
27
     * @return string[][] with key for each action type (specified in admin_stats.yml), including 'total'.
28
     */
29
    public function getStats(Project $project, string $start, string $end, string $group, ?array $actions = null): array
30
    {
31
        $cacheKey = $this->getCacheKey(func_get_args(), 'adminstats');
32
        if ($this->cache->hasItem($cacheKey)) {
33
            return $this->cache->getItem($cacheKey)->get();
34
        }
35
36
        $userTable = $project->getTableName('user');
37
        $loggingTable = $project->getTableName('logging', 'logindex');
38
        [$countSql, $types, $actions] = $this->getLogSqlParts($group, $actions);
39
40
        $sql = "SELECT user_name AS `username`,
41
                    $countSql
42
                    SUM(IF(log_type != '' AND log_action != '', 1, 0)) AS `total`
43
                FROM $loggingTable
44
                JOIN $userTable ON user_id = log_user
45
                WHERE log_timestamp > '$start' AND log_timestamp <= '$end'
46
                  AND log_type IS NOT NULL
47
                  AND log_action IS NOT NULL
48
                  AND log_type IN ($types)
49
                  AND log_action IN ($actions)
50
                GROUP BY user_name
51
                HAVING `total` > 0
52
                ORDER BY 'total' DESC";
53
54
        $results = $this->executeProjectsQuery($sql)->fetchAll();
55
56
        // Cache and return.
57
        return $this->setCache($cacheKey, $results);
58
    }
59
60
    /**
61
     * Get the SQL to query for the given actions and group.
62
     * @param string $group
63
     * @param string[]|null $requestedActions
64
     * @return string[]
65
     */
66
    private function getLogSqlParts(string $group, ?array $requestedActions = null): array
67
    {
68
        $config = $this->container->getParameter('admin_stats')[$group];
69
70
        // 'permissions' is only used for self::getAdminGroups()
71
        unset($config['permissions']);
72
73
        $countSql = '';
74
        $types = [];
75
        $actions = [];
76
77
        foreach ($config as $key => $logTypeActions) {
78
            if (is_array($requestedActions) && !in_array($key, $requestedActions)) {
79
                continue;
80
            }
81
82
            $keyTypes = [];
83
            $keyActions = [];
84
85
            foreach ($logTypeActions as $entry) {
86
                [$type, $action] = explode('/', $entry);
87
                $types[] = $keyTypes[] = $this->getProjectsConnection()->quote($type, \PDO::PARAM_STR);
88
                $actions[] = $keyActions[] = $this->getProjectsConnection()->quote($action, \PDO::PARAM_STR);
89
            }
90
91
            $keyTypes = implode(',', array_unique($keyTypes));
92
            $keyActions = implode(',', array_unique($keyActions));
93
94
            $countSql .= "SUM(IF((log_type IN ($keyTypes) AND log_action IN ($keyActions)), 1, 0)) AS `$key`,\n";
95
        }
96
97
        return [$countSql, implode(',', array_unique($types)), implode(',', array_unique($actions))];
98
    }
99
100
    /**
101
     * Get all user groups with admin-like permissions.
102
     * @param Project $project
103
     * @param string $group Which 'group' we're querying for, as configured in admin_stats.yml
104
     * @return array Each entry contains 'name' (user group) and 'rights' (the permissions).
105
     */
106
    public function getAdminGroups(Project $project, string $group): array
107
    {
108
        $cacheKey = $this->getCacheKey(func_get_args(), 'admingroups');
109
        if ($this->cache->hasItem($cacheKey)) {
110
            return $this->cache->getItem($cacheKey)->get();
111
        }
112
113
        $permissions = $this->container->getParameter('admin_stats')[$group]['permissions'];
114
        $userGroups = [];
115
116
        $api = $this->getMediawikiApi($project);
117
        $query = new SimpleRequest('query', [
118
            'meta' => 'siteinfo',
119
            'siprop' => 'usergroups',
120
        ]);
121
        $res = $api->getRequest($query);
122
123
        // If there isn't a user groups hash than let it error out... Something else must have gone horribly wrong.
124
        foreach ($res['query']['usergroups'] as $userGroup) {
125
            // If they are able to add and remove user groups,
126
            // we'll treat them as having the 'userrights' permission.
127
            if (isset($userGroup['add']) || isset($userGroup['remove'])) {
128
                array_push($userGroup['rights'], 'userrights');
129
            }
130
131
            if (count(array_intersect($userGroup['rights'], $permissions)) > 0) {
132
                $userGroups[] = $userGroup['name'];
133
            }
134
        }
135
136
        // Cache for a week and return.
137
        return $this->setCache($cacheKey, $userGroups, 'P7D');
138
    }
139
}
140