Sn1per has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/261617

Change subject: [WIP] Implement action=mergehistory
......................................................................

[WIP] Implement action=mergehistory

* ApiMergeHistory handles action=mergehistory
* Merge History functionality moved from SpecialMergeHistory to
  MergeHistory

Bug: T69742
Change-Id: Ic5078307dae78a2b3687e34a5d0a584988d483a1
---
M autoload.php
A includes/MergeHistory.php
M includes/api/ApiMain.php
A includes/api/ApiMergeHistory.php
4 files changed, 438 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/17/261617/1

diff --git a/autoload.php b/autoload.php
index ac38fa5..f873aad 100644
--- a/autoload.php
+++ b/autoload.php
@@ -52,6 +52,7 @@
        'ApiLogout' => __DIR__ . '/includes/api/ApiLogout.php',
        'ApiMain' => __DIR__ . '/includes/api/ApiMain.php',
        'ApiManageTags' => __DIR__ . '/includes/api/ApiManageTags.php',
+       'ApiMergeHistory' => __DIR__ . '/includes/api/ApiMergeHistory.php',
        'ApiMessage' => __DIR__ . '/includes/api/ApiMessage.php',
        'ApiModuleManager' => __DIR__ . '/includes/api/ApiModuleManager.php',
        'ApiMove' => __DIR__ . '/includes/api/ApiMove.php',
@@ -796,6 +797,7 @@
        'MemcachedPhpBagOStuff' => __DIR__ . 
'/includes/libs/objectcache/MemcachedPhpBagOStuff.php',
        'MemoizedCallable' => __DIR__ . '/includes/libs/MemoizedCallable.php',
        'MemoryFileBackend' => __DIR__ . 
'/includes/filebackend/MemoryFileBackend.php',
+       'MergeHistory' => __DIR__ . '/includes/MergeHistory.php',
        'MergeHistoryPager' => __DIR__ . 
'/includes/specials/SpecialMergeHistory.php',
        'MergeLogFormatter' => __DIR__ . 
'/includes/logging/MergeLogFormatter.php',
        'MergeMessageFileList' => __DIR__ . 
'/maintenance/mergeMessageFileList.php',
diff --git a/includes/MergeHistory.php b/includes/MergeHistory.php
new file mode 100644
index 0000000..67306b0
--- /dev/null
+++ b/includes/MergeHistory.php
@@ -0,0 +1,267 @@
+<?php
+
+/**
+ *
+ *
+ * Created on Dec 29, 2015
+ *
+ * Copyright © 2015 Geoffrey Mon "[email protected]"
+ *
+ * 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
+ */
+
+/**
+ * Handles the backend logic of merging the histories of two
+ * pages.
+ *
+ * @since 1.27
+ */
+class MergeHistory {
+
+       /** @var Title Page from which history will be merged */
+       protected $mTarget;
+
+       /** @var Title Page to which history will be merged */
+       protected $mDest;
+
+       /** @var string Timestamp up to which history will be merged */
+       protected $mTimestamp;
+
+       /**
+        * MergeHistory constructor.
+        * @param Title $mTarget
+        * @param Title $mDest 
+        */
+       public function __construct( Title $mTarget, Title $mDest, $mTimestamp 
) {
+               $this->mTarget = $mTarget;
+               $this->mDest = $mDest;
+               $this->mTimestamp = $mTimestamp;
+       }
+
+       public function checkPermissions( User $user, $reason ) {
+               return new Status();
+               /*$status = new Status();
+
+               $errors = wfMergeErrorArrays(
+                       $this->mTarget->getUserPermissionsErrors( 'move', $user 
),
+                       $this->mTarget->getUserPermissionsErrors( 'edit', $user 
),
+                       $this->mDest->getUserPermissionsErrors( 'move-target', 
$user ),
+                       $this->mDest->getUserPermissionsErrors( 'edit', $user )
+               );
+
+               // Convert into a Status object
+               if ( $errors ) {
+                       foreach ( $errors as $error ) {
+                               call_user_func_array( array( $status, 'fatal' 
), $error );
+                       }
+               }
+
+               if ( EditPage::matchSummarySpamRegex( $reason ) !== false ) {
+                       // This is kind of lame, won't display nice
+                       $status->fatal( 'spamprotectiontext' );
+               }
+
+               $tp = $this->newTitle->getTitleProtection();
+               if ( $tp !== false && !$user->isAllowed( $tp['permission'] ) ) {
+                               $status->fatal( 'cantmove-titleprotected' );
+               }
+
+               return $status;*/
+       }
+
+       /**
+        * Does various sanity checks that the move is
+        * valid. Only things based on the two titles
+        * should be checked here.
+        *
+        * @return Status
+        */
+       public function isValidMerge() {
+               global $wgContentHandlerUseDB;
+               $status = new Status();
+
+               // Make sure pages exist
+               if ( is_null( $this->mTarget ) || is_null( $this->mDest ) ) {
+                       $status->fatal( 'test' );
+               }
+               // Make sure page aren't the same
+               if ( $this->mTarget->getArticleID() == 
$this->mDest->getArticleID() ) {
+                       $status->fatal( 'test' );
+               }
+               # 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->mDest->getArticleID() ),
+                       __METHOD__
+               );
+               # Destination page must exist with revisions
+               if ( !$maxtimestamp ) {
+                       $status->fatal( 'mergehistory-fail' );
+               }
+               # Get the latest timestamp of the source
+               $lasttimestamp = $dbw->selectField(
+                       array( 'page', 'revision' ),
+                       'rev_timestamp',
+                       array( 'page_id' => $this->mTarget->getArticleID(), 
'page_latest = rev_id' ),
+                       __METHOD__
+               );
+               # $this->mTimestamp must be older than $maxtimestamp
+               if ( $this->mTimestamp >= $maxtimestamp ) {
+                       $status->fatal( 'mergehistory-fail' );
+               }
+               # Get the timestamp pivot condition
+               if ( $this->mTimestamp ) {
+                       $this->timewhere = "rev_timestamp <= 
{$this->mTimestamp}";
+                       $this->timestampLimit = wfTimestamp( TS_MW, 
$this->mTimestamp );
+               } else {
+                       $this->timewhere = "rev_timestamp <= {$maxtimestamp}";
+                       $this->timestampLimit = wfTimestamp( TS_MW, 
$lasttimestamp );
+               }
+               # Check that there are not too many revisions to move
+               $limit = 5000; // avoid too much slave lag
+               $count = $dbw->selectRowCount( 'revision', '1',
+                       array( 'rev_page' => $this->mTarget->getArticleID(), 
$this->timewhere ),
+                       __METHOD__,
+                       array( 'LIMIT' => $limit + 1 )
+               );
+               if ( $count > $limit ) {
+                       $status->fatal( 'mergehistory-fail-toobig' );
+               }
+
+               return $status;
+       }
+
+       /**
+        * 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?
+        *
+        * @param User $user
+        * @param string $reason
+        * @return bool Success
+        */
+       function merge( User $user, $reason = '' ) {
+               $status = new Status(); //TODO
+
+
+               $dbw = wfGetDB( DB_MASTER );
+               # Do the moving...
+               $dbw->update(
+                       'revision',
+                       array( 'rev_page' => $this->mDest->getArticleID() ),
+                       array( 'rev_page' => $this->mTarget->getArticleID(), 
$this->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->mTarget->getArticleID() ),
+                       __METHOD__,
+                       array( 'FOR UPDATE' )
+               );
+               if ( !$haveRevisions ) {
+                       if ( $reason ) {
+                               $reason = wfMessage(
+                                       'mergehistory-comment',
+                                       $this->mTarget->getPrefixedText(),
+                                       $this->mDest->getPrefixedText(),
+                                       $reason
+                               )->inContentLanguage()->text();
+                       } else {
+                               $reason = wfMessage(
+                                       'mergehistory-autocomment',
+                                       $this->mTarget->getPrefixedText(),
+                                       $this->mDest->getPrefixedText()
+                               )->inContentLanguage()->text();
+                       }
+
+                       $contentHandler = ContentHandler::getForTitle( 
$this->mTarget );
+                       $redirectContent = 
$contentHandler->makeRedirectContent( $this->mDest );
+
+                       if ( $redirectContent ) {
+                               $redirectPage = WikiPage::factory( 
$this->mTarget );
+                               $redirectRevision = new Revision( array(
+                                       'title' => $this->mTarget,
+                                       'page' => 
$this->mTarget->getArticleID(),
+                                       'comment' => $reason,
+                                       'content' => $redirectContent ) );
+                               $redirectRevision->insertOn( $dbw );
+                               $redirectPage->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->mDest->getArticleID() ), __METHOD__ );
+                               $dbw->insert( 'pagelinks',
+                                       array(
+                                               'pl_from' => 
$this->mDest->getArticleID(),
+                                               'pl_from_namespace' => 
$this->mDest->getNamespace(),
+                                               'pl_namespace' => 
$this->mDest->getNamespace(),
+                                               'pl_title' => 
$this->mDest->getDBkey() ),
+                                       __METHOD__
+                               );
+                       } else {
+                               // would be nice to show a warning if we 
couldn't create a redirect
+                       }
+               } else {
+                       $this->mTarget->invalidateCache(); // update histories
+               }
+               $this->mDest->invalidateCache(); // update histories
+               # Check if this did anything
+               if ( !$count ) {
+                       //$this->getOutput()->addWikiMsg( 'mergehistory-fail' );
+       // TODO
+                       return false;
+               }
+               # Update our logs
+               $logEntry = new ManualLogEntry( 'merge', 'merge' );
+               $logEntry->setPerformer( $user );
+               $logEntry->setComment( $reason );
+               $logEntry->setTarget( $this->mTarget );
+               $logEntry->setParameters( array(
+                       '4::dest' => $this->mDest->getPrefixedText(),
+                       '5::mergepoint' => $this->timestampLimit
+               ) );
+               $logId = $logEntry->insert();
+               $logEntry->publish( $logId );
+
+               $targetLink = Linker::link(
+                       $this->mTarget,
+                       $this->mTarget->getPrefixedText(),
+                       array(),
+                       array( 'redirect' => 'no' )
+               );
+
+               Hooks::run( 'ArticleMergeComplete', array( $this->mTarget, 
$this->mDest ) );
+
+               return $status;
+       }
+}
diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php
index 89ff19a..d25629d 100644
--- a/includes/api/ApiMain.php
+++ b/includes/api/ApiMain.php
@@ -90,6 +90,7 @@
                'revisiondelete' => 'ApiRevisionDelete',
                'managetags' => 'ApiManageTags',
                'tag' => 'ApiTag',
+               'mergehistory' => 'ApiMergeHistory',
        );
 
        /**
diff --git a/includes/api/ApiMergeHistory.php b/includes/api/ApiMergeHistory.php
new file mode 100644
index 0000000..ffcdd8d
--- /dev/null
+++ b/includes/api/ApiMergeHistory.php
@@ -0,0 +1,168 @@
+<?php
+/**
+ *
+ *
+ * Created on Dec 29, 2015
+ *
+ * Copyright © 2015 Geoffrey Mon "[email protected]"
+ *
+ * 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
+ */
+
+/**
+ * API Module to merge page histories
+ * @ingroup API
+ */
+class ApiMergeHistory extends ApiBase {
+
+       public function execute() {
+               $this->useTransactionalTimeLimit();
+
+               $user = $this->getUser();
+               $params = $this->extractRequestParams();
+
+               $this->requireOnlyOneParameter( $params, 'from', 'fromid' );
+
+               if ( isset( $params['from'] ) ) {
+                       $fromTitle = Title::newFromText( $params['from'] );
+                       if ( !$fromTitle || $fromTitle->isExternal() ) {
+                               $this->dieUsageMsg( array( 'invalidtitle', 
$params['from'] ) );
+                       }
+               } elseif ( isset( $params['fromid'] ) ) {
+                       $fromTitle = Title::newFromID( $params['fromid'] );
+                       if ( !$fromTitle ) {
+                               $this->dieUsageMsg( array( 'nosuchpageid', 
$params['fromid'] ) );
+                       }
+               }
+
+               if ( !$fromTitle->exists() ) {
+                       $this->dieUsageMsg( 'notanarticle' );
+               }
+
+               if ( isset( $params['to'] ) ) {
+                       $toTitle = Title::newFromText( $params['to'] );
+                       if ( !$toTitle || $toTitle->isExternal() ) {
+                               $this->dieUsageMsg( array( 'invalidtitle', 
$params['to'] ) );
+                       }
+               } elseif ( isset( $params['toid'] ) ) {
+                       $toTitle = Title::newFromID( $params['toid'] );
+                       if ( !$toTitle ) {
+                               $this->dieUsageMsg( array( 'nosuchpageid', 
$params['toid'] ) );
+                       }
+               }
+
+               if ( !$toTitle->exists() ) {
+                       $this->dieUsageMsg( 'notanarticle' );
+               }
+
+               $reason = $params['reason'];
+               $timestamp = Title::newFromText( $params['timestamp'] );
+               if ( !$toTitle || $toTitle->isExternal() ) {
+                       $this->dieUsageMsg( array( 'invalidtitle', 
$params['to'] ) );
+               }
+               $toTalk = $toTitle->getTalkPage();
+
+               if ( $toTitle->getNamespace() == NS_FILE
+                       && !RepoGroup::singleton()->getLocalRepo()->findFile( 
$toTitle )
+                       && wfFindFile( $toTitle )
+               ) {
+                       if ( !$params['ignorewarnings'] && $user->isAllowed( 
'reupload-shared' ) ) {
+                               $this->dieUsageMsg( 'sharedfile-exists' );
+                       } elseif ( !$user->isAllowed( 'reupload-shared' ) ) {
+                               $this->dieUsageMsg( 'cantoverwrite-sharedfile' 
);
+                       }
+               }
+
+               // Merge!
+               $status = $this->merge( $fromTitle, $toTitle, $timestamp, 
$reason );
+               if ( !$status->isOK() ) {
+                       $this->dieStatus( $status );
+               }
+
+               $r = array(
+                       'from' => $fromTitle->getPrefixedText(),
+                       'to' => $toTitle->getPrefixedText(),
+                       'reason' => $params['reason']
+               );
+               $result = $this->getResult();
+
+               $result->addValue( null, $this->getModuleName(), $r );
+       }
+
+       /**
+        * @param Title $from
+        * @param Title $to
+        * @param string $timestamp
+        * @param string $reason
+        * @return Status
+        */
+       protected function merge( Title $from, Title $to, $timestamp, $reason ) 
{
+               $mh = new MergeHistory( $from, $to, $timestamp );
+               $valid = $mh->isValidMerge();
+               if ( !$valid->isOK() ) {
+                       return $valid;
+               }
+
+               // TODO
+               //$permStatus = $mp->checkPermissions( $this->getUser(), 
$reason );
+               //if ( !$permStatus->isOK() ) {
+               //      return $permStatus;
+               //}
+
+               return $mh->merge( $this->getUser(), $reason );
+       }
+
+       public function mustBePosted() {
+               return true;
+       }
+
+       public function isWriteMode() {
+               return true;
+       }
+
+       public function getAllowedParams() {
+               return array(
+                       'from' => null,
+                       'fromid' => array(
+                               ApiBase::PARAM_TYPE => 'integer'
+                       ),
+                       'to' => null,
+                       'toid' => array(
+                               ApiBase::PARAM_TYPE => 'integer'
+                       ),
+                       'timestamp' => null, //TODO: required?
+                       'reason' => '',
+               );
+       }
+
+       public function needsToken() {
+               return 'csrf';
+       }
+
+       protected function getExamplesMessages() {
+               return array(
+                       'action=move&from=Badtitle&to=Goodtitle&token=123ABC&' .
+                       'reason=Misspelled%20title&movetalk=&noredirect='
+                       => 'apihelp-move-example-move',
+               );
+       }
+
+       public function getHelpUrls() {
+               return 'https://www.mediawiki.org/wiki/API:Mergehistory';
+       }
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/261617
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ic5078307dae78a2b3687e34a5d0a584988d483a1
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Sn1per <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to