jenkins-bot has submitted this change and it was merged.

Change subject: Implement Client to Repo move change propagation
......................................................................


Implement Client to Repo move change propagation

This will make page moves happening on the client
appear in the repo. To do that it inserts a job
into the repos job queue to update the item. This
will only happen if the user got a global account
and both the local (client) and the repo account
belong to the same global account.

Note: This requires CentralAuth.

Bug: 36729

Change-Id: Iad9bd7065bb0874ebf52e65a8558fc8f091bdeec
---
M client/WikibaseClient.hooks.php
M client/WikibaseClient.i18n.php
M client/WikibaseClient.php
A client/includes/UpdateRepo.php
A client/includes/UpdateRepoOnMove.php
A client/includes/hooks/MovePageNotice.php
A client/tests/phpunit/includes/UpdateRepoOnMoveTest.php
M lib/WikibaseLib.classes.php
M lib/WikibaseLib.php
A lib/includes/UpdateRepoOnMoveJob.php
A lib/tests/phpunit/UpdateRepoOnMoveJobTest.php
M repo/Wikibase.i18n.php
M repo/includes/EditEntity.php
M repo/includes/api/ApiWikibase.php
14 files changed, 927 insertions(+), 23 deletions(-)

Approvals:
  Daniel Kinzler: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/client/WikibaseClient.hooks.php b/client/WikibaseClient.hooks.php
index 0f4c59a..dd29dc9 100644
--- a/client/WikibaseClient.hooks.php
+++ b/client/WikibaseClient.hooks.php
@@ -6,6 +6,7 @@
 use RecursiveIteratorIterator;
 use SplFileInfo;
 use Wikibase\Client\WikibaseClient;
+use Wikibase\Client\MovePageNotice;
 use Wikibase\DataModel\SimpleSiteLink;
 
 /**
@@ -23,6 +24,7 @@
  * @author Daniel Kinzler
  * @author Tobias Gritschacher
  * @author Jeroen De Dauw < [email protected] >
+ * @author Marius Hoch < [email protected] >
  */
 
 final class ClientHooks {
@@ -175,29 +177,21 @@
         * @return bool
         */
        public static function onSpecialMovepageAfterMove( \MovePageForm 
$movePage, \Title &$oldTitle, \Title &$newTitle ) {
-               $siteLinkCache = 
WikibaseClient::getDefaultInstance()->getStore()->getSiteLinkTable();
-               $globalId = Settings::get( 'siteGlobalID' );
-               $itemId = $siteLinkCache->getItemIdForLink(
-                       $globalId,
-                       $oldTitle->getText()
+               $siteLinkLookup = 
WikibaseClient::getDefaultInstance()->getStore()->getSiteLinkTable();
+               $repoLinker = 
WikibaseClient::getDefaultInstance()->newRepoLinker();
+
+               $movePageNotice = new MovePageNotice(
+                       $siteLinkLookup,
+                       Settings::get( 'siteGlobalID' ),
+                       $repoLinker
                );
 
-               if ( $itemId !== false ) {
-                       $repoLinker = 
WikibaseClient::getDefaultInstance()->newRepoLinker();
+               $movePageNotice->reportRepoUpdate(
+                       $movePage->getOutput(),
+                       $oldTitle,
+                       $newTitle
+               );
 
-                       $itemByTitle = 'Special:ItemByTitle/' . $globalId . '/' 
. $oldTitle->getPrefixedDBkey();
-                       $itemByTitleLink = $repoLinker->repoArticleUrl( 
$itemByTitle );
-                       $out = $movePage->getOutput();
-                       $out->addModules( 'wikibase.client.page-move' );
-                       $out->addHTML(
-                               \Html::rawElement(
-                                       'div',
-                                       array( 'id' => 'wbc-after-page-move',
-                                                       'class' => 'plainlinks' 
),
-                                       wfMessage( 'wikibase-after-page-move', 
$itemByTitleLink )->parse()
-                               )
-                       );
-               }
                return true;
        }
 
@@ -675,4 +669,60 @@
                }
                return true;
        }
+
+       /**
+        * After a page has been moved also update the item on the repo
+        * This only works with CentralAuth
+        *
+        * @see https://www.mediawiki.org/wiki/Manual:Hooks/TitleMoveComplete
+        *
+        * @param Title $oldTitle
+        * @param Title $newTitle
+        * @param User $user
+        * @param integer $pageid database ID of the page that's been moved
+        * @param integer $redirid database ID of the created redirect
+        *
+        * @return bool
+        */
+       public static function onTitleMoveComplete( $oldTitle, $newTitle, 
$user, $pageId, $redirectId ) {
+               wfProfileIn( __METHOD__ );
+               $repoDB = Settings::get( 'repoDatabase' );
+               $siteLinkLookup = 
WikibaseClient::getDefaultInstance()->getStore()->getSiteLinkTable();
+               $jobQueueGroup = \JobQueueGroup::singleton( $repoDB );
+
+               if ( !$jobQueueGroup ) {
+                       wfLogWarning( "Failed to acquire a JobQueueGroup for 
$repoDB" );
+                       wfProfileOut( __METHOD__ );
+                       return true;
+               }
+
+               $updateRepo = new UpdateRepoOnMove(
+                       $repoDB,
+                       $siteLinkLookup,
+                       $user,
+                       Settings::get( 'siteGlobalID' ),
+                       $oldTitle,
+                       $newTitle
+               );
+
+               if ( !$updateRepo || !$updateRepo->getEntityId() || 
!$updateRepo->userIsValidOnRepo() ) {
+                       wfProfileOut( __METHOD__ );
+                       return true;
+               }
+
+               try {
+                       $updateRepo->injectJob( $jobQueueGroup );
+
+                       // To be able to find out about this in the 
SpecialMovepageAfterMove hook
+                       $newTitle->wikibasePushedMoveToRepo = true;
+               } catch( \RuntimeException $e ) {
+                       // This is not a reason to let an exception bubble up, 
we just
+                       // show a message to the user that the Wikibase item 
needs to be
+                       // manually updated.
+                       wfLogWarning( $e->getMessage() );
+               }
+
+               wfProfileOut( __METHOD__ );
+               return true;
+       }
 }
diff --git a/client/WikibaseClient.i18n.php b/client/WikibaseClient.i18n.php
index 5ee9c38..ac25845 100644
--- a/client/WikibaseClient.i18n.php
+++ b/client/WikibaseClient.i18n.php
@@ -24,6 +24,7 @@
        'wikibase-client-desc' => 'Client for the Wikibase extension',
        'specialpages-group-wikibaseclient' => 'Wikidata client',
        'wikibase-after-page-move' => 'You may also [$1 update] the associated 
Wikidata item to maintain language links on moved page.',
+       'wikibase-after-page-move-queued' => 'The [$1 Wikidata item] associated 
with this page will be automatically updated soon.',
        'wikibase-comment-remove' => 'Associated Wikidata item deleted. 
Language links removed.',
        'wikibase-comment-linked' => 'A Wikidata item has been linked to this 
page.',
        'wikibase-comment-unlink' => 'This page has been unlinked from Wikidata 
item. Language links removed.',
@@ -90,6 +91,10 @@
 
 Parameters:
 * $1 - the link for the associated Wikibase item.',
+       'wikibase-after-page-move-queued' => 'Message on [[Special:MovePage]] 
on submit and successful move, telling the user that the Wikidata item 
belonging to the page will be automatically updated soon.
+
+Parameters:
+* $1 - the link for the associated Wikibase item.',
        'wikibase-comment-remove' => 'Autocomment message for client (e.g. 
Wikipedia) recent changes when a Wikidata item connected to a page gets 
deleted. This results in all the language links being removed from the page on 
the client.',
        'wikibase-comment-linked' => 'Autocomment message in the client for 
when a Wikidata item is linked to a page in the client.',
        'wikibase-comment-unlink' => 'Autocomment message for client (e.g. 
Wikipedia) recent changes when a site link to a page gets removed. This results 
in the associated item being disconnected from the client page and all the 
language links being removed.',
diff --git a/client/WikibaseClient.php b/client/WikibaseClient.php
index 2583f0d..59eed3d 100644
--- a/client/WikibaseClient.php
+++ b/client/WikibaseClient.php
@@ -84,9 +84,14 @@
        $wgAutoloadClasses['Scribunto_LuaWikibaseLibrary']      = $dir . 
'includes/WikibaseLibrary.php';
        $wgAutoloadClasses['Wikibase\PageUpdater']      = $dir . 
'includes/PageUpdater.php';
        $wgAutoloadClasses['Wikibase\WikiPageUpdater']  = $dir . 
'includes/WikiPageUpdater.php';
+       $wgAutoloadClasses['Wikibase\UpdateRepo']       = $dir . 
'includes/UpdateRepo.php';
+       $wgAutoloadClasses['Wikibase\UpdateRepoOnMove']         = $dir . 
'includes/UpdateRepoOnMove.php';
 
        // includes/api
        $wgAutoloadClasses['Wikibase\ApiClientInfo']            = $dir . 
'includes/api/ApiClientInfo.php';
+
+       // includes/hooks
+       $wgAutoloadClasses['Wikibase\Client\MovePageNotice']    = $dir . 
'includes/hooks/MovePageNotice.php';
 
        // includes/modules
        $wgAutoloadClasses['Wikibase\SiteModule']  = $dir . 
'includes/modules/SiteModule.php';
@@ -134,11 +139,12 @@
        $wgHooks['BeforePageDisplay'][]                         = 
'\Wikibase\ClientHooks::onBeforePageDisplay';
        $wgHooks['ScribuntoExternalLibraries'][]      = 
'\Wikibase\ClientHooks::onScribuntoExternalLibraries';
        $wgHooks['SpecialWatchlistFilters'][]          = 
'\Wikibase\ClientHooks::onSpecialWatchlistFilters';
+       $wgHooks['InfoAction'][]                                                
                = '\Wikibase\ClientHooks::onInfoAction';
+       $wgHooks['TitleMoveComplete'][]          = 
'\Wikibase\ClientHooks::onTitleMoveComplete';
 
        // extension hooks
        $wgHooks['WikibaseDeleteData'][]                                    = 
'\Wikibase\ClientHooks::onWikibaseDeleteData';
        $wgHooks['WikibaseRebuildData'][]                                   = 
'\Wikibase\ClientHooks::onWikibaseRebuildData';
-       $wgHooks['InfoAction'][]                                                
                = '\Wikibase\ClientHooks::onInfoAction';
 
        // api modules
        $wgAPIMetaModules['wikibase'] = 'Wikibase\ApiClientInfo';
diff --git a/client/includes/UpdateRepo.php b/client/includes/UpdateRepo.php
new file mode 100644
index 0000000..6cc0ab9
--- /dev/null
+++ b/client/includes/UpdateRepo.php
@@ -0,0 +1,149 @@
+<?php
+
+namespace Wikibase;
+use Wikibase\Client\WikibaseClient;
+use Wikibase\DataModel\SimpleSiteLink;
+
+/**
+ * Provides logic to update the repo after certain changes have been
+ * performed in the client (like page moves).
+ *
+ * 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
+ *
+ * @since 0.4
+ *
+ * @file
+ * @ingroup WikibaseClient
+ *
+ * @licence GNU GPL v2+
+ * @author Marius Hoch < [email protected] >
+ */
+abstract class UpdateRepo {
+
+       /**
+        * @var string
+        */
+       protected $repoDB;
+
+       /**
+        * @var \User
+        */
+       protected $user;
+
+       /**
+        * @var \SiteLinkLookup
+        */
+       protected $siteLinkLookup;
+
+       /**
+        * @var string
+        */
+       protected $siteId;
+
+       /**
+        * @var \Title
+        */
+       protected $title;
+
+       /**
+        * @param string $repoDB Database name of the repo
+        * @param SiteLinkLookup $siteLinkLookup
+        * @param \User $user
+        * @param string $siteId Global id of the client wiki
+        * @param \Title $title Title in the client that has been changed
+        */
+       public function __construct( $repoDB, $siteLinkLookup, $user, $siteId, 
$title ) {
+               $this->repoDB = $repoDB;
+               $this->siteLinkLookup = $siteLinkLookup;
+               $this->user = $user;
+               $this->siteId = $siteId;
+               $this->title = $title;
+       }
+
+       /**
+        * Get the EntityId that we want to update
+        *
+        * @return EntityId|null
+        */
+       public function getEntityId() {
+               return $this->siteLinkLookup->getEntityIdForSiteLink(
+                       new SimpleSiteLink(
+                               $this->siteId,
+                               $this->title->getFullText()
+                       )
+               );
+       }
+
+       /**
+        * Find out whether the user also exists on the repo and belongs to the
+        * same global account (uses CentralAuth).
+        *
+        * @return bool
+        */
+       public function userIsValidOnRepo() {
+               if ( !class_exists( 'CentralAuthUser' ) ) {
+                       // We can't do anything without CentralAuth as there's 
no way to verify that
+                       // the local user equals the repo one with the same name
+                       return false;
+               }
+
+               $caUser = \CentralAuthUser::getInstance( $this->user );
+               if ( !$caUser || !$caUser->exists() ) {
+                       // The current user doesn't have a central account
+                       return false;
+               }
+
+               // XXX: repoDatabase == CentralAuth site id?!!
+               if ( !$caUser->isAttached() || !$caUser->attachedOn( 
$this->repoDB ) ) {
+                       // Either the user account on this wiki or the one on 
the repo do not exist
+                       // or they aren't connected
+                       return false;
+               }
+
+               return true;
+       }
+
+       /**
+        * Inject the current job into the job queue of the repo
+        *
+        * @throws \RuntimeException
+        *
+        * @param \JobQueueGroup $jobQueueGroup
+        */
+       public function injectJob( \JobQueueGroup $jobQueueGroup ) {
+               wfProfileIn( __METHOD__ );
+
+               $job = $this->createJob();
+
+               wfProfileIn( __METHOD__ . '#push' );
+               $ok = $jobQueueGroup->push( $job );
+               wfProfileOut( __METHOD__ . '#push' );
+
+               if ( !$ok ) {
+                       wfProfileOut( __METHOD__ );
+                       throw new \RuntimeException( "Failed to push job to job 
queue" );
+               }
+
+               wfProfileOut( __METHOD__ );
+       }
+
+       /**
+        * Returns a new job for updating the repo.
+        *
+        * @return \Job
+        */
+       abstract public function createJob();
+}
diff --git a/client/includes/UpdateRepoOnMove.php 
b/client/includes/UpdateRepoOnMove.php
new file mode 100644
index 0000000..9a162e0
--- /dev/null
+++ b/client/includes/UpdateRepoOnMove.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Wikibase;
+
+/**
+ * Provides logic to update the repo after page moves in the client.
+ *
+ * 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
+ *
+ * @since 0.4
+ *
+ * @file
+ * @ingroup WikibaseClient
+ *
+ * @licence GNU GPL v2+
+ * @author Marius Hoch < [email protected] >
+ */
+class UpdateRepoOnMove extends UpdateRepo {
+
+       /**
+        * @var \Title
+        */
+       protected $newTitle;
+
+       /**
+        * @param string $repoDB Database name of the repo
+        * @param SiteLinkLookup $siteLinkLookup
+        * @param \User $user
+        * @param string $siteId Global id of the client wiki
+        * @param \Title $oldTitle
+        * @param \Title $newTitle
+        */
+       public function __construct( $repoDB, $siteLinkLookup, $user, $siteId, 
$oldTitle, $newTitle ) {
+               parent::__construct( $repoDB, $siteLinkLookup, $user, $siteId, 
$oldTitle );
+               $this->newTitle = $newTitle;
+       }
+
+       /**
+        * Returns a new job for updating the repo.
+        *
+        * @return \Job
+        */
+       public function createJob() {
+               wfProfileIn( __METHOD__ );
+
+               $job = UpdateRepoOnMoveJob::newFromMove(
+                       $this->title,
+                       $this->newTitle,
+                       $this->getEntityId(),
+                       $this->user,
+                       $this->siteId
+               );
+
+               wfProfileOut( __METHOD__ );
+
+               return $job;
+       }
+}
diff --git a/client/includes/hooks/MovePageNotice.php 
b/client/includes/hooks/MovePageNotice.php
new file mode 100644
index 0000000..445ece4
--- /dev/null
+++ b/client/includes/hooks/MovePageNotice.php
@@ -0,0 +1,119 @@
+<?php
+
+namespace Wikibase\Client;
+use Wikibase\SiteLinkLookup;
+use Wikibase\RepoLinker;
+use Wikibase\DataModel\SimpleSiteLink;
+
+/**
+ * Adds a notice about the Wikibase Item belonging to the current page
+ * after a move (in case there's one).
+ *
+ * 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
+ *
+ * @since 0.4
+ *
+ * @file
+ * @ingroup WikibaseClient
+ *
+ * @licence GNU GPL v2+
+ * @author Marius Hoch < [email protected] >
+ */
+
+final class MovePageNotice {
+
+       /**
+        * @var \SiteLinkLookup
+        */
+       protected $siteLinkLookup;
+
+       /**
+        * @var string
+        */
+       protected $siteId;
+
+       /**
+        * @var RepoLinker
+        */
+       protected $repoLinker;
+
+       /**
+        * @param Wikibase\SiteLinkLookup $siteLinkLookup
+        * @param string $siteId Global id of the client wiki
+        * @param Wikibase\RepoLinker $repoLinker
+        */
+       public function __construct( SiteLinkLookup $siteLinkLookup, $siteId, 
RepoLinker $repoLinker ) {
+               $this->siteLinkLookup = $siteLinkLookup;
+               $this->siteId = $siteId;
+               $this->repoLinker = $repoLinker;
+       }
+
+       /**
+        * Create a repo link directly to the item.
+        * We can't use Special:ItemByTitle here as the item might have already 
been updated.
+        *
+        * @param \Title $title
+        *
+        * @return string|null
+        */
+       protected function getItemUrl( $title ) {
+               $entityId = $this->siteLinkLookup->getEntityIdForSiteLink(
+                       new SimpleSiteLink(
+                               $this->siteId,
+                               $title->getFullText()
+                       )
+               );
+
+               if ( !$entityId ) {
+                       return null;
+               }
+
+               return $this->repoLinker->repoItemUrl( $entityId );
+       }
+
+       /**
+        * Append the appropriate content to the page
+        *
+        * @param \OutputPage $output
+        * @param \Title $oldTitle Title of the page before the move
+        * @param \Title $newTitle Title of the page after the move
+        */
+       public function reportRepoUpdate( \OutputPage $out, \Title $oldTitle, 
\Title $newTitle ) {
+               $itemLink = $this->getItemUrl( $oldTitle );
+
+               if ( !$itemLink ) {
+                       return;
+               }
+
+               if ( isset( $newTitle->wikibasePushedMoveToRepo ) ) {
+                       // We're going to update the item using the repo job 
queue \o/
+                       $msg = 'wikibase-after-page-move-queued';
+               } else {
+                       // The user has to update the item per hand for some 
reason
+                       $msg = 'wikibase-after-page-move';
+               }
+
+               $out->addModules( 'wikibase.client.page-move' );
+               $out->addHTML(
+                       \Html::rawElement(
+                               'div',
+                               array( 'id' => 'wbc-after-page-move',
+                                               'class' => 'plainlinks' ),
+                               wfMessage( $msg, $itemLink )->parse()
+                       )
+               );
+       }
+}
diff --git a/client/tests/phpunit/includes/UpdateRepoOnMoveTest.php 
b/client/tests/phpunit/includes/UpdateRepoOnMoveTest.php
new file mode 100644
index 0000000..907df77
--- /dev/null
+++ b/client/tests/phpunit/includes/UpdateRepoOnMoveTest.php
@@ -0,0 +1,141 @@
+<?php
+namespace Wikibase\Test;
+
+use Wikibase\UpdateRepoOnMove;
+use Wikibase\Client\WikibaseClient;
+use Wikibase\Settings;
+
+/**
+ * Tests for the UpdateRepoOnMove class.
+ *
+ * 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
+ * @since 0.4
+ *
+ * @ingroup WikibaseClient
+ * @ingroup Test
+ *
+ * @group WikibaseClient
+ *
+ * @licence GNU GPL v2+
+ * @author Marius Hoch < [email protected] >
+ */
+class UpdateRepoOnMoveTest extends \MediaWikiTestCase {
+
+       /**
+        * Return some fake data for testing
+        *
+        * @return array
+        */
+       protected function getFakeMoveData() {
+               static $ret = array();
+
+               if ( !$ret ) {
+                       $ret = array(
+                               'repoDB' => wfWikiID(),
+                               'siteLinkLookup' => 
WikibaseClient::getDefaultInstance()->getStore()->getSiteLinkTable(),
+                               'user' => \User::newFromName( 
'RandomUserWhichDoesntExist' ),
+                               'siteId' => Settings::get( 'siteGlobalID' ),
+                               'oldTitle' => \Title::newFromText( 
'ThisOneDoesntExist' ),
+                               'newTitle' => \Title::newFromText( 'Bar' )
+                       );
+               }
+
+               return $ret;
+       }
+
+       /**
+        * Get a new object which thinks we're both the repo and client
+        *
+        * @return UpdateRepoOnMove
+        */
+       protected function getNewLocal() {
+               $moveData = $this->getFakeMoveData();
+
+               $updateRepo = new UpdateRepoOnMove(
+                       $moveData['repoDB'],
+                       $moveData['siteLinkLookup'],
+                       $moveData['user'],
+                       $moveData['siteId'],
+                       $moveData['oldTitle'],
+                       $moveData['newTitle']
+               );
+
+               return $updateRepo;
+       }
+
+       /**
+        * Get a JobQueueGroup mock for the use in UpdateRepo::injectJob.
+        *
+        * @param \Job $expectedJob The job that is expected to be pushed
+        * @param bool $success Whether the push will succeed
+        *
+        * @return object
+        */
+       protected function getJobQueueGroupMock( $expectedJob, $success ) {
+               $jobQueueGroupMock = $this->getMockBuilder( '\JobQueueGroup' )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $jobQueueGroupMock->expects( $this->once() )
+                       ->method( 'push' )
+                       ->will( $this->returnValue( $success ) )
+                       ->with( $this->equalTo( $expectedJob ) );
+
+               return $jobQueueGroupMock;
+       }
+
+       public function testUserIsValidOnRepo() {
+               $updateRepo = $this->getNewLocal();
+
+               $this->assertFalse( $updateRepo->userIsValidOnRepo() );
+       }
+
+       /**
+        * Create a new job and verify the set params
+        */
+       public function testCreateJob() {
+               $updateRepo = $this->getNewLocal();
+               $job = $updateRepo->createJob();
+
+               $moveData = $this->getFakeMoveData();
+               $this->assertInstanceOf( 'Job', $job );
+               $this->assertEquals( 'UpdateRepoOnMove', $job->getType() );
+
+               $params = $job->getParams();
+               $this->assertEquals( $moveData['siteId'], $params['siteId'] );
+               $this->assertEquals( $moveData['oldTitle'], $params['oldTitle'] 
);
+               $this->assertEquals( $moveData['newTitle'], $params['newTitle'] 
);
+               $this->assertEquals( $moveData['user'], $params['user'] );
+               $this->assertTrue( array_key_exists( 'entityId', $params ) );
+       }
+
+       public function testInjectJob() {
+               $updateRepo = $this->getNewLocal();
+               $job = $updateRepo->createJob();
+
+               $jobQueueGroupMock = $this->getJobQueueGroupMock( $job, true );
+
+               $updateRepo->injectJob( $jobQueueGroupMock );
+
+               // This is supposed to throw an exception in case it fails
+               $jobQueueGroupMock = $this->getJobQueueGroupMock( $job, false );
+               $this->setExpectedException( 'RuntimeException' );
+
+               $updateRepo->injectJob( $jobQueueGroupMock );
+       }
+}
diff --git a/lib/WikibaseLib.classes.php b/lib/WikibaseLib.classes.php
index c61b578..26efe08 100644
--- a/lib/WikibaseLib.classes.php
+++ b/lib/WikibaseLib.classes.php
@@ -36,6 +36,7 @@
                'Wikibase\Arrayalizer' => 'includes/Arrayalizer.php',
                'Wikibase\ChangeNotifier' => 'includes/ChangeNotifier.php',
                'Wikibase\ChangeNotificationJob' => 
'includes/ChangeNotificationJob.php',
+               'Wikibase\UpdateRepoOnMoveJob' => 
'includes/UpdateRepoOnMoveJob.php',
                'Wikibase\ChangesTable' => 'includes/ChangesTable.php',
                'Wikibase\DiffOpValueFormatter' => 
'includes/DiffOpValueFormatter.php',
                'Wikibase\DiffView' => 'includes/DiffView.php',
diff --git a/lib/WikibaseLib.php b/lib/WikibaseLib.php
index 8fe59e8..3ce122c 100644
--- a/lib/WikibaseLib.php
+++ b/lib/WikibaseLib.php
@@ -113,6 +113,7 @@
        $wgValueParsers['wikibase-entityid'] = 'Wikibase\Lib\EntityIdParser';
        $wgDataValues['wikibase-entityid'] = 'Wikibase\EntityId';
        $wgJobClasses['ChangeNotification'] = 'Wikibase\ChangeNotificationJob';
+       $wgJobClasses['UpdateRepoOnMove'] = 'Wikibase\UpdateRepoOnMoveJob';
 
        // Hooks
        $wgHooks['UnitTestsList'][]                                             
        = 'Wikibase\LibHooks::registerPhpUnitTests';
diff --git a/lib/includes/UpdateRepoOnMoveJob.php 
b/lib/includes/UpdateRepoOnMoveJob.php
new file mode 100644
index 0000000..7a99c94
--- /dev/null
+++ b/lib/includes/UpdateRepoOnMoveJob.php
@@ -0,0 +1,260 @@
+<?php
+
+namespace Wikibase;
+use Wikibase\DataModel\SimpleSiteLink;
+use Wikibase\Repo\WikibaseRepo;
+
+/**
+ * Job for updating the repo after a page on the client has been moved.
+ *
+ * This needs to be in lib as the client needs it for injecting it and
+ * the repo to execute it.
+ *
+ * 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
+ *
+ * @since 0.4
+ *
+ * @file
+ * @ingroup WikibaseLib
+ *
+ * @licence GNU GPL v2+
+ * @author Marius Hoch < [email protected] >
+ */
+class UpdateRepoOnMoveJob extends \Job {
+
+       /**
+        * Constructs a UpdateRepoOnMoveJob propagating a page move to the repo
+        *
+        * @note: This is for use by Job::factory, don't call it directly;
+        *           use newFrom*() instead.
+        *
+        * @note: the constructor's signature is dictated by Job::factory, so 
we'll have to
+        *           live with it even though it's rather ugly for our use case.
+        *
+        * @see Job::factory.
+        *
+        * @param \Title $title Ignored
+        * @param array|bool $params
+        * @param integer $id
+        */
+       public function __construct( \Title $title, $params = false, $id = 0 ) {
+               parent::__construct( 'UpdateRepoOnMove', $title, $params, $id );
+       }
+
+       /**
+        * Creates a UpdateRepoOnMoveJob representing the given move.
+        *
+        * @param \Title $oldTitle
+        * @param \Title $newTitle
+        * @param EntityId entityId
+        * @param \User $user User who moved the page
+        * @param string $globalId Global id of the site from which the is 
coming
+        * @param array|bool $params extra job parameters, see Job::__construct 
(default: false).
+        *
+        * @return \Wikibase\UpdateRepoOnMoveJob: the job
+        */
+       public static function newFromMove( $oldTitle, $newTitle, $entityId, 
$user, $globalId, $params = false ) {
+               wfProfileIn( __METHOD__ );
+
+               if ( $params === false ) {
+                       $params = array();
+               }
+
+               $params['siteId'] = $globalId;
+               $params['entityId'] = $entityId;
+               $params['oldTitle'] = $oldTitle->getPrefixedDBkey();
+               $params['newTitle'] = $newTitle->getPrefixedDBkey();
+               $params['user'] = $user->getName();
+
+               // The Title object isn't really being used but \Job demands 
it... so we just insert something
+               // A Title belonging to the entity on the repo would be more 
sane, but it doesn't really matter
+               $job = new self( $newTitle, $params );
+
+               wfProfileOut( __METHOD__ );
+               return $job;
+       }
+
+       /**
+        * Get an EntityContentFactory object
+        *
+        * @return EntityContentFactory
+        */
+       protected function getEntityContentFactory() {
+               return 
WikibaseRepo::getDefaultInstance()->getEntityContentFactory();
+       }
+
+       /**
+        * Get a Site object for a global id
+        *
+        * @param string $globalId
+        *
+        * @return \Site
+        */
+       protected function getSite( $globalId ) {
+               $sitesTable = \SiteSQLStore::newInstance();
+               return $sitesTable->getSite( $globalId );
+       }
+
+       /**
+        * Get a SimpleSiteLink for a specific item and site
+        *
+        * @param Item $item
+        * @param string $globalId
+        *
+        * @return Wikibase\DataModel\SimpleSiteLink|null
+        */
+       protected function getSimpleSiteLink( $item, $globalId ) {
+               try {
+                       return $item->getSimpleSiteLink( $globalId );
+               } catch( \OutOfBoundsException $e ) {
+                       return null;
+               }
+       }
+
+       /**
+        * Get a Summary object for the edit
+        *
+        * @param string $globalId Global id of the target site
+        * @param string $oldPage
+        * @param string $newPage
+        *
+        * @return Summary
+        */
+       public function getSummary( $globalId, $oldPage, $newPage ) {
+               return new Summary(
+                       'clientsitelink',
+                       'update',
+                       $globalId,
+                       array(
+                               $globalId . ":$oldPage",
+                               $globalId . ":$newPage",
+                       )
+               );
+       }
+
+       /**
+        * Update the siteLink on the repo to reflect the change in the client
+        *
+        * @param string $siteId Id of the client the change comes from
+        * @param EntityId $entityId
+        * @param string $oldPage
+        * @param string $newPage
+        * @param \User $user User who we'll attribute the update to
+        *
+        * @return bool Whether something changed
+        */
+       public function updateSiteLink( $siteId, $entityId, $oldPage, $newPage, 
$user ) {
+               wfProfileIn( __METHOD__ );
+
+               $itemContent = $this->getEntityContentFactory()->getFromId( 
$entityId );
+               if ( !$itemContent ) {
+                       // The entity assigned with the moved page can't be 
found
+                       wfDebugLog( __CLASS__, __FUNCTION__ . ": entity with id 
" . $entityId->getPrefixedId() . " not found" );
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+
+               $editEntity = new EditEntity( $itemContent, $user, true );
+
+               $site = $this->getSite( $siteId );
+
+               $item = $itemContent->getItem();
+               $oldSiteLink = $this->getSimpleSiteLink( $item, $siteId );
+               if ( !$oldSiteLink || $oldSiteLink->getPageName() !== $oldPage 
) {
+                       // Probably something changed since the job has been 
inserted
+                       wfDebugLog( __CLASS__, __FUNCTION__ . ": The site link 
to " . $siteId . " is no longer $oldPage" );
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+
+               // Normalize the name again, just in case the page has been 
updated in the mean time
+               $newPage = $site->normalizePageName( $newPage );
+               if ( !$newPage ) {
+                       wfDebugLog( __CLASS__, __FUNCTION__ . ": Normalizing 
the page name $newPage failed" );
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+
+               $siteLink = new SimpleSiteLink(
+                       $siteId,
+                       $newPage
+               );
+
+               $summary = $this->getSummary( $siteId, $oldPage, $newPage );
+
+               return $this->doUpdateSiteLink( $itemContent, $siteLink, 
$editEntity, $summary, $user );
+       }
+
+       /**
+        * Update the given item with the given sitelink
+        *
+        * @param ItemContent $itemContent
+        * @param Wikibase\DataModel\SimpleSiteLink $siteLink
+        * @param EditEntity $editEntity
+        * @param Summary $summary
+        * @param \User $user User who we'll attribute the update to
+        *
+        * @return bool Whether something changed
+        */
+       public function doUpdateSiteLink( $itemContent, $siteLink, $editEntity, 
$summary, $user ) {
+               $item = $itemContent->getItem();
+
+               $item->addSimpleSiteLink( $siteLink );
+
+               $status = $editEntity->attemptSave(
+                       $summary->toString(),
+                       EDIT_UPDATE,
+                       false,
+                       // Don't (un)watch any pages here, as the user didn't 
explicitly kick this off
+                       $user->isWatched( $itemContent->getTitle() )
+               );
+
+               wfProfileOut( __METHOD__ );
+
+               // TODO: Analyze what happened and let the user know in case a 
manual fix could be needed
+               return $status->isOK();
+       }
+
+       /**
+        * Run the job
+        *
+        * @return boolean success
+        */
+       public function run() {
+               wfProfileIn( __METHOD__ );
+               $params = $this->getParams();
+
+               $user = \User::newFromName( $params['user'] );
+               if ( !$user || !$user->isLoggedIn() ) {
+                       // This should never happen as we check with CentralAuth
+                       // that the user actually does exist
+                       wfLogWarning( 'User ' . $params['user'] . " doesn't 
exist while CentralAuth pretends it does" );
+                       wfProfileOut( __METHOD__ );
+                       return true;
+               }
+
+               $this->updateSiteLink(
+                       $params['siteId'],
+                       $params['entityId'],
+                       $params['oldTitle'],
+                       $params['newTitle'],
+                       $user
+               );
+
+               wfProfileOut( __METHOD__ );
+               return true;
+       }
+}
diff --git a/lib/tests/phpunit/UpdateRepoOnMoveJobTest.php 
b/lib/tests/phpunit/UpdateRepoOnMoveJobTest.php
new file mode 100644
index 0000000..352a742
--- /dev/null
+++ b/lib/tests/phpunit/UpdateRepoOnMoveJobTest.php
@@ -0,0 +1,96 @@
+<?php
+namespace Wikibase\Test;
+
+use Wikibase\UpdateRepoOnMoveJob;
+use Wikibase\EntityId;
+use Wikibase\Settings;
+
+/**
+ * Tests for the UpdateRepoOnMoveJob
+ *
+ * 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
+ * @since 0.4
+ *
+ * @ingroup Test
+ *
+ * @licence GNU GPL v2+
+ * @author Marius Hoch < [email protected] >
+ */
+class UpdateRepoOnMoveJobTest extends \MediaWikiTestCase {
+       /**
+        * @param array $moveData
+        *
+        * @return UpdateRepoOnMoveJob
+        */
+       public function getNewFromMove( $moveData ) {
+               return UpdateRepoOnMoveJob::newFromMove(
+                       $moveData['oldTitle'],
+                       $moveData['newTitle'],
+                       $moveData['entityId'],
+                       $moveData['user'],
+                       $moveData['siteId']
+               );
+       }
+
+       /**
+        * @return array
+        */
+       public function getSampleData() {
+               return array(
+                       'oldTitle' => \Title::newFromText( 'Foo' ),
+                       'newTitle' => \Title::newFromText( 'Bar' ),
+                       'entityId' => new EntityId( 'Item', 123 ),
+                       'user' => \User::newFromName( 
'RandomUserWhichDoesntExist' ),
+                       'siteId' => wfWikiID() // Doesn't really matter what we 
use here
+               );
+       }
+
+       /**
+        * Checks the type and params of a job created from a move
+        */
+       public function testNewFromMove() {
+               $moveData = $this->getSampleData();
+               $job = $this->getNewFromMove( $moveData );
+
+               $this->assertInstanceOf( 'Job', $job );
+               $this->assertEquals( 'UpdateRepoOnMove', $job->getType() );
+
+               $params = $job->getParams();
+               $this->assertEquals( $moveData['siteId'], $params['siteId'] );
+               $this->assertEquals( $moveData['entityId'], $params['entityId'] 
);
+               $this->assertEquals( $moveData['oldTitle'], $params['oldTitle'] 
);
+               $this->assertEquals( $moveData['newTitle'], $params['newTitle'] 
);
+               $this->assertEquals( $moveData['user'], $params['user'] );
+       }
+
+       /**
+        * @group WikibaseRepoTest
+        */
+       public function testGetSummary() {
+               $moveData = $this->getSampleData();
+               $job = $this->getNewFromMove( $moveData );
+
+               $summary = $job->getSummary( 'SiteID', 'Test', 'MoarTest' );
+
+               $this->assertEquals(
+                       '/* 
clientsitelink-update:0|SiteID|SiteID:Test|SiteID:MoarTest */',
+                       $summary->toString()
+               );
+
+       }
+}
diff --git a/repo/Wikibase.i18n.php b/repo/Wikibase.i18n.php
index 0cd7f11..07369a4 100644
--- a/repo/Wikibase.i18n.php
+++ b/repo/Wikibase.i18n.php
@@ -321,6 +321,7 @@
        'wikibase-item-summary-wbsetclaim-update-qualifiers' => 'Changed 
{{PLURAL:$4|one qualifier|$4 qualifiers}} of {{PLURAL:$3|claim|claims}}',
        'wikibase-item-summary-wbsetclaim-update-references' => 'Changed 
{{PLURAL:$4|one reference|$4 references}} of {{PLURAL:$3|claim|claims}}',
        'wikibase-item-summary-wbsetclaim-update-rank' => 'Changed rank of 
{{PLURAL:$3|claim|claims}}',
+       'wikibase-item-summary-clientsitelink-update' => 'Page moved from [$3] 
to [$4]',
 
        // property - summary and autocomment, see docs/summaries.txt
        'wikibase-property-summary-wbcreate-new' => 'Created a new property', 
// legacy, backwards compatibility
@@ -1003,6 +1004,10 @@
 * $4 - number of references changed',
        'wikibase-item-summary-wbsetclaim-update-rank' => 'Automatic edit 
summary generated when modifying the rank of a claim using setclaim. Parameters:
 * $3 - number of claims changed',
+       'wikibase-item-summary-clientsitelink-update' => '{{wikibase summary 
messages|sitelinks|Automatic edit summary (autocomment) when a language link 
has been automatically updated after a page move. Parameters:
+* $3 - siteid and name of the old page name (like enwiki:Foo)
+* $4 - siteid and name of the new page name (like enwiki:Bar)
+       }}',
        'wikibase-property-summary-wbcreate-new' => '{{wikibase summary 
messages|item|Automatic edit summary generated when creating a new item. This 
is for backwards compatibility for edits already made and in the database with 
this message.}}',
        'wikibase-property-summary-wbeditentity-create' => 'Automatic edit 
summary generated when creating a new property.',
        'wikibase-property-summary-wbeditentity-update' => 'Automatic edit 
summary generated when updating an existing property.',
diff --git a/repo/includes/EditEntity.php b/repo/includes/EditEntity.php
index 73457ee..fde90cf 100644
--- a/repo/includes/EditEntity.php
+++ b/repo/includes/EditEntity.php
@@ -601,7 +601,7 @@
         * Attempts to save the new entity content, chile first checking for 
permissions, edit conflicts, etc.
         *
         * @param String      $summary    The edit summary
-        * @param int         $flags      The edit flags (see 
WikiPage::toEditContent)
+        * @param int         $flags      The edit flags (see 
WikiPage::doEditContent)
         * @param String|bool $token      Edit token to check, or false to 
disable the token check.
         *                                Null will fail the token text, as 
will the empty string.
         * @param bool|null $watch        Whether the user wants to watch the 
entity.
diff --git a/repo/includes/api/ApiWikibase.php 
b/repo/includes/api/ApiWikibase.php
index 894bb7c..bcfc829 100644
--- a/repo/includes/api/ApiWikibase.php
+++ b/repo/includes/api/ApiWikibase.php
@@ -704,7 +704,7 @@
         *
         * @param EntityContent $content  The content to save
         * @param String $summary    The edit summary
-        * @param int    $flags      The edit flags (see 
WikiPage::toEditContent)
+        * @param int    $flags      The edit flags (see 
WikiPage::doEditContent)
         *
         * @return Status the status of the save operation, as returned by 
EditEntity::attemptSave()
         * @see  EditEntity::attemptSave()

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

Gerrit-MessageType: merged
Gerrit-Change-Id: Iad9bd7065bb0874ebf52e65a8558fc8f091bdeec
Gerrit-PatchSet: 13
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: Hoo man <[email protected]>
Gerrit-Reviewer: Aude <[email protected]>
Gerrit-Reviewer: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Hoo man <[email protected]>
Gerrit-Reviewer: Jeroen De Dauw <[email protected]>
Gerrit-Reviewer: Mattflaschen <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: Tobias Gritschacher <[email protected]>
Gerrit-Reviewer: jenkins-bot

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

Reply via email to