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
