Passed
Push — master ( 1c40ea...a41f56 )
by MusikAnimal
07:57
created

AdminStats::prepareStats()   A

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 0
dl 0
loc 25
rs 9.8666
c 0
b 0
f 0
ccs 13
cts 14
cp 0.9286
crap 4.0058
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[]|null $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 = 'admin',
47
        ?array $actions = null
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
        // UTC to YYYYMMDDHHMMSS.
93 2
        $startDb = date('Ymd000000', $this->start);
0 ignored issues
show
Bug introduced by
It seems like $this->start can also be of type boolean and string; 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

93
        $startDb = date('Ymd000000', /** @scrutinizer ignore-type */ $this->start);
Loading history...
94 2
        $endDb = date('Ymd235959', $this->end);
95
96 2
        $stats = $this->getRepository()->getStats($this->project, $startDb, $endDb, $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

96
        $stats = $this->getRepository()->/** @scrutinizer ignore-call */ getStats($this->project, $startDb, $endDb, $this->type, $this->actions);
Loading history...
97
98
        // Group by username.
99 2
        $stats = $this->groupStatsByUsername($stats);
100
101
        // Resort, as for some reason the SQL isn't doing this properly.
102 2
        uasort($stats, function ($a, $b) {
103 2
            if ($a['total'] === $b['total']) {
104
                return 0;
105
            }
106 2
            return $a['total'] < $b['total'] ? 1 : -1;
107 2
        });
108
109 2
        $this->adminStats = $stats;
110 2
        return $this->adminStats;
111
    }
112
113
    /**
114
     * Get users of the project that are capable of making the relevant actions,
115
     * keyed by user name, with the user groups as the values.
116
     * @return string[][]
117
     */
118 3
    public function getUsersAndGroups(): array
119
    {
120 3
        if (isset($this->usersAndGroups)) {
121
            return $this->usersAndGroups;
122
        }
123
124
        /**
125
         * Each user group that is considered capable of making the relevant actions for $this->group.
126
         * @var string[]
127
         */
128 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

128
        $groupUserGroups = $this->getRepository()->/** @scrutinizer ignore-call */ getUserGroups($this->project, $this->type);
Loading history...
129
130 3
        $this->usersAndGroups = $this->project->getUsersInGroups($groupUserGroups);
131
132
        // Populate $this->usersInGroup with users who are in the relevant user group for $this->group.
133 3
        $this->usersInGroup = array_keys(array_filter($this->usersAndGroups, function ($groups) {
134 3
            return in_array($this->getRelevantUserGroup(), $groups);
135 3
        }));
136
137 3
        return $this->usersAndGroups;
138
    }
139
140
    public function getUserGroupIcons(): array
141
    {
142
        // Quick cache, valid only for the same request.
143
        static $userGroupIcons = null;
144
        if (null !== $userGroupIcons) {
145
            return $userGroupIcons;
146
        }
147
        $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

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