MediaWiki REL1_35
ApiPageSet.php
Go to the documentation of this file.
1<?php
27
42class ApiPageSet extends ApiBase {
47 private const DISABLE_GENERATORS = 1;
48
49 private $mDbSource;
50 private $mParams = [];
54
55 private $mAllPages = []; // [ns][dbkey] => page_id or negative when missing
56 private $mTitles = [];
57 private $mGoodAndMissingPages = []; // [ns][dbkey] => page_id or negative when missing
58 private $mGoodPages = []; // [ns][dbkey] => page_id
59 private $mGoodTitles = [];
60 private $mMissingPages = []; // [ns][dbkey] => fake page_id
61 private $mMissingTitles = [];
63 private $mInvalidTitles = [];
64 private $mMissingPageIDs = [];
65 private $mRedirectTitles = [];
66 private $mSpecialTitles = [];
67 private $mAllSpecials = []; // separate from mAllPages to avoid breaking getAllTitlesByNamespace()
68 private $mNormalizedTitles = [];
69 private $mInterwikiTitles = [];
71 private $mPendingRedirectIDs = [];
72 private $mPendingRedirectSpecialPages = []; // [dbkey] => [ Title $from, Title $to ]
74 private $mConvertedTitles = [];
75 private $mGoodRevIDs = [];
76 private $mLiveRevIDs = [];
77 private $mDeletedRevIDs = [];
78 private $mMissingRevIDs = [];
79 private $mGeneratorData = []; // [ns][dbkey] => data array
80 private $mFakePageId = -1;
81 private $mCacheMode = 'public';
83 private $mRequestedPageFields = [];
85 private $mDefaultNamespace = NS_MAIN;
88
96 private static function addValues( array &$result, $values, $flags = [], $name = null ) {
97 foreach ( $values as $val ) {
98 if ( $val instanceof Title ) {
99 $v = [];
100 ApiQueryBase::addTitleInfo( $v, $val );
101 } elseif ( $name !== null ) {
102 $v = [ $name => $val ];
103 } else {
104 $v = $val;
105 }
106 foreach ( $flags as $flag ) {
107 $v[$flag] = true;
108 }
109 $result[] = $v;
110 }
111 }
112
120 public function __construct( ApiBase $dbSource, $flags = 0, $defaultNamespace = NS_MAIN ) {
121 parent::__construct( $dbSource->getMain(), $dbSource->getModuleName() );
122 $this->mDbSource = $dbSource;
123 $this->mAllowGenerator = ( $flags & self::DISABLE_GENERATORS ) == 0;
124 $this->mDefaultNamespace = $defaultNamespace;
125
126 $this->mParams = $this->extractRequestParams();
127 $this->mResolveRedirects = $this->mParams['redirects'];
128 $this->mConvertTitles = $this->mParams['converttitles'];
129 }
130
135 public function executeDryRun() {
136 $this->executeInternal( true );
137 }
138
142 public function execute() {
143 $this->executeInternal( false );
144 }
145
151 private function executeInternal( $isDryRun ) {
152 $generatorName = $this->mAllowGenerator ? $this->mParams['generator'] : null;
153 if ( isset( $generatorName ) ) {
154 $dbSource = $this->mDbSource;
155 if ( !$dbSource instanceof ApiQuery ) {
156 // If the parent container of this pageset is not ApiQuery, we must create it to run generator
157 $dbSource = $this->getMain()->getModuleManager()->getModule( 'query' );
158 }
159 $generator = $dbSource->getModuleManager()->getModule( $generatorName, null, true );
160 if ( $generator === null ) {
161 $this->dieWithError( [ 'apierror-badgenerator-unknown', $generatorName ], 'badgenerator' );
162 }
163 if ( !$generator instanceof ApiQueryGeneratorBase ) {
164 $this->dieWithError( [ 'apierror-badgenerator-notgenerator', $generatorName ], 'badgenerator' );
165 }
166 // Create a temporary pageset to store generator's output,
167 // add any additional fields generator may need, and execute pageset to populate titles/pageids
168 $tmpPageSet = new ApiPageSet( $dbSource, self::DISABLE_GENERATORS );
169 $generator->setGeneratorMode( $tmpPageSet );
170 $this->mCacheMode = $generator->getCacheMode( $generator->extractRequestParams() );
171
172 if ( !$isDryRun ) {
173 $generator->requestExtraData( $tmpPageSet );
174 }
175 $tmpPageSet->executeInternal( $isDryRun );
176
177 // populate this pageset with the generator output
178 if ( !$isDryRun ) {
179 $generator->executeGenerator( $this );
180
181 $this->getHookRunner()->onAPIQueryGeneratorAfterExecute( $generator, $this );
182 } else {
183 // Prevent warnings from being reported on these parameters
184 $main = $this->getMain();
185 foreach ( $generator->extractRequestParams() as $paramName => $param ) {
186 $main->markParamsUsed( $generator->encodeParamName( $paramName ) );
187 }
188 }
189
190 if ( !$isDryRun ) {
192 }
193 } else {
194 // Only one of the titles/pageids/revids is allowed at the same time
195 $dataSource = null;
196 if ( isset( $this->mParams['titles'] ) ) {
197 $dataSource = 'titles';
198 }
199 if ( isset( $this->mParams['pageids'] ) ) {
200 if ( isset( $dataSource ) ) {
201 $this->dieWithError(
202 [
203 'apierror-invalidparammix-cannotusewith',
204 $this->encodeParamName( 'pageids' ),
205 $this->encodeParamName( $dataSource )
206 ],
207 'multisource'
208 );
209 }
210 $dataSource = 'pageids';
211 }
212 if ( isset( $this->mParams['revids'] ) ) {
213 if ( isset( $dataSource ) ) {
214 $this->dieWithError(
215 [
216 'apierror-invalidparammix-cannotusewith',
217 $this->encodeParamName( 'revids' ),
218 $this->encodeParamName( $dataSource )
219 ],
220 'multisource'
221 );
222 }
223 $dataSource = 'revids';
224 }
225
226 if ( !$isDryRun ) {
227 // Populate page information with the original user input
228 switch ( $dataSource ) {
229 case 'titles':
230 $this->initFromTitles( $this->mParams['titles'] );
231 break;
232 case 'pageids':
233 $this->initFromPageIds( $this->mParams['pageids'] );
234 break;
235 case 'revids':
236 if ( $this->mResolveRedirects ) {
237 $this->addWarning( 'apiwarn-redirectsandrevids' );
238 }
239 $this->mResolveRedirects = false;
240 $this->initFromRevIDs( $this->mParams['revids'] );
241 break;
242 default:
243 // Do nothing - some queries do not need any of the data sources.
244 break;
245 }
246 }
247 }
248 }
249
254 public function isResolvingRedirects() {
255 return $this->mResolveRedirects;
256 }
257
266 public function getDataSource() {
267 if ( $this->mAllowGenerator && isset( $this->mParams['generator'] ) ) {
268 return 'generator';
269 }
270 if ( isset( $this->mParams['titles'] ) ) {
271 return 'titles';
272 }
273 if ( isset( $this->mParams['pageids'] ) ) {
274 return 'pageids';
275 }
276 if ( isset( $this->mParams['revids'] ) ) {
277 return 'revids';
278 }
279
280 return null;
281 }
282
288 public function requestField( $fieldName ) {
289 $this->mRequestedPageFields[$fieldName] = null;
290 }
291
298 public function getCustomField( $fieldName ) {
299 return $this->mRequestedPageFields[$fieldName];
300 }
301
308 public function getPageTableFields() {
309 // Ensure we get minimum required fields
310 // DON'T change this order
311 $pageFlds = [
312 'page_namespace' => null,
313 'page_title' => null,
314 'page_id' => null,
315 ];
316
317 if ( $this->mResolveRedirects ) {
318 $pageFlds['page_is_redirect'] = null;
319 }
320
321 $pageFlds['page_content_model'] = null;
322
323 if ( $this->getConfig()->get( 'PageLanguageUseDB' ) ) {
324 $pageFlds['page_lang'] = null;
325 }
326
327 foreach ( LinkCache::getSelectFields() as $field ) {
328 $pageFlds[$field] = null;
329 }
330
331 $pageFlds = array_merge( $pageFlds, $this->mRequestedPageFields );
332
333 return array_keys( $pageFlds );
334 }
335
342 public function getAllTitlesByNamespace() {
343 return $this->mAllPages;
344 }
345
350 public function getTitles() {
351 return $this->mTitles;
352 }
353
358 public function getTitleCount() {
359 return count( $this->mTitles );
360 }
361
366 public function getGoodTitlesByNamespace() {
367 return $this->mGoodPages;
368 }
369
374 public function getGoodTitles() {
375 return $this->mGoodTitles;
376 }
377
382 public function getGoodTitleCount() {
383 return count( $this->mGoodTitles );
384 }
385
391 public function getMissingTitlesByNamespace() {
392 return $this->mMissingPages;
393 }
394
400 public function getMissingTitles() {
401 return $this->mMissingTitles;
402 }
403
409 return $this->mGoodAndMissingPages;
410 }
411
416 public function getGoodAndMissingTitles() {
417 return $this->mGoodTitles + $this->mMissingTitles;
418 }
419
425 public function getInvalidTitlesAndReasons() {
426 return $this->mInvalidTitles;
427 }
428
433 public function getMissingPageIDs() {
434 return $this->mMissingPageIDs;
435 }
436
442 public function getRedirectTitles() {
443 return $this->mRedirectTitles;
444 }
445
453 public function getRedirectTitlesAsResult( $result = null ) {
454 $values = [];
455 foreach ( $this->getRedirectTitles() as $titleStrFrom => $titleTo ) {
456 $r = [
457 'from' => strval( $titleStrFrom ),
458 'to' => $titleTo->getPrefixedText(),
459 ];
460 if ( $titleTo->hasFragment() ) {
461 $r['tofragment'] = $titleTo->getFragment();
462 }
463 if ( $titleTo->isExternal() ) {
464 $r['tointerwiki'] = $titleTo->getInterwiki();
465 }
466 if ( isset( $this->mResolvedRedirectTitles[$titleStrFrom] ) ) {
467 $titleFrom = $this->mResolvedRedirectTitles[$titleStrFrom];
468 $ns = $titleFrom->getNamespace();
469 $dbkey = $titleFrom->getDBkey();
470 if ( isset( $this->mGeneratorData[$ns][$dbkey] ) ) {
471 $r = array_merge( $this->mGeneratorData[$ns][$dbkey], $r );
472 }
473 }
474
475 $values[] = $r;
476 }
477 if ( !empty( $values ) && $result ) {
478 ApiResult::setIndexedTagName( $values, 'r' );
479 }
480
481 return $values;
482 }
483
489 public function getNormalizedTitles() {
490 return $this->mNormalizedTitles;
491 }
492
500 public function getNormalizedTitlesAsResult( $result = null ) {
501 $values = [];
502 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
503 foreach ( $this->getNormalizedTitles() as $rawTitleStr => $titleStr ) {
504 $encode = $contLang->normalize( $rawTitleStr ) !== $rawTitleStr;
505 $values[] = [
506 'fromencoded' => $encode,
507 'from' => $encode ? rawurlencode( $rawTitleStr ) : $rawTitleStr,
508 'to' => $titleStr
509 ];
510 }
511 if ( !empty( $values ) && $result ) {
512 ApiResult::setIndexedTagName( $values, 'n' );
513 }
514
515 return $values;
516 }
517
523 public function getConvertedTitles() {
524 return $this->mConvertedTitles;
525 }
526
534 public function getConvertedTitlesAsResult( $result = null ) {
535 $values = [];
536 foreach ( $this->getConvertedTitles() as $rawTitleStr => $titleStr ) {
537 $values[] = [
538 'from' => $rawTitleStr,
539 'to' => $titleStr
540 ];
541 }
542 if ( !empty( $values ) && $result ) {
543 ApiResult::setIndexedTagName( $values, 'c' );
544 }
545
546 return $values;
547 }
548
554 public function getInterwikiTitles() {
555 return $this->mInterwikiTitles;
556 }
557
566 public function getInterwikiTitlesAsResult( $result = null, $iwUrl = false ) {
567 $values = [];
568 foreach ( $this->getInterwikiTitles() as $rawTitleStr => $interwikiStr ) {
569 $item = [
570 'title' => $rawTitleStr,
571 'iw' => $interwikiStr,
572 ];
573 if ( $iwUrl ) {
574 $title = Title::newFromText( $rawTitleStr );
575 $item['url'] = $title->getFullURL( '', false, PROTO_CURRENT );
576 }
577 $values[] = $item;
578 }
579 if ( !empty( $values ) && $result ) {
580 ApiResult::setIndexedTagName( $values, 'i' );
581 }
582
583 return $values;
584 }
585
600 public function getInvalidTitlesAndRevisions( $invalidChecks = [ 'invalidTitles',
601 'special', 'missingIds', 'missingRevIds', 'missingTitles', 'interwikiTitles' ]
602 ) {
603 $result = [];
604 if ( in_array( 'invalidTitles', $invalidChecks ) ) {
605 self::addValues( $result, $this->getInvalidTitlesAndReasons(), [ 'invalid' ] );
606 }
607 if ( in_array( 'special', $invalidChecks ) ) {
608 $known = [];
609 $unknown = [];
610 foreach ( $this->getSpecialTitles() as $title ) {
611 if ( $title->isKnown() ) {
612 $known[] = $title;
613 } else {
614 $unknown[] = $title;
615 }
616 }
617 self::addValues( $result, $unknown, [ 'special', 'missing' ] );
618 self::addValues( $result, $known, [ 'special' ] );
619 }
620 if ( in_array( 'missingIds', $invalidChecks ) ) {
621 self::addValues( $result, $this->getMissingPageIDs(), [ 'missing' ], 'pageid' );
622 }
623 if ( in_array( 'missingRevIds', $invalidChecks ) ) {
624 self::addValues( $result, $this->getMissingRevisionIDs(), [ 'missing' ], 'revid' );
625 }
626 if ( in_array( 'missingTitles', $invalidChecks ) ) {
627 $known = [];
628 $unknown = [];
629 foreach ( $this->getMissingTitles() as $title ) {
630 if ( $title->isKnown() ) {
631 $known[] = $title;
632 } else {
633 $unknown[] = $title;
634 }
635 }
636 self::addValues( $result, $unknown, [ 'missing' ] );
637 self::addValues( $result, $known, [ 'missing', 'known' ] );
638 }
639 if ( in_array( 'interwikiTitles', $invalidChecks ) ) {
640 self::addValues( $result, $this->getInterwikiTitlesAsResult() );
641 }
642
643 return $result;
644 }
645
650 public function getRevisionIDs() {
651 return $this->mGoodRevIDs;
652 }
653
658 public function getLiveRevisionIDs() {
659 return $this->mLiveRevIDs;
660 }
661
666 public function getDeletedRevisionIDs() {
667 return $this->mDeletedRevIDs;
668 }
669
674 public function getMissingRevisionIDs() {
675 return $this->mMissingRevIDs;
676 }
677
684 public function getMissingRevisionIDsAsResult( $result = null ) {
685 $values = [];
686 foreach ( $this->getMissingRevisionIDs() as $revid ) {
687 $values[$revid] = [
688 'revid' => $revid,
689 'missing' => true,
690 ];
691 }
692 if ( !empty( $values ) && $result ) {
693 ApiResult::setIndexedTagName( $values, 'rev' );
694 }
695
696 return $values;
697 }
698
703 public function getSpecialTitles() {
704 return $this->mSpecialTitles;
705 }
706
711 public function getRevisionCount() {
712 return count( $this->getRevisionIDs() );
713 }
714
719 public function populateFromTitles( $titles ) {
720 $this->initFromTitles( $titles );
721 }
722
727 public function populateFromPageIDs( $pageIDs ) {
728 $this->initFromPageIds( $pageIDs );
729 }
730
740 public function populateFromQueryResult( $db, $queryResult ) {
741 $this->initFromQueryResult( $queryResult );
742 }
743
748 public function populateFromRevisionIDs( $revIDs ) {
749 $this->initFromRevIDs( $revIDs );
750 }
751
756 public function processDbRow( $row ) {
757 // Store Title object in various data structures
758 $title = Title::newFromRow( $row );
759
760 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
761 $linkCache->addGoodLinkObjFromRow( $title, $row );
762
763 $pageId = (int)$row->page_id;
764 $this->mAllPages[$row->page_namespace][$row->page_title] = $pageId;
765 $this->mTitles[] = $title;
766
767 if ( $this->mResolveRedirects && $row->page_is_redirect == '1' ) {
768 $this->mPendingRedirectIDs[$pageId] = $title;
769 } else {
770 $this->mGoodPages[$row->page_namespace][$row->page_title] = $pageId;
771 $this->mGoodAndMissingPages[$row->page_namespace][$row->page_title] = $pageId;
772 $this->mGoodTitles[$pageId] = $title;
773 }
774
775 foreach ( $this->mRequestedPageFields as $fieldName => &$fieldValues ) {
776 $fieldValues[$pageId] = $row->$fieldName;
777 }
778 }
779
796 private function initFromTitles( $titles ) {
797 // Get validated and normalized title objects
798 $linkBatch = $this->processTitlesArray( $titles );
799 if ( $linkBatch->isEmpty() ) {
800 // There might be special-page redirects
802 return;
803 }
804
805 $db = $this->getDB();
806 $set = $linkBatch->constructSet( 'page', $db );
807
808 // Get pageIDs data from the `page` table
809 $res = $db->select( 'page', $this->getPageTableFields(), $set,
810 __METHOD__ );
811
812 // Hack: get the ns:titles stored in [ ns => [ titles ] ] format
813 $this->initFromQueryResult( $res, $linkBatch->data, true ); // process Titles
814
815 // Resolve any found redirects
817 }
818
824 private function initFromPageIds( $pageids, $filterIds = true ) {
825 if ( !$pageids ) {
826 return;
827 }
828
829 $pageids = array_map( 'intval', $pageids ); // paranoia
830 $remaining = array_flip( $pageids );
831
832 if ( $filterIds ) {
833 $pageids = $this->filterIDs( [ [ 'page', 'page_id' ] ], $pageids );
834 }
835
836 $res = null;
837 if ( !empty( $pageids ) ) {
838 $set = [
839 'page_id' => $pageids
840 ];
841 $db = $this->getDB();
842
843 // Get pageIDs data from the `page` table
844 $res = $db->select( 'page', $this->getPageTableFields(), $set,
845 __METHOD__ );
846 }
847
848 $this->initFromQueryResult( $res, $remaining, false ); // process PageIDs
849
850 // Resolve any found redirects
852 }
853
864 private function initFromQueryResult( $res, &$remaining = null, $processTitles = null ) {
865 if ( $remaining !== null && $processTitles === null ) {
866 ApiBase::dieDebug( __METHOD__, 'Missing $processTitles parameter when $remaining is provided' );
867 }
868
869 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
870
871 $usernames = [];
872 if ( $res ) {
873 foreach ( $res as $row ) {
874 $pageId = (int)$row->page_id;
875
876 // Remove found page from the list of remaining items
877 if ( isset( $remaining ) ) {
878 if ( $processTitles ) {
879 unset( $remaining[$row->page_namespace][$row->page_title] );
880 } else {
881 unset( $remaining[$pageId] );
882 }
883 }
884
885 // Store any extra fields requested by modules
886 $this->processDbRow( $row );
887
888 // Need gender information
889 if ( $nsInfo->hasGenderDistinction( $row->page_namespace ) ) {
890 $usernames[] = $row->page_title;
891 }
892 }
893 }
894
895 if ( isset( $remaining ) ) {
896 // Any items left in the $remaining list are added as missing
897 if ( $processTitles ) {
898 // The remaining titles in $remaining are non-existent pages
899 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
900 foreach ( $remaining as $ns => $dbkeys ) {
901 foreach ( array_keys( $dbkeys ) as $dbkey ) {
902 $title = Title::makeTitle( $ns, $dbkey );
903 $linkCache->addBadLinkObj( $title );
904 $this->mAllPages[$ns][$dbkey] = $this->mFakePageId;
905 $this->mMissingPages[$ns][$dbkey] = $this->mFakePageId;
906 $this->mGoodAndMissingPages[$ns][$dbkey] = $this->mFakePageId;
907 $this->mMissingTitles[$this->mFakePageId] = $title;
908 $this->mFakePageId--;
909 $this->mTitles[] = $title;
910
911 // need gender information
912 if ( $nsInfo->hasGenderDistinction( $ns ) ) {
913 $usernames[] = $dbkey;
914 }
915 }
916 }
917 } else {
918 // The remaining pageids do not exist
919 if ( !$this->mMissingPageIDs ) {
920 $this->mMissingPageIDs = array_keys( $remaining );
921 } else {
922 $this->mMissingPageIDs = array_merge( $this->mMissingPageIDs, array_keys( $remaining ) );
923 }
924 }
925 }
926
927 // Get gender information
928 $genderCache = MediaWikiServices::getInstance()->getGenderCache();
929 $genderCache->doQuery( $usernames, __METHOD__ );
930 }
931
937 private function initFromRevIDs( $revids ) {
938 if ( !$revids ) {
939 return;
940 }
941
942 $revids = array_map( 'intval', $revids ); // paranoia
943 $db = $this->getDB();
944 $pageids = [];
945 $remaining = array_flip( $revids );
946
947 $revids = $this->filterIDs( [ [ 'revision', 'rev_id' ], [ 'archive', 'ar_rev_id' ] ], $revids );
948 $goodRemaining = array_flip( $revids );
949
950 if ( $revids ) {
951 $tables = [ 'revision', 'page' ];
952 $fields = [ 'rev_id', 'rev_page' ];
953 $where = [ 'rev_id' => $revids, 'rev_page = page_id' ];
954
955 // Get pageIDs data from the `page` table
956 $res = $db->select( $tables, $fields, $where, __METHOD__ );
957 foreach ( $res as $row ) {
958 $revid = (int)$row->rev_id;
959 $pageid = (int)$row->rev_page;
960 $this->mGoodRevIDs[$revid] = $pageid;
961 $this->mLiveRevIDs[$revid] = $pageid;
962 $pageids[$pageid] = '';
963 unset( $remaining[$revid] );
964 unset( $goodRemaining[$revid] );
965 }
966 }
967
968 // Populate all the page information
969 $this->initFromPageIds( array_keys( $pageids ), false );
970
971 // If the user can see deleted revisions, pull out the corresponding
972 // titles from the archive table and include them too. We ignore
973 // ar_page_id because deleted revisions are tied by title, not page_id.
974 if ( $goodRemaining &&
975 $this->getPermissionManager()->userHasRight( $this->getUser(), 'deletedhistory' ) ) {
976 $tables = [ 'archive' ];
977 $fields = [ 'ar_rev_id', 'ar_namespace', 'ar_title' ];
978 $where = [ 'ar_rev_id' => array_keys( $goodRemaining ) ];
979
980 $res = $db->select( $tables, $fields, $where, __METHOD__ );
981 $titles = [];
982 foreach ( $res as $row ) {
983 $revid = (int)$row->ar_rev_id;
984 $titles[$revid] = Title::makeTitle( $row->ar_namespace, $row->ar_title );
985 unset( $remaining[$revid] );
986 }
987
988 $this->initFromTitles( $titles );
989
990 foreach ( $titles as $revid => $title ) {
991 $ns = $title->getNamespace();
992 $dbkey = $title->getDBkey();
993
994 // Handle converted titles
995 if ( !isset( $this->mAllPages[$ns][$dbkey] ) &&
996 isset( $this->mConvertedTitles[$title->getPrefixedText()] )
997 ) {
998 $title = Title::newFromText( $this->mConvertedTitles[$title->getPrefixedText()] );
999 $ns = $title->getNamespace();
1000 $dbkey = $title->getDBkey();
1001 }
1002
1003 if ( isset( $this->mAllPages[$ns][$dbkey] ) ) {
1004 $this->mGoodRevIDs[$revid] = $this->mAllPages[$ns][$dbkey];
1005 $this->mDeletedRevIDs[$revid] = $this->mAllPages[$ns][$dbkey];
1006 } else {
1007 $remaining[$revid] = true;
1008 }
1009 }
1010 }
1011
1012 $this->mMissingRevIDs = array_keys( $remaining );
1013 }
1014
1020 private function resolvePendingRedirects() {
1021 if ( $this->mResolveRedirects ) {
1022 $db = $this->getDB();
1023 $pageFlds = $this->getPageTableFields();
1024
1025 // Repeat until all redirects have been resolved
1026 // The infinite loop is prevented by keeping all known pages in $this->mAllPages
1027 while ( $this->mPendingRedirectIDs || $this->mPendingRedirectSpecialPages ) {
1028 // Resolve redirects by querying the pagelinks table, and repeat the process
1029 // Create a new linkBatch object for the next pass
1030 $linkBatch = $this->getRedirectTargets();
1031
1032 if ( $linkBatch->isEmpty() ) {
1033 break;
1034 }
1035
1036 $set = $linkBatch->constructSet( 'page', $db );
1037 if ( $set === false ) {
1038 break;
1039 }
1040
1041 // Get pageIDs data from the `page` table
1042 $res = $db->select( 'page', $pageFlds, $set, __METHOD__ );
1043
1044 // Hack: get the ns:titles stored in [ns => array(titles)] format
1045 $this->initFromQueryResult( $res, $linkBatch->data, true );
1046 }
1047 }
1048 }
1049
1057 private function getRedirectTargets() {
1058 $titlesToResolve = [];
1059 $db = $this->getDB();
1060
1061 if ( $this->mPendingRedirectIDs ) {
1062 $res = $db->select(
1063 'redirect',
1064 [
1065 'rd_from',
1066 'rd_namespace',
1067 'rd_fragment',
1068 'rd_interwiki',
1069 'rd_title'
1070 ], [ 'rd_from' => array_keys( $this->mPendingRedirectIDs ) ],
1071 __METHOD__
1072 );
1073 foreach ( $res as $row ) {
1074 $rdfrom = (int)$row->rd_from;
1075 $from = $this->mPendingRedirectIDs[$rdfrom]->getPrefixedText();
1076 $to = Title::makeTitle(
1077 $row->rd_namespace,
1078 $row->rd_title,
1079 $row->rd_fragment,
1080 $row->rd_interwiki
1081 );
1082 $this->mResolvedRedirectTitles[$from] = $this->mPendingRedirectIDs[$rdfrom];
1083 unset( $this->mPendingRedirectIDs[$rdfrom] );
1084 if ( $to->isExternal() ) {
1085 $this->mInterwikiTitles[$to->getPrefixedText()] = $to->getInterwiki();
1086 } elseif ( !isset( $this->mAllPages[$to->getNamespace()][$to->getDBkey()] )
1087 && !( $this->mConvertTitles && isset( $this->mConvertedTitles[$to->getPrefixedText()] ) )
1088 ) {
1089 $titlesToResolve[] = $to;
1090 }
1091 $this->mRedirectTitles[$from] = $to;
1092 }
1093
1094 if ( $this->mPendingRedirectIDs ) {
1095 // We found pages that aren't in the redirect table
1096 // Add them
1097 foreach ( $this->mPendingRedirectIDs as $id => $title ) {
1098 $page = WikiPage::factory( $title );
1099 $rt = $page->insertRedirect();
1100 if ( !$rt ) {
1101 // What the hell. Let's just ignore this
1102 continue;
1103 }
1104 if ( $rt->isExternal() ) {
1105 $this->mInterwikiTitles[$rt->getPrefixedText()] = $rt->getInterwiki();
1106 } elseif ( !isset( $this->mAllPages[$rt->getNamespace()][$rt->getDBkey()] ) ) {
1107 $titlesToResolve[] = $rt;
1108 }
1109 $from = $title->getPrefixedText();
1110 $this->mResolvedRedirectTitles[$from] = $title;
1111 $this->mRedirectTitles[$from] = $rt;
1112 unset( $this->mPendingRedirectIDs[$id] );
1113 }
1114 }
1115 }
1116
1117 if ( $this->mPendingRedirectSpecialPages ) {
1118 foreach ( $this->mPendingRedirectSpecialPages as $key => list( $from, $to ) ) {
1120 $fromKey = $from->getPrefixedText();
1121 $this->mResolvedRedirectTitles[$fromKey] = $from;
1122 $this->mRedirectTitles[$fromKey] = $to;
1123 if ( $to->isExternal() ) {
1124 $this->mInterwikiTitles[$to->getPrefixedText()] = $to->getInterwiki();
1125 } elseif ( !isset( $this->mAllPages[$to->getNamespace()][$to->getDBkey()] ) ) {
1126 $titlesToResolve[] = $to;
1127 }
1128 }
1129 $this->mPendingRedirectSpecialPages = [];
1130
1131 // Set private caching since we don't know what criteria the
1132 // special pages used to decide on these redirects.
1133 $this->mCacheMode = 'private';
1134 }
1135
1136 return $this->processTitlesArray( $titlesToResolve );
1137 }
1138
1152 public function getCacheMode( $params = null ) {
1153 return $this->mCacheMode;
1154 }
1155
1165 private function processTitlesArray( $titles ) {
1166 $linkBatch = new LinkBatch();
1167 $services = MediaWikiServices::getInstance();
1168 $contLang = $services->getContentLanguage();
1169
1170 $titleObjects = [];
1171 foreach ( $titles as $index => $title ) {
1172 if ( is_string( $title ) ) {
1173 try {
1175 $titleObj = Title::newFromTextThrow( $title, $this->mDefaultNamespace );
1176 } catch ( MalformedTitleException $ex ) {
1177 // Handle invalid titles gracefully
1178 if ( !isset( $this->mAllPages[0][$title] ) ) {
1179 $this->mAllPages[0][$title] = $this->mFakePageId;
1180 $this->mInvalidTitles[$this->mFakePageId] = [
1181 'title' => $title,
1182 'invalidreason' => $this->getErrorFormatter()->formatException( $ex, [ 'bc' => true ] ),
1183 ];
1184 $this->mFakePageId--;
1185 }
1186 continue; // There's nothing else we can do
1187 }
1188 } else {
1189 $titleObj = $title;
1190 }
1191
1192 $titleObjects[$index] = $titleObj;
1193 }
1194
1195 // Get gender information
1196 $genderCache = $services->getGenderCache();
1197 $genderCache->doTitlesArray( $titleObjects, __METHOD__ );
1198
1199 foreach ( $titleObjects as $index => $titleObj ) {
1200 $title = is_string( $titles[$index] ) ? $titles[$index] : false;
1201 $unconvertedTitle = $titleObj->getPrefixedText();
1202 $titleWasConverted = false;
1203 if ( $titleObj->isExternal() ) {
1204 // This title is an interwiki link.
1205 $this->mInterwikiTitles[$unconvertedTitle] = $titleObj->getInterwiki();
1206 } else {
1207 // Variants checking
1208 if (
1209 $this->mConvertTitles && $contLang->hasVariants() && !$titleObj->exists()
1210 ) {
1211 // Language::findVariantLink will modify titleText and titleObj into
1212 // the canonical variant if possible
1213 $titleText = $title !== false ? $title : $titleObj->getPrefixedText();
1214 $contLang->findVariantLink( $titleText, $titleObj );
1215 $titleWasConverted = $unconvertedTitle !== $titleObj->getPrefixedText();
1216 }
1217
1218 if ( $titleObj->getNamespace() < 0 ) {
1219 // Handle Special and Media pages
1220 $titleObj = $titleObj->fixSpecialName();
1221 $ns = $titleObj->getNamespace();
1222 $dbkey = $titleObj->getDBkey();
1223 if ( !isset( $this->mAllSpecials[$ns][$dbkey] ) ) {
1224 $this->mAllSpecials[$ns][$dbkey] = $this->mFakePageId;
1225 $target = null;
1226 if ( $ns === NS_SPECIAL && $this->mResolveRedirects ) {
1227 $spFactory = $services->getSpecialPageFactory();
1228 $special = $spFactory->getPage( $dbkey );
1229 if ( $special instanceof RedirectSpecialArticle ) {
1230 // Only RedirectSpecialArticle is intended to redirect to an article, other kinds of
1231 // RedirectSpecialPage are probably applying weird URL parameters we don't want to
1232 // handle.
1233 $context = new DerivativeContext( $this );
1234 $context->setTitle( $titleObj );
1235 $context->setRequest( new FauxRequest );
1236 $special->setContext( $context );
1237 list( /* $alias */, $subpage ) = $spFactory->resolveAlias( $dbkey );
1238 $target = $special->getRedirect( $subpage );
1239 }
1240 }
1241 if ( $target ) {
1242 $this->mPendingRedirectSpecialPages[$dbkey] = [ $titleObj, $target ];
1243 } else {
1244 $this->mSpecialTitles[$this->mFakePageId] = $titleObj;
1245 $this->mFakePageId--;
1246 }
1247 }
1248 } else {
1249 // Regular page
1250 $linkBatch->addObj( $titleObj );
1251 }
1252 }
1253
1254 // Make sure we remember the original title that was
1255 // given to us. This way the caller can correlate new
1256 // titles with the originally requested when e.g. the
1257 // namespace is localized or the capitalization is
1258 // different
1259 if ( $titleWasConverted ) {
1260 $this->mConvertedTitles[$unconvertedTitle] = $titleObj->getPrefixedText();
1261 // In this case the page can't be Special.
1262 if ( $title !== false && $title !== $unconvertedTitle ) {
1263 $this->mNormalizedTitles[$title] = $unconvertedTitle;
1264 }
1265 } elseif ( $title !== false && $title !== $titleObj->getPrefixedText() ) {
1266 $this->mNormalizedTitles[$title] = $titleObj->getPrefixedText();
1267 }
1268 }
1269
1270 return $linkBatch;
1271 }
1272
1288 public function setGeneratorData( Title $title, array $data ) {
1289 $ns = $title->getNamespace();
1290 $dbkey = $title->getDBkey();
1291 $this->mGeneratorData[$ns][$dbkey] = $data;
1292 }
1293
1313 public function setRedirectMergePolicy( $callable ) {
1314 $this->mRedirectMergePolicy = $callable;
1315 }
1316
1328 private function resolveRedirectTitleDest( Title $titleFrom ): Title {
1329 $seen = [];
1330 $dest = $titleFrom;
1331 while ( isset( $this->mRedirectTitles[$dest->getPrefixedText()] ) ) {
1332 $dest = $this->mRedirectTitles[$dest->getPrefixedText()];
1333 if ( isset( $seen[$dest->getPrefixedText()] ) ) {
1334 return $titleFrom;
1335 }
1336 $seen[$dest->getPrefixedText()] = true;
1337 }
1338 return $dest;
1339 }
1340
1361 public function populateGeneratorData( &$result, array $path = [] ) {
1362 if ( $result instanceof ApiResult ) {
1363 $data = $result->getResultData( $path );
1364 if ( $data === null ) {
1365 return true;
1366 }
1367 } else {
1368 $data = &$result;
1369 foreach ( $path as $key ) {
1370 if ( !isset( $data[$key] ) ) {
1371 // Path isn't in $result, so nothing to add, so everything
1372 // "fits"
1373 return true;
1374 }
1375 $data = &$data[$key];
1376 }
1377 }
1378 foreach ( $this->mGeneratorData as $ns => $dbkeys ) {
1379 if ( $ns === NS_SPECIAL ) {
1380 $pages = [];
1381 foreach ( $this->mSpecialTitles as $id => $title ) {
1382 $pages[$title->getDBkey()] = $id;
1383 }
1384 } else {
1385 if ( !isset( $this->mAllPages[$ns] ) ) {
1386 // No known titles in the whole namespace. Skip it.
1387 continue;
1388 }
1389 $pages = $this->mAllPages[$ns];
1390 }
1391 foreach ( $dbkeys as $dbkey => $genData ) {
1392 if ( !isset( $pages[$dbkey] ) ) {
1393 // Unknown title. Forget it.
1394 continue;
1395 }
1396 $pageId = $pages[$dbkey];
1397 if ( !isset( $data[$pageId] ) ) {
1398 // $pageId didn't make it into the result. Ignore it.
1399 continue;
1400 }
1401
1402 if ( $result instanceof ApiResult ) {
1403 $path2 = array_merge( $path, [ $pageId ] );
1404 foreach ( $genData as $key => $value ) {
1405 if ( !$result->addValue( $path2, $key, $value ) ) {
1406 return false;
1407 }
1408 }
1409 } else {
1410 $data[$pageId] = array_merge( $data[$pageId], $genData );
1411 }
1412 }
1413 }
1414
1415 // Merge data generated about redirect titles into the redirect destination
1416 if ( $this->mRedirectMergePolicy ) {
1417 foreach ( $this->mResolvedRedirectTitles as $titleFrom ) {
1418 $dest = $this->resolveRedirectTitleDest( $titleFrom );
1419 $fromNs = $titleFrom->getNamespace();
1420 $fromDBkey = $titleFrom->getDBkey();
1421 $toPageId = $dest->getArticleID();
1422 if ( isset( $data[$toPageId] ) &&
1423 isset( $this->mGeneratorData[$fromNs][$fromDBkey] )
1424 ) {
1425 // It is necessary to set both $data and add to $result, if an ApiResult,
1426 // to ensure multiple redirects to the same destination are all merged.
1427 $data[$toPageId] = call_user_func(
1428 $this->mRedirectMergePolicy,
1429 $data[$toPageId],
1430 $this->mGeneratorData[$fromNs][$fromDBkey]
1431 );
1432 if ( $result instanceof ApiResult &&
1433 !$result->addValue( $path, $toPageId, $data[$toPageId], ApiResult::OVERRIDE )
1434 ) {
1435 return false;
1436 }
1437 }
1438 }
1439 }
1440
1441 return true;
1442 }
1443
1448 protected function getDB() {
1449 return $this->mDbSource->getDB();
1450 }
1451
1452 public function getAllowedParams( $flags = 0 ) {
1453 $result = [
1454 'titles' => [
1455 ApiBase::PARAM_ISMULTI => true,
1456 ApiBase::PARAM_HELP_MSG => 'api-pageset-param-titles',
1457 ],
1458 'pageids' => [
1459 ApiBase::PARAM_TYPE => 'integer',
1460 ApiBase::PARAM_ISMULTI => true,
1461 ApiBase::PARAM_HELP_MSG => 'api-pageset-param-pageids',
1462 ],
1463 'revids' => [
1464 ApiBase::PARAM_TYPE => 'integer',
1465 ApiBase::PARAM_ISMULTI => true,
1466 ApiBase::PARAM_HELP_MSG => 'api-pageset-param-revids',
1467 ],
1468 'generator' => [
1469 ApiBase::PARAM_TYPE => null,
1470 ApiBase::PARAM_HELP_MSG => 'api-pageset-param-generator',
1472 ],
1473 'redirects' => [
1474 ApiBase::PARAM_DFLT => false,
1475 ApiBase::PARAM_HELP_MSG => $this->mAllowGenerator
1476 ? 'api-pageset-param-redirects-generator'
1477 : 'api-pageset-param-redirects-nogenerator',
1478 ],
1479 'converttitles' => [
1480 ApiBase::PARAM_DFLT => false,
1482 'api-pageset-param-converttitles',
1483 [ Message::listParam( LanguageConverter::$languagesWithVariants, 'text' ) ],
1484 ],
1485 ],
1486 ];
1487
1488 if ( !$this->mAllowGenerator ) {
1489 unset( $result['generator'] );
1490 } elseif ( $flags & ApiBase::GET_VALUES_FOR_HELP ) {
1491 $result['generator'][ApiBase::PARAM_TYPE] = 'submodule';
1492 $result['generator'][ApiBase::PARAM_SUBMODULE_MAP] = $this->getGenerators();
1493 }
1494
1495 return $result;
1496 }
1497
1498 public function handleParamNormalization( $paramName, $value, $rawValue ) {
1499 parent::handleParamNormalization( $paramName, $value, $rawValue );
1500
1501 if ( $paramName === 'titles' ) {
1502 // For the 'titles' parameter, we want to split it like ApiBase would
1503 // and add any changed titles to $this->mNormalizedTitles
1504 $value = ParamValidator::explodeMultiValue( $value, self::LIMIT_SML2 + 1 );
1505 $l = count( $value );
1506 $rawValue = ParamValidator::explodeMultiValue( $rawValue, $l );
1507 for ( $i = 0; $i < $l; $i++ ) {
1508 if ( $value[$i] !== $rawValue[$i] ) {
1509 $this->mNormalizedTitles[$rawValue[$i]] = $value[$i];
1510 }
1511 }
1512 }
1513 }
1514
1515 private static $generators = null;
1516
1521 private function getGenerators() {
1522 if ( self::$generators === null ) {
1523 $query = $this->mDbSource;
1524 if ( !( $query instanceof ApiQuery ) ) {
1525 // If the parent container of this pageset is not ApiQuery,
1526 // we must create it to get module manager
1527 $query = $this->getMain()->getModuleManager()->getModule( 'query' );
1528 }
1529 $gens = [];
1530 $prefix = $query->getModulePath() . '+';
1531 $mgr = $query->getModuleManager();
1532 foreach ( $mgr->getNamesWithClasses() as $name => $class ) {
1533 if ( is_subclass_of( $class, ApiQueryGeneratorBase::class ) ) {
1534 $gens[$name] = $prefix . $name;
1535 }
1536 }
1537 ksort( $gens );
1538 self::$generators = $gens;
1539 }
1540
1541 return self::$generators;
1542 }
1543}
getPermissionManager()
getDB()
getUser()
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:52
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1437
const PARAM_SUBMODULE_MAP
Definition ApiBase.php:106
filterIDs( $fields, array $ids)
Filter out-of-range values from a list of positive integer IDs.
Definition ApiBase.php:1308
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
Definition ApiBase.php:750
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:1629
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
const PARAM_SUBMODULE_PARAM_PREFIX
Definition ApiBase.php:110
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:772
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
const GET_VALUES_FOR_HELP
getAllowedParams() flag: When set, the result could take longer to generate, but should be more thoro...
Definition ApiBase.php:233
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:499
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:717
const PARAM_ISMULTI
Definition ApiBase.php:74
This class contains a list of pages that the client has requested.
getGoodAndMissingTitlesByNamespace()
Returns an array [ns][dbkey] => page_id for all good and missing titles.
populateFromRevisionIDs( $revIDs)
Populate this PageSet from a list of revision IDs.
getInterwikiTitlesAsResult( $result=null, $iwUrl=false)
Get a list of interwiki titles - maps a title to its interwiki prefix as result.
getCustomField( $fieldName)
Get the value of a custom field previously requested through requestField()
getNormalizedTitles()
Get a list of title normalizations - maps a title to its normalized version.
getGenerators()
Get an array of all available generators.
__construct(ApiBase $dbSource, $flags=0, $defaultNamespace=NS_MAIN)
getGoodTitlesByNamespace()
Returns an array [ns][dbkey] => page_id for all good titles.
getAllowedParams( $flags=0)
getRevisionIDs()
Get the list of valid revision IDs (requested with the revids= parameter)
getGoodAndMissingTitles()
Title objects for good and missing titles.
populateFromPageIDs( $pageIDs)
Populate this PageSet from a list of page IDs.
getRedirectTargets()
Get the targets of the pending redirects from the database.
getMissingTitlesByNamespace()
Returns an array [ns][dbkey] => fake_page_id for all missing titles.
array $mRequestedPageFields
getMissingTitles()
Title objects that were NOT found in the database.
getDB()
Get the database connection (read-only)
initFromRevIDs( $revids)
Does the same as initFromTitles(), but is based on revision IDs instead.
executeDryRun()
In case execute() is not called, call this method to mark all relevant parameters as used This preven...
getRedirectTitlesAsResult( $result=null)
Get a list of redirect resolutions - maps a title to its redirect target.
getPageTableFields()
Get the fields that have to be queried from the page table: the ones requested through requestField()...
getGoodTitleCount()
Returns the number of found unique pages (not revisions) in the set.
getRevisionCount()
Returns the number of revisions (requested with revids= parameter).
getAllTitlesByNamespace()
Returns an array [ns][dbkey] => page_id for all requested titles.
processDbRow( $row)
Extract all requested fields from the row received from the database.
processTitlesArray( $titles)
Given an array of title strings, convert them into Title objects.
static addValues(array &$result, $values, $flags=[], $name=null)
Add all items from $values into the result.
setRedirectMergePolicy( $callable)
Controls how generator data about a redirect source is merged into the generator data for the redirec...
handleParamNormalization( $paramName, $value, $rawValue)
Handle when a parameter was Unicode-normalized.
getNormalizedTitlesAsResult( $result=null)
Get a list of title normalizations - maps a title to its normalized version in the form of result arr...
getTitleCount()
Returns the number of unique pages (not revisions) in the set.
initFromTitles( $titles)
This method populates internal variables with page information based on the given array of title stri...
getInvalidTitlesAndReasons()
Titles that were deemed invalid by Title::newFromText() The array's index will be unique and negative...
array $mInvalidTitles
[fake_page_id] => [ 'title' => $title, 'invalidreason' => $reason ]
getSpecialTitles()
Get the list of titles with negative namespace.
Title[] $mPendingRedirectIDs
getTitles()
All Title objects provided.
int $mDefaultNamespace
executeInternal( $isDryRun)
Populate the PageSet from the request parameters.
resolvePendingRedirects()
Resolve any redirects in the result if redirect resolution was requested.
populateFromQueryResult( $db, $queryResult)
Populate this PageSet from a rowset returned from the database.
getMissingPageIDs()
Page IDs that were not found in the database.
requestField( $fieldName)
Request an additional field from the page table.
getInterwikiTitles()
Get a list of interwiki titles - maps a title to its interwiki prefix.
resolveRedirectTitleDest(Title $titleFrom)
Resolve the title a redirect points to.
populateFromTitles( $titles)
Populate this PageSet from a list of Titles.
getMissingRevisionIDs()
Revision IDs that were not found in the database.
initFromPageIds( $pageids, $filterIds=true)
Does the same as initFromTitles(), but is based on page IDs instead.
execute()
Populate the PageSet from the request parameters.
populateGeneratorData(&$result, array $path=[])
Populate the generator data for all titles in the result.
$mPendingRedirectSpecialPages
getConvertedTitlesAsResult( $result=null)
Get a list of title conversions - maps a title to its converted version as a result array.
getConvertedTitles()
Get a list of title conversions - maps a title to its converted version.
getDataSource()
Return the parameter name that is the source of data for this PageSet.
getInvalidTitlesAndRevisions( $invalidChecks=[ 'invalidTitles', 'special', 'missingIds', 'missingRevIds', 'missingTitles', 'interwikiTitles'])
Get an array of invalid/special/missing titles.
getLiveRevisionIDs()
Get the list of non-deleted revision IDs (requested with the revids= parameter)
getCacheMode( $params=null)
Get the cache mode for the data generated by this module.
getGoodTitles()
Title objects that were found in the database.
$mResolvedRedirectTitles
callable null $mRedirectMergePolicy
isResolvingRedirects()
Check whether this PageSet is resolving redirects.
getRedirectTitles()
Get a list of redirect resolutions - maps a title to its redirect target, as an array of output-ready...
setGeneratorData(Title $title, array $data)
Set data for a title.
const DISABLE_GENERATORS
Constructor flag: The new instance of ApiPageSet will ignore the 'generator=' parameter.
initFromQueryResult( $res, &$remaining=null, $processTitles=null)
Iterate through the result of the query on 'page' table, and for each row create and store title obje...
getMissingRevisionIDsAsResult( $result=null)
Revision IDs that were not found in the database as result array.
getDeletedRevisionIDs()
Get the list of revision IDs that were associated with deleted titles.
This is the main query class.
Definition ApiQuery.php:37
This class represents the result of the API operations.
Definition ApiResult.php:35
addValue( $path, $name, $value, $flags=0)
Add value to the output data at the given path.
IContextSource $context
An IContextSource implementation which will inherit context from another source but allow individual ...
WebRequest clone which takes values from a provided array.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:35
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
MediaWikiServices is the service locator for the application scope of MediaWiki.
static listParam(array $list, $type='text')
Definition Message.php:1141
Superclass for any RedirectSpecialPage which redirects the user to a particular article (as opposed t...
Represents a title within MediaWiki.
Definition Title.php:42
getNamespace()
Get the namespace index, i.e.
Definition Title.php:1041
getDBkey()
Get the main part with underscores.
Definition Title.php:1032
getPrefixedText()
Get the prefixed title with spaces.
Definition Title.php:1859
Service for formatting and validating API parameters.
const PROTO_CURRENT
Definition Defines.php:212
const NS_MAIN
Definition Defines.php:70
const NS_SPECIAL
Definition Defines.php:59
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
Result wrapper for grabbing data queried from an IDatabase object.