setHeaders(); $this->outputHeader(); $this->mIsAllowedRevisionMove = $wgUser->isAllowed( 'revisionmove' ); $this->user = $wgUser; $this->skin = $wgUser->getSkin(); if ( !$this->request instanceof WebRequest ) { $this->request = $GLOBALS['wgRequest']; } # Get correct title if ( $this->request->getVal( 'action' ) == 'historysubmit' ) { $this->mOldTitle = Title::newFromText( $this->request->getVal( 'title' ) ); } else { $this->mOldTitle = Title::newFromText( $this->request->getVal( 'oldTitle' ) ); } if ( !$this->mOldTitle instanceof Title ) { $wgOut->showErrorPage( 'revmove-badparam-title', 'revmove-badparam' ); return; } $wgOut->setPagetitle( wfMsg( 'revisionmove', $this->mOldTitle->getPrefixedText() ) ); $oldTitleLink = $this->skin->link( $this->mOldTitle ); $wgOut->setSubtitle( wfMsg( 'revisionmove-backlink', $oldTitleLink ) ); $this->mReason = $this->request->getText( 'wpReason' ); # TODO maybe not needed here? Copied from SpecialRevisiondelete.php. # Keep for now, allow different inputs # Handle our many different possible input types for ids $ids = $this->request->getVal( 'ids' ); if ( !is_null( $ids ) ) { # Allow CSV, for backwards compatibility, or a single ID for show/hide links $this->mIds = explode( ',', $ids ); } else { # Array input $this->mIds = array_keys( $this->request->getArray( 'ids', array() ) ); } $this->mIds = array_unique( array_filter( $this->mIds ) ); if ( is_null ( $this->mIds ) ) { $wgOut->showErrorPage( 'revmove-badparam-title', 'revmove-badparam' ); return; } $this->mRevlist = new RevDel_RevisionList( $this, $this->mOldTitle, $this->mIds ); # Decide what to do: Show the form, or submit the changes if ( $this->request->wasPosted() ) { $this->submit(); } else { $this->showForm(); } } /** * Show a list of items that we will operate on and a field for the target name */ public function showForm() { global $wgOut, $wgUser; if ( !$this->mIsAllowedRevisionMove ) { $permErrors = $this->mOldTitle->getUserPermissionsErrors( 'revisionmove', $this->user ); $wgOut->showPermissionsErrorPage( $permErrors, 'revisionmove' ); return false; } $wgOut->addWikiMsg( 'revmove-explain', $this->mOldTitle->getPrefixedText() ); $listNotEmpty = $this->listItems(); if ( !$listNotEmpty ) { return; # we're done, we already displayed an error earlier } $out = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalUrl( array( 'action' => 'submit' ) ), 'id' => 'mw-revmove-form' ) ) . Xml::fieldset( wfMsg( 'revmove-legend' ) ) . Html::hidden( 'wpEditToken', $wgUser->editToken() ) . Html::hidden( 'oldTitle', $this->mOldTitle->getPrefixedText() ) . '
' . Xml::inputLabel( wfMsg( 'revmove-reasonfield' ), 'wpReason', 'revmove-reasonfield', 60 ) . '
' . Xml::inputLabel( wfMsg( 'revmove-titlefield' ), 'newTitle', 'revmove-titlefield', 20, $this->mOldTitle->getPrefixedText() ) . Html::hidden( 'ids', implode( ',', $this->mIds ) ) . Xml::submitButton( wfMsg( 'revmove-submit' ), array( 'name' => 'wpSubmit' ) ) . Xml::closeElement( 'fieldset' ) . "\n" . Xml::closeElement( 'form' ) . "\n"; $wgOut->addHTML( $out ); } /** * Show a list of selected revisions and check the input */ protected function listItems() { global $wgOut; $wgOut->addHTML( "" ); return true; } /** * Submit the posted changes (in $this->request). * * This function does some checks and then calls moveRevisions(), which does the real work */ public function submit() { global $wgUser, $wgOut; # Confirm permissions if ( !$this->mIsAllowedRevisionMove ) { $permErrors = $this->mOldTitle->getUserPermissionsErrors( 'revisionmove', $this->user ); $wgOut->showPermissionsErrorPage( $permErrors, 'revisionmove' ); return false; } # Confirm Token if ( !$wgUser->matchEditToken( $this->request->getVal( 'wpEditToken' ) ) ) { $wgOut->showErrorPage( 'sessionfailure-title', 'sessionfailure' ); return false; } $this->mNewTitle = Title::newFromText( $this->request->getVal( 'newTitle' ) ); if ( !$this->mNewTitle instanceof Title ) { $wgOut->showErrorPage( 'badtitle', 'badtitletext' ); return false; } if ( $this->mNewTitle->getPrefixedText() == $this->mOldTitle->getPrefixedText() ) { $pagename = array( $this->mOldTitle->getPrefixedText() ); $wgOut->showErrorPage( 'revmove-nullmove-title', 'revmove-nullmove', $pagename ); return; } $this->moveRevisions(); } /** * This function actually move the revision. NEVER call this function, call submit() */ protected function moveRevisions() { $oldArticle = new Article( $this->mOldTitle ); $newArticle = new Article( $this->mNewTitle ); $idstring = implode( ", ", $this->mIds ); # Get DB connection and begin transaction $dbw = wfGetDB( DB_MASTER ); $dbw->begin(); # Check if the target exists. If not, try creating it if ( !$this->mNewTitle->exists() ) { $newArticle->insertOn( $dbw ); $this->createArticle = true; } else { $this->createArticle = false; } # This is where the magic happens: # Update revision table $dbw->update( 'revision', array( 'rev_page' => $this->mNewTitle->getArticleID() ), array( 'rev_id IN (' . $idstring . ')', 'rev_page' => $this->mOldTitle->getArticleID(), ), __METHOD__ ); $modifiedRevsNum = $dbw->affectedRows(); # Check if we need to update page_latest # Get the latest version of the revisions we are moving $timestampNewPage = $this->queryLatestTimestamp( $dbw, $this->mNewTitle->getArticleID(), array( 'rev_id IN (' . $idstring . ')' ) ); # Compare the new page's page_latest against db query. # If we create a new page, we have to update anyway $currentNewPageRev = Revision::newFromId( $this->mNewTitle->getLatestRevID() ); if ( $this->createArticle || $timestampNewPage > $currentNewPageRev->getTimestamp() ) { # we have to set page_latest to $timestampNewPage's revid $this->updatePageLatest( $dbw, $this->mNewTitle, $newArticle, $timestampNewPage, array( 'rev_id IN (' . $idstring . ')' ) ); } # Update the old page's page_latest field $timestampOldPage = $this->queryLatestTimestamp( $dbw, $this->mOldTitle->getArticleID() ); # If the timestamp is null that means the page doesn't have # any revisions associated and should be deleted. In other words, # someone misused revisionmove for the normal move function. if ( is_null( $timestampOldPage ) ) { $dbw->delete( 'page', array( 'page_id = ' . $this->mOldTitle->getArticleID() ), __METHOD__ ); } else { # page_latest has to be updated $currentOldPageRev = Revision::newFromId( $this->mOldTitle->getLatestRevID() ); if ( $timestampOldPage < $currentOldPageRev->getTimestamp() ) { $this->updatePageLatest( $dbw, $this->mOldTitle, $oldArticle, $timestampOldPage ); } # Purge the old one only if it hasn't been deleted $oldArticle->doPurge(); } # All done, commit $dbw->commit(); $this->logMove( $modifiedRevsNum ); # Purge new article $newArticle->doPurge(); # If noting went wrong (i.e. returned), we are successful $this->showSuccess( $modifiedRevsNum ); } /** * Query for the latest timestamp in order to update page_latest and * page_timestamp. * @param &$dbw Database object (Master) * @param $articleId Integer page_id * @param $conds array database conditions * * @return String timestamp */ protected function queryLatestTimestamp( &$dbw, $articleId, $conds = array() ) { $timestampNewRow = $dbw->selectRow( 'revision', 'max(rev_timestamp) AS maxtime', array_merge( array( 'rev_page' => $articleId ), $conds ), __METHOD__ ); return $timestampNewRow->maxtime; } /** * Updates page_latest and similar database fields (see Article::updateRevisionOn). * Called two times, for the new and the old page * * @param &$dbw Database object (Master) * @param $articleTitle Title object of the page * @param $articleObj Article object of the page * @param $timestamp to search for (use queryLatestTimestamp to get the latest) * @param $conds array database conditions * * @return boolean indicating success */ protected function updatePageLatest( &$dbw, $articleTitle, &$articleObj, $timestamp, $conds = array() ) { # Query to find out the rev_id $revisionRow = $dbw->selectRow( 'revision', 'rev_id', array_merge( array( 'rev_timestamp' => $timestamp, 'rev_page' => $articleTitle->getArticleID(), ), $conds ), __METHOD__ ); # Update page_latest $latestRev = Revision::newFromId( $revisionRow->rev_id ); return $articleObj->updateRevisionOn( $dbw, $latestRev, $articleTitle->getLatestRevID(), null, /* set new page flag */ true ); } /** * Add a log entry for the revision move */ protected function logMove( $modifiedRevsNum ) { $paramArray = array( $this->mNewTitle->getPrefixedText(), $modifiedRevsNum ); $log = new LogPage( 'move' ); $log->addEntry( 'move_rev', $this->mOldTitle, $this->mReason, $paramArray, $this->user ); } protected function showSuccess( $modifiedRevsNum ) { global $wgOut; if ( $this->createArticle ) { $wgOut->addWikiMsg( 'revmove-success-created', $modifiedRevsNum, $this->mOldTitle->getPrefixedText(), $this->mNewTitle->getPrefixedText() ); } else { $wgOut->addWikiMsg( 'revmove-success-existing', $modifiedRevsNum, $this->mOldTitle->getPrefixedText(), $this->mNewTitle->getPrefixedText() ); } } }