MediaWiki  REL1_22
ApiQueryRevisions.php
Go to the documentation of this file.
00001 <?php
00034 class ApiQueryRevisions extends ApiQueryBase {
00035 
00036     private $diffto, $difftotext, $expandTemplates, $generateXML, $section,
00037         $token, $parseContent, $contentFormat;
00038 
00039     public function __construct( $query, $moduleName ) {
00040         parent::__construct( $query, $moduleName, 'rv' );
00041     }
00042 
00043     private $fld_ids = false, $fld_flags = false, $fld_timestamp = false, $fld_size = false, $fld_sha1 = false,
00044             $fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
00045             $fld_content = false, $fld_tags = false, $fld_contentmodel = false;
00046 
00047     private $tokenFunctions;
00048 
00049     protected function getTokenFunctions() {
00050         // tokenname => function
00051         // function prototype is func($pageid, $title, $rev)
00052         // should return token or false
00053 
00054         // Don't call the hooks twice
00055         if ( isset( $this->tokenFunctions ) ) {
00056             return $this->tokenFunctions;
00057         }
00058 
00059         // If we're in JSON callback mode, no tokens can be obtained
00060         if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
00061             return array();
00062         }
00063 
00064         $this->tokenFunctions = array(
00065             'rollback' => array( 'ApiQueryRevisions', 'getRollbackToken' )
00066         );
00067         wfRunHooks( 'APIQueryRevisionsTokens', array( &$this->tokenFunctions ) );
00068         return $this->tokenFunctions;
00069     }
00070 
00077     public static function getRollbackToken( $pageid, $title, $rev ) {
00078         global $wgUser;
00079         if ( !$wgUser->isAllowed( 'rollback' ) ) {
00080             return false;
00081         }
00082         return $wgUser->getEditToken(
00083             array( $title->getPrefixedText(), $rev->getUserText() ) );
00084     }
00085 
00086     public function execute() {
00087         $params = $this->extractRequestParams( false );
00088 
00089         // If any of those parameters are used, work in 'enumeration' mode.
00090         // Enum mode can only be used when exactly one page is provided.
00091         // Enumerating revisions on multiple pages make it extremely
00092         // difficult to manage continuations and require additional SQL indexes
00093         $enumRevMode = ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ||
00094                 !is_null( $params['limit'] ) || !is_null( $params['startid'] ) ||
00095                 !is_null( $params['endid'] ) || $params['dir'] === 'newer' ||
00096                 !is_null( $params['start'] ) || !is_null( $params['end'] ) );
00097 
00098         $pageSet = $this->getPageSet();
00099         $pageCount = $pageSet->getGoodTitleCount();
00100         $revCount = $pageSet->getRevisionCount();
00101 
00102         // Optimization -- nothing to do
00103         if ( $revCount === 0 && $pageCount === 0 ) {
00104             return;
00105         }
00106 
00107         if ( $revCount > 0 && $enumRevMode ) {
00108             $this->dieUsage( 'The revids= parameter may not be used with the list options (limit, startid, endid, dirNewer, start, end).', 'revids' );
00109         }
00110 
00111         if ( $pageCount > 1 && $enumRevMode ) {
00112             $this->dieUsage( 'titles, pageids or a generator was used to supply multiple pages, but the limit, startid, endid, dirNewer, user, excludeuser, start and end parameters may only be used on a single page.', 'multpages' );
00113         }
00114 
00115         if ( !is_null( $params['difftotext'] ) ) {
00116             $this->difftotext = $params['difftotext'];
00117         } elseif ( !is_null( $params['diffto'] ) ) {
00118             if ( $params['diffto'] == 'cur' ) {
00119                 $params['diffto'] = 0;
00120             }
00121             if ( ( !ctype_digit( $params['diffto'] ) || $params['diffto'] < 0 )
00122                     && $params['diffto'] != 'prev' && $params['diffto'] != 'next' ) {
00123                 $this->dieUsage( 'rvdiffto must be set to a non-negative number, "prev", "next" or "cur"', 'diffto' );
00124             }
00125             // Check whether the revision exists and is readable,
00126             // DifferenceEngine returns a rather ambiguous empty
00127             // string if that's not the case
00128             if ( $params['diffto'] != 0 ) {
00129                 $difftoRev = Revision::newFromID( $params['diffto'] );
00130                 if ( !$difftoRev ) {
00131                     $this->dieUsageMsg( array( 'nosuchrevid', $params['diffto'] ) );
00132                 }
00133                 if ( $difftoRev->isDeleted( Revision::DELETED_TEXT ) ) {
00134                     $this->setWarning( "Couldn't diff to r{$difftoRev->getID()}: content is hidden" );
00135                     $params['diffto'] = null;
00136                 }
00137             }
00138             $this->diffto = $params['diffto'];
00139         }
00140 
00141         $db = $this->getDB();
00142         $this->addTables( 'page' );
00143         $this->addFields( Revision::selectFields() );
00144         $this->addWhere( 'page_id = rev_page' );
00145 
00146         $prop = array_flip( $params['prop'] );
00147 
00148         // Optional fields
00149         $this->fld_ids = isset( $prop['ids'] );
00150         // $this->addFieldsIf('rev_text_id', $this->fld_ids); // should this be exposed?
00151         $this->fld_flags = isset( $prop['flags'] );
00152         $this->fld_timestamp = isset( $prop['timestamp'] );
00153         $this->fld_comment = isset( $prop['comment'] );
00154         $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
00155         $this->fld_size = isset( $prop['size'] );
00156         $this->fld_sha1 = isset( $prop['sha1'] );
00157         $this->fld_contentmodel = isset( $prop['contentmodel'] );
00158         $this->fld_userid = isset( $prop['userid'] );
00159         $this->fld_user = isset( $prop['user'] );
00160         $this->token = $params['token'];
00161 
00162         if ( !empty( $params['contentformat'] ) ) {
00163             $this->contentFormat = $params['contentformat'];
00164         }
00165 
00166         // Possible indexes used
00167         $index = array();
00168 
00169         $userMax = ( $this->fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1 );
00170         $botMax = ( $this->fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2 );
00171         $limit = $params['limit'];
00172         if ( $limit == 'max' ) {
00173             $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
00174             $this->getResult()->setParsedLimit( $this->getModuleName(), $limit );
00175         }
00176 
00177         if ( !is_null( $this->token ) || $pageCount > 0 ) {
00178             $this->addFields( Revision::selectPageFields() );
00179         }
00180 
00181         if ( isset( $prop['tags'] ) ) {
00182             $this->fld_tags = true;
00183             $this->addTables( 'tag_summary' );
00184             $this->addJoinConds( array( 'tag_summary' => array( 'LEFT JOIN', array( 'rev_id=ts_rev_id' ) ) ) );
00185             $this->addFields( 'ts_tags' );
00186         }
00187 
00188         if ( !is_null( $params['tag'] ) ) {
00189             $this->addTables( 'change_tag' );
00190             $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rev_id=ct_rev_id' ) ) ) );
00191             $this->addWhereFld( 'ct_tag', $params['tag'] );
00192             $index['change_tag'] = 'change_tag_tag_id';
00193         }
00194 
00195         if ( isset( $prop['content'] ) || !is_null( $this->difftotext ) ) {
00196             // For each page we will request, the user must have read rights for that page
00197             $user = $this->getUser();
00199             foreach ( $pageSet->getGoodTitles() as $title ) {
00200                 if ( !$title->userCan( 'read', $user ) ) {
00201                     $this->dieUsage(
00202                         'The current user is not allowed to read ' . $title->getPrefixedText(),
00203                         'accessdenied' );
00204                 }
00205             }
00206 
00207             $this->addTables( 'text' );
00208             $this->addWhere( 'rev_text_id=old_id' );
00209             $this->addFields( 'old_id' );
00210             $this->addFields( Revision::selectTextFields() );
00211 
00212             $this->fld_content = isset( $prop['content'] );
00213 
00214             $this->expandTemplates = $params['expandtemplates'];
00215             $this->generateXML = $params['generatexml'];
00216             $this->parseContent = $params['parse'];
00217             if ( $this->parseContent ) {
00218                 // Must manually initialize unset limit
00219                 if ( is_null( $limit ) ) {
00220                     $limit = 1;
00221                 }
00222                 // We are only going to parse 1 revision per request
00223                 $this->validateLimit( 'limit', $limit, 1, 1, 1 );
00224             }
00225             if ( isset( $params['section'] ) ) {
00226                 $this->section = $params['section'];
00227             } else {
00228                 $this->section = false;
00229             }
00230         }
00231 
00232         // add user name, if needed
00233         if ( $this->fld_user ) {
00234             $this->addTables( 'user' );
00235             $this->addJoinConds( array( 'user' => Revision::userJoinCond() ) );
00236             $this->addFields( Revision::selectUserFields() );
00237         }
00238 
00239         // Bug 24166 - API error when using rvprop=tags
00240         $this->addTables( 'revision' );
00241 
00242         if ( $enumRevMode ) {
00243             // This is mostly to prevent parameter errors (and optimize SQL?)
00244             if ( !is_null( $params['startid'] ) && !is_null( $params['start'] ) ) {
00245                 $this->dieUsage( 'start and startid cannot be used together', 'badparams' );
00246             }
00247 
00248             if ( !is_null( $params['endid'] ) && !is_null( $params['end'] ) ) {
00249                 $this->dieUsage( 'end and endid cannot be used together', 'badparams' );
00250             }
00251 
00252             if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
00253                 $this->dieUsage( 'user and excludeuser cannot be used together', 'badparams' );
00254             }
00255 
00256             // Continuing effectively uses startid. But we can't use rvstartid
00257             // directly, because there is no way to tell the client to ''not''
00258             // send rvstart if it sent it in the original query. So instead we
00259             // send the continuation startid as rvcontinue, and ignore both
00260             // rvstart and rvstartid when that is supplied.
00261             if ( !is_null( $params['continue'] ) ) {
00262                 $params['startid'] = $params['continue'];
00263                 $params['start'] = null;
00264             }
00265 
00266             // This code makes an assumption that sorting by rev_id and rev_timestamp produces
00267             // the same result. This way users may request revisions starting at a given time,
00268             // but to page through results use the rev_id returned after each page.
00269             // Switching to rev_id removes the potential problem of having more than
00270             // one row with the same timestamp for the same page.
00271             // The order needs to be the same as start parameter to avoid SQL filesort.
00272             if ( is_null( $params['startid'] ) && is_null( $params['endid'] ) ) {
00273                 $this->addTimestampWhereRange( 'rev_timestamp', $params['dir'],
00274                     $params['start'], $params['end'] );
00275             } else {
00276                 $this->addWhereRange( 'rev_id', $params['dir'],
00277                     $params['startid'], $params['endid'] );
00278                 // One of start and end can be set
00279                 // If neither is set, this does nothing
00280                 $this->addTimestampWhereRange( 'rev_timestamp', $params['dir'],
00281                     $params['start'], $params['end'], false );
00282             }
00283 
00284             // must manually initialize unset limit
00285             if ( is_null( $limit ) ) {
00286                 $limit = 10;
00287             }
00288             $this->validateLimit( 'limit', $limit, 1, $userMax, $botMax );
00289 
00290             // There is only one ID, use it
00291             $ids = array_keys( $pageSet->getGoodTitles() );
00292             $this->addWhereFld( 'rev_page', reset( $ids ) );
00293 
00294             if ( !is_null( $params['user'] ) ) {
00295                 $this->addWhereFld( 'rev_user_text', $params['user'] );
00296             } elseif ( !is_null( $params['excludeuser'] ) ) {
00297                 $this->addWhere( 'rev_user_text != ' .
00298                     $db->addQuotes( $params['excludeuser'] ) );
00299             }
00300             if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
00301                 // Paranoia: avoid brute force searches (bug 17342)
00302                 $this->addWhere( $db->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' );
00303             }
00304         } elseif ( $revCount > 0 ) {
00305             $max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
00306             $revs = $pageSet->getRevisionIDs();
00307             if ( self::truncateArray( $revs, $max ) ) {
00308                 $this->setWarning( "Too many values supplied for parameter 'revids': the limit is $max" );
00309             }
00310 
00311             // Get all revision IDs
00312             $this->addWhereFld( 'rev_id', array_keys( $revs ) );
00313 
00314             if ( !is_null( $params['continue'] ) ) {
00315                 $this->addWhere( 'rev_id >= ' . intval( $params['continue'] ) );
00316             }
00317             $this->addOption( 'ORDER BY', 'rev_id' );
00318 
00319             // assumption testing -- we should never get more then $revCount rows.
00320             $limit = $revCount;
00321         } elseif ( $pageCount > 0 ) {
00322             $max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
00323             $titles = $pageSet->getGoodTitles();
00324             if ( self::truncateArray( $titles, $max ) ) {
00325                 $this->setWarning( "Too many values supplied for parameter 'titles': the limit is $max" );
00326             }
00327 
00328             // When working in multi-page non-enumeration mode,
00329             // limit to the latest revision only
00330             $this->addWhere( 'page_id=rev_page' );
00331             $this->addWhere( 'page_latest=rev_id' );
00332 
00333             // Get all page IDs
00334             $this->addWhereFld( 'page_id', array_keys( $titles ) );
00335             // Every time someone relies on equality propagation, god kills a kitten :)
00336             $this->addWhereFld( 'rev_page', array_keys( $titles ) );
00337 
00338             if ( !is_null( $params['continue'] ) ) {
00339                 $cont = explode( '|', $params['continue'] );
00340                 $this->dieContinueUsageIf( count( $cont ) != 2 );
00341                 $pageid = intval( $cont[0] );
00342                 $revid = intval( $cont[1] );
00343                 $this->addWhere(
00344                     "rev_page > $pageid OR " .
00345                     "(rev_page = $pageid AND " .
00346                     "rev_id >= $revid)"
00347                 );
00348             }
00349             $this->addOption( 'ORDER BY', array(
00350                 'rev_page',
00351                 'rev_id'
00352             ));
00353 
00354             // assumption testing -- we should never get more then $pageCount rows.
00355             $limit = $pageCount;
00356         } else {
00357             ApiBase::dieDebug( __METHOD__, 'param validation?' );
00358         }
00359 
00360         $this->addOption( 'LIMIT', $limit + 1 );
00361         $this->addOption( 'USE INDEX', $index );
00362 
00363         $count = 0;
00364         $res = $this->select( __METHOD__ );
00365 
00366         foreach ( $res as $row ) {
00367             if ( ++ $count > $limit ) {
00368                 // We've reached the one extra which shows that there are additional pages to be had. Stop here...
00369                 if ( !$enumRevMode ) {
00370                     ApiBase::dieDebug( __METHOD__, 'Got more rows then expected' ); // bug report
00371                 }
00372                 $this->setContinueEnumParameter( 'continue', intval( $row->rev_id ) );
00373                 break;
00374             }
00375 
00376             $fit = $this->addPageSubItem( $row->rev_page, $this->extractRowInfo( $row ), 'rev' );
00377             if ( !$fit ) {
00378                 if ( $enumRevMode ) {
00379                     $this->setContinueEnumParameter( 'continue', intval( $row->rev_id ) );
00380                 } elseif ( $revCount > 0 ) {
00381                     $this->setContinueEnumParameter( 'continue', intval( $row->rev_id ) );
00382                 } else {
00383                     $this->setContinueEnumParameter( 'continue', intval( $row->rev_page ) .
00384                         '|' . intval( $row->rev_id ) );
00385                 }
00386                 break;
00387             }
00388         }
00389     }
00390 
00391     private function extractRowInfo( $row ) {
00392         $revision = new Revision( $row );
00393         $title = $revision->getTitle();
00394         $vals = array();
00395 
00396         if ( $this->fld_ids ) {
00397             $vals['revid'] = intval( $revision->getId() );
00398             // $vals['oldid'] = intval( $row->rev_text_id ); // todo: should this be exposed?
00399             if ( !is_null( $revision->getParentId() ) ) {
00400                 $vals['parentid'] = intval( $revision->getParentId() );
00401             }
00402         }
00403 
00404         if ( $this->fld_flags && $revision->isMinor() ) {
00405             $vals['minor'] = '';
00406         }
00407 
00408         if ( $this->fld_user || $this->fld_userid ) {
00409             if ( $revision->isDeleted( Revision::DELETED_USER ) ) {
00410                 $vals['userhidden'] = '';
00411             } else {
00412                 if ( $this->fld_user ) {
00413                     $vals['user'] = $revision->getUserText();
00414                 }
00415                 $userid = $revision->getUser();
00416                 if ( !$userid ) {
00417                     $vals['anon'] = '';
00418                 }
00419 
00420                 if ( $this->fld_userid ) {
00421                     $vals['userid'] = $userid;
00422                 }
00423             }
00424         }
00425 
00426         if ( $this->fld_timestamp ) {
00427             $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $revision->getTimestamp() );
00428         }
00429 
00430         if ( $this->fld_size ) {
00431             if ( !is_null( $revision->getSize() ) ) {
00432                 $vals['size'] = intval( $revision->getSize() );
00433             } else {
00434                 $vals['size'] = 0;
00435             }
00436         }
00437 
00438         if ( $this->fld_sha1 && !$revision->isDeleted( Revision::DELETED_TEXT ) ) {
00439             if ( $revision->getSha1() != '' ) {
00440                 $vals['sha1'] = wfBaseConvert( $revision->getSha1(), 36, 16, 40 );
00441             } else {
00442                 $vals['sha1'] = '';
00443             }
00444         } elseif ( $this->fld_sha1 ) {
00445             $vals['sha1hidden'] = '';
00446         }
00447 
00448         if ( $this->fld_contentmodel ) {
00449             $vals['contentmodel'] = $revision->getContentModel();
00450         }
00451 
00452         if ( $this->fld_comment || $this->fld_parsedcomment ) {
00453             if ( $revision->isDeleted( Revision::DELETED_COMMENT ) ) {
00454                 $vals['commenthidden'] = '';
00455             } else {
00456                 $comment = $revision->getComment();
00457 
00458                 if ( $this->fld_comment ) {
00459                     $vals['comment'] = $comment;
00460                 }
00461 
00462                 if ( $this->fld_parsedcomment ) {
00463                     $vals['parsedcomment'] = Linker::formatComment( $comment, $title );
00464                 }
00465             }
00466         }
00467 
00468         if ( $this->fld_tags ) {
00469             if ( $row->ts_tags ) {
00470                 $tags = explode( ',', $row->ts_tags );
00471                 $this->getResult()->setIndexedTagName( $tags, 'tag' );
00472                 $vals['tags'] = $tags;
00473             } else {
00474                 $vals['tags'] = array();
00475             }
00476         }
00477 
00478         if ( !is_null( $this->token ) ) {
00479             $tokenFunctions = $this->getTokenFunctions();
00480             foreach ( $this->token as $t ) {
00481                 $val = call_user_func( $tokenFunctions[$t], $title->getArticleID(), $title, $revision );
00482                 if ( $val === false ) {
00483                     $this->setWarning( "Action '$t' is not allowed for the current user" );
00484                 } else {
00485                     $vals[$t . 'token'] = $val;
00486                 }
00487             }
00488         }
00489 
00490         $content = null;
00491         global $wgParser;
00492         if ( $this->fld_content || !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
00493             $content = $revision->getContent();
00494             // Expand templates after getting section content because
00495             // template-added sections don't count and Parser::preprocess()
00496             // will have less input
00497             if ( $content && $this->section !== false ) {
00498                 $content = $content->getSection( $this->section, false );
00499                 if ( !$content ) {
00500                     $this->dieUsage( "There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection' );
00501                 }
00502             }
00503         }
00504         if ( $this->fld_content && $content && !$revision->isDeleted( Revision::DELETED_TEXT ) ) {
00505             $text = null;
00506 
00507             if ( $this->generateXML ) {
00508                 if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
00509                     $t = $content->getNativeData(); # note: don't set $text
00510 
00511                     $wgParser->startExternalParse( $title, ParserOptions::newFromContext( $this->getContext() ), OT_PREPROCESS );
00512                     $dom = $wgParser->preprocessToDom( $t );
00513                     if ( is_callable( array( $dom, 'saveXML' ) ) ) {
00514                         $xml = $dom->saveXML();
00515                     } else {
00516                         $xml = $dom->__toString();
00517                     }
00518                     $vals['parsetree'] = $xml;
00519                 } else {
00520                     $this->setWarning( "Conversion to XML is supported for wikitext only, " .
00521                                         $title->getPrefixedDBkey() .
00522                                         " uses content model " . $content->getModel() );
00523                 }
00524             }
00525 
00526             if ( $this->expandTemplates && !$this->parseContent ) {
00527                 #XXX: implement template expansion for all content types in ContentHandler?
00528                 if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
00529                     $text = $content->getNativeData();
00530 
00531                     $text = $wgParser->preprocess( $text, $title, ParserOptions::newFromContext( $this->getContext() ) );
00532                 } else {
00533                     $this->setWarning( "Template expansion is supported for wikitext only, " .
00534                         $title->getPrefixedDBkey() .
00535                         " uses content model " . $content->getModel() );
00536 
00537                     $text = false;
00538                 }
00539             }
00540             if ( $this->parseContent ) {
00541                 $po = $content->getParserOutput( $title, $revision->getId(), ParserOptions::newFromContext( $this->getContext() ) );
00542                 $text = $po->getText();
00543             }
00544 
00545             if ( $text === null ) {
00546                 $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat();
00547                 $model = $content->getModel();
00548 
00549                 if ( !$content->isSupportedFormat( $format ) ) {
00550                     $name = $title->getPrefixedDBkey();
00551 
00552                     $this->dieUsage( "The requested format {$this->contentFormat} is not supported " .
00553                                     "for content model $model used by $name", 'badformat' );
00554                 }
00555 
00556                 $text = $content->serialize( $format );
00557 
00558                 // always include format and model.
00559                 // Format is needed to deserialize, model is needed to interpret.
00560                 $vals['contentformat'] = $format;
00561                 $vals['contentmodel'] = $model;
00562             }
00563 
00564             if ( $text !== false ) {
00565                 ApiResult::setContent( $vals, $text );
00566             }
00567         } elseif ( $this->fld_content ) {
00568             if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
00569                 $vals['texthidden'] = '';
00570             } else {
00571                 $vals['textmissing'] = '';
00572             }
00573         }
00574 
00575         if ( !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
00576             global $wgAPIMaxUncachedDiffs;
00577             static $n = 0; // Number of uncached diffs we've had
00578 
00579             if ( is_null( $content ) ) {
00580                 $vals['textmissing'] = '';
00581             } elseif ( $n < $wgAPIMaxUncachedDiffs ) {
00582                 $vals['diff'] = array();
00583                 $context = new DerivativeContext( $this->getContext() );
00584                 $context->setTitle( $title );
00585                 $handler = $revision->getContentHandler();
00586 
00587                 if ( !is_null( $this->difftotext ) ) {
00588                     $model = $title->getContentModel();
00589 
00590                     if ( $this->contentFormat
00591                         && !ContentHandler::getForModelID( $model )->isSupportedFormat( $this->contentFormat ) ) {
00592 
00593                         $name = $title->getPrefixedDBkey();
00594 
00595                         $this->dieUsage( "The requested format {$this->contentFormat} is not supported for " .
00596                                             "content model $model used by $name", 'badformat' );
00597                     }
00598 
00599                     $difftocontent = ContentHandler::makeContent( $this->difftotext, $title, $model, $this->contentFormat );
00600 
00601                     $engine = $handler->createDifferenceEngine( $context );
00602                     $engine->setContent( $content, $difftocontent );
00603                 } else {
00604                     $engine = $handler->createDifferenceEngine( $context, $revision->getID(), $this->diffto );
00605                     $vals['diff']['from'] = $engine->getOldid();
00606                     $vals['diff']['to'] = $engine->getNewid();
00607                 }
00608                 $difftext = $engine->getDiffBody();
00609                 ApiResult::setContent( $vals['diff'], $difftext );
00610                 if ( !$engine->wasCacheHit() ) {
00611                     $n++;
00612                 }
00613             } else {
00614                 $vals['diff']['notcached'] = '';
00615             }
00616         }
00617         return $vals;
00618     }
00619 
00620     public function getCacheMode( $params ) {
00621         if ( isset( $params['token'] ) ) {
00622             return 'private';
00623         }
00624         if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
00625             // formatComment() calls wfMessage() among other things
00626             return 'anon-public-user-private';
00627         }
00628         return 'public';
00629     }
00630 
00631     public function getAllowedParams() {
00632         return array(
00633             'prop' => array(
00634                 ApiBase::PARAM_ISMULTI => true,
00635                 ApiBase::PARAM_DFLT => 'ids|timestamp|flags|comment|user',
00636                 ApiBase::PARAM_TYPE => array(
00637                     'ids',
00638                     'flags',
00639                     'timestamp',
00640                     'user',
00641                     'userid',
00642                     'size',
00643                     'sha1',
00644                     'contentmodel',
00645                     'comment',
00646                     'parsedcomment',
00647                     'content',
00648                     'tags'
00649                 )
00650             ),
00651             'limit' => array(
00652                 ApiBase::PARAM_TYPE => 'limit',
00653                 ApiBase::PARAM_MIN => 1,
00654                 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
00655                 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
00656             ),
00657             'startid' => array(
00658                 ApiBase::PARAM_TYPE => 'integer'
00659             ),
00660             'endid' => array(
00661                 ApiBase::PARAM_TYPE => 'integer'
00662             ),
00663             'start' => array(
00664                 ApiBase::PARAM_TYPE => 'timestamp'
00665             ),
00666             'end' => array(
00667                 ApiBase::PARAM_TYPE => 'timestamp'
00668             ),
00669             'dir' => array(
00670                 ApiBase::PARAM_DFLT => 'older',
00671                 ApiBase::PARAM_TYPE => array(
00672                     'newer',
00673                     'older'
00674                 )
00675             ),
00676             'user' => array(
00677                 ApiBase::PARAM_TYPE => 'user'
00678             ),
00679             'excludeuser' => array(
00680                 ApiBase::PARAM_TYPE => 'user'
00681             ),
00682             'tag' => null,
00683             'expandtemplates' => false,
00684             'generatexml' => false,
00685             'parse' => false,
00686             'section' => null,
00687             'token' => array(
00688                 ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
00689                 ApiBase::PARAM_ISMULTI => true
00690             ),
00691             'continue' => null,
00692             'diffto' => null,
00693             'difftotext' => null,
00694             'contentformat' => array(
00695                 ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
00696                 ApiBase::PARAM_DFLT => null
00697             ),
00698         );
00699     }
00700 
00701     public function getParamDescription() {
00702         $p = $this->getModulePrefix();
00703         return array(
00704             'prop' => array(
00705                 'Which properties to get for each revision:',
00706                 ' ids            - The ID of the revision',
00707                 ' flags          - Revision flags (minor)',
00708                 ' timestamp      - The timestamp of the revision',
00709                 ' user           - User that made the revision',
00710                 ' userid         - User id of revision creator',
00711                 ' size           - Length (bytes) of the revision',
00712                 ' sha1           - SHA-1 (base 16) of the revision',
00713                 ' contentmodel   - Content model id',
00714                 ' comment        - Comment by the user for revision',
00715                 ' parsedcomment  - Parsed comment by the user for the revision',
00716                 ' content        - Text of the revision',
00717                 ' tags           - Tags for the revision',
00718             ),
00719             'limit' => 'Limit how many revisions will be returned (enum)',
00720             'startid' => 'From which revision id to start enumeration (enum)',
00721             'endid' => 'Stop revision enumeration on this revid (enum)',
00722             'start' => 'From which revision timestamp to start enumeration (enum)',
00723             'end' => 'Enumerate up to this timestamp (enum)',
00724             'dir' => $this->getDirectionDescription( $p, ' (enum)' ),
00725             'user' => 'Only include revisions made by user (enum)',
00726             'excludeuser' => 'Exclude revisions made by user (enum)',
00727             'expandtemplates' => "Expand templates in revision content (requires {$p}prop=content)",
00728             'generatexml' => "Generate XML parse tree for revision content (requires {$p}prop=content)",
00729             'parse' => array( "Parse revision content (requires {$p}prop=content).",
00730                 'For performance reasons if this option is used, rvlimit is enforced to 1.' ),
00731             'section' => 'Only retrieve the content of this section number',
00732             'token' => 'Which tokens to obtain for each revision',
00733             'continue' => 'When more results are available, use this to continue',
00734             'diffto' => array( 'Revision ID to diff each revision to.',
00735                 'Use "prev", "next" and "cur" for the previous, next and current revision respectively' ),
00736             'difftotext' => array( 'Text to diff each revision to. Only diffs a limited number of revisions.',
00737                 "Overrides {$p}diffto. If {$p}section is set, only that section will be diffed against this text" ),
00738             'tag' => 'Only list revisions tagged with this tag',
00739             'contentformat' => 'Serialization format used for difftotext and expected for output of content',
00740         );
00741     }
00742 
00743     public function getResultProperties() {
00744         $props = array(
00745             '' => array(),
00746             'ids' => array(
00747                 'revid' => 'integer',
00748                 'parentid' => array(
00749                     ApiBase::PROP_TYPE => 'integer',
00750                     ApiBase::PROP_NULLABLE => true
00751                 )
00752             ),
00753             'flags' => array(
00754                 'minor' => 'boolean'
00755             ),
00756             'user' => array(
00757                 'userhidden' => 'boolean',
00758                 'user' => 'string',
00759                 'anon' => 'boolean'
00760             ),
00761             'userid' => array(
00762                 'userhidden' => 'boolean',
00763                 'userid' => 'integer',
00764                 'anon' => 'boolean'
00765             ),
00766             'timestamp' => array(
00767                 'timestamp' => 'timestamp'
00768             ),
00769             'size' => array(
00770                 'size' => 'integer'
00771             ),
00772             'sha1' => array(
00773                 'sha1' => 'string'
00774             ),
00775             'comment' => array(
00776                 'commenthidden' => 'boolean',
00777                 'comment' => array(
00778                     ApiBase::PROP_TYPE => 'string',
00779                     ApiBase::PROP_NULLABLE => true
00780                 )
00781             ),
00782             'parsedcomment' => array(
00783                 'commenthidden' => 'boolean',
00784                 'parsedcomment' => array(
00785                     ApiBase::PROP_TYPE => 'string',
00786                     ApiBase::PROP_NULLABLE => true
00787                 )
00788             ),
00789             'content' => array(
00790                 '*' => array(
00791                     ApiBase::PROP_TYPE => 'string',
00792                     ApiBase::PROP_NULLABLE => true
00793                 ),
00794                 'texthidden' => 'boolean',
00795                 'textmissing' => 'boolean',
00796             ),
00797             'contentmodel' => array(
00798                 'contentmodel' => 'string'
00799             ),
00800         );
00801 
00802         self::addTokenProperties( $props, $this->getTokenFunctions() );
00803 
00804         return $props;
00805     }
00806 
00807     public function getDescription() {
00808         return array(
00809             'Get revision information',
00810             'May be used in several ways:',
00811             ' 1) Get data about a set of pages (last revision), by setting titles or pageids parameter',
00812             ' 2) Get revisions for one given page, by using titles/pageids with start/end/limit params',
00813             ' 3) Get data about a set of revisions by setting their IDs with revids parameter',
00814             'All parameters marked as (enum) may only be used with a single page (#2)'
00815         );
00816     }
00817 
00818     public function getPossibleErrors() {
00819         return array_merge( parent::getPossibleErrors(), array(
00820             array( 'nosuchrevid', 'diffto' ),
00821             array( 'code' => 'revids', 'info' => 'The revids= parameter may not be used with the list options '
00822                     . '(limit, startid, endid, dirNewer, start, end).' ),
00823             array( 'code' => 'multpages', 'info' => 'titles, pageids or a generator was used to supply multiple pages, '
00824                     . ' but the limit, startid, endid, dirNewer, user, excludeuser, '
00825                     . 'start and end parameters may only be used on a single page.' ),
00826             array( 'code' => 'diffto', 'info' => 'rvdiffto must be set to a non-negative number, "prev", "next" or "cur"' ),
00827             array( 'code' => 'badparams', 'info' => 'start and startid cannot be used together' ),
00828             array( 'code' => 'badparams', 'info' => 'end and endid cannot be used together' ),
00829             array( 'code' => 'badparams', 'info' => 'user and excludeuser cannot be used together' ),
00830             array( 'code' => 'nosuchsection', 'info' => 'There is no section section in rID' ),
00831             array( 'code' => 'badformat', 'info' => 'The requested serialization format can not be applied '
00832                                                     . ' to the page\'s content model' ),
00833         ) );
00834     }
00835 
00836     public function getExamples() {
00837         return array(
00838             'Get data with content for the last revision of titles "API" and "Main Page"',
00839             '  api.php?action=query&prop=revisions&titles=API|Main%20Page&rvprop=timestamp|user|comment|content',
00840             'Get last 5 revisions of the "Main Page"',
00841             '  api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment',
00842             'Get first 5 revisions of the "Main Page"',
00843             '  api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvdir=newer',
00844             'Get first 5 revisions of the "Main Page" made after 2006-05-01',
00845             '  api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvdir=newer&rvstart=20060501000000',
00846             'Get first 5 revisions of the "Main Page" that were not made made by anonymous user "127.0.0.1"',
00847             '  api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvexcludeuser=127.0.0.1',
00848             'Get first 5 revisions of the "Main Page" that were made by the user "MediaWiki default"',
00849             '  api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvuser=MediaWiki%20default',
00850         );
00851     }
00852 
00853     public function getHelpUrls() {
00854         return 'https://www.mediawiki.org/wiki/API:Properties#revisions_.2F_rv';
00855     }
00856 }