123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491 |
- <?php
- /**
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
- class ApiComparePages extends ApiBase {
- private $guessed = false, $guessedTitle, $guessedModel, $props;
- public function execute() {
- $params = $this->extractRequestParams();
- // Parameter validation
- $this->requireAtLeastOneParameter( $params, 'fromtitle', 'fromid', 'fromrev', 'fromtext' );
- $this->requireAtLeastOneParameter( $params, 'totitle', 'toid', 'torev', 'totext', 'torelative' );
- $this->props = array_flip( $params['prop'] );
- // Cache responses publicly by default. This may be overridden later.
- $this->getMain()->setCacheMode( 'public' );
- // Get the 'from' Revision and Content
- list( $fromRev, $fromContent, $relRev ) = $this->getDiffContent( 'from', $params );
- // Get the 'to' Revision and Content
- if ( $params['torelative'] !== null ) {
- if ( !$relRev ) {
- $this->dieWithError( 'apierror-compare-relative-to-nothing' );
- }
- switch ( $params['torelative'] ) {
- case 'prev':
- // Swap 'from' and 'to'
- $toRev = $fromRev;
- $toContent = $fromContent;
- $fromRev = $relRev->getPrevious();
- $fromContent = $fromRev
- ? $fromRev->getContent( Revision::FOR_THIS_USER, $this->getUser() )
- : $toContent->getContentHandler()->makeEmptyContent();
- if ( !$fromContent ) {
- $this->dieWithError(
- [ 'apierror-missingcontent-revid', $fromRev->getId() ], 'missingcontent'
- );
- }
- break;
- case 'next':
- $toRev = $relRev->getNext();
- $toContent = $toRev
- ? $toRev->getContent( Revision::FOR_THIS_USER, $this->getUser() )
- : $fromContent;
- if ( !$toContent ) {
- $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' );
- }
- break;
- case 'cur':
- $title = $relRev->getTitle();
- $id = $title->getLatestRevID();
- $toRev = $id ? Revision::newFromId( $id ) : null;
- if ( !$toRev ) {
- $this->dieWithError(
- [ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ], 'nosuchrevid'
- );
- }
- $toContent = $toRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
- if ( !$toContent ) {
- $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' );
- }
- break;
- }
- $relRev2 = null;
- } else {
- list( $toRev, $toContent, $relRev2 ) = $this->getDiffContent( 'to', $params );
- }
- // Should never happen, but just in case...
- if ( !$fromContent || !$toContent ) {
- $this->dieWithError( 'apierror-baddiff' );
- }
- // Get the diff
- $context = new DerivativeContext( $this->getContext() );
- if ( $relRev && $relRev->getTitle() ) {
- $context->setTitle( $relRev->getTitle() );
- } elseif ( $relRev2 && $relRev2->getTitle() ) {
- $context->setTitle( $relRev2->getTitle() );
- } else {
- $this->guessTitleAndModel();
- if ( $this->guessedTitle ) {
- $context->setTitle( $this->guessedTitle );
- }
- }
- $de = $fromContent->getContentHandler()->createDifferenceEngine(
- $context,
- $fromRev ? $fromRev->getId() : 0,
- $toRev ? $toRev->getId() : 0,
- /* $rcid = */ null,
- /* $refreshCache = */ false,
- /* $unhide = */ true
- );
- $de->setContent( $fromContent, $toContent );
- $difftext = $de->getDiffBody();
- if ( $difftext === false ) {
- $this->dieWithError( 'apierror-baddiff' );
- }
- // Fill in the response
- $vals = [];
- $this->setVals( $vals, 'from', $fromRev );
- $this->setVals( $vals, 'to', $toRev );
- if ( isset( $this->props['rel'] ) ) {
- if ( $fromRev ) {
- $rev = $fromRev->getPrevious();
- if ( $rev ) {
- $vals['prev'] = $rev->getId();
- }
- }
- if ( $toRev ) {
- $rev = $toRev->getNext();
- if ( $rev ) {
- $vals['next'] = $rev->getId();
- }
- }
- }
- if ( isset( $this->props['diffsize'] ) ) {
- $vals['diffsize'] = strlen( $difftext );
- }
- if ( isset( $this->props['diff'] ) ) {
- ApiResult::setContentValue( $vals, 'body', $difftext );
- }
- $this->getResult()->addValue( null, $this->getModuleName(), $vals );
- }
- /**
- * Guess an appropriate default Title and content model for this request
- *
- * Fills in $this->guessedTitle based on the first of 'fromrev',
- * 'fromtitle', 'fromid', 'torev', 'totitle', and 'toid' that's present and
- * valid.
- *
- * Fills in $this->guessedModel based on the Revision or Title used to
- * determine $this->guessedTitle, or the 'fromcontentmodel' or
- * 'tocontentmodel' parameters if no title was guessed.
- */
- private function guessTitleAndModel() {
- if ( $this->guessed ) {
- return;
- }
- $this->guessed = true;
- $params = $this->extractRequestParams();
- foreach ( [ 'from', 'to' ] as $prefix ) {
- if ( $params["{$prefix}rev"] !== null ) {
- $revId = $params["{$prefix}rev"];
- $rev = Revision::newFromId( $revId );
- if ( !$rev ) {
- // Titles of deleted revisions aren't secret, per T51088
- $row = $this->getDB()->selectRow(
- 'archive',
- array_merge(
- Revision::selectArchiveFields(),
- [ 'ar_namespace', 'ar_title' ]
- ),
- [ 'ar_rev_id' => $revId ],
- __METHOD__
- );
- if ( $row ) {
- $rev = Revision::newFromArchiveRow( $row );
- }
- }
- if ( $rev ) {
- $this->guessedTitle = $rev->getTitle();
- $this->guessedModel = $rev->getContentModel();
- break;
- }
- }
- if ( $params["{$prefix}title"] !== null ) {
- $title = Title::newFromText( $params["{$prefix}title"] );
- if ( $title && !$title->isExternal() ) {
- $this->guessedTitle = $title;
- break;
- }
- }
- if ( $params["{$prefix}id"] !== null ) {
- $title = Title::newFromID( $params["{$prefix}id"] );
- if ( $title ) {
- $this->guessedTitle = $title;
- break;
- }
- }
- }
- if ( !$this->guessedModel ) {
- if ( $this->guessedTitle ) {
- $this->guessedModel = $this->guessedTitle->getContentModel();
- } elseif ( $params['fromcontentmodel'] !== null ) {
- $this->guessedModel = $params['fromcontentmodel'];
- } elseif ( $params['tocontentmodel'] !== null ) {
- $this->guessedModel = $params['tocontentmodel'];
- }
- }
- }
- /**
- * Get the Revision and Content for one side of the diff
- *
- * This uses the appropriate set of 'rev', 'id', 'title', 'text', 'pst',
- * 'contentmodel', and 'contentformat' parameters to determine what content
- * should be diffed.
- *
- * Returns three values:
- * - The revision used to retrieve the content, if any
- * - The content to be diffed
- * - The revision specified, if any, even if not used to retrieve the
- * Content
- *
- * @param string $prefix 'from' or 'to'
- * @param array $params
- * @return array [ Revision|null, Content, Revision|null ]
- */
- private function getDiffContent( $prefix, array $params ) {
- $title = null;
- $rev = null;
- $suppliedContent = $params["{$prefix}text"] !== null;
- // Get the revision and title, if applicable
- $revId = null;
- if ( $params["{$prefix}rev"] !== null ) {
- $revId = $params["{$prefix}rev"];
- } elseif ( $params["{$prefix}title"] !== null || $params["{$prefix}id"] !== null ) {
- if ( $params["{$prefix}title"] !== null ) {
- $title = Title::newFromText( $params["{$prefix}title"] );
- if ( !$title || $title->isExternal() ) {
- $this->dieWithError(
- [ 'apierror-invalidtitle', wfEscapeWikiText( $params["{$prefix}title"] ) ]
- );
- }
- } else {
- $title = Title::newFromID( $params["{$prefix}id"] );
- if ( !$title ) {
- $this->dieWithError( [ 'apierror-nosuchpageid', $params["{$prefix}id"] ] );
- }
- }
- $revId = $title->getLatestRevID();
- if ( !$revId ) {
- $revId = null;
- // Only die here if we're not using supplied text
- if ( !$suppliedContent ) {
- if ( $title->exists() ) {
- $this->dieWithError(
- [ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ], 'nosuchrevid'
- );
- } else {
- $this->dieWithError(
- [ 'apierror-missingtitle-byname', wfEscapeWikiText( $title->getPrefixedText() ) ],
- 'missingtitle'
- );
- }
- }
- }
- }
- if ( $revId !== null ) {
- $rev = Revision::newFromId( $revId );
- if ( !$rev && $this->getUser()->isAllowedAny( 'deletedtext', 'undelete' ) ) {
- // Try the 'archive' table
- $row = $this->getDB()->selectRow(
- 'archive',
- array_merge(
- Revision::selectArchiveFields(),
- [ 'ar_namespace', 'ar_title' ]
- ),
- [ 'ar_rev_id' => $revId ],
- __METHOD__
- );
- if ( $row ) {
- $rev = Revision::newFromArchiveRow( $row );
- $rev->isArchive = true;
- }
- }
- if ( !$rev ) {
- $this->dieWithError( [ 'apierror-nosuchrevid', $revId ] );
- }
- $title = $rev->getTitle();
- // If we don't have supplied content, return here. Otherwise,
- // continue on below with the supplied content.
- if ( !$suppliedContent ) {
- $content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
- if ( !$content ) {
- $this->dieWithError( [ 'apierror-missingcontent-revid', $revId ], 'missingcontent' );
- }
- return [ $rev, $content, $rev ];
- }
- }
- // Override $content based on supplied text
- $model = $params["{$prefix}contentmodel"];
- $format = $params["{$prefix}contentformat"];
- if ( !$model && $rev ) {
- $model = $rev->getContentModel();
- }
- if ( !$model && $title ) {
- $model = $title->getContentModel();
- }
- if ( !$model ) {
- $this->guessTitleAndModel();
- $model = $this->guessedModel;
- }
- if ( !$model ) {
- $model = CONTENT_MODEL_WIKITEXT;
- $this->addWarning( [ 'apiwarn-compare-nocontentmodel', $model ] );
- }
- if ( !$title ) {
- $this->guessTitleAndModel();
- $title = $this->guessedTitle;
- }
- try {
- $content = ContentHandler::makeContent( $params["{$prefix}text"], $title, $model, $format );
- } catch ( MWContentSerializationException $ex ) {
- $this->dieWithException( $ex, [
- 'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
- ] );
- }
- if ( $params["{$prefix}pst"] ) {
- if ( !$title ) {
- $this->dieWithError( 'apierror-compare-no-title' );
- }
- $popts = ParserOptions::newFromContext( $this->getContext() );
- $content = $content->preSaveTransform( $title, $this->getUser(), $popts );
- }
- return [ null, $content, $rev ];
- }
- /**
- * Set value fields from a Revision object
- * @param array &$vals Result array to set data into
- * @param string $prefix 'from' or 'to'
- * @param Revision|null $rev
- */
- private function setVals( &$vals, $prefix, $rev ) {
- if ( $rev ) {
- $title = $rev->getTitle();
- if ( isset( $this->props['ids'] ) ) {
- $vals["{$prefix}id"] = $title->getArticleId();
- $vals["{$prefix}revid"] = $rev->getId();
- }
- if ( isset( $this->props['title'] ) ) {
- ApiQueryBase::addTitleInfo( $vals, $title, $prefix );
- }
- if ( isset( $this->props['size'] ) ) {
- $vals["{$prefix}size"] = $rev->getSize();
- }
- $anyHidden = false;
- if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
- $vals["{$prefix}texthidden"] = true;
- $anyHidden = true;
- }
- if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
- $vals["{$prefix}userhidden"] = true;
- $anyHidden = true;
- }
- if ( isset( $this->props['user'] ) &&
- $rev->userCan( Revision::DELETED_USER, $this->getUser() )
- ) {
- $vals["{$prefix}user"] = $rev->getUserText( Revision::RAW );
- $vals["{$prefix}userid"] = $rev->getUser( Revision::RAW );
- }
- if ( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
- $vals["{$prefix}commenthidden"] = true;
- $anyHidden = true;
- }
- if ( $rev->userCan( Revision::DELETED_COMMENT, $this->getUser() ) ) {
- if ( isset( $this->props['comment'] ) ) {
- $vals["{$prefix}comment"] = $rev->getComment( Revision::RAW );
- }
- if ( isset( $this->props['parsedcomment'] ) ) {
- $vals["{$prefix}parsedcomment"] = Linker::formatComment(
- $rev->getComment( Revision::RAW ),
- $rev->getTitle()
- );
- }
- }
- if ( $anyHidden ) {
- $this->getMain()->setCacheMode( 'private' );
- if ( $rev->isDeleted( Revision::DELETED_RESTRICTED ) ) {
- $vals["{$prefix}suppressed"] = true;
- }
- }
- if ( !empty( $rev->isArchive ) ) {
- $this->getMain()->setCacheMode( 'private' );
- $vals["{$prefix}archive"] = true;
- }
- }
- }
- public function getAllowedParams() {
- // Parameters for the 'from' and 'to' content
- $fromToParams = [
- 'title' => null,
- 'id' => [
- ApiBase::PARAM_TYPE => 'integer'
- ],
- 'rev' => [
- ApiBase::PARAM_TYPE => 'integer'
- ],
- 'text' => [
- ApiBase::PARAM_TYPE => 'text'
- ],
- 'pst' => false,
- 'contentformat' => [
- ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
- ],
- 'contentmodel' => [
- ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
- ]
- ];
- $ret = [];
- foreach ( $fromToParams as $k => $v ) {
- $ret["from$k"] = $v;
- }
- foreach ( $fromToParams as $k => $v ) {
- $ret["to$k"] = $v;
- }
- $ret = wfArrayInsertAfter(
- $ret,
- [ 'torelative' => [ ApiBase::PARAM_TYPE => [ 'prev', 'next', 'cur' ], ] ],
- 'torev'
- );
- $ret['prop'] = [
- ApiBase::PARAM_DFLT => 'diff|ids|title',
- ApiBase::PARAM_TYPE => [
- 'diff',
- 'diffsize',
- 'rel',
- 'ids',
- 'title',
- 'user',
- 'comment',
- 'parsedcomment',
- 'size',
- ],
- ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
- ];
- return $ret;
- }
- protected function getExamplesMessages() {
- return [
- 'action=compare&fromrev=1&torev=2'
- => 'apihelp-compare-example-1',
- ];
- }
- }
|