Passed
Push — master ( 95d2e4...8875f5 )
by MusikAnimal
05:41
created

AdminStats::getEnd()   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
namespace Xtools;
7
8
/**
9
 * AdminStats returns information about users with administrative
10
 * rights on a given wiki.
11
 */
12
class AdminStats extends Model
13
{
14
    /** @var string[] Keyed by user name, values are arrays containing actions and counts. */
15
    protected $adminStats;
16
17
    /**
18
     * Keys are user names, values are their abbreviated user groups.
19
     * If abbreviations are turned on, this will instead be a string of the abbreviated
20
     * user groups, separated by slashes.
21
     * @var string[]|string
22
     */
23
    protected $adminsAndGroups;
24
25
    /** @var int Number of admins who made any actions within the time period. */
26
    protected $numAdminsWithActions = 0;
27
28
    /** @var string[] Usernames of proper sysops. */
29
    private $admins = [];
30
31
    /**
32
     * TopEdits constructor.
33
     * @param Project $project
34
     * @param int $start as UTC timestamp.
35
     * @param int $end as UTC timestamp.
36
     */
37 3
    public function __construct(Project $project, $start, $end)
38
    {
39 3
        $this->project = $project;
40 3
        $this->start = $start;
41 3
        $this->end = $end;
42 3
    }
43
44
    /**
45
     * Get users of the project that are capable of making 'admin actions',
46
     * keyed by user name with abbreviations for the user groups as the values.
47
     * @param bool $abbreviate If set, the keys of the result with be a string containing
48
     *   abbreviated versions of their user groups, such as 'A' instead of administrator,
49
     *   'CU' instead of CheckUser, etc. If $abbreviate is false, the keys of the result
50
     *   will be an array of the full-named user groups.
51
     * @see Project::getAdmins()
52
     * @return string[]
53
     */
54 3
    public function getAdminsAndGroups($abbreviate = true)
55
    {
56 3
        if ($this->adminsAndGroups) {
57
            return $this->adminsAndGroups;
58
        }
59
60
        /**
61
         * Each user group that is considered capable of making 'admin actions'.
62
         * @var string[]
63
         */
64 3
        $adminGroups = $this->getRepository()->getAdminGroups($this->project);
65
66
        /** @var array Keys are the usernames, values are thier user groups. */
67 3
        $admins = $this->project->getUsersInGroups($adminGroups);
68
69 3
        if ($abbreviate === false) {
70 1
            return $admins;
71
        }
72
73
        /**
74
         * Keys are the database-stored names, values are the abbreviations.
75
         * FIXME: i18n this somehow.
76
         * @var string[]
77
         */
78
        $userGroupAbbrMap = [
79 3
            'sysop' => 'A',
80
            'bureaucrat' => 'B',
81
            'steward' => 'S',
82
            'checkuser' => 'CU',
83
            'oversight' => 'OS',
84
            'bot' => 'Bot',
85
        ];
86
87 3
        foreach ($admins as $admin => $groups) {
88 3
            $abbrGroups = [];
89
90
            // Keep track of actual number of sysops.
91 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

91
            if (in_array('sysop', /** @scrutinizer ignore-type */ $groups)) {
Loading history...
92 3
                $this->admins[] = $admin;
93
            }
94
95 3
            foreach ($groups as $group) {
0 ignored issues
show
Bug introduced by
The expression $groups of type string is not traversable.
Loading history...
96 3
                if (isset($userGroupAbbrMap[$group])) {
97 3
                    $abbrGroups[] = $userGroupAbbrMap[$group];
98
                }
99
            }
100
101
            // Make 'A' (admin) come before 'CU' (CheckUser), etc.
102 3
            sort($abbrGroups);
103
104 3
            $this->adminsAndGroups[$admin] = implode('/', $abbrGroups);
105
        }
106
107 3
        return $this->adminsAndGroups;
108
    }
109
110
    /**
111
     * The number of days we're spanning between the start and end date.
112
     * @return int
113
     */
114 1
    public function numDays()
115
    {
116 1
        return ($this->end - $this->start) / 60 / 60 / 24;
117
    }
118
119
    /**
120
     * Get the array of statistics for each qualifying user. This may be called
121
     * ahead of self::getStats() so certain class-level properties will be supplied
122
     * (such as self::numUsers(), which is called in the view before iterating
123
     * over the master array of statistics).
124
     * @param boolean $abbreviateGroups If set, the 'groups' list will be
125
     *   a string with abbreivated user groups names, as opposed to an array
126
     *   of full-named user groups.
127
     * @return string[]
128
     */
129 2
    public function prepareStats($abbreviateGroups = true)
130
    {
131 2
        if (isset($this->adminStats)) {
132 1
            return $this->adminStats;
133
        }
134
135
        // UTC to YYYYMMDDHHMMSS.
136 2
        $startDb = date('Ymd000000', $this->start);
0 ignored issues
show
Bug introduced by
It seems like $this->start can also be of type string and boolean; however, parameter $timestamp of date() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

136
        $startDb = date('Ymd000000', /** @scrutinizer ignore-type */ $this->start);
Loading history...
137 2
        $endDb = date('Ymd235959', $this->end);
138
139 2
        $stats = $this->getRepository()->getStats($this->project, $startDb, $endDb);
140
141
        // Group by username.
142 2
        $stats = $this->groupAdminStatsByUsername($stats, $abbreviateGroups);
143
144
        // Resort, as for some reason the SQL isn't doing this properly.
145 2
        uasort($stats, function ($a, $b) {
146 2
            if ($a['total'] === $b['total']) {
147
                return 0;
148
            }
149 2
            return ($a['total'] < $b['total']) ? 1 : -1;
150 2
        });
151
152 2
        $this->adminStats = $stats;
153 2
        return $this->adminStats;
154
    }
155
156
    /**
157
     * Get the master array of statistics for each qualifying user.
158
     * @param boolean $abbreviateGroups If set, the 'groups' list will be
159
     *   a string with abbreviated user groups names, as opposed to an array
160
     *   of full-named user groups.
161
     * @return string[]
162
     */
163 1
    public function getStats($abbreviateGroups = true)
164
    {
165 1
        if (isset($this->adminStats)) {
166 1
            $this->adminStats = $this->prepareStats($abbreviateGroups);
167
        }
168 1
        return $this->adminStats;
169
    }
170
171
    /**
172
     * Given the data returned by AdminStatsRepository::getStats,
173
     * return the stats keyed by user name, adding in a key/value for user groups.
174
     * @param string[] $data As retrieved by AdminStatsRepository::getStats
175
     * @param boolean $abbreviateGroups If set, the 'groups' list will be
176
     *   a string with abbreviated user groups names, as opposed to an array
177
     *   of full-named user groups.
178
     * @return string[] Stats keyed by user name.
179
     * Functionality covered in test for self::getStats().
180
     * @codeCoverageIgnore
181
     */
182
    private function groupAdminStatsByUsername($data, $abbreviateGroups = true)
183
    {
184
        $adminsAndGroups = $this->getAdminsAndGroups($abbreviateGroups);
185
        $users = [];
186
187
        foreach ($data as $datum) {
188
            $username = $datum['user_name'];
189
190
            // Push to array containing all users with admin actions.
191
            // We also want numerical values to be integers.
192
            $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

192
            $users[$username] = array_map('intval', /** @scrutinizer ignore-type */ $datum);
Loading history...
193
194
            // Push back username which was casted to an integer.
195
            $users[$username]['user_name'] = $username;
196
197
            // Set the 'groups' property with the user groups they belong to (if any),
198
            // going off of self::getAdminsAndGroups().
199
            if (isset($adminsAndGroups[$username])) {
200
                $users[$username]['groups'] = $adminsAndGroups[$username];
201
            } else {
202
                $users[$username]['groups'] = $abbreviateGroups ? '' : [];
203
            }
204
205
            // Keep track of non-admins who made admin actions.
206
            if (in_array($username, $this->admins)) {
207
                $this->numAdminsWithActions++;
208
            }
209
        }
210
211
        return $users;
212
    }
213
214
    /**
215
     * Get the total number of admins (users currently with qualifying permissions).
216
     * @return int
217
     */
218 1
    public function numAdmins()
219
    {
220 1
        return count($this->admins);
221
    }
222
223
    /**
224
     * Number of admins who made any actions within the time period.
225
     * @return int
226
     */
227
    public function getNumAdminsWithActions()
228
    {
229
        return $this->numAdminsWithActions;
230
    }
231
232
    /**
233
     * Number of currently non-admins who made any actions within the time period.
234
     * @return int
235
     */
236 1
    public function getNumNonAdminsWithActions()
237
    {
238 1
        return count($this->adminStats) - $this->numAdminsWithActions;
239
    }
240
}
241