This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
1 | <?php |
||||
2 | |||||
3 | declare(strict_types = 1); |
||||
4 | |||||
5 | namespace App\Model; |
||||
6 | |||||
7 | use App\Repository\AdminStatsRepository; |
||||
8 | |||||
9 | /** |
||||
10 | * AdminStats returns information about users with rights defined in admin_stats.yaml. |
||||
11 | */ |
||||
12 | class AdminStats extends Model |
||||
13 | { |
||||
14 | |||||
15 | /** @var string[][] Keyed by user name, values are arrays containing actions and counts. */ |
||||
16 | protected array $adminStats; |
||||
17 | |||||
18 | /** @var string[] Keys are user names, values are their user groups. */ |
||||
19 | protected array $usersAndGroups; |
||||
20 | |||||
21 | /** @var int Number of users in the relevant group who made any actions within the time period. */ |
||||
22 | protected int $numWithActions = 0; |
||||
23 | |||||
24 | /** @var string[] Usernames of users who are in the relevant user group (sysop for admins, etc.). */ |
||||
25 | private array $usersInGroup = []; |
||||
26 | |||||
27 | /** @var string Type that we're getting stats for (admin, patroller, steward, etc.). See admin_stats.yaml */ |
||||
28 | private string $type; |
||||
29 | |||||
30 | /** @var string[] Which actions to show ('block', 'protect', etc.) */ |
||||
31 | private array $actions; |
||||
32 | |||||
33 | /** |
||||
34 | * AdminStats constructor. |
||||
35 | * @param AdminStatsRepository $repository |
||||
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.yaml for possible values. |
||||
40 | * @param string[] $actions Which actions to query for ('block', 'protect', etc.). Null for all actions. |
||||
41 | */ |
||||
42 | public function __construct( |
||||
43 | AdminStatsRepository $repository, |
||||
44 | Project $project, |
||||
45 | int $start, |
||||
46 | int $end, |
||||
47 | string $group, |
||||
48 | array $actions |
||||
49 | ) { |
||||
50 | $this->repository = $repository; |
||||
51 | $this->project = $project; |
||||
52 | $this->start = $start; |
||||
53 | $this->end = $end; |
||||
54 | $this->type = $group; |
||||
55 | $this->actions = $actions; |
||||
56 | } |
||||
57 | |||||
58 | /** |
||||
59 | * Get the group for this AdminStats. |
||||
60 | * @return string |
||||
61 | */ |
||||
62 | public function getType(): string |
||||
63 | { |
||||
64 | return $this->type; |
||||
65 | } |
||||
66 | |||||
67 | /** |
||||
68 | * Get the user_group from the config given the 'group'. |
||||
69 | * @return string |
||||
70 | */ |
||||
71 | public function getRelevantUserGroup(): string |
||||
72 | { |
||||
73 | // Quick cache, valid only for the same request. |
||||
74 | static $relevantUserGroup = ''; |
||||
75 | if ('' !== $relevantUserGroup) { |
||||
76 | return $relevantUserGroup; |
||||
77 | } |
||||
78 | |||||
79 | return $relevantUserGroup = $this->getRepository()->getRelevantUserGroup($this->type); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
80 | } |
||||
81 | |||||
82 | /** |
||||
83 | * Get the array of statistics for each qualifying user. This may be called ahead of self::getStats() so certain |
||||
84 | * class-level properties will be supplied (such as self::numUsers(), which is called in the view before iterating |
||||
85 | * over the master array of statistics). |
||||
86 | * @return string[] |
||||
87 | */ |
||||
88 | public function prepareStats(): array |
||||
89 | { |
||||
90 | if (isset($this->adminStats)) { |
||||
91 | return $this->adminStats; |
||||
92 | } |
||||
93 | |||||
94 | $stats = $this->getRepository() |
||||
95 | ->getStats($this->project, $this->start, $this->end, $this->type, $this->actions); |
||||
0 ignored issues
–
show
The method
getStats() does not exist on App\Repository\Repository . It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\AdminStatsRepository .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
96 | |||||
97 | // Group by username. |
||||
98 | $stats = $this->groupStatsByUsername($stats); |
||||
99 | |||||
100 | // Resort, as for some reason the SQL doesn't do this properly. |
||||
101 | uasort($stats, function ($a, $b) { |
||||
102 | if ($a['total'] === $b['total']) { |
||||
103 | return 0; |
||||
104 | } |
||||
105 | return $a['total'] < $b['total'] ? 1 : -1; |
||||
106 | }); |
||||
107 | |||||
108 | $this->adminStats = $stats; |
||||
109 | return $this->adminStats; |
||||
110 | } |
||||
111 | |||||
112 | /** |
||||
113 | * Get users of the project that are capable of making the relevant actions, |
||||
114 | * keyed by user name, with the user groups as the values. |
||||
115 | * @return string[][] |
||||
116 | */ |
||||
117 | public function getUsersAndGroups(): array |
||||
118 | { |
||||
119 | if (isset($this->usersAndGroups)) { |
||||
120 | return $this->usersAndGroups; |
||||
121 | } |
||||
122 | |||||
123 | // All the user groups that are considered capable of making the relevant actions for $this->group. |
||||
124 | $groupUserGroups = $this->getRepository()->getUserGroups($this->project, $this->type); |
||||
0 ignored issues
–
show
The method
getUserGroups() does not exist on App\Repository\Repository . It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\AdminStatsRepository .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
125 | |||||
126 | $this->usersAndGroups = $this->project->getUsersInGroups($groupUserGroups['local'], $groupUserGroups['global']); |
||||
127 | |||||
128 | // Populate $this->usersInGroup with users who are in the relevant user group for $this->group. |
||||
129 | $this->usersInGroup = array_keys(array_filter($this->usersAndGroups, function ($groups) { |
||||
130 | return in_array($this->getRelevantUserGroup(), $groups); |
||||
131 | })); |
||||
132 | |||||
133 | return $this->usersAndGroups; |
||||
134 | } |
||||
135 | |||||
136 | /** |
||||
137 | * Get all user groups with permissions applicable to the $this->group. |
||||
138 | * @param bool $wikiPath Whether to return the title for the on-wiki image, instead of full URL. |
||||
139 | * @return array Each entry contains 'name' (user group) and 'rights' (the permissions). |
||||
140 | */ |
||||
141 | public function getUserGroupIcons(bool $wikiPath = false): array |
||||
142 | { |
||||
143 | // Quick cache, valid only for the same request. |
||||
144 | static $userGroupIcons = null; |
||||
145 | if (null !== $userGroupIcons) { |
||||
146 | $out = $userGroupIcons; |
||||
147 | } else { |
||||
148 | $out = $userGroupIcons = $this->getRepository()->getUserGroupIcons(); |
||||
0 ignored issues
–
show
The method
getUserGroupIcons() does not exist on App\Repository\Repository . It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\AdminStatsRepository .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
149 | } |
||||
150 | |||||
151 | if ($wikiPath) { |
||||
152 | $out = array_map(function ($url) { |
||||
153 | return str_replace('.svg.png', '.svg', preg_replace('/.*\/18px-/', '', $url)); |
||||
154 | }, $out); |
||||
155 | } |
||||
156 | |||||
157 | return $out; |
||||
158 | } |
||||
159 | |||||
160 | /** |
||||
161 | * The number of days we're spanning between the start and end date. |
||||
162 | * @return int |
||||
163 | */ |
||||
164 | public function numDays(): int |
||||
165 | { |
||||
166 | return (int)(($this->end - $this->start) / 60 / 60 / 24) + 1; |
||||
167 | } |
||||
168 | |||||
169 | /** |
||||
170 | * Get the master array of statistics for each qualifying user. |
||||
171 | * @return string[] |
||||
172 | */ |
||||
173 | public function getStats(): array |
||||
174 | { |
||||
175 | if (isset($this->adminStats)) { |
||||
176 | $this->adminStats = $this->prepareStats(); |
||||
177 | } |
||||
178 | return $this->adminStats; |
||||
179 | } |
||||
180 | |||||
181 | /** |
||||
182 | * Get the actions that are shown as columns in the view. |
||||
183 | * @return string[] Each the i18n key of the action. |
||||
184 | */ |
||||
185 | public function getActions(): array |
||||
186 | { |
||||
187 | return count($this->getStats()) > 0 |
||||
188 | ? array_diff(array_keys(array_values($this->getStats())[0]), ['username', 'user-groups', 'total']) |
||||
189 | : []; |
||||
190 | } |
||||
191 | |||||
192 | /** |
||||
193 | * Given the data returned by AdminStatsRepository::getStats, return the stats keyed by user name, |
||||
194 | * adding in a key/value for user groups. |
||||
195 | * @param string[][] $data As retrieved by AdminStatsRepository::getStats |
||||
196 | * @return string[] Stats keyed by user name. |
||||
197 | * Functionality covered in test for self::getStats(). |
||||
198 | * @codeCoverageIgnore |
||||
199 | */ |
||||
200 | private function groupStatsByUsername(array $data): array |
||||
201 | { |
||||
202 | $usersAndGroups = $this->getUsersAndGroups(); |
||||
203 | $users = []; |
||||
204 | |||||
205 | foreach ($data as $datum) { |
||||
206 | $username = $datum['username']; |
||||
207 | |||||
208 | // Push to array containing all users with admin actions. |
||||
209 | // We also want numerical values to be integers. |
||||
210 | $users[$username] = array_map('intval', $datum); |
||||
211 | |||||
212 | // Push back username which was casted to an integer. |
||||
213 | $users[$username]['username'] = $username; |
||||
214 | |||||
215 | // Set the 'user-groups' property with the user groups they belong to (if any), |
||||
216 | // going off of self::getUsersAndGroups(). |
||||
217 | if (isset($usersAndGroups[$username])) { |
||||
218 | $users[$username]['user-groups'] = $usersAndGroups[$username]; |
||||
219 | } else { |
||||
220 | $users[$username]['user-groups'] = []; |
||||
221 | } |
||||
222 | |||||
223 | // Keep track of users who are not in the relevant user group but made applicable actions. |
||||
224 | if (in_array($username, $this->usersInGroup)) { |
||||
225 | $this->numWithActions++; |
||||
226 | } |
||||
227 | } |
||||
228 | |||||
229 | return $users; |
||||
230 | } |
||||
231 | |||||
232 | /** |
||||
233 | * Get the total number of users in the relevant user group. |
||||
234 | * @return int |
||||
235 | */ |
||||
236 | public function getNumInRelevantUserGroup(): int |
||||
237 | { |
||||
238 | return count($this->usersInGroup); |
||||
239 | } |
||||
240 | |||||
241 | /** |
||||
242 | * Number of users who made any relevant actions within the time period. |
||||
243 | * @return int |
||||
244 | */ |
||||
245 | public function getNumWithActions(): int |
||||
246 | { |
||||
247 | return $this->numWithActions; |
||||
248 | } |
||||
249 | |||||
250 | /** |
||||
251 | * Number of currently users who made any actions within the time period who are not in the relevant user group. |
||||
252 | * @return int |
||||
253 | */ |
||||
254 | public function getNumWithActionsNotInGroup(): int |
||||
255 | { |
||||
256 | return count($this->adminStats) - $this->numWithActions; |
||||
257 | } |
||||
258 | } |
||||
259 |