X-Git-Url: https://scripts.mit.edu/gitweb/autoinstallsdev/mediawiki.git/blobdiff_plain/19e297c21b10b1b8a3acad5e73fc71dcb35db44a..6932310fd58ebef145fa01eb76edf7150284d8ea:/includes/specials/SpecialMergeHistory.php diff --git a/includes/specials/SpecialMergeHistory.php b/includes/specials/SpecialMergeHistory.php index 43b4ef6a..f122db8a 100644 --- a/includes/specials/SpecialMergeHistory.php +++ b/includes/specials/SpecialMergeHistory.php @@ -28,15 +28,55 @@ * @ingroup SpecialPage */ class SpecialMergeHistory extends SpecialPage { - var $mAction, $mTarget, $mDest, $mTimestamp, $mTargetID, $mDestID, $mComment; - var $mTargetObj, $mDestObj; + /** @var string */ + protected $mAction; + + /** @var string */ + protected $mTarget; + + /** @var string */ + protected $mDest; + + /** @var string */ + protected $mTimestamp; + + /** @var int */ + protected $mTargetID; + + /** @var int */ + protected $mDestID; + + /** @var string */ + protected $mComment; + + /** @var bool Was posted? */ + protected $mMerge; + + /** @var bool Was submitted? */ + protected $mSubmitted; + + /** @var Title */ + protected $mTargetObj; + + /** @var Title */ + protected $mDestObj; + + /** @var int[] */ + public $prevId; public function __construct() { parent::__construct( 'MergeHistory', 'mergehistory' ); } - private function loadRequestParams( $request ) { - global $wgUser; + public function doesWrites() { + return true; + } + + /** + * @return void + */ + private function loadRequestParams() { + $request = $this->getRequest(); $this->mAction = $request->getVal( 'action' ); $this->mTarget = $request->getVal( 'target' ); $this->mDest = $request->getVal( 'dest' ); @@ -45,183 +85,181 @@ class SpecialMergeHistory extends SpecialPage { $this->mTargetID = intval( $request->getVal( 'targetID' ) ); $this->mDestID = intval( $request->getVal( 'destID' ) ); $this->mTimestamp = $request->getVal( 'mergepoint' ); - if( !preg_match("/[0-9]{14}/",$this->mTimestamp) ) { + if ( !preg_match( '/[0-9]{14}/', $this->mTimestamp ) ) { $this->mTimestamp = ''; } $this->mComment = $request->getText( 'wpComment' ); - $this->mMerge = $request->wasPosted() && $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); + $this->mMerge = $request->wasPosted() + && $this->getUser()->matchEditToken( $request->getVal( 'wpEditToken' ) ); + // target page - if( $this->mSubmitted ) { - $this->mTargetObj = Title::newFromURL( $this->mTarget ); - $this->mDestObj = Title::newFromURL( $this->mDest ); + if ( $this->mSubmitted ) { + $this->mTargetObj = Title::newFromText( $this->mTarget ); + $this->mDestObj = Title::newFromText( $this->mDest ); } else { $this->mTargetObj = null; $this->mDestObj = null; } - $this->preCacheMessages(); } - /** - * As we use the same small set of messages in various methods and that - * they are called often, we call them once and save them in $this->message - */ - function preCacheMessages() { - // Precache various messages - if( !isset( $this->message ) ) { - $this->message['last'] = wfMsgExt( 'last', array( 'escape' ) ); - } - } + public function execute( $par ) { + $this->useTransactionalTimeLimit(); - function execute( $par ) { - global $wgOut, $wgRequest, $wgUser; + $this->checkPermissions(); + $this->checkReadOnly(); - if ( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - - if( !$this->userCanExecute( $wgUser ) ) { - $this->displayRestrictionError(); - return; - } - - $this->loadRequestParams( $wgRequest ); + $this->loadRequestParams(); $this->setHeaders(); $this->outputHeader(); - if( $this->mTargetID && $this->mDestID && $this->mAction=="submit" && $this->mMerge ) { - return $this->merge(); + if ( $this->mTargetID && $this->mDestID && $this->mAction == 'submit' && $this->mMerge ) { + $this->merge(); + + return; } if ( !$this->mSubmitted ) { $this->showMergeForm(); + return; } - $errors = array(); + $errors = []; if ( !$this->mTargetObj instanceof Title ) { - $errors[] = wfMsgExt( 'mergehistory-invalid-source', array( 'parse' ) ); - } elseif( !$this->mTargetObj->exists() ) { - $errors[] = wfMsgExt( 'mergehistory-no-source', array( 'parse' ), + $errors[] = $this->msg( 'mergehistory-invalid-source' )->parseAsBlock(); + } elseif ( !$this->mTargetObj->exists() ) { + $errors[] = $this->msg( 'mergehistory-no-source', wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) - ); + )->parseAsBlock(); } - if ( !$this->mDestObj instanceof Title) { - $errors[] = wfMsgExt( 'mergehistory-invalid-destination', array( 'parse' ) ); - } elseif( !$this->mDestObj->exists() ) { - $errors[] = wfMsgExt( 'mergehistory-no-destination', array( 'parse' ), + if ( !$this->mDestObj instanceof Title ) { + $errors[] = $this->msg( 'mergehistory-invalid-destination' )->parseAsBlock(); + } elseif ( !$this->mDestObj->exists() ) { + $errors[] = $this->msg( 'mergehistory-no-destination', wfEscapeWikiText( $this->mDestObj->getPrefixedText() ) - ); + )->parseAsBlock(); } - + if ( $this->mTargetObj && $this->mDestObj && $this->mTargetObj->equals( $this->mDestObj ) ) { - $errors[] = wfMsgExt( 'mergehistory-same-destination', array( 'parse' ) ); + $errors[] = $this->msg( 'mergehistory-same-destination' )->parseAsBlock(); } if ( count( $errors ) ) { $this->showMergeForm(); - $wgOut->addHTML( implode( "\n", $errors ) ); + $this->getOutput()->addHTML( implode( "\n", $errors ) ); } else { $this->showHistory(); } - } function showMergeForm() { - global $wgOut, $wgScript; + $out = $this->getOutput(); + $out->addWikiMsg( 'mergehistory-header' ); - $wgOut->addWikiMsg( 'mergehistory-header' ); - - $wgOut->addHTML( - Xml::openElement( 'form', array( + $out->addHTML( + Xml::openElement( 'form', [ 'method' => 'get', - 'action' => $wgScript ) ) . - '
' . - Xml::element( 'legend', array(), - wfMsg( 'mergehistory-box' ) ) . - Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) . - Html::hidden( 'submitted', '1' ) . - Html::hidden( 'mergepoint', $this->mTimestamp ) . - Xml::openElement( 'table' ) . - " - ".Xml::label( wfMsg( 'mergehistory-from' ), 'target' )." - ".Xml::input( 'target', 30, $this->mTarget, array('id'=>'target') )." + 'action' => wfScript() ] ) . + '
' . + Xml::element( 'legend', [], + $this->msg( 'mergehistory-box' )->text() ) . + Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) . + Html::hidden( 'submitted', '1' ) . + Html::hidden( 'mergepoint', $this->mTimestamp ) . + Xml::openElement( 'table' ) . + ' + ' . Xml::label( $this->msg( 'mergehistory-from' )->text(), 'target' ) . ' + ' . Xml::input( 'target', 30, $this->mTarget, [ 'id' => 'target' ] ) . ' - ".Xml::label( wfMsg( 'mergehistory-into' ), 'dest' )." - ".Xml::input( 'dest', 30, $this->mDest, array('id'=>'dest') )." - " . - Xml::submitButton( wfMsg( 'mergehistory-go' ) ) . - "" . - Xml::closeElement( 'table' ) . - '
' . - '' ); + ' . Xml::label( $this->msg( 'mergehistory-into' )->text(), 'dest' ) . ' + ' . Xml::input( 'dest', 30, $this->mDest, [ 'id' => 'dest' ] ) . ' + ' . + Xml::submitButton( $this->msg( 'mergehistory-go' )->text() ) . + '' . + Xml::closeElement( 'table' ) . + '
' . + '' + ); + + $this->addHelpLink( 'Help:Merge history' ); } private function showHistory() { - global $wgUser, $wgOut; - - $this->sk = $wgUser->getSkin(); - - $wgOut->setPagetitle( wfMsg( "mergehistory" ) ); - $this->showMergeForm(); # List all stored revisions - $revisions = new MergeHistoryPager( $this, array(), $this->mTargetObj, $this->mDestObj ); + $revisions = new MergeHistoryPager( + $this, [], $this->mTargetObj, $this->mDestObj + ); $haveRevisions = $revisions && $revisions->getNumRows() > 0; - $titleObj = $this->getTitle(); - $action = $titleObj->getLocalURL( array( 'action' => 'submit' ) ); + $out = $this->getOutput(); + $titleObj = $this->getPageTitle(); + $action = $titleObj->getLocalURL( [ 'action' => 'submit' ] ); # Start the form here - $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'merge' ) ); - $wgOut->addHTML( $top ); + $top = Xml::openElement( + 'form', + [ + 'method' => 'post', + 'action' => $action, + 'id' => 'merge' + ] + ); + $out->addHTML( $top ); - if( $haveRevisions ) { + if ( $haveRevisions ) { # Format the user-visible controls (comment field, submission button) # in a nice little table $table = Xml::openElement( 'fieldset' ) . - wfMsgExt( 'mergehistory-merge', array('parseinline'), - $this->mTargetObj->getPrefixedText(), $this->mDestObj->getPrefixedText() ) . - Xml::openElement( 'table', array( 'id' => 'mw-mergehistory-table' ) ) . - " - " . - Xml::label( wfMsg( 'mergehistory-reason' ), 'wpComment' ) . - " - " . - Xml::input( 'wpComment', 50, $this->mComment, array('id' => 'wpComment') ) . - " + $this->msg( 'mergehistory-merge', $this->mTargetObj->getPrefixedText(), + $this->mDestObj->getPrefixedText() )->parse() . + Xml::openElement( 'table', [ 'id' => 'mw-mergehistory-table' ] ) . + ' + ' . + Xml::label( $this->msg( 'mergehistory-reason' )->text(), 'wpComment' ) . + ' + ' . + Xml::input( 'wpComment', 50, $this->mComment, [ 'id' => 'wpComment' ] ) . + '   - " . - Xml::submitButton( wfMsg( 'mergehistory-submit' ), array( 'name' => 'merge', 'id' => 'mw-merge-submit' ) ) . - " - " . - Xml::closeElement( 'table' ) . - Xml::closeElement( 'fieldset' ); - - $wgOut->addHTML( $table ); + ' . + Xml::submitButton( + $this->msg( 'mergehistory-submit' )->text(), + [ 'name' => 'merge', 'id' => 'mw-merge-submit' ] + ) . + ' + ' . + Xml::closeElement( 'table' ) . + Xml::closeElement( 'fieldset' ); + + $out->addHTML( $table ); } - $wgOut->addHTML( "

" . wfMsgHtml( "mergehistory-list" ) . "

\n" ); + $out->addHTML( + '

' . + $this->msg( 'mergehistory-list' )->escaped() . "

\n" + ); - if( $haveRevisions ) { - $wgOut->addHTML( $revisions->getNavigationBar() ); - $wgOut->addHTML( "" ); - $wgOut->addHTML( $revisions->getNavigationBar() ); + if ( $haveRevisions ) { + $out->addHTML( $revisions->getNavigationBar() ); + $out->addHTML( '' ); + $out->addHTML( $revisions->getNavigationBar() ); } else { - $wgOut->addWikiMsg( "mergehistory-empty" ); + $out->addWikiMsg( 'mergehistory-empty' ); } - # Show relevant lines from the deletion log: - $wgOut->addHTML( "

" . htmlspecialchars( LogPage::logName( 'merge' ) ) . "

\n" ); - LogEventsList::showLogExtract( $wgOut, 'merge', $this->mTargetObj->getPrefixedText() ); + # Show relevant lines from the merge log: + $mergeLogPage = new LogPage( 'merge' ); + $out->addHTML( '

' . $mergeLogPage->getName()->escaped() . "

\n" ); + LogEventsList::showLogExtract( $out, 'merge', $this->mTargetObj ); # When we submit, go by page ID to avoid some nasty but unlikely collisions. # Such would happen if a page was renamed after the form loaded, but before submit @@ -229,253 +267,119 @@ class SpecialMergeHistory extends SpecialPage { $misc .= Html::hidden( 'destID', $this->mDestObj->getArticleID() ); $misc .= Html::hidden( 'target', $this->mTarget ); $misc .= Html::hidden( 'dest', $this->mDest ); - $misc .= Html::hidden( 'wpEditToken', $wgUser->editToken() ); + $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ); $misc .= Xml::closeElement( 'form' ); - $wgOut->addHTML( $misc ); + $out->addHTML( $misc ); return true; } function formatRevisionRow( $row ) { - global $wgLang; - $rev = new Revision( $row ); + $linkRenderer = $this->getLinkRenderer(); + $stxt = ''; - $last = $this->message['last']; + $last = $this->msg( 'last' )->escaped(); $ts = wfTimestamp( TS_MW, $row->rev_timestamp ); - $checkBox = Xml::radio( "mergepoint", $ts, false ); + $checkBox = Xml::radio( 'mergepoint', $ts, ( $this->mTimestamp === $ts ) ); + + $user = $this->getUser(); - $pageLink = $this->sk->linkKnown( + $pageLink = $linkRenderer->makeKnownLink( $rev->getTitle(), - htmlspecialchars( $wgLang->timeanddate( $ts ) ), - array(), - array( 'oldid' => $rev->getId() ) + $this->getLanguage()->userTimeAndDate( $ts, $user ), + [], + [ 'oldid' => $rev->getId() ] ); - if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { + if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) { $pageLink = '' . $pageLink . ''; } # Last link - if( !$rev->userCan( Revision::DELETED_TEXT ) ) - $last = $this->message['last']; - else if( isset($this->prevId[$row->rev_id]) ) - $last = $this->sk->linkKnown( + if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) { + $last = $this->msg( 'last' )->escaped(); + } elseif ( isset( $this->prevId[$row->rev_id] ) ) { + $last = $linkRenderer->makeKnownLink( $rev->getTitle(), - $this->message['last'], - array(), - array( + $this->msg( 'last' )->text(), + [], + [ 'diff' => $row->rev_id, 'oldid' => $this->prevId[$row->rev_id] - ) + ] ); + } - $userLink = $this->sk->revUserTools( $rev ); + $userLink = Linker::revUserTools( $rev ); - if(!is_null($size = $row->rev_len)) { - $stxt = $this->sk->formatRevisionSize( $size ); + $size = $row->rev_len; + if ( !is_null( $size ) ) { + $stxt = Linker::formatRevisionSize( $size ); } - $comment = $this->sk->revComment( $rev ); + $comment = Linker::revComment( $rev ); - return "
  • $checkBox ($last) $pageLink . . $userLink $stxt $comment
  • "; + return Html::rawElement( 'li', [], + $this->msg( 'mergehistory-revisionrow' ) + ->rawParams( $checkBox, $last, $pageLink, $userLink, $stxt, $comment )->escaped() ); } /** - * Fetch revision text link if it's available to all users - * @return string + * Actually attempt the history move + * + * @todo if all versions of page A are moved to B and then a user + * tries to do a reverse-merge via the "unmerge" log link, then page + * A will still be a redirect (as it was after the original merge), + * though it will have the old revisions back from before (as expected). + * The user may have to "undo" the redirect manually to finish the "unmerge". + * Maybe this should delete redirects at the target page of merges? + * + * @return bool Success */ - function getPageLink( $row, $titleObj, $ts, $target ) { - global $wgLang; - - if( !$this->userCan($row, Revision::DELETED_TEXT) ) { - return '' . $wgLang->timeanddate( $ts, true ) . ''; - } else { - $link = $this->sk->linkKnown( - $titleObj, - $wgLang->timeanddate( $ts, true ), - array(), - array( - 'target' => $target, - 'timestamp' => $ts - ) - ); - if( $this->isDeleted($row, Revision::DELETED_TEXT) ) - $link = '' . $link . ''; - return $link; - } - } - function merge() { - global $wgOut; # Get the titles directly from the IDs, in case the target page params # were spoofed. The queries are done based on the IDs, so it's best to # keep it consistent... $targetTitle = Title::newFromID( $this->mTargetID ); $destTitle = Title::newFromID( $this->mDestID ); - if( is_null($targetTitle) || is_null($destTitle) ) + if ( is_null( $targetTitle ) || is_null( $destTitle ) ) { return false; // validate these - if( $targetTitle->getArticleId() == $destTitle->getArticleId() ) - return false; - # Verify that this timestamp is valid - # Must be older than the destination page - $dbw = wfGetDB( DB_MASTER ); - # Get timestamp into DB format - $this->mTimestamp = $this->mTimestamp ? $dbw->timestamp($this->mTimestamp) : ''; - # Max timestamp should be min of destination page - $maxtimestamp = $dbw->selectField( 'revision', 'MIN(rev_timestamp)', - array('rev_page' => $this->mDestID ), - __METHOD__ ); - # Destination page must exist with revisions - if( !$maxtimestamp ) { - $wgOut->addWikiMsg('mergehistory-fail'); - return false; - } - # Get the latest timestamp of the source - $lasttimestamp = $dbw->selectField( array('page','revision'), - 'rev_timestamp', - array('page_id' => $this->mTargetID, 'page_latest = rev_id' ), - __METHOD__ ); - # $this->mTimestamp must be older than $maxtimestamp - if( $this->mTimestamp >= $maxtimestamp ) { - $wgOut->addWikiMsg('mergehistory-fail'); - return false; - } - # Update the revisions - if( $this->mTimestamp ) { - $timewhere = "rev_timestamp <= {$this->mTimestamp}"; - $TimestampLimit = wfTimestamp(TS_MW,$this->mTimestamp); - } else { - $timewhere = "rev_timestamp <= {$maxtimestamp}"; - $TimestampLimit = wfTimestamp(TS_MW,$lasttimestamp); - } - # Do the moving... - $dbw->update( 'revision', - array( 'rev_page' => $this->mDestID ), - array( 'rev_page' => $this->mTargetID, - $timewhere ), - __METHOD__ ); - - $count = $dbw->affectedRows(); - # Make the source page a redirect if no revisions are left - $haveRevisions = $dbw->selectField( 'revision', - 'rev_timestamp', - array( 'rev_page' => $this->mTargetID ), - __METHOD__, - array( 'FOR UPDATE' ) ); - if( !$haveRevisions ) { - if( $this->mComment ) { - $comment = wfMsgForContent( 'mergehistory-comment', $targetTitle->getPrefixedText(), - $destTitle->getPrefixedText(), $this->mComment ); - } else { - $comment = wfMsgForContent( 'mergehistory-autocomment', $targetTitle->getPrefixedText(), - $destTitle->getPrefixedText() ); - } - $mwRedir = MagicWord::get( 'redirect' ); - $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $destTitle->getPrefixedText() . "]]\n"; - $redirectArticle = new Article( $targetTitle ); - $redirectRevision = new Revision( array( - 'page' => $this->mTargetID, - 'comment' => $comment, - 'text' => $redirectText ) ); - $redirectRevision->insertOn( $dbw ); - $redirectArticle->updateRevisionOn( $dbw, $redirectRevision ); - - # Now, we record the link from the redirect to the new title. - # It should have no other outgoing links... - $dbw->delete( 'pagelinks', array( 'pl_from' => $this->mDestID ), __METHOD__ ); - $dbw->insert( 'pagelinks', - array( - 'pl_from' => $this->mDestID, - 'pl_namespace' => $destTitle->getNamespace(), - 'pl_title' => $destTitle->getDBkey() ), - __METHOD__ ); - } else { - $targetTitle->invalidateCache(); // update histories } - $destTitle->invalidateCache(); // update histories - # Check if this did anything - if( !$count ) { - $wgOut->addWikiMsg('mergehistory-fail'); + if ( $targetTitle->getArticleID() == $destTitle->getArticleID() ) { return false; } - # Update our logs - $log = new LogPage( 'merge' ); - $log->addEntry( 'merge', $targetTitle, $this->mComment, - array($destTitle->getPrefixedText(),$TimestampLimit) ); - - $wgOut->addHTML( wfMsgExt( 'mergehistory-success', array('parseinline'), - $targetTitle->getPrefixedText(), $destTitle->getPrefixedText(), $count ) ); - - wfRunHooks( 'ArticleMergeComplete', array( $targetTitle, $destTitle ) ); - - return true; - } -} - -class MergeHistoryPager extends ReverseChronologicalPager { - public $mForm, $mConds; - function __construct( $form, $conds = array(), $source, $dest ) { - $this->mForm = $form; - $this->mConds = $conds; - $this->title = $source; - $this->articleID = $source->getArticleID(); + // MergeHistory object + $mh = new MergeHistory( $targetTitle, $destTitle, $this->mTimestamp ); - $dbr = wfGetDB( DB_SLAVE ); - $maxtimestamp = $dbr->selectField( 'revision', 'MIN(rev_timestamp)', - array('rev_page' => $dest->getArticleID() ), - __METHOD__ ); - $this->maxTimestamp = $maxtimestamp; - - parent::__construct(); - } - - function getStartBody() { - wfProfileIn( __METHOD__ ); - # Do a link batch query - $this->mResult->seek( 0 ); - $batch = new LinkBatch(); - # Give some pointers to make (last) links - $this->mForm->prevId = array(); - foreach ( $this->mResult as $row ) { - $batch->addObj( Title::makeTitleSafe( NS_USER, $row->rev_user_text ) ); - $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->rev_user_text ) ); - - $rev_id = isset($rev_id) ? $rev_id : $row->rev_id; - if( $rev_id > $row->rev_id ) - $this->mForm->prevId[$rev_id] = $row->rev_id; - else if( $rev_id < $row->rev_id ) - $this->mForm->prevId[$row->rev_id] = $rev_id; - - $rev_id = $row->rev_id; + // Merge! + $mergeStatus = $mh->merge( $this->getUser(), $this->mComment ); + if ( !$mergeStatus->isOK() ) { + // Failed merge + $this->getOutput()->addWikiMsg( $mergeStatus->getMessage() ); + return false; } - $batch->execute(); - $this->mResult->seek( 0 ); - - wfProfileOut( __METHOD__ ); - return ''; - } + $linkRenderer = $this->getLinkRenderer(); - function formatRow( $row ) { - return $this->mForm->formatRevisionRow( $row ); - } + $targetLink = $linkRenderer->makeLink( + $targetTitle, + null, + [], + [ 'redirect' => 'no' ] + ); - function getQueryInfo() { - $conds = $this->mConds; - $conds['rev_page'] = $this->articleID; - $conds[] = 'page_id = rev_page'; - $conds[] = "rev_timestamp < {$this->maxTimestamp}"; - return array( - 'tables' => array('revision','page'), - 'fields' => array( 'rev_minor_edit', 'rev_timestamp', 'rev_user', 'rev_user_text', 'rev_comment', - 'rev_id', 'rev_page', 'rev_parent_id', 'rev_text_id', 'rev_len', 'rev_deleted' ), - 'conds' => $conds + $this->getOutput()->addWikiMsg( $this->msg( 'mergehistory-done' ) + ->rawParams( $targetLink ) + ->params( $destTitle->getPrefixedText() ) + ->numParams( $mh->getMergedRevisionCount() ) ); + + return true; } - function getIndexField() { - return 'rev_timestamp'; + protected function getGroupName() { + return 'pagetools'; } }