x-tools /
xtools
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
Loading history...
|
|||||
| 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
Loading history...
|
|||||
| 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
Loading history...
|
|||||
| 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
Loading history...
|
|||||
| 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 |