These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * |
||
4 | * |
||
5 | * Created on Sep 25, 2006 |
||
6 | * |
||
7 | * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" |
||
8 | * |
||
9 | * This program is free software; you can redistribute it and/or modify |
||
10 | * it under the terms of the GNU General Public License as published by |
||
11 | * the Free Software Foundation; either version 2 of the License, or |
||
12 | * (at your option) any later version. |
||
13 | * |
||
14 | * This program is distributed in the hope that it will be useful, |
||
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
17 | * GNU General Public License for more details. |
||
18 | * |
||
19 | * You should have received a copy of the GNU General Public License along |
||
20 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
21 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
22 | * http://www.gnu.org/copyleft/gpl.html |
||
23 | * |
||
24 | * @file |
||
25 | */ |
||
26 | use MediaWiki\MediaWikiServices; |
||
27 | use MediaWiki\Linker\LinkTarget; |
||
28 | |||
29 | /** |
||
30 | * A query module to show basic page information. |
||
31 | * |
||
32 | * @ingroup API |
||
33 | */ |
||
34 | class ApiQueryInfo extends ApiQueryBase { |
||
35 | |||
36 | private $fld_protection = false, $fld_talkid = false, |
||
37 | $fld_subjectid = false, $fld_url = false, |
||
38 | $fld_readable = false, $fld_watched = false, |
||
39 | $fld_watchers = false, $fld_visitingwatchers = false, |
||
40 | $fld_notificationtimestamp = false, |
||
41 | $fld_preload = false, $fld_displaytitle = false; |
||
42 | |||
43 | private $params; |
||
44 | |||
45 | /** @var Title[] */ |
||
46 | private $titles; |
||
47 | /** @var Title[] */ |
||
48 | private $missing; |
||
49 | /** @var Title[] */ |
||
50 | private $everything; |
||
51 | |||
52 | private $pageRestrictions, $pageIsRedir, $pageIsNew, $pageTouched, |
||
53 | $pageLatest, $pageLength; |
||
54 | |||
55 | private $protections, $restrictionTypes, $watched, $watchers, $visitingwatchers, |
||
56 | $notificationtimestamps, $talkids, $subjectids, $displaytitles; |
||
57 | private $showZeroWatchers = false; |
||
58 | |||
59 | private $tokenFunctions; |
||
60 | |||
61 | private $countTestedActions = 0; |
||
62 | |||
63 | public function __construct( ApiQuery $query, $moduleName ) { |
||
64 | parent::__construct( $query, $moduleName, 'in' ); |
||
65 | } |
||
66 | |||
67 | /** |
||
68 | * @param ApiPageSet $pageSet |
||
69 | * @return void |
||
70 | */ |
||
71 | public function requestExtraData( $pageSet ) { |
||
72 | $pageSet->requestField( 'page_restrictions' ); |
||
73 | // If the pageset is resolving redirects we won't get page_is_redirect. |
||
74 | // But we can't know for sure until the pageset is executed (revids may |
||
75 | // turn it off), so request it unconditionally. |
||
76 | $pageSet->requestField( 'page_is_redirect' ); |
||
77 | $pageSet->requestField( 'page_is_new' ); |
||
78 | $config = $this->getConfig(); |
||
79 | $pageSet->requestField( 'page_touched' ); |
||
80 | $pageSet->requestField( 'page_latest' ); |
||
81 | $pageSet->requestField( 'page_len' ); |
||
82 | if ( $config->get( 'ContentHandlerUseDB' ) ) { |
||
83 | $pageSet->requestField( 'page_content_model' ); |
||
84 | } |
||
85 | if ( $config->get( 'PageLanguageUseDB' ) ) { |
||
86 | $pageSet->requestField( 'page_lang' ); |
||
87 | } |
||
88 | } |
||
89 | |||
90 | /** |
||
91 | * Get an array mapping token names to their handler functions. |
||
92 | * The prototype for a token function is func($pageid, $title) |
||
93 | * it should return a token or false (permission denied) |
||
94 | * @deprecated since 1.24 |
||
95 | * @return array [ tokenname => function ] |
||
96 | */ |
||
97 | protected function getTokenFunctions() { |
||
98 | // Don't call the hooks twice |
||
99 | if ( isset( $this->tokenFunctions ) ) { |
||
100 | return $this->tokenFunctions; |
||
101 | } |
||
102 | |||
103 | // If we're in a mode that breaks the same-origin policy, no tokens can |
||
104 | // be obtained |
||
105 | if ( $this->lacksSameOriginSecurity() ) { |
||
106 | return []; |
||
107 | } |
||
108 | |||
109 | $this->tokenFunctions = [ |
||
110 | 'edit' => [ 'ApiQueryInfo', 'getEditToken' ], |
||
111 | 'delete' => [ 'ApiQueryInfo', 'getDeleteToken' ], |
||
112 | 'protect' => [ 'ApiQueryInfo', 'getProtectToken' ], |
||
113 | 'move' => [ 'ApiQueryInfo', 'getMoveToken' ], |
||
114 | 'block' => [ 'ApiQueryInfo', 'getBlockToken' ], |
||
115 | 'unblock' => [ 'ApiQueryInfo', 'getUnblockToken' ], |
||
116 | 'email' => [ 'ApiQueryInfo', 'getEmailToken' ], |
||
117 | 'import' => [ 'ApiQueryInfo', 'getImportToken' ], |
||
118 | 'watch' => [ 'ApiQueryInfo', 'getWatchToken' ], |
||
119 | ]; |
||
120 | Hooks::run( 'APIQueryInfoTokens', [ &$this->tokenFunctions ] ); |
||
121 | |||
122 | return $this->tokenFunctions; |
||
123 | } |
||
124 | |||
125 | static protected $cachedTokens = []; |
||
126 | |||
127 | /** |
||
128 | * @deprecated since 1.24 |
||
129 | */ |
||
130 | public static function resetTokenCache() { |
||
131 | ApiQueryInfo::$cachedTokens = []; |
||
132 | } |
||
133 | |||
134 | /** |
||
135 | * @deprecated since 1.24 |
||
136 | */ |
||
137 | View Code Duplication | public static function getEditToken( $pageid, $title ) { |
|
138 | // We could check for $title->userCan('edit') here, |
||
139 | // but that's too expensive for this purpose |
||
140 | // and would break caching |
||
141 | global $wgUser; |
||
142 | if ( !$wgUser->isAllowed( 'edit' ) ) { |
||
143 | return false; |
||
144 | } |
||
145 | |||
146 | // The token is always the same, let's exploit that |
||
147 | if ( !isset( ApiQueryInfo::$cachedTokens['edit'] ) ) { |
||
148 | ApiQueryInfo::$cachedTokens['edit'] = $wgUser->getEditToken(); |
||
149 | } |
||
150 | |||
151 | return ApiQueryInfo::$cachedTokens['edit']; |
||
152 | } |
||
153 | |||
154 | /** |
||
155 | * @deprecated since 1.24 |
||
156 | */ |
||
157 | View Code Duplication | public static function getDeleteToken( $pageid, $title ) { |
|
158 | global $wgUser; |
||
159 | if ( !$wgUser->isAllowed( 'delete' ) ) { |
||
160 | return false; |
||
161 | } |
||
162 | |||
163 | // The token is always the same, let's exploit that |
||
164 | if ( !isset( ApiQueryInfo::$cachedTokens['delete'] ) ) { |
||
165 | ApiQueryInfo::$cachedTokens['delete'] = $wgUser->getEditToken(); |
||
166 | } |
||
167 | |||
168 | return ApiQueryInfo::$cachedTokens['delete']; |
||
169 | } |
||
170 | |||
171 | /** |
||
172 | * @deprecated since 1.24 |
||
173 | */ |
||
174 | View Code Duplication | public static function getProtectToken( $pageid, $title ) { |
|
175 | global $wgUser; |
||
176 | if ( !$wgUser->isAllowed( 'protect' ) ) { |
||
177 | return false; |
||
178 | } |
||
179 | |||
180 | // The token is always the same, let's exploit that |
||
181 | if ( !isset( ApiQueryInfo::$cachedTokens['protect'] ) ) { |
||
182 | ApiQueryInfo::$cachedTokens['protect'] = $wgUser->getEditToken(); |
||
183 | } |
||
184 | |||
185 | return ApiQueryInfo::$cachedTokens['protect']; |
||
186 | } |
||
187 | |||
188 | /** |
||
189 | * @deprecated since 1.24 |
||
190 | */ |
||
191 | View Code Duplication | public static function getMoveToken( $pageid, $title ) { |
|
192 | global $wgUser; |
||
193 | if ( !$wgUser->isAllowed( 'move' ) ) { |
||
194 | return false; |
||
195 | } |
||
196 | |||
197 | // The token is always the same, let's exploit that |
||
198 | if ( !isset( ApiQueryInfo::$cachedTokens['move'] ) ) { |
||
199 | ApiQueryInfo::$cachedTokens['move'] = $wgUser->getEditToken(); |
||
200 | } |
||
201 | |||
202 | return ApiQueryInfo::$cachedTokens['move']; |
||
203 | } |
||
204 | |||
205 | /** |
||
206 | * @deprecated since 1.24 |
||
207 | */ |
||
208 | View Code Duplication | public static function getBlockToken( $pageid, $title ) { |
|
209 | global $wgUser; |
||
210 | if ( !$wgUser->isAllowed( 'block' ) ) { |
||
211 | return false; |
||
212 | } |
||
213 | |||
214 | // The token is always the same, let's exploit that |
||
215 | if ( !isset( ApiQueryInfo::$cachedTokens['block'] ) ) { |
||
216 | ApiQueryInfo::$cachedTokens['block'] = $wgUser->getEditToken(); |
||
217 | } |
||
218 | |||
219 | return ApiQueryInfo::$cachedTokens['block']; |
||
220 | } |
||
221 | |||
222 | /** |
||
223 | * @deprecated since 1.24 |
||
224 | */ |
||
225 | public static function getUnblockToken( $pageid, $title ) { |
||
226 | // Currently, this is exactly the same as the block token |
||
227 | return self::getBlockToken( $pageid, $title ); |
||
228 | } |
||
229 | |||
230 | /** |
||
231 | * @deprecated since 1.24 |
||
232 | */ |
||
233 | View Code Duplication | public static function getEmailToken( $pageid, $title ) { |
|
234 | global $wgUser; |
||
235 | if ( !$wgUser->canSendEmail() || $wgUser->isBlockedFromEmailuser() ) { |
||
236 | return false; |
||
237 | } |
||
238 | |||
239 | // The token is always the same, let's exploit that |
||
240 | if ( !isset( ApiQueryInfo::$cachedTokens['email'] ) ) { |
||
241 | ApiQueryInfo::$cachedTokens['email'] = $wgUser->getEditToken(); |
||
242 | } |
||
243 | |||
244 | return ApiQueryInfo::$cachedTokens['email']; |
||
245 | } |
||
246 | |||
247 | /** |
||
248 | * @deprecated since 1.24 |
||
249 | */ |
||
250 | View Code Duplication | public static function getImportToken( $pageid, $title ) { |
|
251 | global $wgUser; |
||
252 | if ( !$wgUser->isAllowedAny( 'import', 'importupload' ) ) { |
||
253 | return false; |
||
254 | } |
||
255 | |||
256 | // The token is always the same, let's exploit that |
||
257 | if ( !isset( ApiQueryInfo::$cachedTokens['import'] ) ) { |
||
258 | ApiQueryInfo::$cachedTokens['import'] = $wgUser->getEditToken(); |
||
259 | } |
||
260 | |||
261 | return ApiQueryInfo::$cachedTokens['import']; |
||
262 | } |
||
263 | |||
264 | /** |
||
265 | * @deprecated since 1.24 |
||
266 | */ |
||
267 | View Code Duplication | public static function getWatchToken( $pageid, $title ) { |
|
268 | global $wgUser; |
||
269 | if ( !$wgUser->isLoggedIn() ) { |
||
270 | return false; |
||
271 | } |
||
272 | |||
273 | // The token is always the same, let's exploit that |
||
274 | if ( !isset( ApiQueryInfo::$cachedTokens['watch'] ) ) { |
||
275 | ApiQueryInfo::$cachedTokens['watch'] = $wgUser->getEditToken( 'watch' ); |
||
276 | } |
||
277 | |||
278 | return ApiQueryInfo::$cachedTokens['watch']; |
||
279 | } |
||
280 | |||
281 | /** |
||
282 | * @deprecated since 1.24 |
||
283 | */ |
||
284 | View Code Duplication | public static function getOptionsToken( $pageid, $title ) { |
|
285 | global $wgUser; |
||
286 | if ( !$wgUser->isLoggedIn() ) { |
||
287 | return false; |
||
288 | } |
||
289 | |||
290 | // The token is always the same, let's exploit that |
||
291 | if ( !isset( ApiQueryInfo::$cachedTokens['options'] ) ) { |
||
292 | ApiQueryInfo::$cachedTokens['options'] = $wgUser->getEditToken(); |
||
293 | } |
||
294 | |||
295 | return ApiQueryInfo::$cachedTokens['options']; |
||
296 | } |
||
297 | |||
298 | public function execute() { |
||
299 | $this->params = $this->extractRequestParams(); |
||
300 | if ( !is_null( $this->params['prop'] ) ) { |
||
301 | $prop = array_flip( $this->params['prop'] ); |
||
302 | $this->fld_protection = isset( $prop['protection'] ); |
||
303 | $this->fld_watched = isset( $prop['watched'] ); |
||
304 | $this->fld_watchers = isset( $prop['watchers'] ); |
||
305 | $this->fld_visitingwatchers = isset( $prop['visitingwatchers'] ); |
||
306 | $this->fld_notificationtimestamp = isset( $prop['notificationtimestamp'] ); |
||
307 | $this->fld_talkid = isset( $prop['talkid'] ); |
||
308 | $this->fld_subjectid = isset( $prop['subjectid'] ); |
||
309 | $this->fld_url = isset( $prop['url'] ); |
||
310 | $this->fld_readable = isset( $prop['readable'] ); |
||
311 | $this->fld_preload = isset( $prop['preload'] ); |
||
312 | $this->fld_displaytitle = isset( $prop['displaytitle'] ); |
||
313 | } |
||
314 | |||
315 | $pageSet = $this->getPageSet(); |
||
316 | $this->titles = $pageSet->getGoodTitles(); |
||
317 | $this->missing = $pageSet->getMissingTitles(); |
||
318 | $this->everything = $this->titles + $this->missing; |
||
319 | $result = $this->getResult(); |
||
320 | |||
321 | uasort( $this->everything, [ 'Title', 'compare' ] ); |
||
322 | if ( !is_null( $this->params['continue'] ) ) { |
||
323 | // Throw away any titles we're gonna skip so they don't |
||
324 | // clutter queries |
||
325 | $cont = explode( '|', $this->params['continue'] ); |
||
326 | $this->dieContinueUsageIf( count( $cont ) != 2 ); |
||
327 | $conttitle = Title::makeTitleSafe( $cont[0], $cont[1] ); |
||
328 | foreach ( $this->everything as $pageid => $title ) { |
||
329 | if ( Title::compare( $title, $conttitle ) >= 0 ) { |
||
0 ignored issues
–
show
|
|||
330 | break; |
||
331 | } |
||
332 | unset( $this->titles[$pageid] ); |
||
333 | unset( $this->missing[$pageid] ); |
||
334 | unset( $this->everything[$pageid] ); |
||
335 | } |
||
336 | } |
||
337 | |||
338 | $this->pageRestrictions = $pageSet->getCustomField( 'page_restrictions' ); |
||
339 | // when resolving redirects, no page will have this field |
||
340 | $this->pageIsRedir = !$pageSet->isResolvingRedirects() |
||
341 | ? $pageSet->getCustomField( 'page_is_redirect' ) |
||
342 | : []; |
||
343 | $this->pageIsNew = $pageSet->getCustomField( 'page_is_new' ); |
||
344 | |||
345 | $this->pageTouched = $pageSet->getCustomField( 'page_touched' ); |
||
346 | $this->pageLatest = $pageSet->getCustomField( 'page_latest' ); |
||
347 | $this->pageLength = $pageSet->getCustomField( 'page_len' ); |
||
348 | |||
349 | // Get protection info if requested |
||
350 | if ( $this->fld_protection ) { |
||
351 | $this->getProtectionInfo(); |
||
352 | } |
||
353 | |||
354 | if ( $this->fld_watched || $this->fld_notificationtimestamp ) { |
||
355 | $this->getWatchedInfo(); |
||
356 | } |
||
357 | |||
358 | if ( $this->fld_watchers ) { |
||
359 | $this->getWatcherInfo(); |
||
360 | } |
||
361 | |||
362 | if ( $this->fld_visitingwatchers ) { |
||
363 | $this->getVisitingWatcherInfo(); |
||
364 | } |
||
365 | |||
366 | // Run the talkid/subjectid query if requested |
||
367 | if ( $this->fld_talkid || $this->fld_subjectid ) { |
||
368 | $this->getTSIDs(); |
||
369 | } |
||
370 | |||
371 | if ( $this->fld_displaytitle ) { |
||
372 | $this->getDisplayTitle(); |
||
373 | } |
||
374 | |||
375 | /** @var $title Title */ |
||
376 | foreach ( $this->everything as $pageid => $title ) { |
||
377 | $pageInfo = $this->extractPageInfo( $pageid, $title ); |
||
378 | $fit = $pageInfo !== null && $result->addValue( [ |
||
379 | 'query', |
||
380 | 'pages' |
||
381 | ], $pageid, $pageInfo ); |
||
382 | if ( !$fit ) { |
||
383 | $this->setContinueEnumParameter( 'continue', |
||
384 | $title->getNamespace() . '|' . |
||
385 | $title->getText() ); |
||
386 | break; |
||
387 | } |
||
388 | } |
||
389 | } |
||
390 | |||
391 | /** |
||
392 | * Get a result array with information about a title |
||
393 | * @param int $pageid Page ID (negative for missing titles) |
||
394 | * @param Title $title |
||
395 | * @return array|null |
||
396 | */ |
||
397 | private function extractPageInfo( $pageid, $title ) { |
||
398 | $pageInfo = []; |
||
399 | // $title->exists() needs pageid, which is not set for all title objects |
||
400 | $titleExists = $pageid > 0; |
||
401 | $ns = $title->getNamespace(); |
||
402 | $dbkey = $title->getDBkey(); |
||
403 | |||
404 | $pageInfo['contentmodel'] = $title->getContentModel(); |
||
405 | |||
406 | $pageLanguage = $title->getPageLanguage(); |
||
407 | $pageInfo['pagelanguage'] = $pageLanguage->getCode(); |
||
408 | $pageInfo['pagelanguagehtmlcode'] = $pageLanguage->getHtmlCode(); |
||
409 | $pageInfo['pagelanguagedir'] = $pageLanguage->getDir(); |
||
410 | |||
411 | if ( $titleExists ) { |
||
412 | $pageInfo['touched'] = wfTimestamp( TS_ISO_8601, $this->pageTouched[$pageid] ); |
||
413 | $pageInfo['lastrevid'] = intval( $this->pageLatest[$pageid] ); |
||
414 | $pageInfo['length'] = intval( $this->pageLength[$pageid] ); |
||
415 | |||
416 | if ( isset( $this->pageIsRedir[$pageid] ) && $this->pageIsRedir[$pageid] ) { |
||
417 | $pageInfo['redirect'] = true; |
||
418 | } |
||
419 | if ( $this->pageIsNew[$pageid] ) { |
||
420 | $pageInfo['new'] = true; |
||
421 | } |
||
422 | } |
||
423 | |||
424 | if ( !is_null( $this->params['token'] ) ) { |
||
425 | $tokenFunctions = $this->getTokenFunctions(); |
||
426 | $pageInfo['starttimestamp'] = wfTimestamp( TS_ISO_8601, time() ); |
||
427 | foreach ( $this->params['token'] as $t ) { |
||
428 | $val = call_user_func( $tokenFunctions[$t], $pageid, $title ); |
||
429 | if ( $val === false ) { |
||
430 | $this->setWarning( "Action '$t' is not allowed for the current user" ); |
||
431 | } else { |
||
432 | $pageInfo[$t . 'token'] = $val; |
||
433 | } |
||
434 | } |
||
435 | } |
||
436 | |||
437 | if ( $this->fld_protection ) { |
||
438 | $pageInfo['protection'] = []; |
||
439 | if ( isset( $this->protections[$ns][$dbkey] ) ) { |
||
440 | $pageInfo['protection'] = |
||
441 | $this->protections[$ns][$dbkey]; |
||
442 | } |
||
443 | ApiResult::setIndexedTagName( $pageInfo['protection'], 'pr' ); |
||
444 | |||
445 | $pageInfo['restrictiontypes'] = []; |
||
446 | if ( isset( $this->restrictionTypes[$ns][$dbkey] ) ) { |
||
447 | $pageInfo['restrictiontypes'] = |
||
448 | $this->restrictionTypes[$ns][$dbkey]; |
||
449 | } |
||
450 | ApiResult::setIndexedTagName( $pageInfo['restrictiontypes'], 'rt' ); |
||
451 | } |
||
452 | |||
453 | if ( $this->fld_watched && $this->watched !== null ) { |
||
454 | $pageInfo['watched'] = $this->watched[$ns][$dbkey]; |
||
455 | } |
||
456 | |||
457 | View Code Duplication | if ( $this->fld_watchers ) { |
|
458 | if ( $this->watchers !== null && $this->watchers[$ns][$dbkey] !== 0 ) { |
||
459 | $pageInfo['watchers'] = $this->watchers[$ns][$dbkey]; |
||
460 | } elseif ( $this->showZeroWatchers ) { |
||
461 | $pageInfo['watchers'] = 0; |
||
462 | } |
||
463 | } |
||
464 | |||
465 | View Code Duplication | if ( $this->fld_visitingwatchers ) { |
|
466 | if ( $this->visitingwatchers !== null && $this->visitingwatchers[$ns][$dbkey] !== 0 ) { |
||
467 | $pageInfo['visitingwatchers'] = $this->visitingwatchers[$ns][$dbkey]; |
||
468 | } elseif ( $this->showZeroWatchers ) { |
||
469 | $pageInfo['visitingwatchers'] = 0; |
||
470 | } |
||
471 | } |
||
472 | |||
473 | if ( $this->fld_notificationtimestamp ) { |
||
474 | $pageInfo['notificationtimestamp'] = ''; |
||
475 | if ( $this->notificationtimestamps[$ns][$dbkey] ) { |
||
476 | $pageInfo['notificationtimestamp'] = |
||
477 | wfTimestamp( TS_ISO_8601, $this->notificationtimestamps[$ns][$dbkey] ); |
||
478 | } |
||
479 | } |
||
480 | |||
481 | View Code Duplication | if ( $this->fld_talkid && isset( $this->talkids[$ns][$dbkey] ) ) { |
|
482 | $pageInfo['talkid'] = $this->talkids[$ns][$dbkey]; |
||
483 | } |
||
484 | |||
485 | View Code Duplication | if ( $this->fld_subjectid && isset( $this->subjectids[$ns][$dbkey] ) ) { |
|
486 | $pageInfo['subjectid'] = $this->subjectids[$ns][$dbkey]; |
||
487 | } |
||
488 | |||
489 | if ( $this->fld_url ) { |
||
490 | $pageInfo['fullurl'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ); |
||
491 | $pageInfo['editurl'] = wfExpandUrl( $title->getFullURL( 'action=edit' ), PROTO_CURRENT ); |
||
492 | $pageInfo['canonicalurl'] = wfExpandUrl( $title->getFullURL(), PROTO_CANONICAL ); |
||
493 | } |
||
494 | if ( $this->fld_readable ) { |
||
495 | $pageInfo['readable'] = $title->userCan( 'read', $this->getUser() ); |
||
496 | } |
||
497 | |||
498 | if ( $this->fld_preload ) { |
||
499 | if ( $titleExists ) { |
||
500 | $pageInfo['preload'] = ''; |
||
501 | } else { |
||
502 | $text = null; |
||
503 | Hooks::run( 'EditFormPreloadText', [ &$text, &$title ] ); |
||
504 | |||
505 | $pageInfo['preload'] = $text; |
||
506 | } |
||
507 | } |
||
508 | |||
509 | if ( $this->fld_displaytitle ) { |
||
510 | if ( isset( $this->displaytitles[$pageid] ) ) { |
||
511 | $pageInfo['displaytitle'] = $this->displaytitles[$pageid]; |
||
512 | } else { |
||
513 | $pageInfo['displaytitle'] = $title->getPrefixedText(); |
||
514 | } |
||
515 | } |
||
516 | |||
517 | if ( $this->params['testactions'] ) { |
||
518 | $limit = $this->getMain()->canApiHighLimits() ? self::LIMIT_SML1 : self::LIMIT_SML2; |
||
519 | if ( $this->countTestedActions >= $limit ) { |
||
520 | return null; // force a continuation |
||
521 | } |
||
522 | |||
523 | $user = $this->getUser(); |
||
524 | $pageInfo['actions'] = []; |
||
525 | foreach ( $this->params['testactions'] as $action ) { |
||
526 | $this->countTestedActions++; |
||
527 | $pageInfo['actions'][$action] = $title->userCan( $action, $user ); |
||
528 | } |
||
529 | } |
||
530 | |||
531 | return $pageInfo; |
||
532 | } |
||
533 | |||
534 | /** |
||
535 | * Get information about protections and put it in $protections |
||
536 | */ |
||
537 | private function getProtectionInfo() { |
||
538 | global $wgContLang; |
||
539 | $this->protections = []; |
||
540 | $db = $this->getDB(); |
||
541 | |||
542 | // Get normal protections for existing titles |
||
543 | if ( count( $this->titles ) ) { |
||
544 | $this->resetQueryParams(); |
||
545 | $this->addTables( 'page_restrictions' ); |
||
546 | $this->addFields( [ 'pr_page', 'pr_type', 'pr_level', |
||
547 | 'pr_expiry', 'pr_cascade' ] ); |
||
548 | $this->addWhereFld( 'pr_page', array_keys( $this->titles ) ); |
||
549 | |||
550 | $res = $this->select( __METHOD__ ); |
||
551 | foreach ( $res as $row ) { |
||
552 | /** @var $title Title */ |
||
553 | $title = $this->titles[$row->pr_page]; |
||
554 | $a = [ |
||
555 | 'type' => $row->pr_type, |
||
556 | 'level' => $row->pr_level, |
||
557 | 'expiry' => $wgContLang->formatExpiry( $row->pr_expiry, TS_ISO_8601 ) |
||
558 | ]; |
||
559 | if ( $row->pr_cascade ) { |
||
560 | $a['cascade'] = true; |
||
561 | } |
||
562 | $this->protections[$title->getNamespace()][$title->getDBkey()][] = $a; |
||
563 | } |
||
564 | // Also check old restrictions |
||
565 | foreach ( $this->titles as $pageId => $title ) { |
||
566 | if ( $this->pageRestrictions[$pageId] ) { |
||
567 | $namespace = $title->getNamespace(); |
||
568 | $dbKey = $title->getDBkey(); |
||
569 | $restrictions = explode( ':', trim( $this->pageRestrictions[$pageId] ) ); |
||
570 | foreach ( $restrictions as $restrict ) { |
||
571 | $temp = explode( '=', trim( $restrict ) ); |
||
572 | if ( count( $temp ) == 1 ) { |
||
573 | // old old format should be treated as edit/move restriction |
||
574 | $restriction = trim( $temp[0] ); |
||
575 | |||
576 | if ( $restriction == '' ) { |
||
577 | continue; |
||
578 | } |
||
579 | $this->protections[$namespace][$dbKey][] = [ |
||
580 | 'type' => 'edit', |
||
581 | 'level' => $restriction, |
||
582 | 'expiry' => 'infinity', |
||
583 | ]; |
||
584 | $this->protections[$namespace][$dbKey][] = [ |
||
585 | 'type' => 'move', |
||
586 | 'level' => $restriction, |
||
587 | 'expiry' => 'infinity', |
||
588 | ]; |
||
589 | } else { |
||
590 | $restriction = trim( $temp[1] ); |
||
591 | if ( $restriction == '' ) { |
||
592 | continue; |
||
593 | } |
||
594 | $this->protections[$namespace][$dbKey][] = [ |
||
595 | 'type' => $temp[0], |
||
596 | 'level' => $restriction, |
||
597 | 'expiry' => 'infinity', |
||
598 | ]; |
||
599 | } |
||
600 | } |
||
601 | } |
||
602 | } |
||
603 | } |
||
604 | |||
605 | // Get protections for missing titles |
||
606 | if ( count( $this->missing ) ) { |
||
607 | $this->resetQueryParams(); |
||
608 | $lb = new LinkBatch( $this->missing ); |
||
609 | $this->addTables( 'protected_titles' ); |
||
610 | $this->addFields( [ 'pt_title', 'pt_namespace', 'pt_create_perm', 'pt_expiry' ] ); |
||
611 | $this->addWhere( $lb->constructSet( 'pt', $db ) ); |
||
612 | $res = $this->select( __METHOD__ ); |
||
613 | foreach ( $res as $row ) { |
||
614 | $this->protections[$row->pt_namespace][$row->pt_title][] = [ |
||
615 | 'type' => 'create', |
||
616 | 'level' => $row->pt_create_perm, |
||
617 | 'expiry' => $wgContLang->formatExpiry( $row->pt_expiry, TS_ISO_8601 ) |
||
618 | ]; |
||
619 | } |
||
620 | } |
||
621 | |||
622 | // Separate good and missing titles into files and other pages |
||
623 | // and populate $this->restrictionTypes |
||
624 | $images = $others = []; |
||
625 | foreach ( $this->everything as $title ) { |
||
626 | if ( $title->getNamespace() == NS_FILE ) { |
||
627 | $images[] = $title->getDBkey(); |
||
628 | } else { |
||
629 | $others[] = $title; |
||
630 | } |
||
631 | // Applicable protection types |
||
632 | $this->restrictionTypes[$title->getNamespace()][$title->getDBkey()] = |
||
633 | array_values( $title->getRestrictionTypes() ); |
||
634 | } |
||
635 | |||
636 | if ( count( $others ) ) { |
||
637 | // Non-images: check templatelinks |
||
638 | $lb = new LinkBatch( $others ); |
||
639 | $this->resetQueryParams(); |
||
640 | $this->addTables( [ 'page_restrictions', 'page', 'templatelinks' ] ); |
||
641 | $this->addFields( [ 'pr_type', 'pr_level', 'pr_expiry', |
||
642 | 'page_title', 'page_namespace', |
||
643 | 'tl_title', 'tl_namespace' ] ); |
||
644 | $this->addWhere( $lb->constructSet( 'tl', $db ) ); |
||
645 | $this->addWhere( 'pr_page = page_id' ); |
||
646 | $this->addWhere( 'pr_page = tl_from' ); |
||
647 | $this->addWhereFld( 'pr_cascade', 1 ); |
||
648 | |||
649 | $res = $this->select( __METHOD__ ); |
||
650 | View Code Duplication | foreach ( $res as $row ) { |
|
651 | $source = Title::makeTitle( $row->page_namespace, $row->page_title ); |
||
652 | $this->protections[$row->tl_namespace][$row->tl_title][] = [ |
||
653 | 'type' => $row->pr_type, |
||
654 | 'level' => $row->pr_level, |
||
655 | 'expiry' => $wgContLang->formatExpiry( $row->pr_expiry, TS_ISO_8601 ), |
||
656 | 'source' => $source->getPrefixedText() |
||
657 | ]; |
||
658 | } |
||
659 | } |
||
660 | |||
661 | if ( count( $images ) ) { |
||
662 | // Images: check imagelinks |
||
663 | $this->resetQueryParams(); |
||
664 | $this->addTables( [ 'page_restrictions', 'page', 'imagelinks' ] ); |
||
665 | $this->addFields( [ 'pr_type', 'pr_level', 'pr_expiry', |
||
666 | 'page_title', 'page_namespace', 'il_to' ] ); |
||
667 | $this->addWhere( 'pr_page = page_id' ); |
||
668 | $this->addWhere( 'pr_page = il_from' ); |
||
669 | $this->addWhereFld( 'pr_cascade', 1 ); |
||
670 | $this->addWhereFld( 'il_to', $images ); |
||
671 | |||
672 | $res = $this->select( __METHOD__ ); |
||
673 | View Code Duplication | foreach ( $res as $row ) { |
|
674 | $source = Title::makeTitle( $row->page_namespace, $row->page_title ); |
||
675 | $this->protections[NS_FILE][$row->il_to][] = [ |
||
676 | 'type' => $row->pr_type, |
||
677 | 'level' => $row->pr_level, |
||
678 | 'expiry' => $wgContLang->formatExpiry( $row->pr_expiry, TS_ISO_8601 ), |
||
679 | 'source' => $source->getPrefixedText() |
||
680 | ]; |
||
681 | } |
||
682 | } |
||
683 | } |
||
684 | |||
685 | /** |
||
686 | * Get talk page IDs (if requested) and subject page IDs (if requested) |
||
687 | * and put them in $talkids and $subjectids |
||
688 | */ |
||
689 | private function getTSIDs() { |
||
690 | $getTitles = $this->talkids = $this->subjectids = []; |
||
691 | |||
692 | /** @var $t Title */ |
||
693 | foreach ( $this->everything as $t ) { |
||
694 | if ( MWNamespace::isTalk( $t->getNamespace() ) ) { |
||
695 | if ( $this->fld_subjectid ) { |
||
696 | $getTitles[] = $t->getSubjectPage(); |
||
697 | } |
||
698 | } elseif ( $this->fld_talkid ) { |
||
699 | $getTitles[] = $t->getTalkPage(); |
||
700 | } |
||
701 | } |
||
702 | if ( !count( $getTitles ) ) { |
||
703 | return; |
||
704 | } |
||
705 | |||
706 | $db = $this->getDB(); |
||
707 | |||
708 | // Construct a custom WHERE clause that matches |
||
709 | // all titles in $getTitles |
||
710 | $lb = new LinkBatch( $getTitles ); |
||
711 | $this->resetQueryParams(); |
||
712 | $this->addTables( 'page' ); |
||
713 | $this->addFields( [ 'page_title', 'page_namespace', 'page_id' ] ); |
||
714 | $this->addWhere( $lb->constructSet( 'page', $db ) ); |
||
715 | $res = $this->select( __METHOD__ ); |
||
716 | foreach ( $res as $row ) { |
||
717 | if ( MWNamespace::isTalk( $row->page_namespace ) ) { |
||
718 | $this->talkids[MWNamespace::getSubject( $row->page_namespace )][$row->page_title] = |
||
719 | intval( $row->page_id ); |
||
720 | } else { |
||
721 | $this->subjectids[MWNamespace::getTalk( $row->page_namespace )][$row->page_title] = |
||
722 | intval( $row->page_id ); |
||
723 | } |
||
724 | } |
||
725 | } |
||
726 | |||
727 | private function getDisplayTitle() { |
||
728 | $this->displaytitles = []; |
||
729 | |||
730 | $pageIds = array_keys( $this->titles ); |
||
731 | |||
732 | if ( !count( $pageIds ) ) { |
||
733 | return; |
||
734 | } |
||
735 | |||
736 | $this->resetQueryParams(); |
||
737 | $this->addTables( 'page_props' ); |
||
738 | $this->addFields( [ 'pp_page', 'pp_value' ] ); |
||
739 | $this->addWhereFld( 'pp_page', $pageIds ); |
||
740 | $this->addWhereFld( 'pp_propname', 'displaytitle' ); |
||
741 | $res = $this->select( __METHOD__ ); |
||
742 | |||
743 | foreach ( $res as $row ) { |
||
744 | $this->displaytitles[$row->pp_page] = $row->pp_value; |
||
745 | } |
||
746 | } |
||
747 | |||
748 | /** |
||
749 | * Get information about watched status and put it in $this->watched |
||
750 | * and $this->notificationtimestamps |
||
751 | */ |
||
752 | private function getWatchedInfo() { |
||
753 | $user = $this->getUser(); |
||
754 | |||
755 | if ( $user->isAnon() || count( $this->everything ) == 0 |
||
756 | || !$user->isAllowed( 'viewmywatchlist' ) |
||
757 | ) { |
||
758 | return; |
||
759 | } |
||
760 | |||
761 | $this->watched = []; |
||
762 | $this->notificationtimestamps = []; |
||
763 | |||
764 | $store = MediaWikiServices::getInstance()->getWatchedItemStore(); |
||
765 | $timestamps = $store->getNotificationTimestampsBatch( $user, $this->everything ); |
||
766 | |||
767 | if ( $this->fld_watched ) { |
||
768 | foreach ( $timestamps as $namespaceId => $dbKeys ) { |
||
769 | $this->watched[$namespaceId] = array_map( |
||
770 | function( $x ) { |
||
771 | return $x !== false; |
||
772 | }, |
||
773 | $dbKeys |
||
774 | ); |
||
775 | } |
||
776 | } |
||
777 | if ( $this->fld_notificationtimestamp ) { |
||
778 | $this->notificationtimestamps = $timestamps; |
||
779 | } |
||
780 | } |
||
781 | |||
782 | /** |
||
783 | * Get the count of watchers and put it in $this->watchers |
||
784 | */ |
||
785 | private function getWatcherInfo() { |
||
786 | if ( count( $this->everything ) == 0 ) { |
||
787 | return; |
||
788 | } |
||
789 | |||
790 | $user = $this->getUser(); |
||
791 | $canUnwatchedpages = $user->isAllowed( 'unwatchedpages' ); |
||
792 | $unwatchedPageThreshold = $this->getConfig()->get( 'UnwatchedPageThreshold' ); |
||
793 | if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) { |
||
794 | return; |
||
795 | } |
||
796 | |||
797 | $this->showZeroWatchers = $canUnwatchedpages; |
||
798 | |||
799 | $countOptions = []; |
||
800 | if ( !$canUnwatchedpages ) { |
||
801 | $countOptions['minimumWatchers'] = $unwatchedPageThreshold; |
||
802 | } |
||
803 | |||
804 | $this->watchers = MediaWikiServices::getInstance()->getWatchedItemStore()->countWatchersMultiple( |
||
805 | $this->everything, |
||
806 | $countOptions |
||
807 | ); |
||
808 | } |
||
809 | |||
810 | /** |
||
811 | * Get the count of watchers who have visited recent edits and put it in |
||
812 | * $this->visitingwatchers |
||
813 | * |
||
814 | * Based on InfoAction::pageCounts |
||
815 | */ |
||
816 | private function getVisitingWatcherInfo() { |
||
817 | $config = $this->getConfig(); |
||
818 | $user = $this->getUser(); |
||
819 | $db = $this->getDB(); |
||
820 | |||
821 | $canUnwatchedpages = $user->isAllowed( 'unwatchedpages' ); |
||
822 | $unwatchedPageThreshold = $this->getConfig()->get( 'UnwatchedPageThreshold' ); |
||
823 | if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) { |
||
824 | return; |
||
825 | } |
||
826 | |||
827 | $this->showZeroWatchers = $canUnwatchedpages; |
||
828 | |||
829 | $titlesWithThresholds = []; |
||
830 | if ( $this->titles ) { |
||
831 | $lb = new LinkBatch( $this->titles ); |
||
832 | |||
833 | // Fetch last edit timestamps for pages |
||
834 | $this->resetQueryParams(); |
||
835 | $this->addTables( [ 'page', 'revision' ] ); |
||
836 | $this->addFields( [ 'page_namespace', 'page_title', 'rev_timestamp' ] ); |
||
837 | $this->addWhere( [ |
||
838 | 'page_latest = rev_id', |
||
839 | $lb->constructSet( 'page', $db ), |
||
840 | ] ); |
||
841 | $this->addOption( 'GROUP BY', [ 'page_namespace', 'page_title' ] ); |
||
842 | $timestampRes = $this->select( __METHOD__ ); |
||
843 | |||
844 | $age = $config->get( 'WatchersMaxAge' ); |
||
845 | $timestamps = []; |
||
846 | foreach ( $timestampRes as $row ) { |
||
847 | $revTimestamp = wfTimestamp( TS_UNIX, (int)$row->rev_timestamp ); |
||
848 | $timestamps[$row->page_namespace][$row->page_title] = $revTimestamp - $age; |
||
849 | } |
||
850 | $titlesWithThresholds = array_map( |
||
851 | function( LinkTarget $target ) use ( $timestamps ) { |
||
852 | return [ |
||
853 | $target, $timestamps[$target->getNamespace()][$target->getDBkey()] |
||
854 | ]; |
||
855 | }, |
||
856 | $this->titles |
||
857 | ); |
||
858 | } |
||
859 | |||
860 | if ( $this->missing ) { |
||
861 | $titlesWithThresholds = array_merge( |
||
862 | $titlesWithThresholds, |
||
863 | array_map( |
||
864 | function( LinkTarget $target ) { |
||
865 | return [ $target, null ]; |
||
866 | }, |
||
867 | $this->missing |
||
868 | ) |
||
869 | ); |
||
870 | } |
||
871 | $store = MediaWikiServices::getInstance()->getWatchedItemStore(); |
||
872 | $this->visitingwatchers = $store->countVisitingWatchersMultiple( |
||
873 | $titlesWithThresholds, |
||
874 | !$canUnwatchedpages ? $unwatchedPageThreshold : null |
||
875 | ); |
||
876 | } |
||
877 | |||
878 | public function getCacheMode( $params ) { |
||
879 | // Other props depend on something about the current user |
||
880 | $publicProps = [ |
||
881 | 'protection', |
||
882 | 'talkid', |
||
883 | 'subjectid', |
||
884 | 'url', |
||
885 | 'preload', |
||
886 | 'displaytitle', |
||
887 | ]; |
||
888 | if ( array_diff( (array)$params['prop'], $publicProps ) ) { |
||
889 | return 'private'; |
||
890 | } |
||
891 | |||
892 | // testactions also depends on the current user |
||
893 | if ( $params['testactions'] ) { |
||
894 | return 'private'; |
||
895 | } |
||
896 | |||
897 | if ( !is_null( $params['token'] ) ) { |
||
898 | return 'private'; |
||
899 | } |
||
900 | |||
901 | return 'public'; |
||
902 | } |
||
903 | |||
904 | public function getAllowedParams() { |
||
905 | return [ |
||
906 | 'prop' => [ |
||
907 | ApiBase::PARAM_ISMULTI => true, |
||
908 | ApiBase::PARAM_TYPE => [ |
||
909 | 'protection', |
||
910 | 'talkid', |
||
911 | 'watched', # private |
||
912 | 'watchers', # private |
||
913 | 'visitingwatchers', # private |
||
914 | 'notificationtimestamp', # private |
||
915 | 'subjectid', |
||
916 | 'url', |
||
917 | 'readable', # private |
||
918 | 'preload', |
||
919 | 'displaytitle', |
||
920 | // If you add more properties here, please consider whether they |
||
921 | // need to be added to getCacheMode() |
||
922 | ], |
||
923 | ApiBase::PARAM_HELP_MSG_PER_VALUE => [], |
||
924 | ], |
||
925 | 'testactions' => [ |
||
926 | ApiBase::PARAM_TYPE => 'string', |
||
927 | ApiBase::PARAM_ISMULTI => true, |
||
928 | ], |
||
929 | 'token' => [ |
||
930 | ApiBase::PARAM_DEPRECATED => true, |
||
931 | ApiBase::PARAM_ISMULTI => true, |
||
932 | ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ) |
||
933 | ], |
||
934 | 'continue' => [ |
||
935 | ApiBase::PARAM_HELP_MSG => 'api-help-param-continue', |
||
936 | ], |
||
937 | ]; |
||
938 | } |
||
939 | |||
940 | protected function getExamplesMessages() { |
||
941 | return [ |
||
942 | 'action=query&prop=info&titles=Main%20Page' |
||
943 | => 'apihelp-query+info-example-simple', |
||
944 | 'action=query&prop=info&inprop=protection&titles=Main%20Page' |
||
945 | => 'apihelp-query+info-example-protection', |
||
946 | ]; |
||
947 | } |
||
948 | |||
949 | public function getHelpUrls() { |
||
950 | return 'https://www.mediawiki.org/wiki/API:Info'; |
||
951 | } |
||
952 | } |
||
953 |
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: