MediaWiki REL1_35
ApiQueryInfo.php
Go to the documentation of this file.
1<?php
25
32
33 private $fld_protection = false, $fld_talkid = false,
34 $fld_subjectid = false, $fld_url = false,
35 $fld_readable = false, $fld_watched = false,
39
40 private $params;
41
43 private $titles;
45 private $missing;
47 private $everything;
48
51
54 private $showZeroWatchers = false;
55
57
59
60 public function __construct( ApiQuery $query, $moduleName ) {
61 parent::__construct( $query, $moduleName, 'in' );
62 }
63
68 public function requestExtraData( $pageSet ) {
69 $pageSet->requestField( 'page_restrictions' );
70 // If the pageset is resolving redirects we won't get page_is_redirect.
71 // But we can't know for sure until the pageset is executed (revids may
72 // turn it off), so request it unconditionally.
73 $pageSet->requestField( 'page_is_redirect' );
74 $pageSet->requestField( 'page_is_new' );
75 $config = $this->getConfig();
76 $pageSet->requestField( 'page_touched' );
77 $pageSet->requestField( 'page_latest' );
78 $pageSet->requestField( 'page_len' );
79 $pageSet->requestField( 'page_content_model' );
80 if ( $config->get( 'PageLanguageUseDB' ) ) {
81 $pageSet->requestField( 'page_lang' );
82 }
83 }
84
92 protected function getTokenFunctions() {
93 // Don't call the hooks twice
94 if ( isset( $this->tokenFunctions ) ) {
96 }
97
98 // If we're in a mode that breaks the same-origin policy, no tokens can
99 // be obtained
100 if ( $this->lacksSameOriginSecurity() ) {
101 return [];
102 }
103
104 $this->tokenFunctions = [
105 'edit' => [ self::class, 'getEditToken' ],
106 'delete' => [ self::class, 'getDeleteToken' ],
107 'protect' => [ self::class, 'getProtectToken' ],
108 'move' => [ self::class, 'getMoveToken' ],
109 'block' => [ self::class, 'getBlockToken' ],
110 'unblock' => [ self::class, 'getUnblockToken' ],
111 'email' => [ self::class, 'getEmailToken' ],
112 'import' => [ self::class, 'getImportToken' ],
113 'watch' => [ self::class, 'getWatchToken' ],
114 ];
115 $this->getHookRunner()->onAPIQueryInfoTokens( $this->tokenFunctions );
116
118 }
119
121 protected static $cachedTokens = [];
122
126 public static function resetTokenCache() {
127 self::$cachedTokens = [];
128 }
129
138 private static function getUserToken( string $right ) {
139 global $wgUser;
140 $user = $wgUser;
141
142 if ( !MediaWikiServices::getInstance()
144 ->userHasRight( $user, $right )
145 ) {
146 return false;
147 }
148
149 // The token is always the same, let's exploit that
150 if ( !isset( self::$cachedTokens['edit'] ) ) {
151 self::$cachedTokens['edit'] = $user->getEditToken();
152 }
153
154 return self::$cachedTokens['edit'];
155 }
156
160 public static function getEditToken( $pageid, $title ) {
161 return self::getUserToken( 'edit' );
162 }
163
167 public static function getDeleteToken( $pageid, $title ) {
168 return self::getUserToken( 'delete' );
169 }
170
174 public static function getProtectToken( $pageid, $title ) {
175 return self::getUserToken( 'protect' );
176 }
177
181 public static function getMoveToken( $pageid, $title ) {
182 return self::getUserToken( 'move' );
183 }
184
188 public static function getBlockToken( $pageid, $title ) {
189 return self::getUserToken( 'block' );
190 }
191
195 public static function getUnblockToken( $pageid, $title ) {
196 // Currently, this is exactly the same as the block token
197 return self::getBlockToken( $pageid, $title );
198 }
199
203 public static function getEmailToken( $pageid, $title ) {
204 global $wgUser;
205 $user = $wgUser;
206
207 if ( !$user->canSendEmail() || $user->isBlockedFromEmailuser() ) {
208 return false;
209 }
210
211 // The token is always the same, let's exploit that
212 if ( !isset( self::$cachedTokens['email'] ) ) {
213 self::$cachedTokens['email'] = $user->getEditToken();
214 }
215
216 return self::$cachedTokens['email'];
217 }
218
222 public static function getImportToken( $pageid, $title ) {
223 global $wgUser;
224 $user = $wgUser;
225
226 if ( !MediaWikiServices::getInstance()
228 ->userHasAnyRight( $user, 'import', 'importupload' ) ) {
229 return false;
230 }
231
232 // The token is always the same, let's exploit that
233 if ( !isset( self::$cachedTokens['import'] ) ) {
234 self::$cachedTokens['import'] = $user->getEditToken();
235 }
236
237 return self::$cachedTokens['import'];
238 }
239
243 public static function getWatchToken( $pageid, $title ) {
244 global $wgUser;
245 $user = $wgUser;
246
247 if ( !$user->isLoggedIn() ) {
248 return false;
249 }
250
251 // The token is always the same, let's exploit that
252 if ( !isset( self::$cachedTokens['watch'] ) ) {
253 self::$cachedTokens['watch'] = $user->getEditToken( 'watch' );
254 }
255
256 return self::$cachedTokens['watch'];
257 }
258
262 public static function getOptionsToken( $pageid, $title ) {
263 global $wgUser;
264 $user = $wgUser;
265
266 if ( !$user->isLoggedIn() ) {
267 return false;
268 }
269
270 // The token is always the same, let's exploit that
271 if ( !isset( self::$cachedTokens['options'] ) ) {
272 self::$cachedTokens['options'] = $user->getEditToken();
273 }
274
275 return self::$cachedTokens['options'];
276 }
277
278 public function execute() {
279 $this->params = $this->extractRequestParams();
280 if ( $this->params['prop'] !== null ) {
281 $prop = array_flip( $this->params['prop'] );
282 $this->fld_protection = isset( $prop['protection'] );
283 $this->fld_watched = isset( $prop['watched'] );
284 $this->fld_watchers = isset( $prop['watchers'] );
285 $this->fld_visitingwatchers = isset( $prop['visitingwatchers'] );
286 $this->fld_notificationtimestamp = isset( $prop['notificationtimestamp'] );
287 $this->fld_talkid = isset( $prop['talkid'] );
288 $this->fld_subjectid = isset( $prop['subjectid'] );
289 $this->fld_url = isset( $prop['url'] );
290 $this->fld_readable = isset( $prop['readable'] );
291 $this->fld_preload = isset( $prop['preload'] );
292 $this->fld_displaytitle = isset( $prop['displaytitle'] );
293 $this->fld_varianttitles = isset( $prop['varianttitles'] );
294 }
295
296 $pageSet = $this->getPageSet();
297 $this->titles = $pageSet->getGoodTitles();
298 $this->missing = $pageSet->getMissingTitles();
299 $this->everything = $this->titles + $this->missing;
300 $result = $this->getResult();
301
302 uasort( $this->everything, [ Title::class, 'compare' ] );
303 if ( $this->params['continue'] !== null ) {
304 // Throw away any titles we're gonna skip so they don't
305 // clutter queries
306 $cont = explode( '|', $this->params['continue'] );
307 $this->dieContinueUsageIf( count( $cont ) != 2 );
308 $conttitle = Title::makeTitleSafe( $cont[0], $cont[1] );
309 foreach ( $this->everything as $pageid => $title ) {
310 if ( Title::compare( $title, $conttitle ) >= 0 ) {
311 break;
312 }
313 unset( $this->titles[$pageid] );
314 unset( $this->missing[$pageid] );
315 unset( $this->everything[$pageid] );
316 }
317 }
318
319 $this->pageRestrictions = $pageSet->getCustomField( 'page_restrictions' );
320 // when resolving redirects, no page will have this field
321 $this->pageIsRedir = !$pageSet->isResolvingRedirects()
322 ? $pageSet->getCustomField( 'page_is_redirect' )
323 : [];
324 $this->pageIsNew = $pageSet->getCustomField( 'page_is_new' );
325
326 $this->pageTouched = $pageSet->getCustomField( 'page_touched' );
327 $this->pageLatest = $pageSet->getCustomField( 'page_latest' );
328 $this->pageLength = $pageSet->getCustomField( 'page_len' );
329
330 // Get protection info if requested
331 if ( $this->fld_protection ) {
332 $this->getProtectionInfo();
333 }
334
335 if ( $this->fld_watched || $this->fld_notificationtimestamp ) {
336 $this->getWatchedInfo();
337 }
338
339 if ( $this->fld_watchers ) {
340 $this->getWatcherInfo();
341 }
342
343 if ( $this->fld_visitingwatchers ) {
344 $this->getVisitingWatcherInfo();
345 }
346
347 // Run the talkid/subjectid query if requested
348 if ( $this->fld_talkid || $this->fld_subjectid ) {
349 $this->getTSIDs();
350 }
351
352 if ( $this->fld_displaytitle ) {
353 $this->getDisplayTitle();
354 }
355
356 if ( $this->fld_varianttitles ) {
357 $this->getVariantTitles();
358 }
359
361 foreach ( $this->everything as $pageid => $title ) {
362 $pageInfo = $this->extractPageInfo( $pageid, $title );
363 $fit = $pageInfo !== null && $result->addValue( [
364 'query',
365 'pages'
366 ], $pageid, $pageInfo );
367 if ( !$fit ) {
368 $this->setContinueEnumParameter( 'continue',
369 $title->getNamespace() . '|' .
370 $title->getText() );
371 break;
372 }
373 }
374 }
375
382 private function extractPageInfo( $pageid, $title ) {
383 $pageInfo = [];
384 // $title->exists() needs pageid, which is not set for all title objects
385 $titleExists = $pageid > 0;
386 $ns = $title->getNamespace();
387 $dbkey = $title->getDBkey();
388
389 $pageInfo['contentmodel'] = $title->getContentModel();
390
391 $pageLanguage = $title->getPageLanguage();
392 $pageInfo['pagelanguage'] = $pageLanguage->getCode();
393 $pageInfo['pagelanguagehtmlcode'] = $pageLanguage->getHtmlCode();
394 $pageInfo['pagelanguagedir'] = $pageLanguage->getDir();
395
396 $user = $this->getUser();
397
398 if ( $titleExists ) {
399 $pageInfo['touched'] = wfTimestamp( TS_ISO_8601, $this->pageTouched[$pageid] );
400 $pageInfo['lastrevid'] = (int)$this->pageLatest[$pageid];
401 $pageInfo['length'] = (int)$this->pageLength[$pageid];
402
403 if ( isset( $this->pageIsRedir[$pageid] ) && $this->pageIsRedir[$pageid] ) {
404 $pageInfo['redirect'] = true;
405 }
406 if ( $this->pageIsNew[$pageid] ) {
407 $pageInfo['new'] = true;
408 }
409 }
410
411 if ( $this->params['token'] !== null ) {
413 $pageInfo['starttimestamp'] = wfTimestamp( TS_ISO_8601, time() );
414 foreach ( $this->params['token'] as $t ) {
415 $val = call_user_func( $tokenFunctions[$t], $pageid, $title );
416 if ( $val === false ) {
417 $this->addWarning( [ 'apiwarn-tokennotallowed', $t ] );
418 } else {
419 $pageInfo[$t . 'token'] = $val;
420 }
421 }
422 }
423
424 if ( $this->fld_protection ) {
425 $pageInfo['protection'] = [];
426 if ( isset( $this->protections[$ns][$dbkey] ) ) {
427 $pageInfo['protection'] =
428 $this->protections[$ns][$dbkey];
429 }
430 ApiResult::setIndexedTagName( $pageInfo['protection'], 'pr' );
431
432 $pageInfo['restrictiontypes'] = [];
433 if ( isset( $this->restrictionTypes[$ns][$dbkey] ) ) {
434 $pageInfo['restrictiontypes'] =
435 $this->restrictionTypes[$ns][$dbkey];
436 }
437 ApiResult::setIndexedTagName( $pageInfo['restrictiontypes'], 'rt' );
438 }
439
440 if ( $this->fld_watched && $this->watched !== null ) {
441 $pageInfo['watched'] = $this->watched[$ns][$dbkey];
442 }
443
444 if ( $this->fld_watchers ) {
445 if ( $this->watchers !== null && $this->watchers[$ns][$dbkey] !== 0 ) {
446 $pageInfo['watchers'] = $this->watchers[$ns][$dbkey];
447 } elseif ( $this->showZeroWatchers ) {
448 $pageInfo['watchers'] = 0;
449 }
450 }
451
452 if ( $this->fld_visitingwatchers ) {
453 if ( $this->visitingwatchers !== null && $this->visitingwatchers[$ns][$dbkey] !== 0 ) {
454 $pageInfo['visitingwatchers'] = $this->visitingwatchers[$ns][$dbkey];
455 } elseif ( $this->showZeroWatchers ) {
456 $pageInfo['visitingwatchers'] = 0;
457 }
458 }
459
460 if ( $this->fld_notificationtimestamp ) {
461 $pageInfo['notificationtimestamp'] = '';
462 if ( $this->notificationtimestamps[$ns][$dbkey] ) {
463 $pageInfo['notificationtimestamp'] =
464 wfTimestamp( TS_ISO_8601, $this->notificationtimestamps[$ns][$dbkey] );
465 }
466 }
467
468 if ( $this->fld_talkid && isset( $this->talkids[$ns][$dbkey] ) ) {
469 $pageInfo['talkid'] = $this->talkids[$ns][$dbkey];
470 }
471
472 if ( $this->fld_subjectid && isset( $this->subjectids[$ns][$dbkey] ) ) {
473 $pageInfo['subjectid'] = $this->subjectids[$ns][$dbkey];
474 }
475
476 if ( $this->fld_url ) {
477 $pageInfo['fullurl'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
478 $pageInfo['editurl'] = wfExpandUrl( $title->getFullURL( 'action=edit' ), PROTO_CURRENT );
479 $pageInfo['canonicalurl'] = wfExpandUrl( $title->getFullURL(), PROTO_CANONICAL );
480 }
481 if ( $this->fld_readable ) {
482 $pageInfo['readable'] = $this->getPermissionManager()->userCan(
483 'read', $user, $title
484 );
485 }
486
487 if ( $this->fld_preload ) {
488 if ( $titleExists ) {
489 $pageInfo['preload'] = '';
490 } else {
491 $text = null;
492 $this->getHookRunner()->onEditFormPreloadText( $text, $title );
493
494 $pageInfo['preload'] = $text;
495 }
496 }
497
498 if ( $this->fld_displaytitle ) {
499 if ( isset( $this->displaytitles[$pageid] ) ) {
500 $pageInfo['displaytitle'] = $this->displaytitles[$pageid];
501 } else {
502 $pageInfo['displaytitle'] = $title->getPrefixedText();
503 }
504 }
505
506 if ( $this->fld_varianttitles && isset( $this->variantTitles[$pageid] ) ) {
507 $pageInfo['varianttitles'] = $this->variantTitles[$pageid];
508 }
509
510 if ( $this->params['testactions'] ) {
511 $limit = $this->getMain()->canApiHighLimits() ? self::LIMIT_SML2 : self::LIMIT_SML1;
512 if ( $this->countTestedActions >= $limit ) {
513 return null; // force a continuation
514 }
515
516 $detailLevel = $this->params['testactionsdetail'];
517 $rigor = $detailLevel === 'quick'
518 ? PermissionManager::RIGOR_QUICK
519 // Not using RIGOR_SECURE here, because that results in master connection
520 : PermissionManager::RIGOR_FULL;
521 $errorFormatter = $this->getErrorFormatter();
522 if ( $errorFormatter->getFormat() === 'bc' ) {
523 // Eew, no. Use a more modern format here.
524 $errorFormatter = $errorFormatter->newWithFormat( 'plaintext' );
525 }
526
527 $user = $this->getUser();
528 $pageInfo['actions'] = [];
529 foreach ( $this->params['testactions'] as $action ) {
530 $this->countTestedActions++;
531
532 if ( $detailLevel === 'boolean' ) {
533 $pageInfo['actions'][$action] = $this->getPermissionManager()->userCan(
534 $action, $user, $title
535 );
536 } else {
537 $pageInfo['actions'][$action] = $errorFormatter->arrayFromStatus( $this->errorArrayToStatus(
538 $this->getPermissionManager()->getPermissionErrors(
539 $action, $user, $title, $rigor
540 ),
541 $user
542 ) );
543 }
544 }
545 }
546
547 return $pageInfo;
548 }
549
553 private function getProtectionInfo() {
554 $this->protections = [];
555 $db = $this->getDB();
556
557 // Get normal protections for existing titles
558 if ( count( $this->titles ) ) {
559 $this->resetQueryParams();
560 $this->addTables( 'page_restrictions' );
561 $this->addFields( [ 'pr_page', 'pr_type', 'pr_level',
562 'pr_expiry', 'pr_cascade' ] );
563 $this->addWhereFld( 'pr_page', array_keys( $this->titles ) );
564
565 $res = $this->select( __METHOD__ );
566 foreach ( $res as $row ) {
568 $title = $this->titles[$row->pr_page];
569 $a = [
570 'type' => $row->pr_type,
571 'level' => $row->pr_level,
572 'expiry' => ApiResult::formatExpiry( $row->pr_expiry )
573 ];
574 if ( $row->pr_cascade ) {
575 $a['cascade'] = true;
576 }
577 $this->protections[$title->getNamespace()][$title->getDBkey()][] = $a;
578 }
579 // Also check old restrictions
580 foreach ( $this->titles as $pageId => $title ) {
581 if ( $this->pageRestrictions[$pageId] ) {
582 $namespace = $title->getNamespace();
583 $dbKey = $title->getDBkey();
584 $restrictions = explode( ':', trim( $this->pageRestrictions[$pageId] ) );
585 foreach ( $restrictions as $restrict ) {
586 $temp = explode( '=', trim( $restrict ) );
587 if ( count( $temp ) == 1 ) {
588 // old old format should be treated as edit/move restriction
589 $restriction = trim( $temp[0] );
590
591 if ( $restriction == '' ) {
592 continue;
593 }
594 $this->protections[$namespace][$dbKey][] = [
595 'type' => 'edit',
596 'level' => $restriction,
597 'expiry' => 'infinity',
598 ];
599 $this->protections[$namespace][$dbKey][] = [
600 'type' => 'move',
601 'level' => $restriction,
602 'expiry' => 'infinity',
603 ];
604 } else {
605 $restriction = trim( $temp[1] );
606 if ( $restriction == '' ) {
607 continue;
608 }
609 $this->protections[$namespace][$dbKey][] = [
610 'type' => $temp[0],
611 'level' => $restriction,
612 'expiry' => 'infinity',
613 ];
614 }
615 }
616 }
617 }
618 }
619
620 // Get protections for missing titles
621 if ( count( $this->missing ) ) {
622 $this->resetQueryParams();
623 $lb = new LinkBatch( $this->missing );
624 $this->addTables( 'protected_titles' );
625 $this->addFields( [ 'pt_title', 'pt_namespace', 'pt_create_perm', 'pt_expiry' ] );
626 $this->addWhere( $lb->constructSet( 'pt', $db ) );
627 $res = $this->select( __METHOD__ );
628 foreach ( $res as $row ) {
629 $this->protections[$row->pt_namespace][$row->pt_title][] = [
630 'type' => 'create',
631 'level' => $row->pt_create_perm,
632 'expiry' => ApiResult::formatExpiry( $row->pt_expiry )
633 ];
634 }
635 }
636
637 // Separate good and missing titles into files and other pages
638 // and populate $this->restrictionTypes
639 $images = $others = [];
640 foreach ( $this->everything as $title ) {
641 if ( $title->getNamespace() == NS_FILE ) {
642 $images[] = $title->getDBkey();
643 } else {
644 $others[] = $title;
645 }
646 // Applicable protection types
647 $this->restrictionTypes[$title->getNamespace()][$title->getDBkey()] =
648 array_values( $title->getRestrictionTypes() );
649 }
650
651 if ( count( $others ) ) {
652 // Non-images: check templatelinks
653 $lb = new LinkBatch( $others );
654 $this->resetQueryParams();
655 $this->addTables( [ 'page_restrictions', 'page', 'templatelinks' ] );
656 $this->addFields( [ 'pr_type', 'pr_level', 'pr_expiry',
657 'page_title', 'page_namespace',
658 'tl_title', 'tl_namespace' ] );
659 $this->addWhere( $lb->constructSet( 'tl', $db ) );
660 $this->addWhere( 'pr_page = page_id' );
661 $this->addWhere( 'pr_page = tl_from' );
662 $this->addWhereFld( 'pr_cascade', 1 );
663
664 $res = $this->select( __METHOD__ );
665 foreach ( $res as $row ) {
666 $source = Title::makeTitle( $row->page_namespace, $row->page_title );
667 $this->protections[$row->tl_namespace][$row->tl_title][] = [
668 'type' => $row->pr_type,
669 'level' => $row->pr_level,
670 'expiry' => ApiResult::formatExpiry( $row->pr_expiry ),
671 'source' => $source->getPrefixedText()
672 ];
673 }
674 }
675
676 if ( count( $images ) ) {
677 // Images: check imagelinks
678 $this->resetQueryParams();
679 $this->addTables( [ 'page_restrictions', 'page', 'imagelinks' ] );
680 $this->addFields( [ 'pr_type', 'pr_level', 'pr_expiry',
681 'page_title', 'page_namespace', 'il_to' ] );
682 $this->addWhere( 'pr_page = page_id' );
683 $this->addWhere( 'pr_page = il_from' );
684 $this->addWhereFld( 'pr_cascade', 1 );
685 $this->addWhereFld( 'il_to', $images );
686
687 $res = $this->select( __METHOD__ );
688 foreach ( $res as $row ) {
689 $source = Title::makeTitle( $row->page_namespace, $row->page_title );
690 $this->protections[NS_FILE][$row->il_to][] = [
691 'type' => $row->pr_type,
692 'level' => $row->pr_level,
693 'expiry' => ApiResult::formatExpiry( $row->pr_expiry ),
694 'source' => $source->getPrefixedText()
695 ];
696 }
697 }
698 }
699
704 private function getTSIDs() {
705 $getTitles = $this->talkids = $this->subjectids = [];
706 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
707
709 foreach ( $this->everything as $t ) {
710 if ( $nsInfo->isTalk( $t->getNamespace() ) ) {
711 if ( $this->fld_subjectid ) {
712 $getTitles[] = $t->getSubjectPage();
713 }
714 } elseif ( $this->fld_talkid ) {
715 $getTitles[] = $t->getTalkPage();
716 }
717 }
718 if ( $getTitles === [] ) {
719 return;
720 }
721
722 $db = $this->getDB();
723
724 // Construct a custom WHERE clause that matches
725 // all titles in $getTitles
726 $lb = new LinkBatch( $getTitles );
727 $this->resetQueryParams();
728 $this->addTables( 'page' );
729 $this->addFields( [ 'page_title', 'page_namespace', 'page_id' ] );
730 $this->addWhere( $lb->constructSet( 'page', $db ) );
731 $res = $this->select( __METHOD__ );
732 foreach ( $res as $row ) {
733 if ( $nsInfo->isTalk( $row->page_namespace ) ) {
734 $this->talkids[$nsInfo->getSubject( $row->page_namespace )][$row->page_title] =
735 (int)( $row->page_id );
736 } else {
737 $this->subjectids[$nsInfo->getTalk( $row->page_namespace )][$row->page_title] =
738 (int)( $row->page_id );
739 }
740 }
741 }
742
743 private function getDisplayTitle() {
744 $this->displaytitles = [];
745
746 $pageIds = array_keys( $this->titles );
747
748 if ( $pageIds === [] ) {
749 return;
750 }
751
752 $this->resetQueryParams();
753 $this->addTables( 'page_props' );
754 $this->addFields( [ 'pp_page', 'pp_value' ] );
755 $this->addWhereFld( 'pp_page', $pageIds );
756 $this->addWhereFld( 'pp_propname', 'displaytitle' );
757 $res = $this->select( __METHOD__ );
758
759 foreach ( $res as $row ) {
760 $this->displaytitles[$row->pp_page] = $row->pp_value;
761 }
762 }
763
764 private function getVariantTitles() {
765 if ( $this->titles === [] ) {
766 return;
767 }
768 $this->variantTitles = [];
769 foreach ( $this->titles as $pageId => $t ) {
770 $this->variantTitles[$pageId] = isset( $this->displaytitles[$pageId] )
771 ? $this->getAllVariants( $this->displaytitles[$pageId] )
772 : $this->getAllVariants( $t->getText(), $t->getNamespace() );
773 }
774 }
775
776 private function getAllVariants( $text, $ns = NS_MAIN ) {
777 $result = [];
778 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
779 foreach ( $contLang->getVariants() as $variant ) {
780 $convertTitle = $contLang->autoConvert( $text, $variant );
781 if ( $ns !== NS_MAIN ) {
782 $convertNs = $contLang->convertNamespace( $ns, $variant );
783 $convertTitle = $convertNs . ':' . $convertTitle;
784 }
785 $result[$variant] = $convertTitle;
786 }
787 return $result;
788 }
789
794 private function getWatchedInfo() {
795 $user = $this->getUser();
796
797 if ( $user->isAnon() || count( $this->everything ) == 0
798 || !$this->getPermissionManager()->userHasRight( $user, 'viewmywatchlist' )
799 ) {
800 return;
801 }
802
803 $this->watched = [];
804 $this->notificationtimestamps = [];
805
806 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
807 $timestamps = $store->getNotificationTimestampsBatch( $user, $this->everything );
808
809 if ( $this->fld_watched ) {
810 foreach ( $timestamps as $namespaceId => $dbKeys ) {
811 $this->watched[$namespaceId] = array_map(
812 function ( $x ) {
813 return $x !== false;
814 },
815 $dbKeys
816 );
817 }
818 }
819 if ( $this->fld_notificationtimestamp ) {
820 $this->notificationtimestamps = $timestamps;
821 }
822 }
823
827 private function getWatcherInfo() {
828 if ( count( $this->everything ) == 0 ) {
829 return;
830 }
831
832 $user = $this->getUser();
833 $canUnwatchedpages = $this->getPermissionManager()->userHasRight( $user, 'unwatchedpages' );
834 $unwatchedPageThreshold = $this->getConfig()->get( 'UnwatchedPageThreshold' );
835 if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) {
836 return;
837 }
838
839 $this->showZeroWatchers = $canUnwatchedpages;
840
841 $countOptions = [];
842 if ( !$canUnwatchedpages ) {
843 $countOptions['minimumWatchers'] = $unwatchedPageThreshold;
844 }
845
846 $this->watchers = MediaWikiServices::getInstance()->getWatchedItemStore()->countWatchersMultiple(
847 $this->everything,
848 $countOptions
849 );
850 }
851
858 private function getVisitingWatcherInfo() {
859 $config = $this->getConfig();
860 $user = $this->getUser();
861 $db = $this->getDB();
862
863 $canUnwatchedpages = $this->getPermissionManager()->userHasRight( $user, 'unwatchedpages' );
864 $unwatchedPageThreshold = $this->getConfig()->get( 'UnwatchedPageThreshold' );
865 if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) {
866 return;
867 }
868
869 $this->showZeroWatchers = $canUnwatchedpages;
870
871 $titlesWithThresholds = [];
872 if ( $this->titles ) {
873 $lb = new LinkBatch( $this->titles );
874
875 // Fetch last edit timestamps for pages
876 $this->resetQueryParams();
877 $this->addTables( [ 'page', 'revision' ] );
878 $this->addFields( [ 'page_namespace', 'page_title', 'rev_timestamp' ] );
879 $this->addWhere( [
880 'page_latest = rev_id',
881 $lb->constructSet( 'page', $db ),
882 ] );
883 $this->addOption( 'GROUP BY', [ 'page_namespace', 'page_title' ] );
884 $timestampRes = $this->select( __METHOD__ );
885
886 $age = $config->get( 'WatchersMaxAge' );
887 $timestamps = [];
888 foreach ( $timestampRes as $row ) {
889 $revTimestamp = wfTimestamp( TS_UNIX, (int)$row->rev_timestamp );
890 $timestamps[$row->page_namespace][$row->page_title] = (int)$revTimestamp - $age;
891 }
892 $titlesWithThresholds = array_map(
893 function ( LinkTarget $target ) use ( $timestamps ) {
894 return [
895 $target, $timestamps[$target->getNamespace()][$target->getDBkey()]
896 ];
897 },
899 );
900 }
901
902 if ( $this->missing ) {
903 $titlesWithThresholds = array_merge(
904 $titlesWithThresholds,
905 array_map(
906 function ( LinkTarget $target ) {
907 return [ $target, null ];
908 },
910 )
911 );
912 }
913 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
914 $this->visitingwatchers = $store->countVisitingWatchersMultiple(
915 $titlesWithThresholds,
916 !$canUnwatchedpages ? $unwatchedPageThreshold : null
917 );
918 }
919
920 public function getCacheMode( $params ) {
921 // Other props depend on something about the current user
922 $publicProps = [
923 'protection',
924 'talkid',
925 'subjectid',
926 'url',
927 'preload',
928 'displaytitle',
929 'varianttitles',
930 ];
931 if ( array_diff( (array)$params['prop'], $publicProps ) ) {
932 return 'private';
933 }
934
935 // testactions also depends on the current user
936 if ( $params['testactions'] ) {
937 return 'private';
938 }
939
940 if ( $params['token'] !== null ) {
941 return 'private';
942 }
943
944 return 'public';
945 }
946
947 public function getAllowedParams() {
948 return [
949 'prop' => [
952 'protection',
953 'talkid',
954 'watched', # private
955 'watchers', # private
956 'visitingwatchers', # private
957 'notificationtimestamp', # private
958 'subjectid',
959 'url',
960 'readable', # private
961 'preload',
962 'displaytitle',
963 'varianttitles',
964 // If you add more properties here, please consider whether they
965 // need to be added to getCacheMode()
966 ],
969 'readable' => true, // Since 1.32
970 ],
971 ],
972 'testactions' => [
973 ApiBase::PARAM_TYPE => 'string',
975 ],
976 'testactionsdetail' => [
977 ApiBase::PARAM_TYPE => [ 'boolean', 'full', 'quick' ],
978 ApiBase::PARAM_DFLT => 'boolean',
980 ],
981 'token' => [
984 ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() )
985 ],
986 'continue' => [
987 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
988 ],
989 ];
990 }
991
992 protected function getExamplesMessages() {
993 return [
994 'action=query&prop=info&titles=Main%20Page'
995 => 'apihelp-query+info-example-simple',
996 'action=query&prop=info&inprop=protection&titles=Main%20Page'
997 => 'apihelp-query+info-example-protection',
998 ];
999 }
1000
1001 public function getHelpUrls() {
1002 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Info';
1003 }
1004}
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
const PARAM_DEPRECATED
Definition ApiBase.php:98
dieContinueUsageIf( $condition)
Die with the 'badcontinue' error.
Definition ApiBase.php:1617
const PARAM_DEPRECATED_VALUES
Definition ApiBase.php:126
getMain()
Get the main module.
Definition ApiBase.php:515
const PARAM_TYPE
Definition ApiBase.php:78
getErrorFormatter()
Get the error formatter Stable to override.
Definition ApiBase.php:635
const PARAM_DFLT
Definition ApiBase.php:70
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition ApiBase.php:692
errorArrayToStatus(array $errors, User $user=null)
Turn an array of message keys or key+param arrays into a Status.
Definition ApiBase.php:1248
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, this is an array mapping those values to $msg...
Definition ApiBase.php:195
const LIMIT_SML2
Slow query, apihighlimits limit.
Definition ApiBase.php:226
getResult()
Get the result object.
Definition ApiBase.php:620
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:772
const LIMIT_SML1
Slow query, standard limit.
Definition ApiBase.php:224
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:162
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition ApiBase.php:1356
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:717
const PARAM_ISMULTI
Definition ApiBase.php:74
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
Definition ApiBase.php:548
This is a base class for all Query modules.
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
resetQueryParams()
Blank the internal arrays with query parameters.
addFields( $value)
Add a set of fields to select to the internal array.
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
getDB()
Get the Query database connection (read-only) Stable to override.
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
getPageSet()
Get the PageSet object to work on Stable to override.
addWhere( $value)
Add a set of WHERE clauses to the internal array.
A query module to show basic page information.
static resetTokenCache()
getTokenFunctions()
Get an array mapping token names to their handler functions.
getVisitingWatcherInfo()
Get the count of watchers who have visited recent edits and put it in $this->visitingwatchers.
static getUserToken(string $right)
Temporary method until the token methods are removed entirely.
getExamplesMessages()
Returns usage examples for this module.
static getUnblockToken( $pageid, $title)
static getWatchToken( $pageid, $title)
static getDeleteToken( $pageid, $title)
getAllVariants( $text, $ns=NS_MAIN)
Title[] $titles
Title[] $everything
static getEditToken( $pageid, $title)
static string[] $cachedTokens
Title[] $missing
static getMoveToken( $pageid, $title)
static getEmailToken( $pageid, $title)
static getProtectToken( $pageid, $title)
getWatchedInfo()
Get information about watched status and put it in $this->watched and $this->notificationtimestamps.
__construct(ApiQuery $query, $moduleName)
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
static getOptionsToken( $pageid, $title)
getProtectionInfo()
Get information about protections and put it in $protections.
static getBlockToken( $pageid, $title)
requestExtraData( $pageSet)
getWatcherInfo()
Get the count of watchers and put it in $this->watchers.
extractPageInfo( $pageid, $title)
Get a result array with information about a title.
getHelpUrls()
Return links to more detailed help pages about the module.
static getImportToken( $pageid, $title)
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
getTSIDs()
Get talk page IDs (if requested) and subject page IDs (if requested) and put them in $talkids and $su...
getCacheMode( $params)
Get the cache mode for the data generated by this module.
This is the main query class.
Definition ApiQuery.php:37
getUser()
Stable to override.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:35
MediaWikiServices is the service locator for the application scope of MediaWiki.
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Represents a title within MediaWiki.
Definition Title.php:42
const PROTO_CANONICAL
Definition Defines.php:213
const NS_FILE
Definition Defines.php:76
const PROTO_CURRENT
Definition Defines.php:212
const NS_MAIN
Definition Defines.php:70
getNamespace()
Get the namespace index.
getDBkey()
Get the main part with underscores.
$source