MediaWiki REL1_34
ApiPageSet.php
Go to the documentation of this file.
1<?php
25
40class ApiPageSet extends ApiBase {
46
47 private $mDbSource;
48 private $mParams;
52
53 private $mAllPages = []; // [ns][dbkey] => page_id or negative when missing
54 private $mTitles = [];
55 private $mGoodAndMissingPages = []; // [ns][dbkey] => page_id or negative when missing
56 private $mGoodPages = []; // [ns][dbkey] => page_id
57 private $mGoodTitles = [];
58 private $mMissingPages = []; // [ns][dbkey] => fake page_id
59 private $mMissingTitles = [];
61 private $mInvalidTitles = [];
62 private $mMissingPageIDs = [];
63 private $mRedirectTitles = [];
64 private $mSpecialTitles = [];
65 private $mAllSpecials = []; // separate from mAllPages to avoid breaking getAllTitlesByNamespace()
66 private $mNormalizedTitles = [];
67 private $mInterwikiTitles = [];
70 private $mPendingRedirectSpecialPages = []; // [dbkey] => [ Title $from, Title $to ]
72 private $mConvertedTitles = [];
73 private $mGoodRevIDs = [];
74 private $mLiveRevIDs = [];
75 private $mDeletedRevIDs = [];
76 private $mMissingRevIDs = [];
77 private $mGeneratorData = []; // [ns][dbkey] => data array
78 private $mFakePageId = -1;
79 private $mCacheMode = 'public';
86
94 private static function addValues( array &$result, $values, $flags = [], $name = null ) {
95 foreach ( $values as $val ) {
96 if ( $val instanceof Title ) {
97 $v = [];
98 ApiQueryBase::addTitleInfo( $v, $val );
99 } elseif ( $name !== null ) {
100 $v = [ $name => $val ];
101 } else {
102 $v = $val;
103 }
104 foreach ( $flags as $flag ) {
105 $v[$flag] = true;
106 }
107 $result[] = $v;
108 }
109 }
110
118 public function __construct( ApiBase $dbSource, $flags = 0, $defaultNamespace = NS_MAIN ) {
119 parent::__construct( $dbSource->getMain(), $dbSource->getModuleName() );
120 $this->mDbSource = $dbSource;
121 $this->mAllowGenerator = ( $flags & self::DISABLE_GENERATORS ) == 0;
122 $this->mDefaultNamespace = $defaultNamespace;
123
124 $this->mParams = $this->extractRequestParams();
125 $this->mResolveRedirects = $this->mParams['redirects'];
126 $this->mConvertTitles = $this->mParams['converttitles'];
127 }
128
133 public function executeDryRun() {
134 $this->executeInternal( true );
135 }
136
140 public function execute() {
141 $this->executeInternal( false );
142 }
143
149 private function executeInternal( $isDryRun ) {
150 $generatorName = $this->mAllowGenerator ? $this->mParams['generator'] : null;
151 if ( isset( $generatorName ) ) {
152 $dbSource = $this->mDbSource;
153 if ( !$dbSource instanceof ApiQuery ) {
154 // If the parent container of this pageset is not ApiQuery, we must create it to run generator
155 $dbSource = $this->getMain()->getModuleManager()->getModule( 'query' );
156 }
157 $generator = $dbSource->getModuleManager()->getModule( $generatorName, null, true );
158 if ( $generator === null ) {
159 $this->dieWithError( [ 'apierror-badgenerator-unknown', $generatorName ], 'badgenerator' );
160 }
161 if ( !$generator instanceof ApiQueryGeneratorBase ) {
162 $this->dieWithError( [ 'apierror-badgenerator-notgenerator', $generatorName ], 'badgenerator' );
163 }
164 // Create a temporary pageset to store generator's output,
165 // add any additional fields generator may need, and execute pageset to populate titles/pageids
166 $tmpPageSet = new ApiPageSet( $dbSource, self::DISABLE_GENERATORS );
167 $generator->setGeneratorMode( $tmpPageSet );
168 $this->mCacheMode = $generator->getCacheMode( $generator->extractRequestParams() );
169
170 if ( !$isDryRun ) {
171 $generator->requestExtraData( $tmpPageSet );
172 }
173 $tmpPageSet->executeInternal( $isDryRun );
174
175 // populate this pageset with the generator output
176 if ( !$isDryRun ) {
177 $generator->executeGenerator( $this );
178
179 // Avoid PHP 7.1 warning of passing $this by reference
180 $apiModule = $this;
181 Hooks::run( 'APIQueryGeneratorAfterExecute', [ &$generator, &$apiModule ] );
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 if ( $this->getConfig()->get( 'ContentHandlerUseDB' ) ) {
322 $pageFlds['page_content_model'] = null;
323 }
324
325 if ( $this->getConfig()->get( 'PageLanguageUseDB' ) ) {
326 $pageFlds['page_lang'] = null;
327 }
328
329 foreach ( LinkCache::getSelectFields() as $field ) {
330 $pageFlds[$field] = null;
331 }
332
333 $pageFlds = array_merge( $pageFlds, $this->mRequestedPageFields );
334
335 return array_keys( $pageFlds );
336 }
337
344 public function getAllTitlesByNamespace() {
345 return $this->mAllPages;
346 }
347
352 public function getTitles() {
353 return $this->mTitles;
354 }
355
360 public function getTitleCount() {
361 return count( $this->mTitles );
362 }
363
368 public function getGoodTitlesByNamespace() {
369 return $this->mGoodPages;
370 }
371
376 public function getGoodTitles() {
377 return $this->mGoodTitles;
378 }
379
384 public function getGoodTitleCount() {
385 return count( $this->mGoodTitles );
386 }
387
393 public function getMissingTitlesByNamespace() {
394 return $this->mMissingPages;
395 }
396
402 public function getMissingTitles() {
403 return $this->mMissingTitles;
404 }
405
411 return $this->mGoodAndMissingPages;
412 }
413
418 public function getGoodAndMissingTitles() {
419 return $this->mGoodTitles + $this->mMissingTitles;
420 }
421
427 public function getInvalidTitlesAndReasons() {
428 return $this->mInvalidTitles;
429 }
430
435 public function getMissingPageIDs() {
436 return $this->mMissingPageIDs;
437 }
438
444 public function getRedirectTitles() {
445 return $this->mRedirectTitles;
446 }
447
455 public function getRedirectTitlesAsResult( $result = null ) {
456 $values = [];
457 foreach ( $this->getRedirectTitles() as $titleStrFrom => $titleTo ) {
458 $r = [
459 'from' => strval( $titleStrFrom ),
460 'to' => $titleTo->getPrefixedText(),
461 ];
462 if ( $titleTo->hasFragment() ) {
463 $r['tofragment'] = $titleTo->getFragment();
464 }
465 if ( $titleTo->isExternal() ) {
466 $r['tointerwiki'] = $titleTo->getInterwiki();
467 }
468 if ( isset( $this->mResolvedRedirectTitles[$titleStrFrom] ) ) {
469 $titleFrom = $this->mResolvedRedirectTitles[$titleStrFrom];
470 $ns = $titleFrom->getNamespace();
471 $dbkey = $titleFrom->getDBkey();
472 if ( isset( $this->mGeneratorData[$ns][$dbkey] ) ) {
473 $r = array_merge( $this->mGeneratorData[$ns][$dbkey], $r );
474 }
475 }
476
477 $values[] = $r;
478 }
479 if ( !empty( $values ) && $result ) {
480 ApiResult::setIndexedTagName( $values, 'r' );
481 }
482
483 return $values;
484 }
485
491 public function getNormalizedTitles() {
492 return $this->mNormalizedTitles;
493 }
494
502 public function getNormalizedTitlesAsResult( $result = null ) {
503 $values = [];
504 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
505 foreach ( $this->getNormalizedTitles() as $rawTitleStr => $titleStr ) {
506 $encode = $contLang->normalize( $rawTitleStr ) !== $rawTitleStr;
507 $values[] = [
508 'fromencoded' => $encode,
509 'from' => $encode ? rawurlencode( $rawTitleStr ) : $rawTitleStr,
510 'to' => $titleStr
511 ];
512 }
513 if ( !empty( $values ) && $result ) {
514 ApiResult::setIndexedTagName( $values, 'n' );
515 }
516
517 return $values;
518 }
519
525 public function getConvertedTitles() {
526 return $this->mConvertedTitles;
527 }
528
536 public function getConvertedTitlesAsResult( $result = null ) {
537 $values = [];
538 foreach ( $this->getConvertedTitles() as $rawTitleStr => $titleStr ) {
539 $values[] = [
540 'from' => $rawTitleStr,
541 'to' => $titleStr
542 ];
543 }
544 if ( !empty( $values ) && $result ) {
545 ApiResult::setIndexedTagName( $values, 'c' );
546 }
547
548 return $values;
549 }
550
556 public function getInterwikiTitles() {
557 return $this->mInterwikiTitles;
558 }
559
568 public function getInterwikiTitlesAsResult( $result = null, $iwUrl = false ) {
569 $values = [];
570 foreach ( $this->getInterwikiTitles() as $rawTitleStr => $interwikiStr ) {
571 $item = [
572 'title' => $rawTitleStr,
573 'iw' => $interwikiStr,
574 ];
575 if ( $iwUrl ) {
576 $title = Title::newFromText( $rawTitleStr );
577 $item['url'] = $title->getFullURL( '', false, PROTO_CURRENT );
578 }
579 $values[] = $item;
580 }
581 if ( !empty( $values ) && $result ) {
582 ApiResult::setIndexedTagName( $values, 'i' );
583 }
584
585 return $values;
586 }
587
602 public function getInvalidTitlesAndRevisions( $invalidChecks = [ 'invalidTitles',
603 'special', 'missingIds', 'missingRevIds', 'missingTitles', 'interwikiTitles' ]
604 ) {
605 $result = [];
606 if ( in_array( 'invalidTitles', $invalidChecks ) ) {
607 self::addValues( $result, $this->getInvalidTitlesAndReasons(), [ 'invalid' ] );
608 }
609 if ( in_array( 'special', $invalidChecks ) ) {
610 $known = [];
611 $unknown = [];
612 foreach ( $this->getSpecialTitles() as $title ) {
613 if ( $title->isKnown() ) {
614 $known[] = $title;
615 } else {
616 $unknown[] = $title;
617 }
618 }
619 self::addValues( $result, $unknown, [ 'special', 'missing' ] );
620 self::addValues( $result, $known, [ 'special' ] );
621 }
622 if ( in_array( 'missingIds', $invalidChecks ) ) {
623 self::addValues( $result, $this->getMissingPageIDs(), [ 'missing' ], 'pageid' );
624 }
625 if ( in_array( 'missingRevIds', $invalidChecks ) ) {
626 self::addValues( $result, $this->getMissingRevisionIDs(), [ 'missing' ], 'revid' );
627 }
628 if ( in_array( 'missingTitles', $invalidChecks ) ) {
629 $known = [];
630 $unknown = [];
631 foreach ( $this->getMissingTitles() as $title ) {
632 if ( $title->isKnown() ) {
633 $known[] = $title;
634 } else {
635 $unknown[] = $title;
636 }
637 }
638 self::addValues( $result, $unknown, [ 'missing' ] );
639 self::addValues( $result, $known, [ 'missing', 'known' ] );
640 }
641 if ( in_array( 'interwikiTitles', $invalidChecks ) ) {
642 self::addValues( $result, $this->getInterwikiTitlesAsResult() );
643 }
644
645 return $result;
646 }
647
652 public function getRevisionIDs() {
653 return $this->mGoodRevIDs;
654 }
655
660 public function getLiveRevisionIDs() {
661 return $this->mLiveRevIDs;
662 }
663
668 public function getDeletedRevisionIDs() {
669 return $this->mDeletedRevIDs;
670 }
671
676 public function getMissingRevisionIDs() {
677 return $this->mMissingRevIDs;
678 }
679
686 public function getMissingRevisionIDsAsResult( $result = null ) {
687 $values = [];
688 foreach ( $this->getMissingRevisionIDs() as $revid ) {
689 $values[$revid] = [
690 'revid' => $revid
691 ];
692 }
693 if ( !empty( $values ) && $result ) {
694 ApiResult::setIndexedTagName( $values, 'rev' );
695 }
696
697 return $values;
698 }
699
704 public function getSpecialTitles() {
705 return $this->mSpecialTitles;
706 }
707
712 public function getRevisionCount() {
713 return count( $this->getRevisionIDs() );
714 }
715
720 public function populateFromTitles( $titles ) {
721 $this->initFromTitles( $titles );
722 }
723
728 public function populateFromPageIDs( $pageIDs ) {
729 $this->initFromPageIds( $pageIDs );
730 }
731
741 public function populateFromQueryResult( $db, $queryResult ) {
742 $this->initFromQueryResult( $queryResult );
743 }
744
749 public function populateFromRevisionIDs( $revIDs ) {
750 $this->initFromRevIDs( $revIDs );
751 }
752
757 public function processDbRow( $row ) {
758 // Store Title object in various data structures
759 $title = Title::newFromRow( $row );
760
761 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
762 $linkCache->addGoodLinkObjFromRow( $title, $row );
763
764 $pageId = (int)$row->page_id;
765 $this->mAllPages[$row->page_namespace][$row->page_title] = $pageId;
766 $this->mTitles[] = $title;
767
768 if ( $this->mResolveRedirects && $row->page_is_redirect == '1' ) {
769 $this->mPendingRedirectIDs[$pageId] = $title;
770 } else {
771 $this->mGoodPages[$row->page_namespace][$row->page_title] = $pageId;
772 $this->mGoodAndMissingPages[$row->page_namespace][$row->page_title] = $pageId;
773 $this->mGoodTitles[$pageId] = $title;
774 }
775
776 foreach ( $this->mRequestedPageFields as $fieldName => &$fieldValues ) {
777 $fieldValues[$pageId] = $row->$fieldName;
778 }
779 }
780
797 private function initFromTitles( $titles ) {
798 // Get validated and normalized title objects
799 $linkBatch = $this->processTitlesArray( $titles );
800 if ( $linkBatch->isEmpty() ) {
801 // There might be special-page redirects
803 return;
804 }
805
806 $db = $this->getDB();
807 $set = $linkBatch->constructSet( 'page', $db );
808
809 // Get pageIDs data from the `page` table
810 $res = $db->select( 'page', $this->getPageTableFields(), $set,
811 __METHOD__ );
812
813 // Hack: get the ns:titles stored in [ ns => [ titles ] ] format
814 $this->initFromQueryResult( $res, $linkBatch->data, true ); // process Titles
815
816 // Resolve any found redirects
818 }
819
825 private function initFromPageIds( $pageids, $filterIds = true ) {
826 if ( !$pageids ) {
827 return;
828 }
829
830 $pageids = array_map( 'intval', $pageids ); // paranoia
831 $remaining = array_flip( $pageids );
832
833 if ( $filterIds ) {
834 $pageids = $this->filterIDs( [ [ 'page', 'page_id' ] ], $pageids );
835 }
836
837 $res = null;
838 if ( !empty( $pageids ) ) {
839 $set = [
840 'page_id' => $pageids
841 ];
842 $db = $this->getDB();
843
844 // Get pageIDs data from the `page` table
845 $res = $db->select( 'page', $this->getPageTableFields(), $set,
846 __METHOD__ );
847 }
848
849 $this->initFromQueryResult( $res, $remaining, false ); // process PageIDs
850
851 // Resolve any found redirects
853 }
854
865 private function initFromQueryResult( $res, &$remaining = null, $processTitles = null ) {
866 if ( !is_null( $remaining ) && is_null( $processTitles ) ) {
867 ApiBase::dieDebug( __METHOD__, 'Missing $processTitles parameter when $remaining is provided' );
868 }
869
870 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
871
872 $usernames = [];
873 if ( $res ) {
874 foreach ( $res as $row ) {
875 $pageId = (int)$row->page_id;
876
877 // Remove found page from the list of remaining items
878 if ( isset( $remaining ) ) {
879 if ( $processTitles ) {
880 unset( $remaining[$row->page_namespace][$row->page_title] );
881 } else {
882 unset( $remaining[$pageId] );
883 }
884 }
885
886 // Store any extra fields requested by modules
887 $this->processDbRow( $row );
888
889 // Need gender information
890 if ( $nsInfo->hasGenderDistinction( $row->page_namespace ) ) {
891 $usernames[] = $row->page_title;
892 }
893 }
894 }
895
896 if ( isset( $remaining ) ) {
897 // Any items left in the $remaining list are added as missing
898 if ( $processTitles ) {
899 // The remaining titles in $remaining are non-existent pages
900 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
901 foreach ( $remaining as $ns => $dbkeys ) {
902 foreach ( array_keys( $dbkeys ) as $dbkey ) {
903 $title = Title::makeTitle( $ns, $dbkey );
904 $linkCache->addBadLinkObj( $title );
905 $this->mAllPages[$ns][$dbkey] = $this->mFakePageId;
906 $this->mMissingPages[$ns][$dbkey] = $this->mFakePageId;
907 $this->mGoodAndMissingPages[$ns][$dbkey] = $this->mFakePageId;
908 $this->mMissingTitles[$this->mFakePageId] = $title;
909 $this->mFakePageId--;
910 $this->mTitles[] = $title;
911
912 // need gender information
913 if ( $nsInfo->hasGenderDistinction( $ns ) ) {
914 $usernames[] = $dbkey;
915 }
916 }
917 }
918 } else {
919 // The remaining pageids do not exist
920 if ( !$this->mMissingPageIDs ) {
921 $this->mMissingPageIDs = array_keys( $remaining );
922 } else {
923 $this->mMissingPageIDs = array_merge( $this->mMissingPageIDs, array_keys( $remaining ) );
924 }
925 }
926 }
927
928 // Get gender information
929 $genderCache = MediaWikiServices::getInstance()->getGenderCache();
930 $genderCache->doQuery( $usernames, __METHOD__ );
931 }
932
938 private function initFromRevIDs( $revids ) {
939 if ( !$revids ) {
940 return;
941 }
942
943 $revids = array_map( 'intval', $revids ); // paranoia
944 $db = $this->getDB();
945 $pageids = [];
946 $remaining = array_flip( $revids );
947
948 $revids = $this->filterIDs( [ [ 'revision', 'rev_id' ], [ 'archive', 'ar_rev_id' ] ], $revids );
949 $goodRemaining = array_flip( $revids );
950
951 if ( $revids ) {
952 $tables = [ 'revision', 'page' ];
953 $fields = [ 'rev_id', 'rev_page' ];
954 $where = [ 'rev_id' => $revids, 'rev_page = page_id' ];
955
956 // Get pageIDs data from the `page` table
957 $res = $db->select( $tables, $fields, $where, __METHOD__ );
958 foreach ( $res as $row ) {
959 $revid = (int)$row->rev_id;
960 $pageid = (int)$row->rev_page;
961 $this->mGoodRevIDs[$revid] = $pageid;
962 $this->mLiveRevIDs[$revid] = $pageid;
963 $pageids[$pageid] = '';
964 unset( $remaining[$revid] );
965 unset( $goodRemaining[$revid] );
966 }
967 }
968
969 // Populate all the page information
970 $this->initFromPageIds( array_keys( $pageids ), false );
971
972 // If the user can see deleted revisions, pull out the corresponding
973 // titles from the archive table and include them too. We ignore
974 // ar_page_id because deleted revisions are tied by title, not page_id.
975 if ( $goodRemaining &&
976 $this->getPermissionManager()->userHasRight( $this->getUser(), 'deletedhistory' ) ) {
977 $tables = [ 'archive' ];
978 $fields = [ 'ar_rev_id', 'ar_namespace', 'ar_title' ];
979 $where = [ 'ar_rev_id' => array_keys( $goodRemaining ) ];
980
981 $res = $db->select( $tables, $fields, $where, __METHOD__ );
982 $titles = [];
983 foreach ( $res as $row ) {
984 $revid = (int)$row->ar_rev_id;
985 $titles[$revid] = Title::makeTitle( $row->ar_namespace, $row->ar_title );
986 unset( $remaining[$revid] );
987 }
988
989 $this->initFromTitles( $titles );
990
991 foreach ( $titles as $revid => $title ) {
992 $ns = $title->getNamespace();
993 $dbkey = $title->getDBkey();
994
995 // Handle converted titles
996 if ( !isset( $this->mAllPages[$ns][$dbkey] ) &&
997 isset( $this->mConvertedTitles[$title->getPrefixedText()] )
998 ) {
999 $title = Title::newFromText( $this->mConvertedTitles[$title->getPrefixedText()] );
1000 $ns = $title->getNamespace();
1001 $dbkey = $title->getDBkey();
1002 }
1003
1004 if ( isset( $this->mAllPages[$ns][$dbkey] ) ) {
1005 $this->mGoodRevIDs[$revid] = $this->mAllPages[$ns][$dbkey];
1006 $this->mDeletedRevIDs[$revid] = $this->mAllPages[$ns][$dbkey];
1007 } else {
1008 $remaining[$revid] = true;
1009 }
1010 }
1011 }
1012
1013 $this->mMissingRevIDs = array_keys( $remaining );
1014 }
1015
1021 private function resolvePendingRedirects() {
1022 if ( $this->mResolveRedirects ) {
1023 $db = $this->getDB();
1024 $pageFlds = $this->getPageTableFields();
1025
1026 // Repeat until all redirects have been resolved
1027 // The infinite loop is prevented by keeping all known pages in $this->mAllPages
1028 while ( $this->mPendingRedirectIDs || $this->mPendingRedirectSpecialPages ) {
1029 // Resolve redirects by querying the pagelinks table, and repeat the process
1030 // Create a new linkBatch object for the next pass
1031 $linkBatch = $this->getRedirectTargets();
1032
1033 if ( $linkBatch->isEmpty() ) {
1034 break;
1035 }
1036
1037 $set = $linkBatch->constructSet( 'page', $db );
1038 if ( $set === false ) {
1039 break;
1040 }
1041
1042 // Get pageIDs data from the `page` table
1043 $res = $db->select( 'page', $pageFlds, $set, __METHOD__ );
1044
1045 // Hack: get the ns:titles stored in [ns => array(titles)] format
1046 $this->initFromQueryResult( $res, $linkBatch->data, true );
1047 }
1048 }
1049 }
1050
1058 private function getRedirectTargets() {
1059 $titlesToResolve = [];
1060 $db = $this->getDB();
1061
1062 if ( $this->mPendingRedirectIDs ) {
1063 $res = $db->select(
1064 'redirect',
1065 [
1066 'rd_from',
1067 'rd_namespace',
1068 'rd_fragment',
1069 'rd_interwiki',
1070 'rd_title'
1071 ], [ 'rd_from' => array_keys( $this->mPendingRedirectIDs ) ],
1072 __METHOD__
1073 );
1074 foreach ( $res as $row ) {
1075 $rdfrom = (int)$row->rd_from;
1076 $from = $this->mPendingRedirectIDs[$rdfrom]->getPrefixedText();
1077 $to = Title::makeTitle(
1078 $row->rd_namespace,
1079 $row->rd_title,
1080 $row->rd_fragment,
1081 $row->rd_interwiki
1082 );
1083 $this->mResolvedRedirectTitles[$from] = $this->mPendingRedirectIDs[$rdfrom];
1084 unset( $this->mPendingRedirectIDs[$rdfrom] );
1085 if ( $to->isExternal() ) {
1086 $this->mInterwikiTitles[$to->getPrefixedText()] = $to->getInterwiki();
1087 } elseif ( !isset( $this->mAllPages[$to->getNamespace()][$to->getDBkey()] ) ) {
1088 $titlesToResolve[] = $to;
1089 }
1090 $this->mRedirectTitles[$from] = $to;
1091 }
1092
1093 if ( $this->mPendingRedirectIDs ) {
1094 // We found pages that aren't in the redirect table
1095 // Add them
1096 foreach ( $this->mPendingRedirectIDs as $id => $title ) {
1097 $page = WikiPage::factory( $title );
1098 $rt = $page->insertRedirect();
1099 if ( !$rt ) {
1100 // What the hell. Let's just ignore this
1101 continue;
1102 }
1103 if ( $rt->isExternal() ) {
1104 $this->mInterwikiTitles[$rt->getPrefixedText()] = $rt->getInterwiki();
1105 } elseif ( !isset( $this->mAllPages[$rt->getNamespace()][$rt->getDBkey()] ) ) {
1106 $titlesToResolve[] = $rt;
1107 }
1108 $from = $title->getPrefixedText();
1109 $this->mResolvedRedirectTitles[$from] = $title;
1110 $this->mRedirectTitles[$from] = $rt;
1111 unset( $this->mPendingRedirectIDs[$id] );
1112 }
1113 }
1114 }
1115
1116 if ( $this->mPendingRedirectSpecialPages ) {
1117 foreach ( $this->mPendingRedirectSpecialPages as $key => list( $from, $to ) ) {
1118 $fromKey = $from->getPrefixedText();
1119 $this->mResolvedRedirectTitles[$fromKey] = $from;
1120 $this->mRedirectTitles[$fromKey] = $to;
1121 if ( $to->isExternal() ) {
1122 $this->mInterwikiTitles[$to->getPrefixedText()] = $to->getInterwiki();
1123 } elseif ( !isset( $this->mAllPages[$to->getNamespace()][$to->getDBkey()] ) ) {
1124 $titlesToResolve[] = $to;
1125 }
1126 }
1127 $this->mPendingRedirectSpecialPages = [];
1128
1129 // Set private caching since we don't know what criteria the
1130 // special pages used to decide on these redirects.
1131 $this->mCacheMode = 'private';
1132 }
1133
1134 return $this->processTitlesArray( $titlesToResolve );
1135 }
1136
1150 public function getCacheMode( $params = null ) {
1151 return $this->mCacheMode;
1152 }
1153
1163 private function processTitlesArray( $titles ) {
1164 $usernames = [];
1165 $linkBatch = new LinkBatch();
1166 $services = MediaWikiServices::getInstance();
1167 $contLang = $services->getContentLanguage();
1168
1169 $titleObjects = [];
1170 foreach ( $titles as $index => $title ) {
1171 if ( is_string( $title ) ) {
1172 try {
1173 $titleObj = Title::newFromTextThrow( $title, $this->mDefaultNamespace );
1174 } catch ( MalformedTitleException $ex ) {
1175 // Handle invalid titles gracefully
1176 if ( !isset( $this->mAllPages[0][$title] ) ) {
1177 $this->mAllPages[0][$title] = $this->mFakePageId;
1178 $this->mInvalidTitles[$this->mFakePageId] = [
1179 'title' => $title,
1180 'invalidreason' => $this->getErrorFormatter()->formatException( $ex, [ 'bc' => true ] ),
1181 ];
1182 $this->mFakePageId--;
1183 }
1184 continue; // There's nothing else we can do
1185 }
1186 } else {
1187 $titleObj = $title;
1188 }
1189
1190 $titleObjects[$index] = $titleObj;
1191 }
1192
1193 // Get gender information
1194 $genderCache = $services->getGenderCache();
1195 $genderCache->doTitlesArray( $titleObjects, __METHOD__ );
1196
1197 foreach ( $titleObjects as $index => $titleObj ) {
1198 $title = is_string( $titles[$index] ) ? $titles[$index] : false;
1199 $unconvertedTitle = $titleObj->getPrefixedText();
1200 $titleWasConverted = false;
1201 if ( $titleObj->isExternal() ) {
1202 // This title is an interwiki link.
1203 $this->mInterwikiTitles[$unconvertedTitle] = $titleObj->getInterwiki();
1204 } else {
1205 // Variants checking
1206 if (
1207 $this->mConvertTitles && $contLang->hasVariants() && !$titleObj->exists()
1208 ) {
1209 // Language::findVariantLink will modify titleText and titleObj into
1210 // the canonical variant if possible
1211 $titleText = $title !== false ? $title : $titleObj->getPrefixedText();
1212 $contLang->findVariantLink( $titleText, $titleObj );
1213 $titleWasConverted = $unconvertedTitle !== $titleObj->getPrefixedText();
1214 }
1215
1216 if ( $titleObj->getNamespace() < 0 ) {
1217 // Handle Special and Media pages
1218 $titleObj = $titleObj->fixSpecialName();
1219 $ns = $titleObj->getNamespace();
1220 $dbkey = $titleObj->getDBkey();
1221 if ( !isset( $this->mAllSpecials[$ns][$dbkey] ) ) {
1222 $this->mAllSpecials[$ns][$dbkey] = $this->mFakePageId;
1223 $target = null;
1224 if ( $ns === NS_SPECIAL && $this->mResolveRedirects ) {
1225 $spFactory = $services->getSpecialPageFactory();
1226 $special = $spFactory->getPage( $dbkey );
1227 if ( $special instanceof RedirectSpecialArticle ) {
1228 // Only RedirectSpecialArticle is intended to redirect to an article, other kinds of
1229 // RedirectSpecialPage are probably applying weird URL parameters we don't want to handle.
1230 $context = new DerivativeContext( $this );
1231 $context->setTitle( $titleObj );
1232 $context->setRequest( new FauxRequest );
1233 $special->setContext( $context );
1234 list( /* $alias */, $subpage ) = $spFactory->resolveAlias( $dbkey );
1235 $target = $special->getRedirect( $subpage );
1236 }
1237 }
1238 if ( $target ) {
1239 $this->mPendingRedirectSpecialPages[$dbkey] = [ $titleObj, $target ];
1240 } else {
1241 $this->mSpecialTitles[$this->mFakePageId] = $titleObj;
1242 $this->mFakePageId--;
1243 }
1244 }
1245 } else {
1246 // Regular page
1247 $linkBatch->addObj( $titleObj );
1248 }
1249 }
1250
1251 // Make sure we remember the original title that was
1252 // given to us. This way the caller can correlate new
1253 // titles with the originally requested when e.g. the
1254 // namespace is localized or the capitalization is
1255 // different
1256 if ( $titleWasConverted ) {
1257 $this->mConvertedTitles[$unconvertedTitle] = $titleObj->getPrefixedText();
1258 // In this case the page can't be Special.
1259 if ( $title !== false && $title !== $unconvertedTitle ) {
1260 $this->mNormalizedTitles[$title] = $unconvertedTitle;
1261 }
1262 } elseif ( $title !== false && $title !== $titleObj->getPrefixedText() ) {
1263 $this->mNormalizedTitles[$title] = $titleObj->getPrefixedText();
1264 }
1265 }
1266
1267 return $linkBatch;
1268 }
1269
1285 public function setGeneratorData( Title $title, array $data ) {
1286 $ns = $title->getNamespace();
1287 $dbkey = $title->getDBkey();
1288 $this->mGeneratorData[$ns][$dbkey] = $data;
1289 }
1290
1310 public function setRedirectMergePolicy( $callable ) {
1311 $this->mRedirectMergePolicy = $callable;
1312 }
1313
1334 public function populateGeneratorData( &$result, array $path = [] ) {
1335 if ( $result instanceof ApiResult ) {
1336 $data = $result->getResultData( $path );
1337 if ( $data === null ) {
1338 return true;
1339 }
1340 } else {
1341 $data = &$result;
1342 foreach ( $path as $key ) {
1343 if ( !isset( $data[$key] ) ) {
1344 // Path isn't in $result, so nothing to add, so everything
1345 // "fits"
1346 return true;
1347 }
1348 $data = &$data[$key];
1349 }
1350 }
1351 foreach ( $this->mGeneratorData as $ns => $dbkeys ) {
1352 if ( $ns === NS_SPECIAL ) {
1353 $pages = [];
1354 foreach ( $this->mSpecialTitles as $id => $title ) {
1355 $pages[$title->getDBkey()] = $id;
1356 }
1357 } else {
1358 if ( !isset( $this->mAllPages[$ns] ) ) {
1359 // No known titles in the whole namespace. Skip it.
1360 continue;
1361 }
1362 $pages = $this->mAllPages[$ns];
1363 }
1364 foreach ( $dbkeys as $dbkey => $genData ) {
1365 if ( !isset( $pages[$dbkey] ) ) {
1366 // Unknown title. Forget it.
1367 continue;
1368 }
1369 $pageId = $pages[$dbkey];
1370 if ( !isset( $data[$pageId] ) ) {
1371 // $pageId didn't make it into the result. Ignore it.
1372 continue;
1373 }
1374
1375 if ( $result instanceof ApiResult ) {
1376 $path2 = array_merge( $path, [ $pageId ] );
1377 foreach ( $genData as $key => $value ) {
1378 if ( !$result->addValue( $path2, $key, $value ) ) {
1379 return false;
1380 }
1381 }
1382 } else {
1383 $data[$pageId] = array_merge( $data[$pageId], $genData );
1384 }
1385 }
1386 }
1387
1388 // Merge data generated about redirect titles into the redirect destination
1389 if ( $this->mRedirectMergePolicy ) {
1390 foreach ( $this->mResolvedRedirectTitles as $titleFrom ) {
1391 $dest = $titleFrom;
1392 while ( isset( $this->mRedirectTitles[$dest->getPrefixedText()] ) ) {
1393 $dest = $this->mRedirectTitles[$dest->getPrefixedText()];
1394 }
1395 $fromNs = $titleFrom->getNamespace();
1396 $fromDBkey = $titleFrom->getDBkey();
1397 $toPageId = $dest->getArticleID();
1398 if ( isset( $data[$toPageId] ) &&
1399 isset( $this->mGeneratorData[$fromNs][$fromDBkey] )
1400 ) {
1401 // It is necessary to set both $data and add to $result, if an ApiResult,
1402 // to ensure multiple redirects to the same destination are all merged.
1403 $data[$toPageId] = call_user_func(
1404 $this->mRedirectMergePolicy,
1405 $data[$toPageId],
1406 $this->mGeneratorData[$fromNs][$fromDBkey]
1407 );
1408 if ( $result instanceof ApiResult &&
1409 !$result->addValue( $path, $toPageId, $data[$toPageId], ApiResult::OVERRIDE )
1410 ) {
1411 return false;
1412 }
1413 }
1414 }
1415 }
1416
1417 return true;
1418 }
1419
1424 protected function getDB() {
1425 return $this->mDbSource->getDB();
1426 }
1427
1428 public function getAllowedParams( $flags = 0 ) {
1429 $result = [
1430 'titles' => [
1431 ApiBase::PARAM_ISMULTI => true,
1432 ApiBase::PARAM_HELP_MSG => 'api-pageset-param-titles',
1433 ],
1434 'pageids' => [
1435 ApiBase::PARAM_TYPE => 'integer',
1436 ApiBase::PARAM_ISMULTI => true,
1437 ApiBase::PARAM_HELP_MSG => 'api-pageset-param-pageids',
1438 ],
1439 'revids' => [
1440 ApiBase::PARAM_TYPE => 'integer',
1441 ApiBase::PARAM_ISMULTI => true,
1442 ApiBase::PARAM_HELP_MSG => 'api-pageset-param-revids',
1443 ],
1444 'generator' => [
1445 ApiBase::PARAM_TYPE => null,
1446 ApiBase::PARAM_HELP_MSG => 'api-pageset-param-generator',
1448 ],
1449 'redirects' => [
1450 ApiBase::PARAM_DFLT => false,
1451 ApiBase::PARAM_HELP_MSG => $this->mAllowGenerator
1452 ? 'api-pageset-param-redirects-generator'
1453 : 'api-pageset-param-redirects-nogenerator',
1454 ],
1455 'converttitles' => [
1456 ApiBase::PARAM_DFLT => false,
1458 'api-pageset-param-converttitles',
1459 [ Message::listParam( LanguageConverter::$languagesWithVariants, 'text' ) ],
1460 ],
1461 ],
1462 ];
1463
1464 if ( !$this->mAllowGenerator ) {
1465 unset( $result['generator'] );
1466 } elseif ( $flags & ApiBase::GET_VALUES_FOR_HELP ) {
1467 $result['generator'][ApiBase::PARAM_TYPE] = 'submodule';
1468 $result['generator'][ApiBase::PARAM_SUBMODULE_MAP] = $this->getGenerators();
1469 }
1470
1471 return $result;
1472 }
1473
1474 protected function handleParamNormalization( $paramName, $value, $rawValue ) {
1475 parent::handleParamNormalization( $paramName, $value, $rawValue );
1476
1477 if ( $paramName === 'titles' ) {
1478 // For the 'titles' parameter, we want to split it like ApiBase would
1479 // and add any changed titles to $this->mNormalizedTitles
1480 $value = $this->explodeMultiValue( $value, self::LIMIT_SML2 + 1 );
1481 $l = count( $value );
1482 $rawValue = $this->explodeMultiValue( $rawValue, $l );
1483 for ( $i = 0; $i < $l; $i++ ) {
1484 if ( $value[$i] !== $rawValue[$i] ) {
1485 $this->mNormalizedTitles[$rawValue[$i]] = $value[$i];
1486 }
1487 }
1488 }
1489 }
1490
1491 private static $generators = null;
1492
1497 private function getGenerators() {
1498 if ( self::$generators === null ) {
1499 $query = $this->mDbSource;
1500 if ( !( $query instanceof ApiQuery ) ) {
1501 // If the parent container of this pageset is not ApiQuery,
1502 // we must create it to get module manager
1503 $query = $this->getMain()->getModuleManager()->getModule( 'query' );
1504 }
1505 $gens = [];
1506 $prefix = $query->getModulePath() . '+';
1507 $mgr = $query->getModuleManager();
1508 foreach ( $mgr->getNamesWithClasses() as $name => $class ) {
1509 if ( is_subclass_of( $class, ApiQueryGeneratorBase::class ) ) {
1510 $gens[$name] = $prefix . $name;
1511 }
1512 }
1513 ksort( $gens );
1514 self::$generators = $gens;
1515 }
1516
1517 return self::$generators;
1518 }
1519}
getPermissionManager()
getDB()
getUser()
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:42
const PARAM_SUBMODULE_MAP
(string[]) When PARAM_TYPE is 'submodule', map parameter values to submodule paths.
Definition ApiBase.php:172
filterIDs( $fields, array $ids)
Filter out-of-range values from a list of positive integer IDs.
Definition ApiBase.php:1885
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
Definition ApiBase.php:739
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition ApiBase.php:2014
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:2220
getMain()
Get the main module.
Definition ApiBase.php:536
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below.
Definition ApiBase.php:94
getErrorFormatter()
Get the error formatter.
Definition ApiBase.php:654
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
Definition ApiBase.php:55
const PARAM_SUBMODULE_PARAM_PREFIX
(string) When PARAM_TYPE is 'submodule', used to indicate the 'g' prefix added by ApiQueryGeneratorBa...
Definition ApiBase.php:179
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:761
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:131
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition ApiBase.php:1933
const GET_VALUES_FOR_HELP
getAllowedParams() flag: When set, the result could take longer to generate, but should be more thoro...
Definition ApiBase.php:272
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:520
explodeMultiValue( $value, $limit)
Split a multi-valued parameter string, like explode()
Definition ApiBase.php:1469
const PARAM_ISMULTI
(boolean) Accept multiple pipe-separated values for this parameter (e.g.
Definition ApiBase.php:58
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)
static $generators
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.
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.
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:34
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:1115
Superclass for any RedirectSpecialPage which redirects the user to a particular article (as opposed t...
Represents a title within MediaWiki.
Definition Title.php:42
const PROTO_CURRENT
Definition Defines.php:211
const NS_MAIN
Definition Defines.php:69
const NS_SPECIAL
Definition Defines.php:58
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.
$context
Definition load.php:45