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.