Passed
Push — master ( 6ef6ed...3181c4 )
by MusikAnimal
07:28
created

AdminStats   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 236
Duplicated Lines 0 %

Test Coverage

Coverage 72.41%

Importance

Changes 0
Metric Value
eloc 62
dl 0
loc 236
ccs 42
cts 58
cp 0.7241
rs 10
c 0
b 0
f 0
wmc 24

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 1
A getRelevantUserGroup() 0 9 2
A getType() 0 3 1
A groupStatsByUsername() 0 30 4
A getNumInRelevantUserGroup() 0 3 1
A numDays() 0 3 1
A getActions() 0 5 2
A getStats() 0 6 2
A prepareStats() 0 22 4
A getUserGroupIcons() 0 9 2
A getNumWithActionsNotInGroup() 0 3 1
A getNumWithActions() 0 3 1
A getUsersAndGroups() 0 20 2
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, patrollers, stewards, 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 2
        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
         * Each user group that is 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);
128
129
        // Populate $this->usersInGroup with users who are in the relevant user group for $this->group.
130 3
        $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
     * @return array Each entry contains 'name' (user group) and 'rights' (the permissions).
140
     */
141
    public function getUserGroupIcons(): array
142
    {
143
        // Quick cache, valid only for the same request.
144
        static $userGroupIcons = null;
145
        if (null !== $userGroupIcons) {
146
            return $userGroupIcons;
147
        }
148
        $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

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