]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/api/ApiPageSet.php
MediaWiki 1.17.4
[autoinstalls/mediawiki.git] / includes / api / ApiPageSet.php
1 <?php
2 /**
3  * API for MediaWiki 1.8+
4  *
5  * Created on Sep 24, 2006
6  *
7  * Copyright © 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22  * http://www.gnu.org/copyleft/gpl.html
23  *
24  * @file
25  */
26
27 if ( !defined( 'MEDIAWIKI' ) ) {
28         // Eclipse helper - will be ignored in production
29         require_once( 'ApiQueryBase.php' );
30 }
31
32 /**
33  * This class contains a list of pages that the client has requested.
34  * Initially, when the client passes in titles=, pageids=, or revisions=
35  * parameter, an instance of the ApiPageSet class will normalize titles,
36  * determine if the pages/revisions exist, and prefetch any additional page
37  * data requested.
38  *
39  * When a generator is used, the result of the generator will become the input
40  * for the second instance of this class, and all subsequent actions will use
41  * the second instance for all their work.
42  *
43  * @ingroup API
44  */
45 class ApiPageSet extends ApiQueryBase {
46
47         private $mAllPages; // [ns][dbkey] => page_id or negative when missing
48         private $mTitles, $mGoodTitles, $mMissingTitles, $mInvalidTitles;
49         private $mMissingPageIDs, $mRedirectTitles, $mSpecialTitles;
50         private $mNormalizedTitles, $mInterwikiTitles;
51         private $mResolveRedirects, $mPendingRedirectIDs;
52         private $mConvertTitles, $mConvertedTitles;
53         private $mGoodRevIDs, $mMissingRevIDs;
54         private $mFakePageId;
55
56         private $mRequestedPageFields;
57
58         /**
59          * Constructor
60          * @param $query ApiQuery
61          * @param $resolveRedirects bool Whether redirects should be resolved
62          */
63         public function __construct( $query, $resolveRedirects = false, $convertTitles = false ) {
64                 parent::__construct( $query, 'query' );
65
66                 $this->mAllPages = array();
67                 $this->mTitles = array();
68                 $this->mGoodTitles = array();
69                 $this->mMissingTitles = array();
70                 $this->mInvalidTitles = array();
71                 $this->mMissingPageIDs = array();
72                 $this->mRedirectTitles = array();
73                 $this->mNormalizedTitles = array();
74                 $this->mInterwikiTitles = array();
75                 $this->mGoodRevIDs = array();
76                 $this->mMissingRevIDs = array();
77                 $this->mSpecialTitles = array();
78
79                 $this->mRequestedPageFields = array();
80                 $this->mResolveRedirects = $resolveRedirects;
81                 if ( $resolveRedirects ) {
82                         $this->mPendingRedirectIDs = array();
83                 }
84
85                 $this->mConvertTitles = $convertTitles;
86                 $this->mConvertedTitles = array();
87
88                 $this->mFakePageId = - 1;
89         }
90
91         /**
92          * Check whether this PageSet is resolving redirects
93          * @return bool
94          */
95         public function isResolvingRedirects() {
96                 return $this->mResolveRedirects;
97         }
98
99         /**
100          * Request an additional field from the page table. Must be called
101          * before execute()
102          * @param $fieldName string Field name
103          */
104         public function requestField( $fieldName ) {
105                 $this->mRequestedPageFields[$fieldName] = null;
106         }
107
108         /**
109          * Get the value of a custom field previously requested through
110          * requestField()
111          * @param $fieldName string Field name
112          * @return mixed Field value
113          */
114         public function getCustomField( $fieldName ) {
115                 return $this->mRequestedPageFields[$fieldName];
116         }
117
118         /**
119          * Get the fields that have to be queried from the page table:
120          * the ones requested through requestField() and a few basic ones
121          * we always need
122          * @return array of field names
123          */
124         public function getPageTableFields() {
125                 // Ensure we get minimum required fields
126                 // DON'T change this order
127                 $pageFlds = array(
128                         'page_namespace' => null,
129                         'page_title' => null,
130                         'page_id' => null,
131                 );
132
133                 if ( $this->mResolveRedirects ) {
134                         $pageFlds['page_is_redirect'] = null;
135                 }
136
137                 // only store non-default fields
138                 $this->mRequestedPageFields = array_diff_key( $this->mRequestedPageFields, $pageFlds );
139
140                 $pageFlds = array_merge( $pageFlds, $this->mRequestedPageFields );
141                 return array_keys( $pageFlds );
142         }
143
144         /**
145          * Returns an array [ns][dbkey] => page_id for all requested titles.
146          * page_id is a unique negative number in case title was not found.
147          * Invalid titles will also have negative page IDs and will be in namespace 0
148          * @return array
149          */
150         public function getAllTitlesByNamespace() {
151                 return $this->mAllPages;
152         }
153
154         /**
155          * All Title objects provided.
156          * @return array of Title objects
157          */
158         public function getTitles() {
159                 return $this->mTitles;
160         }
161
162         /**
163          * Returns the number of unique pages (not revisions) in the set.
164          * @return int
165          */
166         public function getTitleCount() {
167                 return count( $this->mTitles );
168         }
169
170         /**
171          * Title objects that were found in the database.
172          * @return array page_id (int) => Title (obj)
173          */
174         public function getGoodTitles() {
175                 return $this->mGoodTitles;
176         }
177
178         /**
179          * Returns the number of found unique pages (not revisions) in the set.
180          * @return int
181          */
182         public function getGoodTitleCount() {
183                 return count( $this->mGoodTitles );
184         }
185
186         /**
187          * Title objects that were NOT found in the database.
188          * The array's index will be negative for each item
189          * @return array of Title objects
190          */
191         public function getMissingTitles() {
192                 return $this->mMissingTitles;
193         }
194
195         /**
196          * Titles that were deemed invalid by Title::newFromText()
197          * The array's index will be unique and negative for each item
198          * @return array of strings (not Title objects)
199          */
200         public function getInvalidTitles() {
201                 return $this->mInvalidTitles;
202         }
203
204         /**
205          * Page IDs that were not found in the database
206          * @return array of page IDs
207          */
208         public function getMissingPageIDs() {
209                 return $this->mMissingPageIDs;
210         }
211
212         /**
213          * Get a list of redirect resolutions - maps a title to its redirect
214          * target.
215          * @return array prefixed_title (string) => prefixed_title (string)
216          */
217         public function getRedirectTitles() {
218                 return $this->mRedirectTitles;
219         }
220
221         /**
222          * Get a list of title normalizations - maps a title to its normalized
223          * version.
224          * @return array raw_prefixed_title (string) => prefixed_title (string)
225          */
226         public function getNormalizedTitles() {
227                 return $this->mNormalizedTitles;
228         }
229
230         /**
231          * Get a list of title conversions - maps a title to its converted
232          * version.
233          * @return array raw_prefixed_title (string) => prefixed_title (string)
234          */
235         public function getConvertedTitles() {
236                 return $this->mConvertedTitles;
237         }
238
239         /**
240          * Get a list of interwiki titles - maps a title to its interwiki
241          * prefix.
242          * @return array raw_prefixed_title (string) => interwiki_prefix (string)
243          */
244         public function getInterwikiTitles() {
245                 return $this->mInterwikiTitles;
246         }
247
248         /**
249          * Get the list of revision IDs (requested with the revids= parameter)
250          * @return array revID (int) => pageID (int)
251          */
252         public function getRevisionIDs() {
253                 return $this->mGoodRevIDs;
254         }
255
256         /**
257          * Revision IDs that were not found in the database
258          * @return array of revision IDs
259          */
260         public function getMissingRevisionIDs() {
261                 return $this->mMissingRevIDs;
262         }
263
264         /**
265          * Get the list of titles with negative namespace
266          * @return array Title
267          */
268         public function getSpecialTitles() {
269                 return $this->mSpecialTitles;
270         }
271
272         /**
273          * Returns the number of revisions (requested with revids= parameter)\
274          * @return int
275          */
276         public function getRevisionCount() {
277                 return count( $this->getRevisionIDs() );
278         }
279
280         /**
281          * Populate the PageSet from the request parameters.
282          */
283         public function execute() {
284                 $this->profileIn();
285                 $params = $this->extractRequestParams();
286
287                 // Only one of the titles/pageids/revids is allowed at the same time
288                 $dataSource = null;
289                 if ( isset( $params['titles'] ) ) {
290                         $dataSource = 'titles';
291                 }
292                 if ( isset( $params['pageids'] ) ) {
293                         if ( isset( $dataSource ) ) {
294                                 $this->dieUsage( "Cannot use 'pageids' at the same time as '$dataSource'", 'multisource' );
295                         }
296                         $dataSource = 'pageids';
297                 }
298                 if ( isset( $params['revids'] ) ) {
299                         if ( isset( $dataSource ) ) {
300                                 $this->dieUsage( "Cannot use 'revids' at the same time as '$dataSource'", 'multisource' );
301                         }
302                         $dataSource = 'revids';
303                 }
304
305                 switch ( $dataSource ) {
306                         case 'titles':
307                                 $this->initFromTitles( $params['titles'] );
308                                 break;
309                         case 'pageids':
310                                 $this->initFromPageIds( $params['pageids'] );
311                                 break;
312                         case 'revids':
313                                 if ( $this->mResolveRedirects ) {
314                                         $this->setWarning( 'Redirect resolution cannot be used together with the revids= parameter. ' .
315                                         'Any redirects the revids= point to have not been resolved.' );
316                                 }
317                                 $this->mResolveRedirects = false;
318                                 $this->initFromRevIDs( $params['revids'] );
319                                 break;
320                         default:
321                                 // Do nothing - some queries do not need any of the data sources.
322                                 break;
323                 }
324                 $this->profileOut();
325         }
326
327         /**
328          * Populate this PageSet from a list of Titles
329          * @param $titles array of Title objects
330          */
331         public function populateFromTitles( $titles ) {
332                 $this->profileIn();
333                 $this->initFromTitles( $titles );
334                 $this->profileOut();
335         }
336
337         /**
338          * Populate this PageSet from a list of page IDs
339          * @param $pageIDs array of page IDs
340          */
341         public function populateFromPageIDs( $pageIDs ) {
342                 $this->profileIn();
343                 $this->initFromPageIds( $pageIDs );
344                 $this->profileOut();
345         }
346
347         /**
348          * Populate this PageSet from a rowset returned from the database
349          * @param $db Database object
350          * @param $queryResult ResultWrapper Query result object
351          */
352         public function populateFromQueryResult( $db, $queryResult ) {
353                 $this->profileIn();
354                 $this->initFromQueryResult( $db, $queryResult );
355                 $this->profileOut();
356         }
357
358         /**
359          * Populate this PageSet from a list of revision IDs
360          * @param $revIDs array of revision IDs
361          */
362         public function populateFromRevisionIDs( $revIDs ) {
363                 $this->profileIn();
364                 $this->initFromRevIDs( $revIDs );
365                 $this->profileOut();
366         }
367
368         /**
369          * Extract all requested fields from the row received from the database
370          * @param $row Result row
371          */
372         public function processDbRow( $row ) {
373                 // Store Title object in various data structures
374                 $title = Title::makeTitle( $row->page_namespace, $row->page_title );
375
376                 $pageId = intval( $row->page_id );
377                 $this->mAllPages[$row->page_namespace][$row->page_title] = $pageId;
378                 $this->mTitles[] = $title;
379
380                 if ( $this->mResolveRedirects && $row->page_is_redirect == '1' ) {
381                         $this->mPendingRedirectIDs[$pageId] = $title;
382                 } else {
383                         $this->mGoodTitles[$pageId] = $title;
384                 }
385
386                 foreach ( $this->mRequestedPageFields as $fieldName => &$fieldValues ) {
387                         $fieldValues[$pageId] = $row-> $fieldName;
388                 }
389         }
390
391         /**
392          * Resolve redirects, if applicable
393          */
394         public function finishPageSetGeneration() {
395                 $this->profileIn();
396                 $this->resolvePendingRedirects();
397                 $this->profileOut();
398         }
399
400         /**
401          * This method populates internal variables with page information
402          * based on the given array of title strings.
403          *
404          * Steps:
405          * #1 For each title, get data from `page` table
406          * #2 If page was not found in the DB, store it as missing
407          *
408          * Additionally, when resolving redirects:
409          * #3 If no more redirects left, stop.
410          * #4 For each redirect, get its target from the `redirect` table.
411          * #5 Substitute the original LinkBatch object with the new list
412          * #6 Repeat from step #1
413          *
414          * @param $titles array of Title objects or strings
415          */
416         private function initFromTitles( $titles ) {
417                 // Get validated and normalized title objects
418                 $linkBatch = $this->processTitlesArray( $titles );
419                 if ( $linkBatch->isEmpty() ) {
420                         return;
421                 }
422
423                 $db = $this->getDB();
424                 $set = $linkBatch->constructSet( 'page', $db );
425
426                 // Get pageIDs data from the `page` table
427                 $this->profileDBIn();
428                 $res = $db->select( 'page', $this->getPageTableFields(), $set,
429                                         __METHOD__ );
430                 $this->profileDBOut();
431
432                 // Hack: get the ns:titles stored in array(ns => array(titles)) format
433                 $this->initFromQueryResult( $db, $res, $linkBatch->data, true ); // process Titles
434
435                 // Resolve any found redirects
436                 $this->resolvePendingRedirects();
437         }
438
439         /**
440          * Does the same as initFromTitles(), but is based on page IDs instead
441          * @param $pageids array of page IDs
442          */
443         private function initFromPageIds( $pageids ) {
444                 if ( !count( $pageids ) ) {
445                         return;
446                 }
447
448                 $pageids = array_map( 'intval', $pageids ); // paranoia
449                 $set = array(
450                         'page_id' => $pageids
451                 );
452                 $db = $this->getDB();
453
454                 // Get pageIDs data from the `page` table
455                 $this->profileDBIn();
456                 $res = $db->select( 'page', $this->getPageTableFields(), $set,
457                                         __METHOD__ );
458                 $this->profileDBOut();
459
460                 $remaining = array_flip( $pageids );
461                 $this->initFromQueryResult( $db, $res, $remaining, false );     // process PageIDs
462
463                 // Resolve any found redirects
464                 $this->resolvePendingRedirects();
465         }
466
467         /**
468          * Iterate through the result of the query on 'page' table,
469          * and for each row create and store title object and save any extra fields requested.
470          * @param $db Database
471          * @param $res ResultWrapper DB Query result
472          * @param $remaining array of either pageID or ns/title elements (optional).
473          *        If given, any missing items will go to $mMissingPageIDs and $mMissingTitles
474          * @param $processTitles bool Must be provided together with $remaining.
475          *        If true, treat $remaining as an array of [ns][title]
476          *        If false, treat it as an array of [pageIDs]
477          */
478         private function initFromQueryResult( $db, $res, &$remaining = null, $processTitles = null ) {
479                 if ( !is_null( $remaining ) && is_null( $processTitles ) ) {
480                         ApiBase::dieDebug( __METHOD__, 'Missing $processTitles parameter when $remaining is provided' );
481                 }
482
483                 foreach ( $res as $row ) {
484                         $pageId = intval( $row->page_id );
485
486                         // Remove found page from the list of remaining items
487                         if ( isset( $remaining ) ) {
488                                 if ( $processTitles ) {
489                                         unset( $remaining[$row->page_namespace][$row->page_title] );
490                                 } else {
491                                         unset( $remaining[$pageId] );
492                                 }
493                         }
494
495                         // Store any extra fields requested by modules
496                         $this->processDbRow( $row );
497                 }
498
499                 if ( isset( $remaining ) ) {
500                         // Any items left in the $remaining list are added as missing
501                         if ( $processTitles ) {
502                                 // The remaining titles in $remaining are non-existent pages
503                                 foreach ( $remaining as $ns => $dbkeys ) {
504                                         foreach ( array_keys( $dbkeys ) as $dbkey ) {
505                                                 $title = Title::makeTitle( $ns, $dbkey );
506                                                 $this->mAllPages[$ns][$dbkey] = $this->mFakePageId;
507                                                 $this->mMissingTitles[$this->mFakePageId] = $title;
508                                                 $this->mFakePageId--;
509                                                 $this->mTitles[] = $title;
510                                         }
511                                 }
512                         } else {
513                                 // The remaining pageids do not exist
514                                 if ( !$this->mMissingPageIDs ) {
515                                         $this->mMissingPageIDs = array_keys( $remaining );
516                                 } else {
517                                         $this->mMissingPageIDs = array_merge( $this->mMissingPageIDs, array_keys( $remaining ) );
518                                 }
519                         }
520                 }
521         }
522
523         /**
524          * Does the same as initFromTitles(), but is based on revision IDs
525          * instead
526          * @param $revids array of revision IDs
527          */
528         private function initFromRevIDs( $revids ) {
529                 if ( !count( $revids ) ) {
530                         return;
531                 }
532
533                 $revids = array_map( 'intval', $revids ); // paranoia
534                 $db = $this->getDB();
535                 $pageids = array();
536                 $remaining = array_flip( $revids );
537
538                 $tables = array( 'revision', 'page' );
539                 $fields = array( 'rev_id', 'rev_page' );
540                 $where = array( 'rev_id' => $revids, 'rev_page = page_id' );
541
542                 // Get pageIDs data from the `page` table
543                 $this->profileDBIn();
544                 $res = $db->select( $tables, $fields, $where,  __METHOD__ );
545                 foreach ( $res as $row ) {
546                         $revid = intval( $row->rev_id );
547                         $pageid = intval( $row->rev_page );
548                         $this->mGoodRevIDs[$revid] = $pageid;
549                         $pageids[$pageid] = '';
550                         unset( $remaining[$revid] );
551                 }
552                 $this->profileDBOut();
553
554                 $this->mMissingRevIDs = array_keys( $remaining );
555
556                 // Populate all the page information
557                 $this->initFromPageIds( array_keys( $pageids ) );
558         }
559
560         /**
561          * Resolve any redirects in the result if redirect resolution was
562          * requested. This function is called repeatedly until all redirects
563          * have been resolved.
564          */
565         private function resolvePendingRedirects() {
566                 if ( $this->mResolveRedirects ) {
567                         $db = $this->getDB();
568                         $pageFlds = $this->getPageTableFields();
569
570                         // Repeat until all redirects have been resolved
571                         // The infinite loop is prevented by keeping all known pages in $this->mAllPages
572                         while ( $this->mPendingRedirectIDs ) {
573                                 // Resolve redirects by querying the pagelinks table, and repeat the process
574                                 // Create a new linkBatch object for the next pass
575                                 $linkBatch = $this->getRedirectTargets();
576
577                                 if ( $linkBatch->isEmpty() ) {
578                                         break;
579                                 }
580
581                                 $set = $linkBatch->constructSet( 'page', $db );
582                                 if ( $set === false ) {
583                                         break;
584                                 }
585
586                                 // Get pageIDs data from the `page` table
587                                 $this->profileDBIn();
588                                 $res = $db->select( 'page', $pageFlds, $set, __METHOD__ );
589                                 $this->profileDBOut();
590
591                                 // Hack: get the ns:titles stored in array(ns => array(titles)) format
592                                 $this->initFromQueryResult( $db, $res, $linkBatch->data, true );
593                         }
594                 }
595         }
596
597         /**
598          * Get the targets of the pending redirects from the database
599          *
600          * Also creates entries in the redirect table for redirects that don't
601          * have one.
602          * @return LinkBatch
603          */
604         private function getRedirectTargets() {
605                 $lb = new LinkBatch();
606                 $db = $this->getDB();
607
608                 $this->profileDBIn();
609                 $res = $db->select(
610                         'redirect',
611                         array(
612                                 'rd_from',
613                                 'rd_namespace',
614                                 'rd_title'
615                         ), array( 'rd_from' => array_keys( $this->mPendingRedirectIDs ) ),
616                         __METHOD__
617                 );
618                 $this->profileDBOut();
619
620                 foreach ( $res as $row ) {
621                         $rdfrom = intval( $row->rd_from );
622                         $from = $this->mPendingRedirectIDs[$rdfrom]->getPrefixedText();
623                         $to = Title::makeTitle( $row->rd_namespace, $row->rd_title )->getPrefixedText();
624                         unset( $this->mPendingRedirectIDs[$rdfrom] );
625                         if ( !isset( $this->mAllPages[$row->rd_namespace][$row->rd_title] ) ) {
626                                 $lb->add( $row->rd_namespace, $row->rd_title );
627                         }
628                         $this->mRedirectTitles[$from] = $to;
629                 }
630
631                 if ( $this->mPendingRedirectIDs ) {
632                         // We found pages that aren't in the redirect table
633                         // Add them
634                         foreach ( $this->mPendingRedirectIDs as $id => $title ) {
635                                 $article = new Article( $title );
636                                 $rt = $article->insertRedirect();
637                                 if ( !$rt ) {
638                                         // What the hell. Let's just ignore this
639                                         continue;
640                                 }
641                                 $lb->addObj( $rt );
642                                 $this->mRedirectTitles[$title->getPrefixedText()] = $rt->getPrefixedText();
643                                 unset( $this->mPendingRedirectIDs[$id] );
644                         }
645                 }
646                 return $lb;
647         }
648
649         /**
650          * Given an array of title strings, convert them into Title objects.
651          * Alternativelly, an array of Title objects may be given.
652          * This method validates access rights for the title,
653          * and appends normalization values to the output.
654          *
655          * @param $titles array of Title objects or strings
656          * @return LinkBatch
657          */
658         private function processTitlesArray( $titles ) {
659                 $linkBatch = new LinkBatch();
660
661                 foreach ( $titles as $title ) {
662                         $titleObj = is_string( $title ) ? Title::newFromText( $title ) : $title;
663                         if ( !$titleObj ) {
664                                 // Handle invalid titles gracefully
665                                 $this->mAllpages[0][$title] = $this->mFakePageId;
666                                 $this->mInvalidTitles[$this->mFakePageId] = $title;
667                                 $this->mFakePageId--;
668                                 continue; // There's nothing else we can do
669                         }
670                         $unconvertedTitle = $titleObj->getPrefixedText();
671                         $titleWasConverted = false;
672                         $iw = $titleObj->getInterwiki();
673                         if ( strval( $iw ) !== '' ) {
674                                 // This title is an interwiki link.
675                                 $this->mInterwikiTitles[$titleObj->getPrefixedText()] = $iw;
676                         } else {
677                                 // Variants checking
678                                 global $wgContLang;
679                                 if ( $this->mConvertTitles &&
680                                                 count( $wgContLang->getVariants() ) > 1  &&
681                                                 !$titleObj->exists() ) {
682                                         // Language::findVariantLink will modify titleObj into
683                                         // the canonical variant if possible
684                                         $wgContLang->findVariantLink( $title, $titleObj );
685                                         $titleWasConverted = $unconvertedTitle !== $titleObj->getPrefixedText();
686                                 }
687
688
689                                 if ( $titleObj->getNamespace() < 0 ) {
690                                         // Handle Special and Media pages
691                                         $titleObj = $titleObj->fixSpecialName();
692                                         $this->mSpecialTitles[$this->mFakePageId] = $titleObj;
693                                         $this->mFakePageId--;
694                                 } else {
695                                         // Regular page
696                                         $linkBatch->addObj( $titleObj );
697                                 }
698                         }
699
700                         // Make sure we remember the original title that was
701                         // given to us. This way the caller can correlate new
702                         // titles with the originally requested when e.g. the
703                         // namespace is localized or the capitalization is
704                         // different
705                         if ( $titleWasConverted ) {
706                                 $this->mConvertedTitles[$title] = $titleObj->getPrefixedText();
707                         } elseif ( is_string( $title ) && $title !== $titleObj->getPrefixedText() ) {
708                                 $this->mNormalizedTitles[$title] = $titleObj->getPrefixedText();
709                         }
710                 }
711
712                 return $linkBatch;
713         }
714
715         protected function getAllowedParams() {
716                 return array(
717                         'titles' => array(
718                                 ApiBase::PARAM_ISMULTI => true
719                         ),
720                         'pageids' => array(
721                                 ApiBase::PARAM_TYPE => 'integer',
722                                 ApiBase::PARAM_ISMULTI => true
723                         ),
724                         'revids' => array(
725                                 ApiBase::PARAM_TYPE => 'integer',
726                                 ApiBase::PARAM_ISMULTI => true
727                         )
728                 );
729         }
730
731         protected function getParamDescription() {
732                 return array(
733                         'titles' => 'A list of titles to work on',
734                         'pageids' => 'A list of page IDs to work on',
735                         'revids' => 'A list of revision IDs to work on'
736                 );
737         }
738
739         public function getPossibleErrors() {
740                 return array_merge( parent::getPossibleErrors(), array(
741                         array( 'code' => 'multisource', 'info' => "Cannot use 'pageids' at the same time as 'dataSource'" ),
742                         array( 'code' => 'multisource', 'info' => "Cannot use 'revids' at the same time as 'dataSource'" ),
743                 ) );
744         }
745
746         public function getVersion() {
747                 return __CLASS__ . ': $Id$';
748         }
749 }