Passed
Push — master ( ed4035...f2dd38 )
by MusikAnimal
10:32
created

AdminStats::getNumWithActionsNotInGroup()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

77
        return $relevantUserGroup = $this->getRepository()->/** @scrutinizer ignore-call */ getRelevantUserGroup($this->type);
Loading history...
78
    }
79
80
    /**
81
     * Get the array of statistics for each qualifying user. This may be called ahead of self::getStats() so certain
82
     * class-level properties will be supplied (such as self::numUsers(), which is called in the view before iterating
83
     * over the master array of statistics).
84
     * @return string[]
85
     */
86 2
    public function prepareStats(): array
87
    {
88 2
        if (isset($this->adminStats)) {
89 1
            return $this->adminStats;
90
        }
91
92 2
        $stats = $this->getRepository()
93 2
            ->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 AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\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

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

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

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