wikimedia /
mediawiki
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.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | /** |
||
| 3 | * Accessors and mutators for the site-wide statistics. |
||
| 4 | * |
||
| 5 | * This program is free software; you can redistribute it and/or modify |
||
| 6 | * it under the terms of the GNU General Public License as published by |
||
| 7 | * the Free Software Foundation; either version 2 of the License, or |
||
| 8 | * (at your option) any later version. |
||
| 9 | * |
||
| 10 | * This program is distributed in the hope that it will be useful, |
||
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
| 13 | * GNU General Public License for more details. |
||
| 14 | * |
||
| 15 | * You should have received a copy of the GNU General Public License along |
||
| 16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
| 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
| 18 | * http://www.gnu.org/copyleft/gpl.html |
||
| 19 | * |
||
| 20 | * @file |
||
| 21 | */ |
||
| 22 | |||
| 23 | /** |
||
| 24 | * Static accessor class for site_stats and related things |
||
| 25 | */ |
||
| 26 | class SiteStats { |
||
| 27 | /** @var bool|stdClass */ |
||
| 28 | private static $row; |
||
| 29 | |||
| 30 | /** @var bool */ |
||
| 31 | private static $loaded = false; |
||
| 32 | |||
| 33 | /** @var int */ |
||
| 34 | private static $jobs; |
||
| 35 | |||
| 36 | /** @var int[] */ |
||
| 37 | private static $pageCount = []; |
||
| 38 | |||
| 39 | static function unload() { |
||
| 40 | self::$loaded = false; |
||
| 41 | } |
||
| 42 | |||
| 43 | static function recache() { |
||
| 44 | self::load( true ); |
||
| 45 | } |
||
| 46 | |||
| 47 | /** |
||
| 48 | * @param bool $recache |
||
| 49 | */ |
||
| 50 | static function load( $recache = false ) { |
||
| 51 | if ( self::$loaded && !$recache ) { |
||
| 52 | return; |
||
| 53 | } |
||
| 54 | |||
| 55 | self::$row = self::loadAndLazyInit(); |
||
| 56 | |||
| 57 | # This code is somewhat schema-agnostic, because I'm changing it in a minor release -- TS |
||
| 58 | if ( !isset( self::$row->ss_total_pages ) && self::$row->ss_total_pages == -1 ) { |
||
| 59 | # Update schema |
||
| 60 | $u = new SiteStatsUpdate( 0, 0, 0 ); |
||
| 61 | $u->doUpdate(); |
||
| 62 | self::$row = self::doLoad( wfGetDB( DB_REPLICA ) ); |
||
| 63 | } |
||
| 64 | |||
| 65 | self::$loaded = true; |
||
| 66 | } |
||
| 67 | |||
| 68 | /** |
||
| 69 | * @return bool|stdClass |
||
| 70 | */ |
||
| 71 | static function loadAndLazyInit() { |
||
| 72 | global $wgMiserMode; |
||
| 73 | |||
| 74 | wfDebug( __METHOD__ . ": reading site_stats from replica DB\n" ); |
||
| 75 | $row = self::doLoad( wfGetDB( DB_REPLICA ) ); |
||
| 76 | |||
| 77 | if ( !self::isSane( $row ) ) { |
||
| 78 | // Might have just been initialized during this request? Underflow? |
||
| 79 | wfDebug( __METHOD__ . ": site_stats damaged or missing on replica DB\n" ); |
||
| 80 | $row = self::doLoad( wfGetDB( DB_MASTER ) ); |
||
| 81 | } |
||
| 82 | |||
| 83 | if ( !$wgMiserMode && !self::isSane( $row ) ) { |
||
| 84 | // Normally the site_stats table is initialized at install time. |
||
| 85 | // Some manual construction scenarios may leave the table empty or |
||
| 86 | // broken, however, for instance when importing from a dump into a |
||
| 87 | // clean schema with mwdumper. |
||
| 88 | wfDebug( __METHOD__ . ": initializing damaged or missing site_stats\n" ); |
||
| 89 | |||
| 90 | SiteStatsInit::doAllAndCommit( wfGetDB( DB_REPLICA ) ); |
||
| 91 | |||
| 92 | $row = self::doLoad( wfGetDB( DB_MASTER ) ); |
||
| 93 | } |
||
| 94 | |||
| 95 | if ( !self::isSane( $row ) ) { |
||
| 96 | wfDebug( __METHOD__ . ": site_stats persistently nonsensical o_O\n" ); |
||
| 97 | } |
||
| 98 | return $row; |
||
| 99 | } |
||
| 100 | |||
| 101 | /** |
||
| 102 | * @param IDatabase $db |
||
| 103 | * @return bool|stdClass |
||
| 104 | */ |
||
| 105 | static function doLoad( $db ) { |
||
| 106 | return $db->selectRow( 'site_stats', [ |
||
| 107 | 'ss_row_id', |
||
| 108 | 'ss_total_edits', |
||
| 109 | 'ss_good_articles', |
||
| 110 | 'ss_total_pages', |
||
| 111 | 'ss_users', |
||
| 112 | 'ss_active_users', |
||
| 113 | 'ss_images', |
||
| 114 | ], [], __METHOD__ ); |
||
| 115 | } |
||
| 116 | |||
| 117 | /** |
||
| 118 | * Return the total number of page views. Except we don't track those anymore. |
||
| 119 | * Stop calling this function, it will be removed some time in the future. It's |
||
| 120 | * kept here simply to prevent fatal errors. |
||
| 121 | * |
||
| 122 | * @deprecated since 1.25 |
||
| 123 | * @return int |
||
| 124 | */ |
||
| 125 | static function views() { |
||
| 126 | wfDeprecated( __METHOD__, '1.25' ); |
||
| 127 | return 0; |
||
| 128 | } |
||
| 129 | |||
| 130 | /** |
||
| 131 | * @return int |
||
| 132 | */ |
||
| 133 | static function edits() { |
||
| 134 | self::load(); |
||
| 135 | return self::$row->ss_total_edits; |
||
| 136 | } |
||
| 137 | |||
| 138 | /** |
||
| 139 | * @return int |
||
| 140 | */ |
||
| 141 | static function articles() { |
||
| 142 | self::load(); |
||
| 143 | return self::$row->ss_good_articles; |
||
| 144 | } |
||
| 145 | |||
| 146 | /** |
||
| 147 | * @return int |
||
| 148 | */ |
||
| 149 | static function pages() { |
||
| 150 | self::load(); |
||
| 151 | return self::$row->ss_total_pages; |
||
| 152 | } |
||
| 153 | |||
| 154 | /** |
||
| 155 | * @return int |
||
| 156 | */ |
||
| 157 | static function users() { |
||
| 158 | self::load(); |
||
| 159 | return self::$row->ss_users; |
||
| 160 | } |
||
| 161 | |||
| 162 | /** |
||
| 163 | * @return int |
||
| 164 | */ |
||
| 165 | static function activeUsers() { |
||
| 166 | self::load(); |
||
| 167 | return self::$row->ss_active_users; |
||
| 168 | } |
||
| 169 | |||
| 170 | /** |
||
| 171 | * @return int |
||
| 172 | */ |
||
| 173 | static function images() { |
||
| 174 | self::load(); |
||
| 175 | return self::$row->ss_images; |
||
| 176 | } |
||
| 177 | |||
| 178 | /** |
||
| 179 | * Find the number of users in a given user group. |
||
| 180 | * @param string $group Name of group |
||
| 181 | * @return int |
||
| 182 | */ |
||
| 183 | static function numberingroup( $group ) { |
||
| 184 | $cache = ObjectCache::getMainWANInstance(); |
||
| 185 | return $cache->getWithSetCallback( |
||
| 186 | wfMemcKey( 'SiteStats', 'groupcounts', $group ), |
||
| 187 | $cache::TTL_HOUR, |
||
| 188 | function ( $oldValue, &$ttl, array &$setOpts ) use ( $group ) { |
||
| 189 | $dbr = wfGetDB( DB_REPLICA ); |
||
| 190 | |||
| 191 | $setOpts += Database::getCacheSetOptions( $dbr ); |
||
| 192 | |||
| 193 | return $dbr->selectField( |
||
| 194 | 'user_groups', |
||
| 195 | 'COUNT(*)', |
||
| 196 | [ 'ug_group' => $group ], |
||
| 197 | __METHOD__ |
||
| 198 | ); |
||
| 199 | }, |
||
| 200 | [ 'pcTTL' => $cache::TTL_PROC_LONG ] |
||
| 201 | ); |
||
| 202 | } |
||
| 203 | |||
| 204 | /** |
||
| 205 | * @return int |
||
| 206 | */ |
||
| 207 | static function jobs() { |
||
| 208 | if ( !isset( self::$jobs ) ) { |
||
| 209 | try{ |
||
| 210 | self::$jobs = array_sum( JobQueueGroup::singleton()->getQueueSizes() ); |
||
| 211 | } catch ( JobQueueError $e ) { |
||
| 212 | self::$jobs = 0; |
||
| 213 | } |
||
| 214 | /** |
||
| 215 | * Zero rows still do single row read for row that doesn't exist, |
||
| 216 | * but people are annoyed by that |
||
| 217 | */ |
||
| 218 | if ( self::$jobs == 1 ) { |
||
| 219 | self::$jobs = 0; |
||
| 220 | } |
||
| 221 | } |
||
| 222 | return self::$jobs; |
||
| 223 | } |
||
| 224 | |||
| 225 | /** |
||
| 226 | * @param int $ns |
||
| 227 | * |
||
| 228 | * @return int |
||
| 229 | */ |
||
| 230 | static function pagesInNs( $ns ) { |
||
| 231 | if ( !isset( self::$pageCount[$ns] ) ) { |
||
| 232 | $dbr = wfGetDB( DB_REPLICA ); |
||
| 233 | self::$pageCount[$ns] = (int)$dbr->selectField( |
||
| 234 | 'page', |
||
| 235 | 'COUNT(*)', |
||
| 236 | [ 'page_namespace' => $ns ], |
||
| 237 | __METHOD__ |
||
| 238 | ); |
||
| 239 | } |
||
| 240 | return self::$pageCount[$ns]; |
||
| 241 | } |
||
| 242 | |||
| 243 | /** |
||
| 244 | * Is the provided row of site stats sane, or should it be regenerated? |
||
| 245 | * |
||
| 246 | * Checks only fields which are filled by SiteStatsInit::refresh. |
||
| 247 | * |
||
| 248 | * @param bool|object $row |
||
| 249 | * |
||
| 250 | * @return bool |
||
| 251 | */ |
||
| 252 | private static function isSane( $row ) { |
||
| 253 | if ( $row === false |
||
| 254 | || $row->ss_total_pages < $row->ss_good_articles |
||
| 255 | || $row->ss_total_edits < $row->ss_total_pages |
||
| 256 | ) { |
||
| 257 | return false; |
||
| 258 | } |
||
| 259 | // Now check for underflow/overflow |
||
| 260 | foreach ( [ |
||
| 261 | 'ss_total_edits', |
||
| 262 | 'ss_good_articles', |
||
| 263 | 'ss_total_pages', |
||
| 264 | 'ss_users', |
||
| 265 | 'ss_images', |
||
| 266 | ] as $member ) { |
||
| 267 | if ( $row->$member > 2000000000 || $row->$member < 0 ) { |
||
| 268 | return false; |
||
| 269 | } |
||
| 270 | } |
||
| 271 | return true; |
||
| 272 | } |
||
| 273 | } |
||
| 274 | |||
| 275 | /** |
||
| 276 | * Class designed for counting of stats. |
||
| 277 | */ |
||
| 278 | class SiteStatsInit { |
||
| 279 | |||
| 280 | // Database connection |
||
| 281 | private $db; |
||
| 282 | |||
| 283 | // Various stats |
||
| 284 | private $mEdits = null, $mArticles = null, $mPages = null; |
||
|
0 ignored issues
–
show
|
|||
| 285 | private $mUsers = null, $mFiles = null; |
||
| 286 | |||
| 287 | /** |
||
| 288 | * Constructor |
||
| 289 | * @param bool|IDatabase $database |
||
| 290 | * - boolean: Whether to use the master DB |
||
| 291 | * - IDatabase: Database connection to use |
||
| 292 | */ |
||
| 293 | public function __construct( $database = false ) { |
||
| 294 | if ( $database instanceof IDatabase ) { |
||
| 295 | $this->db = $database; |
||
| 296 | } elseif ( $database ) { |
||
| 297 | $this->db = wfGetDB( DB_MASTER ); |
||
| 298 | } else { |
||
| 299 | $this->db = wfGetDB( DB_REPLICA, 'vslow' ); |
||
| 300 | } |
||
| 301 | } |
||
| 302 | |||
| 303 | /** |
||
| 304 | * Count the total number of edits |
||
| 305 | * @return int |
||
| 306 | */ |
||
| 307 | public function edits() { |
||
| 308 | $this->mEdits = $this->db->selectField( 'revision', 'COUNT(*)', '', __METHOD__ ); |
||
| 309 | $this->mEdits += $this->db->selectField( 'archive', 'COUNT(*)', '', __METHOD__ ); |
||
| 310 | return $this->mEdits; |
||
| 311 | } |
||
| 312 | |||
| 313 | /** |
||
| 314 | * Count pages in article space(s) |
||
| 315 | * @return int |
||
| 316 | */ |
||
| 317 | public function articles() { |
||
| 318 | global $wgArticleCountMethod; |
||
| 319 | |||
| 320 | $tables = [ 'page' ]; |
||
| 321 | $conds = [ |
||
| 322 | 'page_namespace' => MWNamespace::getContentNamespaces(), |
||
| 323 | 'page_is_redirect' => 0, |
||
| 324 | ]; |
||
| 325 | |||
| 326 | if ( $wgArticleCountMethod == 'link' ) { |
||
| 327 | $tables[] = 'pagelinks'; |
||
| 328 | $conds[] = 'pl_from=page_id'; |
||
| 329 | } elseif ( $wgArticleCountMethod == 'comma' ) { |
||
| 330 | // To make a correct check for this, we would need, for each page, |
||
| 331 | // to load the text, maybe uncompress it, maybe decode it and then |
||
| 332 | // check if there's one comma. |
||
| 333 | // But one thing we are sure is that if the page is empty, it can't |
||
| 334 | // contain a comma :) |
||
| 335 | $conds[] = 'page_len > 0'; |
||
| 336 | } |
||
| 337 | |||
| 338 | $this->mArticles = $this->db->selectField( $tables, 'COUNT(DISTINCT page_id)', |
||
| 339 | $conds, __METHOD__ ); |
||
| 340 | return $this->mArticles; |
||
| 341 | } |
||
| 342 | |||
| 343 | /** |
||
| 344 | * Count total pages |
||
| 345 | * @return int |
||
| 346 | */ |
||
| 347 | public function pages() { |
||
| 348 | $this->mPages = $this->db->selectField( 'page', 'COUNT(*)', '', __METHOD__ ); |
||
| 349 | return $this->mPages; |
||
| 350 | } |
||
| 351 | |||
| 352 | /** |
||
| 353 | * Count total users |
||
| 354 | * @return int |
||
| 355 | */ |
||
| 356 | public function users() { |
||
| 357 | $this->mUsers = $this->db->selectField( 'user', 'COUNT(*)', '', __METHOD__ ); |
||
| 358 | return $this->mUsers; |
||
| 359 | } |
||
| 360 | |||
| 361 | /** |
||
| 362 | * Count total files |
||
| 363 | * @return int |
||
| 364 | */ |
||
| 365 | public function files() { |
||
| 366 | $this->mFiles = $this->db->selectField( 'image', 'COUNT(*)', '', __METHOD__ ); |
||
| 367 | return $this->mFiles; |
||
| 368 | } |
||
| 369 | |||
| 370 | /** |
||
| 371 | * Do all updates and commit them. More or less a replacement |
||
| 372 | * for the original initStats, but without output. |
||
| 373 | * |
||
| 374 | * @param IDatabase|bool $database |
||
| 375 | * - boolean: Whether to use the master DB |
||
| 376 | * - IDatabase: Database connection to use |
||
| 377 | * @param array $options Array of options, may contain the following values |
||
| 378 | * - activeUsers boolean: Whether to update the number of active users (default: false) |
||
| 379 | */ |
||
| 380 | public static function doAllAndCommit( $database, array $options = [] ) { |
||
| 381 | $options += [ 'update' => false, 'activeUsers' => false ]; |
||
| 382 | |||
| 383 | // Grab the object and count everything |
||
| 384 | $counter = new SiteStatsInit( $database ); |
||
| 385 | |||
| 386 | $counter->edits(); |
||
| 387 | $counter->articles(); |
||
| 388 | $counter->pages(); |
||
| 389 | $counter->users(); |
||
| 390 | $counter->files(); |
||
| 391 | |||
| 392 | $counter->refresh(); |
||
| 393 | |||
| 394 | // Count active users if need be |
||
| 395 | if ( $options['activeUsers'] ) { |
||
| 396 | SiteStatsUpdate::cacheUpdate( wfGetDB( DB_MASTER ) ); |
||
| 397 | } |
||
| 398 | } |
||
| 399 | |||
| 400 | /** |
||
| 401 | * Refresh site_stats |
||
| 402 | */ |
||
| 403 | public function refresh() { |
||
| 404 | $values = [ |
||
| 405 | 'ss_row_id' => 1, |
||
| 406 | 'ss_total_edits' => ( $this->mEdits === null ? $this->edits() : $this->mEdits ), |
||
| 407 | 'ss_good_articles' => ( $this->mArticles === null ? $this->articles() : $this->mArticles ), |
||
| 408 | 'ss_total_pages' => ( $this->mPages === null ? $this->pages() : $this->mPages ), |
||
| 409 | 'ss_users' => ( $this->mUsers === null ? $this->users() : $this->mUsers ), |
||
| 410 | 'ss_images' => ( $this->mFiles === null ? $this->files() : $this->mFiles ), |
||
| 411 | ]; |
||
| 412 | |||
| 413 | $dbw = wfGetDB( DB_MASTER ); |
||
| 414 | $dbw->upsert( 'site_stats', $values, [ 'ss_row_id' ], $values, __METHOD__ ); |
||
| 415 | } |
||
| 416 | } |
||
| 417 |
Only declaring a single property per statement allows you to later on add doc comments more easily.
It is also recommended by PSR2, so it is a common style that many people expect.