Passed
Push — master ( 3d4ba3...a5dea4 )
by MusikAnimal
05:14
created

AdminStats::prepareStats()   B

Complexity

Conditions 4
Paths 2

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4.0058

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 2
nop 1
dl 0
loc 25
ccs 13
cts 14
cp 0.9286
crap 4.0058
rs 8.5806
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains only the AdminStats class.
4
 */
5
6
namespace Xtools;
7
8
use Symfony\Component\DependencyInjection\Container;
9
use DateTime;
10
11
/**
12
 * AdminStats returns information about users with administrative
13
 * rights on a given wiki.
14
 */
15
class AdminStats extends Model
16
{
17
    /** @var Project Project associated with this AdminStats instance. */
18
    protected $project;
19
20
    /** @var string[] Keyed by user name, values are arrays containing actions and counts. */
21
    protected $adminStats;
22
23
    /**
24
     * Keys are user names, values are their abbreviated user groups.
25
     * If abbreviations are turned on, this will instead be a string of the abbreviated
26
     * user groups, separated by slashes.
27
     * @var string[]|string
28
     */
29
    protected $adminsAndGroups;
30
31
    /** @var int Number of admins who made any actions within the time period. */
32
    protected $numAdminsWithActions = 0;
33
34
    /** @var string[] Usernames of proper sysops. */
35
    private $admins = [];
36
37
    /** @var int Start of time period as UTC timestamp */
38
    protected $start;
39
40
    /** @var int End of time period as UTC timestamp */
41
    protected $end;
42
43
    /**
44
     * TopEdits constructor.
45
     * @param Project $project
46
     * @param int $start as UTC timestamp.
47
     * @param int $end as UTC timestamp.
48
     */
49 3
    public function __construct(Project $project, $start = null, $end = null)
50
    {
51 3
        $this->project = $project;
52 3
        $this->start = $start;
53 3
        $this->end = $end;
54 3
    }
55
56
    /**
57
     * Get users of the project that are capable of making 'admin actions',
58
     * keyed by user name with abbreviations for the user groups as the values.
59
     * @param  string $abbreviate If set, the keys of the result with be a string containing
60
     *   abbreviated versions of their user groups, such as 'A' instead of administrator,
61
     *   'CU' instead of CheckUser, etc. If $abbreviate is false, the keys of the result
62
     *   will be an array of the full-named user groups.
63
     * @see Project::getAdmins()
64
     * @return string[]
65
     */
66 3
    public function getAdminsAndGroups($abbreviate = true)
67
    {
68 3
        if ($this->adminsAndGroups) {
69
            return $this->adminsAndGroups;
70
        }
71
72
        /**
73
         * Each user group that is considered capable of making 'admin actions'.
74
         * @var string[]
75
         */
76 3
        $adminGroups = $this->getRepository()->getAdminGroups($this->project);
77
78
        /** @var array Keys are the usernames, values are thier user groups. */
79 3
        $admins = $this->project->getUsersInGroups($adminGroups);
80
81 3
        if ($abbreviate === false) {
0 ignored issues
show
introduced by
The condition $abbreviate === false is always false.
Loading history...
82 1
            return $admins;
83
        }
84
85
        /**
86
         * Keys are the database-stored names, values are the abbreviations.
87
         * FIXME: i18n this somehow.
88
         * @var string[]
89
         */
90
        $userGroupAbbrMap = [
91 3
            'sysop' => 'A',
92
            'bureaucrat' => 'B',
93
            'steward' => 'S',
94
            'checkuser' => 'CU',
95
            'oversight' => 'OS',
96
            'bot' => 'Bot',
97
        ];
98
99 3
        foreach ($admins as $admin => $groups) {
100 3
            $abbrGroups = [];
101
102
            // Keep track of actual number of sysops.
103 3
            if (in_array('sysop', $groups)) {
0 ignored issues
show
Bug introduced by
$groups of type string is incompatible with the type array expected by parameter $haystack of in_array(). ( Ignorable by Annotation )

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

103
            if (in_array('sysop', /** @scrutinizer ignore-type */ $groups)) {
Loading history...
104 3
                $this->admins[] = $admin;
105
            }
106
107 3
            foreach ($groups as $group) {
0 ignored issues
show
Bug introduced by
The expression $groups of type string is not traversable.
Loading history...
108 3
                if (isset($userGroupAbbrMap[$group])) {
109 3
                    $abbrGroups[] = $userGroupAbbrMap[$group];
110
                }
111
            }
112
113
            // Make 'A' (admin) come before 'CU' (CheckUser), etc.
114 3
            sort($abbrGroups);
115
116 3
            $this->adminsAndGroups[$admin] = implode('/', $abbrGroups);
117
        }
118
119 3
        return $this->adminsAndGroups;
120
    }
121
122
    /**
123
     * The number of days we're spanning between the start and end date.
124
     * @return int
125
     */
126 1
    public function numDays()
127
    {
128 1
        return ($this->end - $this->start) / 60 / 60 / 24;
129
    }
130
131
    /**
132
     * Get the array of statistics for each qualifying user. This may be called
133
     * ahead of self::getStats() so certain class-level properties will be supplied
134
     * (such as self::numUsers(), which is called in the view before iterating
135
     * over the master array of statistics).
136
     * @param boolean $abbreviateGroups If set, the 'groups' list will be
137
     *   a string with abbreivated user groups names, as opposed to an array
138
     *   of full-named user groups.
139
     * @return string[]
140
     */
141 2
    public function prepareStats($abbreviateGroups = true)
142
    {
143 2
        if (isset($this->adminStats)) {
144 1
            return $this->adminStats;
145
        }
146
147
        // UTC to YYYYMMDDHHMMSS.
148 2
        $startDb = date('Ymd000000', $this->start);
149 2
        $endDb = date('Ymd235959', $this->end);
150
151 2
        $stats = $this->getRepository()->getStats($this->project, $startDb, $endDb);
152
153
        // Group by username.
154 2
        $stats = $this->groupAdminStatsByUsername($stats, $abbreviateGroups);
155
156
        // Resort, as for some reason the SQL isn't doing this properly.
157 2
        uasort($stats, function ($a, $b) {
158 2
            if ($a['total'] === $b['total']) {
159
                return 0;
160
            }
161 2
            return ($a['total'] < $b['total']) ? 1 : -1;
162 2
        });
163
164 2
        $this->adminStats = $stats;
165 2
        return $this->adminStats;
166
    }
167
168
    /**
169
     * Get the master array of statistics for each qualifying user.
170
     * @param boolean $abbreviateGroups If set, the 'groups' list will be
171
     *   a string with abbreivated user groups names, as opposed to an array
172
     *   of full-named user groups.
173
     * @return string[]
174
     */
175 1
    public function getStats($abbreviateGroups = true)
176
    {
177 1
        if (isset($this->adminStats)) {
178 1
            $this->adminStats = $this->prepareStats($abbreviateGroups);
179
        }
180 1
        return $this->adminStats;
181
    }
182
183
    /**
184
     * Given the data returned by AdminStatsRepository::getStats,
185
     * return the stats keyed by user name, adding in a key/value for user groups.
186
     * @param  string[] $data As retrieved by AdminStatsRepository::getStats
187
     * @param boolean $abbreviateGroups If set, the 'groups' list will be
188
     *   a string with abbreivated user groups names, as opposed to an array
189
     *   of full-named user groups.
190
     * @return string[] Stats keyed by user name.
191
     * Functionality covered in test for self::getStats().
192
     * @codeCoverageIgnore
193
     */
194
    private function groupAdminStatsByUsername($data, $abbreviateGroups = true)
195
    {
196
        $adminsAndGroups = $this->getAdminsAndGroups($abbreviateGroups);
197
        $users = [];
198
199
        foreach ($data as $datum) {
200
            $username = $datum['user_name'];
201
202
            // Push to array containing all users with admin actions.
203
            // We also want numerical values to be integers.
204
            $users[$username] = array_map('intval', $datum);
0 ignored issues
show
Bug introduced by
$datum of type string is incompatible with the type array expected by parameter $arr1 of array_map(). ( Ignorable by Annotation )

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

204
            $users[$username] = array_map('intval', /** @scrutinizer ignore-type */ $datum);
Loading history...
205
206
            // Push back username which was casted to an integer.
207
            $users[$username]['user_name'] = $username;
208
209
            // Set the 'groups' property with the user groups they belong to (if any),
210
            // going off of self::getAdminsAndGroups().
211
            if (isset($adminsAndGroups[$username])) {
212
                $users[$username]['groups'] = $adminsAndGroups[$username];
213
            } else {
214
                $users[$username]['groups'] = $abbreviateGroups ? '' : [];
215
            }
216
217
            // Keep track of non-admins who made admin actions.
218
            if (in_array($username, $this->admins)) {
219
                $this->numAdminsWithActions++;
220
            }
221
        }
222
223
        return $users;
224
    }
225
226
    /**
227
     * Get the formatted start date.
228
     * @return int As UTC timestamp.
229
     */
230 1
    public function getStart()
231
    {
232 1
        return $this->start;
233
    }
234
235
    /**
236
     * Get the formatted end date.
237
     * @return int As UTC timestamp.
238
     */
239 1
    public function getEnd()
240
    {
241 1
        return $this->end;
242
    }
243
244
    /**
245
     * Get the total number of admins (users currently with qualifying permissions).
246
     * @return int
247
     */
248 1
    public function numAdmins()
249
    {
250 1
        return count($this->admins);
251
    }
252
253
    /**
254
     * Number of admins who made any actions within the time period.
255
     * @return int
256
     */
257
    public function getNumAdminsWithActions()
258
    {
259
        return $this->numAdminsWithActions;
260
    }
261
262
    /**
263
     * Number of currently non-admins who made any actions within the time period.
264
     * @return int
265
     */
266 1
    public function getNumNonAdminsWithActions()
267
    {
268 1
        return count($this->adminStats) - $this->numAdminsWithActions;
269
    }
270
}
271