AdminStats::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 6
dl 0
loc 14
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace App\Model;
6
7
use App\Repository\AdminStatsRepository;
8
9
/**
10
 * AdminStats returns information about users with rights defined in admin_stats.yaml.
11
 */
12
class AdminStats extends Model
13
{
14
15
    /** @var string[][] Keyed by user name, values are arrays containing actions and counts. */
16
    protected array $adminStats;
17
18
    /** @var string[] Keys are user names, values are their user groups. */
19
    protected array $usersAndGroups;
20
21
    /** @var int Number of users in the relevant group who made any actions within the time period. */
22
    protected int $numWithActions = 0;
23
24
    /** @var string[] Usernames of users who are in the relevant user group (sysop for admins, etc.). */
25
    private array $usersInGroup = [];
26
27
    /** @var string Type that we're getting stats for (admin, patroller, steward, etc.). See admin_stats.yaml */
28
    private string $type;
29
30
    /** @var string[] Which actions to show ('block', 'protect', etc.) */
31
    private array $actions;
32
33
    /**
34
     * AdminStats constructor.
35
     * @param AdminStatsRepository $repository
36
     * @param Project $project
37
     * @param int $start as UTC timestamp.
38
     * @param int $end as UTC timestamp.
39
     * @param string $group Which user group to get stats for. Refer to admin_stats.yaml for possible values.
40
     * @param string[] $actions Which actions to query for ('block', 'protect', etc.). Null for all actions.
41
     */
42
    public function __construct(
43
        AdminStatsRepository $repository,
44
        Project $project,
45
        int $start,
46
        int $end,
47
        string $group,
48
        array $actions
49
    ) {
50
        $this->repository = $repository;
51
        $this->project = $project;
52
        $this->start = $start;
53
        $this->end = $end;
54
        $this->type = $group;
55
        $this->actions = $actions;
56
    }
57
58
    /**
59
     * Get the group for this AdminStats.
60
     * @return string
61
     */
62
    public function getType(): string
63
    {
64
        return $this->type;
65
    }
66
67
    /**
68
     * Get the user_group from the config given the 'group'.
69
     * @return string
70
     */
71
    public function getRelevantUserGroup(): string
72
    {
73
        // Quick cache, valid only for the same request.
74
        static $relevantUserGroup = '';
75
        if ('' !== $relevantUserGroup) {
76
            return $relevantUserGroup;
77
        }
78
79
        return $relevantUserGroup = $this->getRepository()->getRelevantUserGroup($this->type);
0 ignored issues
show
Bug introduced by
The method getRelevantUserGroup() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\AdminStatsRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

79
        return $relevantUserGroup = $this->getRepository()->/** @scrutinizer ignore-call */ getRelevantUserGroup($this->type);
Loading history...
80
    }
81
82
    /**
83
     * Get the array of statistics for each qualifying user. This may be called ahead of self::getStats() so certain
84
     * class-level properties will be supplied (such as self::numUsers(), which is called in the view before iterating
85
     * over the master array of statistics).
86
     * @return string[]
87
     */
88
    public function prepareStats(): array
89
    {
90
        if (isset($this->adminStats)) {
91
            return $this->adminStats;
92
        }
93
94
        $stats = $this->getRepository()
95
            ->getStats($this->project, $this->start, $this->end, $this->type, $this->actions);
0 ignored issues
show
Bug introduced by
The method getStats() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\AdminStatsRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

95
            ->/** @scrutinizer ignore-call */ getStats($this->project, $this->start, $this->end, $this->type, $this->actions);
Loading history...
96
97
        // Group by username.
98
        $stats = $this->groupStatsByUsername($stats);
99
100
        // Resort, as for some reason the SQL doesn't do this properly.
101
        uasort($stats, function ($a, $b) {
102
            if ($a['total'] === $b['total']) {
103
                return 0;
104
            }
105
            return $a['total'] < $b['total'] ? 1 : -1;
106
        });
107
108
        $this->adminStats = $stats;
109
        return $this->adminStats;
110
    }
111
112
    /**
113
     * Get users of the project that are capable of making the relevant actions,
114
     * keyed by user name, with the user groups as the values.
115
     * @return string[][]
116
     */
117
    public function getUsersAndGroups(): array
118
    {
119
        if (isset($this->usersAndGroups)) {
120
            return $this->usersAndGroups;
121
        }
122
123
        // All the user groups that are considered capable of making the relevant actions for $this->group.
124
        $groupUserGroups = $this->getRepository()->getUserGroups($this->project, $this->type);
0 ignored issues
show
Bug introduced by
The method getUserGroups() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\AdminStatsRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

124
        $groupUserGroups = $this->getRepository()->/** @scrutinizer ignore-call */ getUserGroups($this->project, $this->type);
Loading history...
125
126
        $this->usersAndGroups = $this->project->getUsersInGroups($groupUserGroups['local'], $groupUserGroups['global']);
127
128
        // Populate $this->usersInGroup with users who are in the relevant user group for $this->group.
129
        $this->usersInGroup = array_keys(array_filter($this->usersAndGroups, function ($groups) {
130
            return in_array($this->getRelevantUserGroup(), $groups);
131
        }));
132
133
        return $this->usersAndGroups;
134
    }
135
136
    /**
137
     * Get all user groups with permissions applicable to the $this->group.
138
     * @param bool $wikiPath Whether to return the title for the on-wiki image, instead of full URL.
139
     * @return array Each entry contains 'name' (user group) and 'rights' (the permissions).
140
     */
141
    public function getUserGroupIcons(bool $wikiPath = false): array
142
    {
143
        // Quick cache, valid only for the same request.
144
        static $userGroupIcons = null;
145
        if (null !== $userGroupIcons) {
146
            $out = $userGroupIcons;
147
        } else {
148
            $out = $userGroupIcons = $this->getRepository()->getUserGroupIcons();
0 ignored issues
show
Bug introduced by
The method getUserGroupIcons() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\AdminStatsRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

148
            $out = $userGroupIcons = $this->getRepository()->/** @scrutinizer ignore-call */ getUserGroupIcons();
Loading history...
149
        }
150
151
        if ($wikiPath) {
152
            $out = array_map(function ($url) {
153
                return str_replace('.svg.png', '.svg', preg_replace('/.*\/18px-/', '', $url));
154
            }, $out);
155
        }
156
157
        return $out;
158
    }
159
160
    /**
161
     * The number of days we're spanning between the start and end date.
162
     * @return int
163
     */
164
    public function numDays(): int
165
    {
166
        return (int)(($this->end - $this->start) / 60 / 60 / 24) + 1;
167
    }
168
169
    /**
170
     * Get the master array of statistics for each qualifying user.
171
     * @return string[]
172
     */
173
    public function getStats(): array
174
    {
175
        if (isset($this->adminStats)) {
176
            $this->adminStats = $this->prepareStats();
177
        }
178
        return $this->adminStats;
179
    }
180
181
    /**
182
     * Get the actions that are shown as columns in the view.
183
     * @return string[] Each the i18n key of the action.
184
     */
185
    public function getActions(): array
186
    {
187
        return count($this->getStats()) > 0
188
            ? array_diff(array_keys(array_values($this->getStats())[0]), ['username', 'user-groups', 'total'])
189
            : [];
190
    }
191
192
    /**
193
     * Given the data returned by AdminStatsRepository::getStats, return the stats keyed by user name,
194
     * adding in a key/value for user groups.
195
     * @param string[][] $data As retrieved by AdminStatsRepository::getStats
196
     * @return string[] Stats keyed by user name.
197
     * Functionality covered in test for self::getStats().
198
     * @codeCoverageIgnore
199
     */
200
    private function groupStatsByUsername(array $data): array
201
    {
202
        $usersAndGroups = $this->getUsersAndGroups();
203
        $users = [];
204
205
        foreach ($data as $datum) {
206
            $username = $datum['username'];
207
208
            // Push to array containing all users with admin actions.
209
            // We also want numerical values to be integers.
210
            $users[$username] = array_map('intval', $datum);
211
212
            // Push back username which was casted to an integer.
213
            $users[$username]['username'] = $username;
214
215
            // Set the 'user-groups' property with the user groups they belong to (if any),
216
            // going off of self::getUsersAndGroups().
217
            if (isset($usersAndGroups[$username])) {
218
                $users[$username]['user-groups'] = $usersAndGroups[$username];
219
            } else {
220
                $users[$username]['user-groups'] = [];
221
            }
222
223
            // Keep track of users who are not in the relevant user group but made applicable actions.
224
            if (in_array($username, $this->usersInGroup)) {
225
                $this->numWithActions++;
226
            }
227
        }
228
229
        return $users;
230
    }
231
232
    /**
233
     * Get the total number of users in the relevant user group.
234
     * @return int
235
     */
236
    public function getNumInRelevantUserGroup(): int
237
    {
238
        return count($this->usersInGroup);
239
    }
240
241
    /**
242
     * Number of users who made any relevant actions within the time period.
243
     * @return int
244
     */
245
    public function getNumWithActions(): int
246
    {
247
        return $this->numWithActions;
248
    }
249
250
    /**
251
     * Number of currently users who made any actions within the time period who are not in the relevant user group.
252
     * @return int
253
     */
254
    public function getNumWithActionsNotInGroup(): int
255
    {
256
        return count($this->adminStats) - $this->numWithActions;
257
    }
258
}
259