jenkins-bot has submitted this change and it was merged.
Change subject: Replace ExternalRecentChange with RecentChangeFactory.
......................................................................
Replace ExternalRecentChange with RecentChangeFactory.
This moves the logic for constructing RC entries from EntityChanges
from ChangeHandler and ExternalRecentChange into RecentChangeFactory and
RecentChangeDuplicateDetector. ExternalRecentChange is dropped.
NOTE: This touches a lot of lines and files, but does not introduce
new logic. Also, this change adds over 850 lines worth of test cases.
Bug: T111521
Change-Id: I8edc4177d24a8f8c6b10b84107c3ee119a21bee6
---
M client/includes/Changes/ChangeHandler.php
M client/includes/Changes/PageUpdater.php
M client/includes/Changes/WikiPageUpdater.php
M client/includes/WikibaseClient.php
D client/includes/recentchanges/ExternalRecentChange.php
A client/includes/recentchanges/RecentChangeFactory.php
A client/includes/recentchanges/RecentChangesDuplicateDetector.php
M client/tests/phpunit/includes/Changes/ChangeHandlerTest.php
M client/tests/phpunit/includes/Changes/MockPageUpdater.php
A client/tests/phpunit/includes/Changes/WikiPageUpdaterTest.php
M client/tests/phpunit/includes/recentchanges/ChangeLineFormatterTest.php
A client/tests/phpunit/includes/recentchanges/RecentChangeFactoryTest.php
A
client/tests/phpunit/includes/recentchanges/RecentChangesDuplicateDetectorTest.php
13 files changed, 1,342 insertions(+), 468 deletions(-)
Approvals:
Hoo man: Looks good to me, approved
jenkins-bot: Verified
diff --git a/client/includes/Changes/ChangeHandler.php
b/client/includes/Changes/ChangeHandler.php
index 83d91fb..d7c6b8f 100644
--- a/client/includes/Changes/ChangeHandler.php
+++ b/client/includes/Changes/ChangeHandler.php
@@ -5,8 +5,6 @@
use Exception;
use Hooks;
use InvalidArgumentException;
-use Language;
-use Message;
use MWException;
use SiteStore;
use Title;
@@ -15,9 +13,6 @@
use Wikibase\Client\Usage\EntityUsage;
use Wikibase\Client\Usage\PageEntityUsages;
use Wikibase\EntityChange;
-use Wikibase\ItemChange;
-use Wikibase\SiteLinkCommentCreator;
-use Wikimedia\Assert\Assert;
/**
* Interface for change handling. Whenever a change is detected,
@@ -77,19 +72,9 @@
private $changeListTransformer;
/**
- * @var Language
- */
- private $language;
-
- /**
* @var SiteStore
*/
private $siteStore;
-
- /**
- * @var string
- */
- private $localSiteId;
/**
* @var string
@@ -106,7 +91,6 @@
* @param TitleFactory $titleFactory
* @param PageUpdater $updater
* @param ChangeListTransformer $changeListTransformer
- * @param Language $language
* @param SiteStore $siteStore
* @param string $localSiteId
* @param string $repoId
@@ -118,16 +102,10 @@
TitleFactory $titleFactory,
PageUpdater $updater,
ChangeListTransformer $changeListTransformer,
- Language $language,
SiteStore $siteStore,
- $localSiteId,
$repoId,
$injectRecentChanges = true
) {
- if ( !is_string( $localSiteId ) ) {
- throw new InvalidArgumentException( '$localSiteId must
be a string' );
- }
-
if ( !is_bool( $injectRecentChanges ) ) {
throw new InvalidArgumentException(
'$injectRecentChanges must be a bool' );
}
@@ -136,9 +114,7 @@
$this->titleFactory = $titleFactory;
$this->updater = $updater;
$this->changeListTransformer = $changeListTransformer;
- $this->language = $language;
$this->siteStore = $siteStore;
- $this->localSiteId = $localSiteId;
$this->repoId = $repoId;
$this->injectRecentChanges = $injectRecentChanges;
}
@@ -276,14 +252,8 @@
break;
case self::RC_ENTRY_ACTION:
- $rcAttribs = $this->getRCAttributes( $change );
-
- if ( $rcAttribs !== false &&
$this->injectRecentChanges ) {
- //FIXME: The same change may be
reported to several target pages;
- // The comment we generate should
be adapted to the role that page
- // plays in the change, e.g. when
a sitelink changes from one page to another,
- // the link was effectively
removed from one and added to the other page.
- $this->updater->injectRCRecords(
$titlesToUpdate, $rcAttribs );
+ if ( $this->injectRecentChanges ) {
+ $this->updater->injectRCRecords(
$titlesToUpdate, $change );
}
break;
@@ -332,129 +302,6 @@
}
return $change->getId();
- }
-
- /**
- * Constructs RC attributes for the given change
- *
- * @see ExternalRecentChange::buildAttributes
- *
- * @param EntityChange $change The Change that caused the update
- *
- * @return array[]|bool an array of RC attributes,
- * as understood by ExternalRecentChange::buildAttributes.
- */
- private function getRCAttributes( EntityChange $change ) {
- $rcinfo = $change->getMetadata();
-
- //@todo: add getFields() to the interface, or provide getters!
- $fields = $change->getFields();
- $fields['entity_type'] =
$change->getEntityId()->getEntityType();
-
- if ( isset( $fields['info']['changes'] ) ) {
- $changesForComment = $fields['info']['changes'];
- } else {
- $changesForComment = array( $change );
- }
-
- unset( $fields['info'] );
- $changeParams = array_merge( $fields, $rcinfo );
-
- if ( !isset( $changeParams['site_id'] ) ) {
- $changeParams['site_id'] = $this->repoId;
- }
-
- $comment = $this->getEditCommentMulti( $changesForComment );
-
- // Use keys known to ExternalRecentChange::buildAttributes.
- // FIXME: Simplify the way this is passed around.
- // FIXME: Move all this into a factory for RecentChange objects.
- // FIXME: ExternalRecentChange could be converted to such a
factory.
- return array(
- 'wikibase-repo-change' => $changeParams,
- 'comment' => $comment
- );
- }
-
- /**
- * Returns a human readable comment representing the given changes.
- *
- * @param EntityChange[] $changes
- *
- * @throws MWException
- * @return string
- */
- private function getEditCommentMulti( array $changes ) {
- $comments = array();
- $c = 0;
-
- foreach ( $changes as $change ) {
- $c++;
- $comments[] = $this->getEditComment( $change );
- }
-
- if ( $c === 0 ) {
- return '';
- } elseif ( $c === 1 ) {
- return reset( $comments );
- } else {
- //@todo: handle overly long lists nicely!
- return $this->language->semicolonList( $comments );
- }
- }
-
- /**
- * Returns a human readable comment representing the change.
- *
- * @since 0.4
- *
- * @param EntityChange $change the change to get a comment for
- *
- * @throws MWException
- * @return string
- */
- public function getEditComment( EntityChange $change ) {
- $siteLinkDiff = $change instanceof ItemChange
- ? $change->getSiteLinkDiff()
- : null;
-
- $editComment = '';
-
- if ( $siteLinkDiff !== null && !$siteLinkDiff->isEmpty() ) {
- $action = $change->getAction();
- $commentCreator = new SiteLinkCommentCreator(
$this->language, $this->siteStore, $this->localSiteId );
- $siteLinkComment = $commentCreator->getEditComment(
$siteLinkDiff, $action );
- $editComment = $siteLinkComment === null ? '' :
$siteLinkComment;
- }
-
- if ( $editComment === '' ) {
- $editComment = $change->getComment();
- }
-
- if ( $editComment === '' ) {
- // If there is no comment, use something generic. This
shouldn't happen.
- wfWarn( 'Failed to generate edit comment for
EntityChange' );
- $editComment = $this->msg( 'wikibase-comment-update'
)->text();
- }
-
- Assert::postcondition( is_string( $editComment ), '$editComment
must be a string' );
- return $editComment;
- }
-
- /**
- * @param string $key
- *
- * @return Message
- * @throws MWException
- */
- private function msg( $key ) {
- $params = func_get_args();
- array_shift( $params );
- if ( isset( $params[0] ) && is_array( $params[0] ) ) {
- $params = $params[0];
- }
-
- return wfMessage( $key, $params )->inLanguage( $this->language
);
}
}
diff --git a/client/includes/Changes/PageUpdater.php
b/client/includes/Changes/PageUpdater.php
index fbdb794..17888d7 100644
--- a/client/includes/Changes/PageUpdater.php
+++ b/client/includes/Changes/PageUpdater.php
@@ -3,6 +3,7 @@
namespace Wikibase\Client\Changes;
use Title;
+use Wikibase\EntityChange;
/**
* Service interface for triggering different kinds of page updates
@@ -50,8 +51,8 @@
* @since 0.5
*
* @param Title[] $titles
- * @param array $attribs
+ * @param EntityChange $change
*/
- public function injectRCRecords( array $titles, array $attribs );
+ public function injectRCRecords( array $titles, EntityChange $change );
}
diff --git a/client/includes/Changes/WikiPageUpdater.php
b/client/includes/Changes/WikiPageUpdater.php
index f898d45..98e120e 100644
--- a/client/includes/Changes/WikiPageUpdater.php
+++ b/client/includes/Changes/WikiPageUpdater.php
@@ -6,7 +6,9 @@
use JobQueueGroup;
use RefreshLinksJob;
use Title;
-use Wikibase\Client\RecentChanges\ExternalRecentChange;
+use Wikibase\Client\RecentChanges\RecentChangeFactory;
+use Wikibase\Client\RecentChanges\RecentChangesDuplicateDetector;
+use Wikibase\EntityChange;
/**
* Service object for triggering different kinds of page updates
@@ -20,6 +22,36 @@
* @author Daniel Kinzler
*/
class WikiPageUpdater implements PageUpdater {
+
+ /**
+ * @var JobQueueGroup
+ */
+ private $jobQueueGroup;
+
+ /**
+ * @var RecentChangeFactory
+ */
+ private $recentChangeFactory;
+
+ /**
+ * @var RecentChangesDuplicateDetector|null
+ */
+ private $recentChangesDuplicateDetector;
+
+ /**
+ * @param JobQueueGroup $jobQueueGroup
+ * @param RecentChangeFactory $recentChangeFactory
+ * @param RecentChangesDuplicateDetector|null
$recentChangesDuplicateDetector
+ */
+ public function __construct(
+ JobQueueGroup $jobQueueGroup,
+ RecentChangeFactory $recentChangeFactory,
+ RecentChangesDuplicateDetector $recentChangesDuplicateDetector
= null
+ ) {
+ $this->jobQueueGroup = $jobQueueGroup;
+ $this->recentChangeFactory = $recentChangeFactory;
+ $this->recentChangesDuplicateDetector =
$recentChangesDuplicateDetector;
+ }
/**
* Invalidates local cached of the given pages.
@@ -66,13 +98,13 @@
$job = new RefreshLinksJob(
$title,
- Job::newRootJobParams( //XXX: the right thing?
+ Job::newRootJobParams(
$title->getPrefixedDBkey()
)
);
- JobQueueGroup::singleton()->push( $job );
- JobQueueGroup::singleton()->deduplicateRootJob( $job );
+ $this->jobQueueGroup->push( $job );
+ $this->jobQueueGroup->deduplicateRootJob( $job );
}
}
@@ -80,18 +112,25 @@
* Injects an RC entry into the recentchanges, using the the given
title and attribs
*
* @param Title[] $titles
- * @param array $attribs
+ * @param EntityChange $change
*/
- public function injectRCRecords( array $titles, array $attribs ) {
+ public function injectRCRecords( array $titles, EntityChange $change ) {
+ $rcAttribs =
$this->recentChangeFactory->prepareChangeAttributes( $change );
+
+ // TODO: do this via the job queue, in batches, see T107722
foreach ( $titles as $title ) {
if ( !$title->exists() ) {
continue;
}
- $rc = ExternalRecentChange::newFromAttribs( $attribs,
$title );
+ $rc = $this->recentChangeFactory->newRecentChange(
$change, $title, $rcAttribs );
- wfDebugLog( __CLASS__, __FUNCTION__ . ": saving RC
entry for " . $title->getFullText() );
- $rc->save();
+ if ( $this->recentChangesDuplicateDetector &&
$this->recentChangesDuplicateDetector->changeExists( $rc ) ) {
+ wfDebugLog( __CLASS__, __FUNCTION__ . ":
skipping duplicate RC entry for " . $title->getFullText() );
+ } else {
+ wfDebugLog( __CLASS__, __FUNCTION__ . ": saving
RC entry for " . $title->getFullText() );
+ $rc->save();
+ }
}
}
diff --git a/client/includes/WikibaseClient.php
b/client/includes/WikibaseClient.php
index 0c42dd8..9dd09e0 100644
--- a/client/includes/WikibaseClient.php
+++ b/client/includes/WikibaseClient.php
@@ -7,6 +7,7 @@
use Deserializers\Deserializer;
use Exception;
use Hooks;
+use JobQueueGroup;
use Language;
use LogicException;
use MediaWikiSite;
@@ -22,6 +23,7 @@
use Wikibase\Client\DataAccess\PropertyIdResolver;
use
Wikibase\Client\DataAccess\PropertyParserFunction\StatementGroupRendererFactory;
use Wikibase\Client\DataAccess\PropertyParserFunction\Runner;
+use Wikibase\Client\RecentChanges\RecentChangeFactory;
use Wikibase\DataModel\Services\Lookup\RestrictedEntityLookup;
use Wikibase\Client\DataAccess\SnaksFinder;
use Wikibase\Client\Hooks\LanguageLinkBadgeDisplay;
@@ -61,6 +63,7 @@
use Wikibase\Lib\Interactors\TermIndexSearchInteractor;
use Wikibase\NamespaceChecker;
use Wikibase\SettingsArray;
+use Wikibase\SiteLinkCommentCreator;
use Wikibase\Store\BufferingTermLookup;
use Wikibase\StringNormalizer;
@@ -907,23 +910,55 @@
* @return ChangeHandler
*/
public function getChangeHandler() {
- $siteId = $this->getSite()->getGlobalId();
- $repoId = $this->settings->getSetting( 'repoSiteId' );
-
return new ChangeHandler(
$this->getAffectedPagesFinder(),
new TitleFactory(),
- new WikiPageUpdater(),
- new ChangeRunCoalescer(
- $this->getStore()->getEntityRevisionLookup(),
- $this->getEntityChangeFactory(),
- $siteId
- ),
+ $this->getWikiPageUpdater(),
+ $this->getChangeRunCoalescer(),
+ $this->getSiteStore(),
+ $this->settings->getSetting( 'injectRecentChanges' )
+ );
+ }
+
+ /**
+ * @return WikiPageUpdater
+ */
+ private function getWikiPageUpdater() {
+ return new WikiPageUpdater(
+ JobQueueGroup::singleton(),
+ $this->getRecentChangeFactory()
+ );
+ }
+
+ /**
+ * @return ChangeRunCoalescer
+ */
+ private function getChangeRunCoalescer() {
+ return new ChangeRunCoalescer(
+ $this->getStore()->getEntityRevisionLookup(),
+ $this->getEntityChangeFactory(),
+ $this->settings->getSetting( 'siteGlobalID' )
+ );
+ }
+
+ /**
+ * @return RecentChangeFactory
+ */
+ private function getRecentChangeFactory() {
+ return new RecentChangeFactory(
+ $this->getContentLanguage(),
+ $this->getSiteLinkCommentCreator()
+ );
+ }
+
+ /**
+ * @return SiteLinkCommentCreator
+ */
+ private function getSiteLinkCommentCreator() {
+ return new SiteLinkCommentCreator(
$this->getContentLanguage(),
$this->getSiteStore(),
- $siteId,
- $repoId,
- $this->settings->getSetting( 'injectRecentChanges' )
+ $this->settings->getSetting( 'siteGlobalID' )
);
}
diff --git a/client/includes/recentchanges/ExternalRecentChange.php
b/client/includes/recentchanges/ExternalRecentChange.php
deleted file mode 100644
index b95ccf6..0000000
--- a/client/includes/recentchanges/ExternalRecentChange.php
+++ /dev/null
@@ -1,197 +0,0 @@
-<?php
-
-namespace Wikibase\Client\RecentChanges;
-
-use DatabaseBase;
-use Linker;
-use MWException;
-use Title;
-
-/**
- * @since 0.5
- *
- * @todo test case!
- *
- * @licence GNU GPL v2+
- * @author Katie Filbert < [email protected] >
- */
-class ExternalRecentChange {
-
- public $mAttribs = array();
-
- /**
- * Builds a new external recent change object from attribute array
- *
- * @since 0.3
- *
- * @param array $attribs
- * @param Title $title
- *
- * @return ExternalRecentChange
- */
- public static function newFromAttribs( array $attribs, Title $title ) {
- $rc = new ExternalRecentChange;
- $rc->buildAttributes( $attribs, $title );
- return $rc;
- }
-
- /**
- * @return array
- */
- public function getAttributes() {
- return $this->mAttribs;
- }
-
- /**
- * Builds the attribute array for saving into recentchanges table
- *
- * @param array $attribs
- * @param Title $title
- */
- private function buildAttributes( array $attribs, Title $title ) {
- $metadata = $attribs['wikibase-repo-change'];
-
- $isBot = false;
- if ( array_key_exists( 'bot', $metadata ) ) {
- $isBot = $metadata['bot'];
- }
-
- // compatibility
- if ( array_key_exists( 'user_text', $metadata ) ) {
- $userText = $metadata['user_text'];
- } else {
- $userText = $metadata['rc_user_text'];
- }
-
- $repoId = isset( $metadata['site_id'] ) ? $metadata['site_id']
: null;
- $time = isset( $metadata['time'] ) ? $metadata['time'] :
wfTimestamp( TS_MW );
- $comment = isset( $attribs['comment'] ) ? $attribs['comment'] :
'';
-
- if ( !isset( $attribs['comment-html'] ) ) {
- //XXX: wrap Linker in something we can inject here
- $attribs['comment-html'] = Linker::formatComment(
$comment, $title, false, $repoId );
- }
-
- $this->mAttribs = array(
- 'rc_namespace' => $title->getNamespace(),
- 'rc_title' => $title->getDBkey(),
- 'rc_user' => 0,
- 'rc_user_text' => $userText,
- 'rc_type' => RC_EXTERNAL,
- 'rc_minor' => true, // for now, always consider these
minor
- 'rc_bot' => $isBot,
- 'rc_patrolled' => true,
- 'rc_old_len' => $title->getLength(),
- 'rc_new_len' => $title->getLength(),
- 'rc_this_oldid' => $title->getLatestRevID(),
- 'rc_last_oldid' => $title->getLatestRevID(),
- 'rc_params' => serialize( $attribs ),
- 'rc_cur_id' => $title->getArticleID(),
- 'rc_comment' => $comment,
- 'rc_timestamp' => $time,
- 'rc_log_action' => '',
- 'rc_source' => 'wb'
- );
- }
-
- /**
- * Get a param from wikibase-repo-change array in rc_params
- *
- * @since 0.4
- *
- * @param string $param metadata array key
- * @param array|string $rc_params
- *
- * @return mixed|bool
- */
- public function getParam( $param, $rc_params ) {
- if ( is_string( $rc_params ) ) {
- $rc_params = unserialize( $rc_params );
- }
-
- if ( is_array( $rc_params ) && array_key_exists(
'wikibase-repo-change', $rc_params ) ) {
- $metadata = $rc_params['wikibase-repo-change'];
- if ( is_array( $metadata ) && array_key_exists( $param,
$metadata ) ) {
- return $metadata[$param];
- }
- }
- return false;
- }
-
- /**
- * Checks if a recent change entry already exists in the recentchanges
table
- *
- * @since 0.4
- *
- * @param DatabaseBase $db
- *
- * @throws MWException
- *
- * @return bool
- */
- public function exists( DatabaseBase $db = null ) {
- if ( ! is_array( $this->mAttribs ) ) {
- throw new MWException( 'Recent change attributes are
missing.' );
- }
-
- // because this is used before a write operation, to help ensure
- // data integrity and issues with slave rep lag
- if ( $db === null ) {
- $db = wfGetDB( DB_SLAVE );
- }
-
- $res = $db->select(
- 'recentchanges',
- array( 'rc_id', 'rc_timestamp', 'rc_type', 'rc_params'
),
- array(
- 'rc_namespace' =>
$this->mAttribs['rc_namespace'],
- 'rc_title' => $this->mAttribs['rc_title'],
- 'rc_timestamp' =>
$this->mAttribs['rc_timestamp'],
- 'rc_type' => RC_EXTERNAL
- ),
- __METHOD__
- );
-
- if ( $res->numRows() == 0 ) {
- return false;
- }
-
- $changeRevId = self::getParam( 'rev_id',
$this->mAttribs['rc_params'] );
- $changeParentId = self::getParam( 'parent_id',
$this->mAttribs['rc_params'] );
-
- foreach ( $res as $rc ) {
- $parent_id = self::getParam( 'parent_id',
$rc->rc_params );
- $rev_id = self::getParam( 'rev_id', $rc->rc_params );
-
- if ( $rev_id === $changeRevId
- && $parent_id === $changeParentId ) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Saves an external recent change
- *
- * @since 0.3
- *
- * @return bool
- */
- public function save() {
- if ( !isset( $this->mAttribs ) || !is_array( $this->mAttribs )
) {
- wfDebugLog( __CLASS__, __FUNCTION__ . ': mAttribs in
ExternalRecentChange is missing.' );
- return false;
- }
-
- $dbw = wfGetDB( DB_MASTER );
- if ( $this->exists( $dbw ) === false ) {
- $res = $dbw->insert( 'recentchanges', $this->mAttribs,
__METHOD__ );
- return $res;
- }
-
- return false;
- }
-
-}
diff --git a/client/includes/recentchanges/RecentChangeFactory.php
b/client/includes/recentchanges/RecentChangeFactory.php
new file mode 100644
index 0000000..b6574cd
--- /dev/null
+++ b/client/includes/recentchanges/RecentChangeFactory.php
@@ -0,0 +1,242 @@
+<?php
+
+namespace Wikibase\Client\RecentChanges;
+
+use Language;
+use Message;
+use MWException;
+use RecentChange;
+use Title;
+use Wikibase\EntityChange;
+use Wikibase\ItemChange;
+use Wikibase\SiteLinkCommentCreator;
+use Wikimedia\Assert\Assert;
+
+/**
+ * @since 0.5
+ *
+ * @licence GNU GPL v2+
+ * @author Katie Filbert < [email protected] >
+ * @author Daniel Kinzler
+ */
+class RecentChangeFactory {
+
+ /**
+ * @var Language
+ */
+ private $language;
+
+ /**
+ * @var SiteLinkCommentCreator
+ */
+ private $siteLinkCommentCreator;
+
+ /**
+ * @param Language $language
+ * @param SiteLinkCommentCreator $siteLinkCommentCreator
+ */
+ public function __construct( Language $language, SiteLinkCommentCreator
$siteLinkCommentCreator ) {
+ $this->language = $language;
+ $this->siteLinkCommentCreator = $siteLinkCommentCreator;
+ }
+
+ /**
+ * Creates a local RecentChange object that corresponds to the
EntityChange from the
+ * repo, with respect to the given target page
+ *
+ * @since 0.5
+ *
+ * @param EntityChange $change A change reported from the wikibase
repository
+ * @param Title $target The title of a page affected by the change
+ * @param array|null $preparedAttribs Attributes pre-calculated by
calling prepareChangeAttributes()
+ * to avoid re-calculating common change attributes for each
target page.
+ *
+ * @return RecentChange
+ */
+ public function newRecentChange( EntityChange $change, Title $target,
array $preparedAttribs = null ) {
+ if ( $preparedAttribs === null ) {
+ $preparedAttribs = $this->prepareChangeAttributes(
$change );
+ }
+
+ $targetSpecificAttributes =
$this->buildTargetSpecificAttributes( $change, $target );
+ $attribs = array_merge( $preparedAttribs,
$targetSpecificAttributes );
+ $rc = RecentChange::newFromRow( (object)$attribs );
+ $rc->setExtra( array( 'pageStatus' => 'changed' ) );
+
+ return $rc;
+ }
+
+ /**
+ * Prepare change attributes for the given EntityChange. This can be
used to avoid
+ * re-calculating these attributes for each target page, when
processing a change
+ * with respect to a batch of affected target pages.
+ *
+ * @param EntityChange $change
+ *
+ * @return array Associative array of prepared change attributes, for
use with the
+ * $preparedAttribs of newRecentChange().
+ */
+ public function prepareChangeAttributes( EntityChange $change ) {
+ $rcinfo = $change->getMetadata();
+
+ $fields = $change->getFields();
+ $fields['entity_type'] =
$change->getEntityId()->getEntityType();
+
+ if ( isset( $fields['info']['changes'] ) ) {
+ $changesForComment = $fields['info']['changes'];
+ } else {
+ $changesForComment = array( $change );
+ }
+
+ //TODO: The same change may be reported to several target pages;
+ // The comment we generate should be adapted to the role
that page
+ // plays in the change, e.g. when a sitelink changes from
one page to another,
+ // the link was effectively removed from one and added to
the other page.
+ // This should be handled in
buildTargetSpecificAttributes().
+ $comment = $this->getEditCommentMulti( $changesForComment );
+
+ unset( $fields['info'] );
+ $metadata = array_merge( $fields, $rcinfo );
+
+ $isBot = false;
+ if ( array_key_exists( 'bot', $metadata ) ) {
+ $isBot = $metadata['bot'];
+ }
+
+ // compatibility
+ if ( array_key_exists( 'user_text', $metadata ) ) {
+ $userText = $metadata['user_text'];
+ } elseif ( array_key_exists( 'rc_user_text', $metadata ) ) {
+ $userText = $metadata['rc_user_text'];
+ } else {
+ $userText = '';
+ }
+
+ $time = isset( $metadata['time'] ) ? $metadata['time'] :
wfTimestamp( TS_MW );
+
+ $params = array(
+ 'wikibase-repo-change' => $metadata,
+ );
+
+ return array(
+ 'rc_user' => 0,
+ 'rc_user_text' => $userText,
+ 'rc_type' => RC_EXTERNAL,
+ 'rc_minor' => true, // for now, always consider these
minor
+ 'rc_bot' => $isBot,
+ 'rc_patrolled' => true,
+ 'rc_params' => serialize( $params ),
+ 'rc_comment' => $comment,
+ 'rc_timestamp' => $time,
+ 'rc_log_type' => null,
+ 'rc_log_action' => '',
+ 'rc_source' => 'wb',
+ 'rc_deleted' => false,
+ );
+ }
+
+ /**
+ * Builds change attribute specific to the given target page.
+ *
+ * @param EntityChange $change
+ * @param Title $target
+ *
+ * @return array
+ */
+ private function buildTargetSpecificAttributes( EntityChange $change,
Title $target ) {
+ $attribs = array(
+ 'rc_namespace' => $target->getNamespace(),
+ 'rc_title' => $target->getDBkey(),
+ 'rc_old_len' => $target->getLength(),
+ 'rc_new_len' => $target->getLength(),
+ 'rc_this_oldid' => $target->getLatestRevID(),
+ 'rc_last_oldid' => $target->getLatestRevID(),
+ 'rc_cur_id' => $target->getArticleID(),
+ );
+
+ //TODO: override for "special" changes (e.g. link/unlink, by
edit or create/restore/delete)
+
+ return $attribs;
+ }
+
+ /**
+ * Returns a human readable comment representing the given changes.
+ *
+ * @param EntityChange[] $changes
+ *
+ * @throws MWException
+ * @return string
+ */
+ private function getEditCommentMulti( array $changes ) {
+ $comments = array();
+ $c = 0;
+
+ foreach ( $changes as $change ) {
+ $c++;
+ $comments[] = $this->getEditComment( $change );
+ }
+
+ if ( $c === 0 ) {
+ return '';
+ } elseif ( $c === 1 ) {
+ return reset( $comments );
+ } else {
+ //@todo: handle overly long lists nicely!
+ return $this->language->semicolonList( $comments );
+ }
+ }
+
+ /**
+ * Returns a human readable comment representing the change.
+ *
+ * @since 0.4
+ *
+ * @param EntityChange $change the change to get a comment for
+ *
+ * @throws MWException
+ * @return string
+ */
+ private function getEditComment( EntityChange $change ) {
+ $siteLinkDiff = $change instanceof ItemChange
+ ? $change->getSiteLinkDiff()
+ : null;
+
+ $editComment = '';
+
+ if ( $siteLinkDiff !== null && !$siteLinkDiff->isEmpty() ) {
+ $action = $change->getAction();
+ $siteLinkComment =
$this->siteLinkCommentCreator->getEditComment( $siteLinkDiff, $action );
+ $editComment = $siteLinkComment === null ? '' :
$siteLinkComment;
+ }
+
+ if ( $editComment === '' ) {
+ $editComment = $change->getComment();
+ }
+
+ if ( $editComment === '' ) {
+ // If there is no comment, use something generic. This
shouldn't happen.
+ wfWarn( 'Failed to find edit comment for EntityChange'
);
+ $editComment = $this->msg( 'wikibase-comment-update'
)->text();
+ }
+
+ Assert::postcondition( is_string( $editComment ), '$editComment
must be a string' );
+ return $editComment;
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return Message
+ * @throws MWException
+ */
+ private function msg( $key ) {
+ $params = func_get_args();
+ array_shift( $params );
+ if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+
+ return wfMessage( $key, $params )->inLanguage( $this->language
);
+ }
+
+}
diff --git a/client/includes/recentchanges/RecentChangesDuplicateDetector.php
b/client/includes/recentchanges/RecentChangesDuplicateDetector.php
new file mode 100644
index 0000000..5e2ca3e
--- /dev/null
+++ b/client/includes/recentchanges/RecentChangesDuplicateDetector.php
@@ -0,0 +1,101 @@
+<?php
+
+namespace Wikibase\Client\RecentChanges;
+
+use MWException;
+use RecentChange;
+use Wikibase\Client\Store\Sql\ConsistentReadConnectionManager;
+
+/**
+ * @since 0.5
+ *
+ * @licence GNU GPL v2+
+ * @author Katie Filbert < [email protected] >
+ * @author Daniel Kinzler
+ */
+class RecentChangesDuplicateDetector {
+
+ /**
+ * @var ConsistentReadConnectionManager
+ */
+ private $connectionManager;
+
+ public function __construct( ConsistentReadConnectionManager
$connectionManager ) {
+ $this->connectionManager = $connectionManager;
+ }
+
+ /**
+ * Checks if a recent change entry already exists in the recentchanges
table
+ *
+ * @since 0.4
+ *
+ * @throws MWException
+ *
+ * @return bool
+ */
+ public function changeExists( RecentChange $change ) {
+ $attribs = $change->getAttributes();
+
+ //XXX: need to check master?
+ $db = $this->connectionManager->getReadConnection();
+
+ $res = $db->select(
+ 'recentchanges',
+ array( 'rc_id', 'rc_timestamp', 'rc_type', 'rc_params'
),
+ array(
+ 'rc_namespace' => $attribs['rc_namespace'],
+ 'rc_title' => $attribs['rc_title'],
+ 'rc_timestamp' => $attribs['rc_timestamp'],
+ 'rc_type' => RC_EXTERNAL
+ ),
+ __METHOD__
+ );
+
+ if ( $res->numRows() === 0 ) {
+ return false;
+ }
+
+ $changeMetadata = $this->getMetadata( $attribs['rc_params'] );
+
+ $changeRevId = $changeMetadata[ 'rev_id' ];
+ $changeParentId = $changeMetadata[ 'parent_id' ];
+
+ foreach ( $res as $rc ) {
+ $metadata = $this->getMetadata( $rc->rc_params );
+
+ $parent_id = $metadata[ 'parent_id' ];
+ $rev_id = $metadata[ 'rev_id' ];
+
+ if ( $rev_id === $changeRevId
+ && $parent_id === $changeParentId ) {
+ return true;
+ }
+ }
+
+ $this->connectionManager->releaseConnection( $db );
+ return false;
+ }
+
+ /**
+ * Extracts the metadata array from the value of an rc_params field.
+ *
+ * @param array|string $rc_params
+ *
+ * @return array
+ */
+ private function getMetadata( $rc_params ) {
+ if ( is_string( $rc_params ) ) {
+ $rc_params = unserialize( $rc_params );
+ }
+
+ if ( is_array( $rc_params ) && array_key_exists(
'wikibase-repo-change', $rc_params ) ) {
+ $metadata = $rc_params['wikibase-repo-change'];
+ } else {
+ $metadata = array();
+ }
+
+ $metadata = array_merge( array( 'parent_id' => 0, 'rev_id' => 0
), $metadata );
+ return $metadata;
+ }
+
+}
diff --git a/client/tests/phpunit/includes/Changes/ChangeHandlerTest.php
b/client/tests/phpunit/includes/Changes/ChangeHandlerTest.php
index bbda812..0de6c6e 100644
--- a/client/tests/phpunit/includes/Changes/ChangeHandlerTest.php
+++ b/client/tests/phpunit/includes/Changes/ChangeHandlerTest.php
@@ -3,7 +3,6 @@
namespace Wikibase\Client\Tests\Changes;
use ArrayIterator;
-use Language;
use MediaWikiTestCase;
use Title;
use Wikibase\Change;
@@ -74,9 +73,7 @@
$titleFactory,
$updater ?: new MockPageUpdater(),
$changeListTransformer,
- Language::factory( 'qqx' ),
$this->getMock( 'SiteStore' ),
- 'enwiki',
'repowiki',
true
);
@@ -239,75 +236,6 @@
$this->assertEmpty( array_values( $unexpected ), 'unexpected
actions: ' . implode( '|', $unexpected ) );
}
- public function provideGetEditComment() {
- $changes = TestChanges::getChanges();
-
- $dummy = Title::newFromText( 'Dummy' );
-
- return array(
- array( // #0
- $changes['item-deletion-linked'],
- $dummy,
- array( 'q100' => array( 'Emmy' ) ),
- '(wikibase-comment-remove)'
- ),
- array( // #1
- $changes['set-de-label'],
- $dummy,
- array( 'q100' => array( 'Emmy' ) ),
- '/* set-de-label:1| */ bla bla'
- ),
- array( // #2
- $changes['add-claim'],
- $dummy,
- array( 'q100' => array( 'Emmy' ) ),
- '/* add-claim:1| */ bla bla'
- ),
- array( // #3
- $changes['remove-claim'],
- $dummy,
- array( 'q100' => array( 'Emmy' ) ),
- '/* remove-claim:1| */ bla bla'
- ),
- array( // #4
- $changes['set-dewiki-sitelink'],
- $dummy,
- array( 'q100' => array( 'Emmy' ) ),
- '(wikibase-comment-sitelink-add:
[[:dewiki:Dummy]])'
- ),
- array( // #5
- $changes['change-dewiki-sitelink'],
- $dummy,
- array( 'q100' => array( 'Emmy' ) ),
- '(wikibase-comment-sitelink-change:
[[:dewiki:Dummy]], [[:dewiki:Dummy2]])',
- ),
- array( // #6
- $changes['change-enwiki-sitelink'],
- $dummy,
- array( 'q100' => array( 'Emmy' ) ),
- '(wikibase-comment-sitelink-change:
[[:enwiki:Emmy]], [[:enwiki:Emmy2]])',
- ),
- array( // #7
- $changes['remove-dewiki-sitelink'],
- $dummy,
- array( 'q100' => array( 'Emmy2' ) ),
- '(wikibase-comment-sitelink-remove:
[[:dewiki:Dummy2]])',
- ),
- array( // #8
- $changes['remove-enwiki-sitelink'],
- $dummy,
- array( 'q100' => array( 'Emmy2' ) ),
- '(wikibase-comment-unlink)',
- ),
- array( // #9
- $changes['remove-enwiki-sitelink'],
- $dummy,
- array( 'q100' => array() ),
- '(wikibase-comment-unlink)',
- ),
- );
- }
-
/**
* Returns a map of fake local page IDs to the corresponding local page
names.
* The fake page IDs are the IDs of the items that have a sitelink to
the
@@ -425,16 +353,6 @@
} ) );
return $usageLookup;
- }
-
- /**
- * @dataProvider provideGetEditComment
- */
- public function testGetEditComment( Change $change, Title $title, array
$pageNamesPerItemId, $expected ) {
- $handler = $this->getChangeHandler( $pageNamesPerItemId );
- $comment = $handler->getEditComment( $change, $title );
-
- $this->assertEquals( $expected, $comment );
}
/**
diff --git a/client/tests/phpunit/includes/Changes/MockPageUpdater.php
b/client/tests/phpunit/includes/Changes/MockPageUpdater.php
index 8213d51..0f8798f 100644
--- a/client/tests/phpunit/includes/Changes/MockPageUpdater.php
+++ b/client/tests/phpunit/includes/Changes/MockPageUpdater.php
@@ -4,6 +4,7 @@
use Title;
use Wikibase\Client\Changes\PageUpdater;
+use Wikibase\EntityChange;
/**
* Mock version of the service object for triggering different kinds of page
updates
@@ -57,12 +58,12 @@
/**
* @param Title[] $titles
- * @param array $attribs
+ * @param EntityChange $change
*/
- public function injectRCRecords( array $titles, array $attribs ) {
+ public function injectRCRecords( array $titles, EntityChange $change ) {
foreach ( $titles as $title ) {
$key = $title->getPrefixedDBkey();
- $this->updates['injectRCRecord'][ $key ] = $attribs;
+ $this->updates['injectRCRecord'][ $key ] = $change;
}
}
diff --git a/client/tests/phpunit/includes/Changes/WikiPageUpdaterTest.php
b/client/tests/phpunit/includes/Changes/WikiPageUpdaterTest.php
new file mode 100644
index 0000000..0e2ec6b
--- /dev/null
+++ b/client/tests/phpunit/includes/Changes/WikiPageUpdaterTest.php
@@ -0,0 +1,226 @@
+<?php
+
+namespace Wikibase\Client\Tests\Changes;
+
+use Diff\Differ\MapDiffer;
+use Diff\DiffOp\AtomicDiffOp;
+use Job;
+use JobQueueGroup;
+use PHPUnit_Framework_Assert;
+use RecentChange;
+use RefreshLinksJob;
+use Title;
+use Wikibase\Client\Changes\WikiPageUpdater;
+use Wikibase\Client\RecentChanges\RecentChangeFactory;
+use Wikibase\Client\RecentChanges\RecentChangesDuplicateDetector;
+use Wikibase\DataModel\Entity\EntityId;
+use Wikibase\DataModel\Entity\Item;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Services\Diff\EntityDiff;
+use Wikibase\DataModel\Services\Diff\EntityDiffer;
+use Wikibase\DataModel\Services\Diff\ItemDiffer;
+use Wikibase\DataModel\SiteLink;
+use Wikibase\EntityChange;
+
+/**
+ * @covers Wikibase\Client\Changes\WikiPageUpdater
+ *
+ * @group Wikibase
+ * @group WikibaseClient
+ * @group WikibaseChange
+ * @group ChangeHandlerTest
+ *
+ * @group Database
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class WikiPageUpdaterTest extends \MediaWikiTestCase {
+
+ /**
+ * @return JobQueueGroup
+ */
+ private function getJobQueueGroupMock() {
+ $jobQueueGroup = $this->getMockBuilder( 'JobQueueGroup' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ return $jobQueueGroup;
+ }
+
+ /**
+ * @return RecentChangeFactory
+ */
+ private function getRCFactoryMock() {
+ $rcFactory = $this->getMockBuilder(
'Wikibase\Client\RecentChanges\RecentChangeFactory' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $rcFactory->expects( $this->any() )
+ ->method( 'prepareChangeAttributes' )
+ ->will( $this->returnValue( array() ) );
+
+ return $rcFactory;
+ }
+
+ /**
+ * @return RecentChangesDuplicateDetector
+ */
+ private function getRCDupeDetectorMock() {
+ $rcDupeDetector = $this->getMockBuilder(
'Wikibase\Client\RecentChanges\RecentChangesDuplicateDetector' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ return $rcDupeDetector;
+ }
+
+ /**
+ * @return Title
+ */
+ private function getTitleMock( $text ) {
+ $title = $this->getMockBuilder( 'Title' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $title->expects( $this->any() )
+ ->method( 'getArticleID' )
+ ->will( $this->returnValue( 23 ) );
+
+ $title->expects( $this->any() )
+ ->method( 'exists' )
+ ->will( $this->returnValue( true ) );
+
+ $title->expects( $this->any() )
+ ->method( 'getPrefixedDBKey' )
+ ->will( $this->returnValue( $text ) );
+
+ return $title;
+ }
+
+ /**
+ * @return EntityChange
+ */
+ private function getEntityChangeMock() {
+ $change = $this->getMockBuilder( 'Wikibase\EntityChange' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ return $change;
+ }
+
+ /**
+ * @return RecentChange
+ */
+ private function getRecentChangeMock() {
+ $change = $this->getMockBuilder( 'RecentChange' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ return $change;
+ }
+
+ public function testPurgeParserCache() {
+ $updater = new WikiPageUpdater(
+ $this->getJobQueueGroupMock(),
+ $this->getRCFactoryMock(),
+ $this->getRCDupeDetectorMock()
+ );
+
+ $title = $this->getTitleMock( 'Foo' );
+
+ $title->expects( $this->once() )
+ ->method( 'invalidateCache' );
+
+ $updater->purgeParserCache( array(
+ $title
+ ) );
+ }
+
+ public function testPurgeWebCache() {
+ $updater = new WikiPageUpdater(
+ $this->getJobQueueGroupMock(),
+ $this->getRCFactoryMock(),
+ $this->getRCDupeDetectorMock()
+ );
+
+ $title = $this->getTitleMock( 'Foo' );
+ $title->expects( $this->once() )
+ ->method( 'purgeSquid' );
+
+ $updater->purgeWebCache( array(
+ $title
+ ) );
+ }
+
+ public function testScheduleRefreshLinks() {
+ $title = $this->getTitleMock( 'Foo' );
+
+ $jobQueueGroup = $this->getJobQueueGroupMock();
+
+ $jobMatcher = function( Job $job ) use ( $title ) {
+ PHPUnit_Framework_Assert::assertInstanceOf(
'RefreshLinksJob', $job );
+ PHPUnit_Framework_Assert::assertEquals(
+ $title->getPrefixedDBkey(),
+ $job->getTitle()->getPrefixedDBkey()
+ );
+
+ $expectedSignature = RefreshLinksJob::newRootJobParams(
$title->getPrefixedDBkey() );
+ $actualSignature = $job->getRootJobParams();
+ PHPUnit_Framework_Assert::assertEquals(
+ $expectedSignature['rootJobSignature'],
+ $actualSignature['rootJobSignature']
+ );
+
+ return true;
+ };
+
+ $jobQueueGroup->expects( $this->any() )
+ ->method( 'push' )
+ ->with( $this->callback( $jobMatcher ) );
+
+ $jobQueueGroup->expects( $this->any() )
+ ->method( 'deduplicateRootJob' )
+ ->with( $this->callback( $jobMatcher ) );
+
+ $updater = new WikiPageUpdater(
+ $jobQueueGroup,
+ $this->getRCFactoryMock(),
+ $this->getRCDupeDetectorMock()
+ );
+
+ $updater->scheduleRefreshLinks( array(
+ $title
+ ) );
+ }
+
+ public function testInjectRCRecords() {
+ $title = $this->getTitleMock( 'Foo' );
+ $change = $this->getEntityChangeMock();
+ $rc = $this->getRecentChangeMock();
+
+ $rcFactory = $this->getRCFactoryMock();
+
+ $rcFactory->expects( $this->any() )
+ ->method( 'newRecentChange' )
+ ->with( $change, $title, array() )
+ ->will( $this->returnValue( $rc ) );
+
+ $rcDupeDetector = $this->getRCDupeDetectorMock();
+
+ $rcDupeDetector->expects( $this->any() )
+ ->method( 'changeExists' )
+ ->with( $rc );
+
+ $updater = new WikiPageUpdater(
+ $this->getJobQueueGroupMock(),
+ $rcFactory,
+ $rcDupeDetector
+ );
+
+ $updater->injectRCRecords( array(
+ $title
+ ), $change );
+
+ }
+
+}
diff --git
a/client/tests/phpunit/includes/recentchanges/ChangeLineFormatterTest.php
b/client/tests/phpunit/includes/recentchanges/ChangeLineFormatterTest.php
index 0a33a52..2365649 100644
--- a/client/tests/phpunit/includes/recentchanges/ChangeLineFormatterTest.php
+++ b/client/tests/phpunit/includes/recentchanges/ChangeLineFormatterTest.php
@@ -11,7 +11,6 @@
use User;
use Wikibase\Client\RecentChanges\ChangeLineFormatter;
use Wikibase\Client\RecentChanges\ExternalChangeFactory;
-use Wikibase\Client\RecentChanges\ExternalRecentChange;
use Wikibase\Client\RepoLinker;
use Wikibase\Client\WikibaseClient;
@@ -366,6 +365,7 @@
'page_id' => 0,
'rev_id' => 0,
'parent_id' => 0,
+ 'bot' => false
)
);
@@ -412,14 +412,35 @@
}
private function makeRecentChange( array $params, Title $title,
$comment ) {
- $recentChange = new RecentChange();
+ $attribs = array(
+ 'rc_id' => 1234,
+ 'rc_timestamp' =>
$params['wikibase-repo-change']['time'],
+ 'rc_user' => 0,
+ 'rc_user_text' =>
$params['wikibase-repo-change']['user_text'],
+ 'rc_namespace' => $title->getNamespace(),
+ 'rc_title' => $title->getDBkey(),
+ 'rc_comment' => $comment,
+ 'rc_minor' => true,
+ 'rc_bot' => $params['wikibase-repo-change']['bot'],
+ 'rc_new' => false,
+ 'rc_cur_id' => $title->getArticleID(),
+ 'rc_this_oldid' => $title->getLatestRevID(),
+ 'rc_last_oldid' => $title->getLatestRevID(),
+ 'rc_type' => RC_EXTERNAL,
+ 'rc_source' => 'wb',
+ 'rc_patrolled' => true,
+ 'rc_ip' => '127.0.0.1',
+ 'rc_old_len' => 123,
+ 'rc_new_len' => 123,
+ 'rc_deleted' => false,
+ 'rc_logid' => 0,
+ 'rc_log_type' => null,
+ 'rc_log_action' => '',
+ 'rc_params' => serialize( $params ),
+ );
+
+ $recentChange = RecentChange::newFromRow( (object)$attribs );
$recentChange->counter = 1;
-
- $externalChange = ExternalRecentChange::newFromAttribs(
$params, $title );
- $attribs = $externalChange->getAttributes();
-
- $attribs['rc_comment'] = $comment;
- $recentChange->setAttribs( $attribs );
return $recentChange;
}
diff --git
a/client/tests/phpunit/includes/recentchanges/RecentChangeFactoryTest.php
b/client/tests/phpunit/includes/recentchanges/RecentChangeFactoryTest.php
new file mode 100644
index 0000000..c87f840
--- /dev/null
+++ b/client/tests/phpunit/includes/recentchanges/RecentChangeFactoryTest.php
@@ -0,0 +1,412 @@
+<?php
+
+namespace Wikibase\Client\Tests\RecentChanges;
+
+use Diff\DiffOp\Diff\Diff;
+use Diff\MapDiffer;
+use Language;
+use Title;
+use Wikibase\Client\RecentChanges\RecentChangeFactory;
+use Wikibase\DataModel\Entity\EntityId;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Services\Diff\ItemDiff;
+use Wikibase\EntityChange;
+use Wikibase\ItemChange;
+use Wikibase\SiteLinkCommentCreator;
+
+/**
+ * @covers Wikibase\Client\RecentChanges\RecentChangeFactory
+ *
+ * @group WikibaseClient
+ * @group Wikibase
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class RecentChangeFactoryTest extends \PHPUnit_Framework_TestCase {
+
+ /**
+ * @return RecentChangeFactory
+ */
+ private function newRecentChangeFactory() {
+ $siteStore = $this->getMock( 'SiteStore' );
+
+ $lang = Language::factory( 'qqx' );
+ $siteLinkCommentCreator = new SiteLinkCommentCreator( $lang,
$siteStore, 'testwiki' );
+ return new RecentChangeFactory( $lang, $siteLinkCommentCreator
);
+ }
+
+ /**
+ * @param string $action
+ * @param EntityId $entityId
+ * @param Diff $diff
+ * @param array|null $fields
+ *
+ * @return EntityChange
+ */
+ private function newEntityChange( $action, EntityId $entityId, Diff
$diff, array $fields = null ) {
+ /** @var EntityChange $instance */
+ $instance = new ItemChange( $fields );
+
+ if ( !$instance->hasField( 'object_id' ) ) {
+ $instance->setField( 'object_id',
$entityId->getSerialization() );
+ }
+
+ if ( !$instance->hasField( 'info' ) ) {
+ $instance->setField( 'info', array() );
+ }
+
+ // Note: the change type determines how the client will
+ // instantiate and handle the change
+ $type = 'wikibase-' . $entityId->getEntityType() . '~' .
$action;
+ $instance->setField( 'type', $type );
+ $instance->setDiff( $diff );
+
+ return $instance;
+ }
+
+ /**
+ * @param int $ns
+ * @param string $text
+ * @param int $pageId
+ * @param int $revId
+ * @param int $length
+ *
+ * @return Title
+ */
+ private function newTitle( $ns, $text, $pageId, $revId, $length ) {
+ $title = $this->getMockBuilder( 'Title' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $title->expects( $this->any() )
+ ->method( 'getNamespace' )
+ ->will( $this->returnValue( $ns ) );
+
+ $title->expects( $this->any() )
+ ->method( 'getDBKey' )
+ ->will( $this->returnValue( str_replace( ' ', '_',
$text ) ) );
+
+ $title->expects( $this->any() )
+ ->method( 'getArticleID' )
+ ->will( $this->returnValue( $pageId ) );
+
+ $title->expects( $this->any() )
+ ->method( 'getLatestRevID' )
+ ->will( $this->returnValue( $revId ) );
+
+ $title->expects( $this->any() )
+ ->method( 'getLength' )
+ ->will( $this->returnValue( $length ) );
+
+ return $title;
+ }
+
+ public function provideNewRecentChange() {
+ $target = $this->newTitle( NS_MAIN, 'RecentChangeFactoryTest',
7, 77, 210 );
+
+ $fields = array(
+ 'id' => '13',
+ 'time' => '20150202030303',
+ );
+ $metadata = array(
+ 'rev_id' => 2,
+ 'parent_id' => 3,
+ 'bot' => false,
+ 'user_text' => 'RecentChangeFactoryTestUser',
+ 'comment' => 'Actual Comment'
+ );
+
+ $emptyDiff = new ItemDiff();
+ $change = $this->newEntityChange( 'change', new ItemId( 'Q17'
), $emptyDiff, $fields );
+ $change->setMetadata( $metadata );
+
+ $fields = $change->getFields();
+ unset( $fields['info'] );
+
+ $metadata = array_merge( $fields, $change->getMetadata() );
+ $metadata['entity_type'] = 'item';
+
+ $targetAttr = array(
+ 'rc_namespace' => $target->getNamespace(),
+ 'rc_title' => $target->getDBkey(),
+ 'rc_old_len' => $target->getLength(),
+ 'rc_new_len' => $target->getLength(),
+ 'rc_this_oldid' => $target->getLatestRevID(),
+ 'rc_last_oldid' => $target->getLatestRevID(),
+ 'rc_cur_id' => $target->getArticleID(),
+ );
+
+ $changeAttr = array(
+ 'rc_user' => 0,
+ 'rc_user_text' => 'RecentChangeFactoryTestUser',
+ 'rc_type' => RC_EXTERNAL,
+ 'rc_minor' => true, // for now, always consider these
minor
+ 'rc_bot' => $metadata['bot'],
+ 'rc_patrolled' => true,
+ 'rc_params' => serialize( array(
+ 'wikibase-repo-change' => $metadata,
+ //'comment-html' => 'Generated Comment HTML',
// later
+ ) ),
+ 'rc_comment' => $metadata['comment'],
+ 'rc_timestamp' => $metadata['time'],
+ 'rc_log_action' => '',
+ 'rc_log_type' => null,
+ 'rc_source' => 'wb',
+ 'rc_deleted' => false,
+ );
+
+ $preparedAttr = array(
+ 'rc_user' => 0,
+ 'rc_user_text' => 'HungryKitten',
+ 'rc_type' => RC_EXTERNAL,
+ 'rc_minor' => true, // for now, always consider these
minor
+ 'rc_bot' => false,
+ 'rc_patrolled' => true,
+ 'rc_params' => serialize( array(
+ 'wikibase-repo-change' => array(
+ 'rev_id' => 7,
+ 'parent_id' => 5,
+ 'time' => '20150606050505',
+ ),
+ 'comment-html' => 'Override Comment HTML',
+ ) ),
+ 'rc_comment' => 'Override Comment',
+ 'rc_timestamp' => '20150606050505',
+ 'rc_log_action' => '',
+ 'rc_log_type' => null,
+ 'rc_source' => 'wb',
+ 'rc_deleted' => false,
+ );
+
+ return array(
+ 'no prepared' => array(
+ array_merge( $changeAttr, $targetAttr ),
+ $change,
+ $target,
+ null
+ ),
+
+ 'use prepared' => array(
+ array_merge( $preparedAttr, $targetAttr ),
+ $change,
+ $target,
+ $preparedAttr
+ ),
+
+ //TODO:
+ //'sitelink change' => array(),
+ //'composite change' => array(),
+ );
+ }
+
+ /**
+ * @dataProvider provideNewRecentChange
+ */
+ public function testNewRecentChange( array $expected, EntityChange
$change, Title $target, array $preparedAttribs = null ) {
+ $factory = $this->newRecentChangeFactory();
+
+ $rc = $factory->newRecentChange( $change, $target,
$preparedAttribs );
+
+ $this->assertRCEquals( $expected, $rc->getAttributes() );
+ }
+
+ private function assertRCEquals( array $expected, array $actual ) {
+ if ( isset( $expected['rc_params'] ) ) {
+ $this->assertArrayHasKey( 'rc_params', $actual );
+
+ $expectedParams = unserialize( $expected['rc_params'] );
+ $actualParams = unserialize( $actual['rc_params'] );
+
+ unset( $expected['rc_params'] );
+ unset( $actual['rc_params'] );
+
+ ksort( $expectedParams );
+ ksort( $actualParams );
+ $this->assertEquals( $expectedParams, $actualParams,
'rc_params' );
+ } else {
+ $this->assertArrayNotHasKey( 'rc_params', $actual );
+ }
+
+ ksort( $expected );
+ ksort( $actual );
+ $this->assertEquals( $expected, $actual, 'attributes' );
+ }
+
+ private function makeItemChangeFromMetaData( $action, Diff $diff, array
$fields = array(), array $metadata = array() ) {
+ $fields = array_merge( array(
+ 'id' => '13',
+ 'time' => '20150202030303',
+ ), $fields );
+
+ $metadata = array_merge( array(
+ 'rev_id' => 2,
+ 'parent_id' => 3,
+ 'bot' => false,
+ 'user_text' => 'RecentChangeFactoryTestUser',
+ 'comment' => 'Actual Comment'
+ ), $metadata );
+
+ if ( isset( $fields['info']['changes'] ) ) {
+ foreach ( $fields['info']['changes'] as &$innerChange )
{
+ if ( is_array( $innerChange ) ) {
+ $innerDiff = new ItemDiff();
+ $innerChange =
$this->makeItemChangeFromMetaData(
+ $action,
+ $innerDiff,
+ $innerChange['fields'],
+ $innerChange['metadata']
+ );
+ }
+ }
+ }
+
+ $change = $this->newEntityChange( $action, new ItemId( 'Q17' ),
$diff, $fields );
+ $change->setMetadata( $metadata );
+
+ return $change;
+ }
+
+ /**
+ * @dataProvider provideNewRecentChange_summary
+ */
+ public function testNewRecentChange_summary(
+ $expectedComment,
+ $action,
+ Diff $diff,
+ array $fields = array(),
+ array $metadata = array()
+ ) {
+ //@todo: also check pre-generated HTML when I5439a76c is merged
+
+ $change = $this->makeItemChangeFromMetaData( $action, $diff,
$fields, $metadata );
+
+ $target = $this->newTitle( NS_MAIN, 'RecentChangeFactoryTest',
7, 77, 210 );
+
+ $factory = $this->newRecentChangeFactory();
+ $rc = $factory->newRecentChange( $change, $target );
+
+ $this->assertEquals( $expectedComment, $rc->getAttribute(
'rc_comment' ) );
+ }
+
+ private function makeItemDiff( array $from, array $to ) {
+ $differ = new MapDiffer( true );
+ $diffOps = $differ->doDiff(
+ $from,
+ $to
+ );
+
+ return new ItemDiff( $diffOps );
+ }
+
+ public function provideNewRecentChange_summary() {
+ $emptyDiff = new ItemDiff();
+
+ // TODO: special cases:
+ // page connected by edit
+ // page connected by creation
+ // page connected by undeletion
+ // page disconnected by edit
+ // page disconnected by deletion
+
+ $linksEmpty = array(
+ 'links' => array()
+ );
+
+ $linksDewikiDummy = array(
+ 'links' => array(
+ 'dewiki' => array( 'name' => 'Dummy' )
+ )
+ );
+
+ $linksDewikiBummy = array(
+ 'links' => array(
+ 'dewiki' => array( 'name' => 'Bummy' )
+ )
+ );
+
+ return array(
+ 'repo comment' => array(
+ '/* set-de-label:1| */ bla bla',
+ 'change',
+ $emptyDiff,
+ array(),
+ array(
+ 'comment' => '/* set-de-label:1| */ bla
bla',
+ )
+ ),
+ 'sitelink update' => array(
+ '(wikibase-comment-sitelink-change:
[[:dewiki:Dummy]], [[:dewiki:Bummy]])',
+ 'change',
+ $this->makeItemDiff( $linksDewikiDummy,
$linksDewikiBummy ),
+ array(),
+ array(
+ 'comment' => '/* IGNORE-KITTENS:1| */
SILLY KITTENS',
+ )
+ ),
+ 'sitelink added' => array(
+ '(wikibase-comment-sitelink-add:
[[:dewiki:Bummy]])',
+ 'change',
+ $this->makeItemDiff( $linksEmpty,
$linksDewikiBummy ),
+ array(),
+ array(
+ 'comment' => '/* IGNORE-KITTENS:1| */
SILLY KITTENS',
+ )
+ ),
+ 'sitelink removed' => array(
+ '(wikibase-comment-sitelink-remove:
[[:dewiki:Dummy]])',
+ 'change',
+ $this->makeItemDiff( $linksDewikiDummy,
$linksEmpty ),
+ array(),
+ array(
+ 'comment' => '/* IGNORE-KITTENS:1| */
SILLY KITTENS',
+ )
+ ),
+ 'composite change' => array(
+ '/* set-de-description:1| */
Fuh(semicolon-separator)/* set-en-description:1| */ Foo',
+ 'change',
+ $emptyDiff,
+ array(
+ 'info' => array( 'changes' => array(
+ array(
+ 'fields' => array(),
+ 'metadata' => array(
+ 'comment' =>
'/* set-de-description:1| */ Fuh',
+ ),
+ ),
+ array(
+ 'fields' => array(),
+ 'metadata' => array(
+ 'comment' =>
'/* set-en-description:1| */ Foo',
+ ),
+ ),
+ ) )
+ ),
+ array()
+ ),
+ );
+ }
+
+ public function testNewRecentChange_no_summary() {
+ $change = $this->makeItemChangeFromMetaData(
+ 'change',
+ new ItemDiff(),
+ array(),
+ array(
+ 'comment' => '' // repo sent no comment
+ )
+ );
+
+ $target = $this->newTitle( NS_MAIN, 'RecentChangeFactoryTest',
7, 77, 210 );
+
+ $factory = $this->newRecentChangeFactory();
+
+ \MediaWiki\suppressWarnings();
+ $rc = $factory->newRecentChange( $change, $target );
+ \MediaWiki\restoreWarnings();
+
+ $expectedComment = '(wikibase-comment-update)';
+ $this->assertEquals( $expectedComment, $rc->getAttribute(
'rc_comment' ) );
+ }
+
+}
diff --git
a/client/tests/phpunit/includes/recentchanges/RecentChangesDuplicateDetectorTest.php
b/client/tests/phpunit/includes/recentchanges/RecentChangesDuplicateDetectorTest.php
new file mode 100644
index 0000000..840331c
--- /dev/null
+++
b/client/tests/phpunit/includes/recentchanges/RecentChangesDuplicateDetectorTest.php
@@ -0,0 +1,228 @@
+<?php
+
+namespace Wikibase\Client\Tests\RecentChanges;
+
+use RecentChange;
+use Wikibase\Client\RecentChanges\RecentChangesDuplicateDetector;
+use Wikibase\Client\Store\Sql\ConsistentReadConnectionManager;
+
+/**
+ * @covers Wikibase\Client\RecentChanges\RecentChangesDuplicateDetector
+ *
+ * @group WikibaseClient
+ * @group Wikibase
+ * @group Database
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class RecentChangesDuplicateDetectorTest extends \MediaWikiTestCase {
+
+ public function setUp() {
+ parent::setUp();
+
+ $this->tablesUsed[] = 'recentchanges';
+ }
+
+ public function provideChangeExists() {
+ return array(
+ 'same' => array( true, array(
+ 'rc_id' => 17,
+ 'rc_timestamp' => '20111111111111',
+ 'rc_user' => 23,
+ 'rc_user_text' => 'Test',
+ 'rc_namespace' => 0,
+ 'rc_title' => 'Test',
+ 'rc_comment' => 'Testing',
+ 'rc_type' => RC_EXTERNAL,
+ 'rc_source' => 'wb',
+ 'rc_last_oldid' => 11,
+ 'rc_this_oldid' => 12,
+ 'rc_params' => array(
+ 'wikibase-repo-change' => array(
+ 'parent_id' => 1,
+ 'rev_id' => 2,
+ )
+ ),
+ ) ),
+ 'irrelevant differences' => array( true, array(
+ 'rc_id' => 1117, // ignored
+ 'rc_timestamp' => '20111111111111',
+ 'rc_user' => 23,
+ 'rc_user_text' => 'Test',
+ 'rc_namespace' => 0,
+ 'rc_title' => 'Test',
+ 'rc_comment' => 'Kittens', // ignored
+ 'rc_type' => RC_EXTERNAL,
+ 'rc_source' => 'wb',
+ 'rc_last_oldid' => 1111, // ignored
+ 'rc_this_oldid' => 1112, // ignored
+ 'rc_params' => array(
+ 'wikibase-repo-change' => array(
+ 'parent_id' => 1,
+ 'rev_id' => 2,
+ )
+ ),
+ ) ),
+ 'repo change mismatch' => array( false, array(
+ 'rc_id' => 17,
+ 'rc_timestamp' => '20111111111111',
+ 'rc_user' => 23,
+ 'rc_user_text' => 'Test',
+ 'rc_namespace' => 0,
+ 'rc_title' => 'Test',
+ 'rc_comment' => 'Testing',
+ 'rc_type' => RC_EXTERNAL,
+ 'rc_source' => 'wb',
+ 'rc_last_oldid' => 11,
+ 'rc_this_oldid' => 12,
+ 'rc_params' => array(
+ 'wikibase-repo-change' => array(
+ 'parent_id' => 7,
+ 'rev_id' => 8,
+ )
+ ),
+ ) ),
+ 'local timestamp mismatch' => array( false, array(
+ 'rc_id' => 17,
+ 'rc_timestamp' => '20111111112233',
+ 'rc_user' => 23,
+ 'rc_user_text' => 'Test',
+ 'rc_namespace' => 0,
+ 'rc_title' => 'Test',
+ 'rc_comment' => 'Testing',
+ 'rc_type' => RC_EXTERNAL,
+ 'rc_source' => 'wb',
+ 'rc_last_oldid' => 11,
+ 'rc_this_oldid' => 12,
+ 'rc_params' => array(
+ 'wikibase-repo-change' => array(
+ 'parent_id' => 1,
+ 'rev_id' => 2,
+ )
+ ),
+ ) ),
+ 'local title mismatch' => array( false, array(
+ 'rc_id' => 17,
+ 'rc_timestamp' => '20111111111111',
+ 'rc_user' => 23,
+ 'rc_user_text' => 'Test',
+ 'rc_namespace' => 0,
+ 'rc_title' => 'Kittens',
+ 'rc_comment' => 'Testing',
+ 'rc_type' => RC_EXTERNAL,
+ 'rc_source' => 'wb',
+ 'rc_last_oldid' => 11,
+ 'rc_this_oldid' => 12,
+ 'rc_params' => array(
+ 'wikibase-repo-change' => array(
+ 'parent_id' => 1,
+ 'rev_id' => 2,
+ )
+ ),
+ ) ),
+ );
+ }
+
+ /**
+ * @dataProvider provideChangeExists
+ */
+ public function testChangeExists( $expected, array $changeData ) {
+ $connectionManager = new ConsistentReadConnectionManager(
wfGetLB() );
+ $detector = new RecentChangesDuplicateDetector(
$connectionManager );
+
+ $this->initRecentChanges();
+
+ $change = $this->newChange( $changeData );
+
+ $this->assertEquals( $expected, $detector->changeExists(
$change ), 'changeExists()' );
+ }
+
+ private function newChange( array $changeData ) {
+ if ( isset( $changeData['rc_params'] ) && !is_string(
$changeData['rc_params'] ) ) {
+ $changeData['rc_params'] = serialize(
$changeData['rc_params'] );
+ }
+
+ $defaults = array(
+ 'rc_id' => 0,
+ 'rc_timestamp' => '20000000000000',
+ 'rc_user' => 0,
+ 'rc_user_text' => '',
+ 'rc_namespace' => 0,
+ 'rc_title' => '',
+ 'rc_comment' => '',
+ 'rc_minor' => false,
+ 'rc_bot' => false,
+ 'rc_new' => false,
+ 'rc_cur_id' => 0,
+ 'rc_this_oldid' => 0,
+ 'rc_last_oldid' => 0,
+ 'rc_type' => RC_EXTERNAL,
+ 'rc_source' => 'wb',
+ 'rc_patrolled' => false,
+ 'rc_ip' => '127.0.0.1',
+ 'rc_old_len' => 0,
+ 'rc_new_len' => 0,
+ 'rc_deleted' => false,
+ 'rc_logid' => 0,
+ 'rc_log_type' => null,
+ 'rc_log_action' => '',
+ 'rc_params' => '',
+ );
+
+ $changeData = array_merge( $defaults, $changeData );
+
+ $change = RecentChange::newFromRow( (object)$changeData );
+ $change->setExtra( array(
+ 'pageStatus' => 'changed'
+ ) );
+
+ return $change;
+ }
+
+ private function initRecentChanges() {
+ $change = $this->newChange( array(
+ 'rc_id' => 17,
+ 'rc_timestamp' => '20111111111111',
+ 'rc_user' => 23,
+ 'rc_user_text' => 'Test',
+ 'rc_namespace' => 0,
+ 'rc_title' => 'Test',
+ 'rc_comment' => 'Testing',
+ 'rc_type' => RC_EXTERNAL,
+ 'rc_source' => 'wb',
+ 'rc_last_oldid' => 11,
+ 'rc_this_oldid' => 12,
+ 'rc_params' => array(
+ 'wikibase-repo-change' => array(
+ 'parent_id' => 1001, // different id
+ 'rev_id' => 1002, // different id
+ )
+ )
+ ) );
+ $change->save();
+
+ $change = $this->newChange( array(
+ 'rc_id' => 18,
+ 'rc_timestamp' => '20111111111111',
+ 'rc_user' => 23,
+ 'rc_user_text' => 'Test',
+ 'rc_namespace' => 0,
+ 'rc_title' => 'Test',
+ 'rc_comment' => 'Testing',
+ 'rc_type' => RC_EXTERNAL,
+ 'rc_source' => 'wb',
+ 'rc_last_oldid' => 11,
+ 'rc_this_oldid' => 12,
+ 'rc_params' => array(
+ 'wikibase-repo-change' => array(
+ 'parent_id' => 1,
+ 'rev_id' => 2,
+ )
+ )
+ ) );
+
+ $change->save();
+ }
+
+}
--
To view, visit https://gerrit.wikimedia.org/r/238137
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I8edc4177d24a8f8c6b10b84107c3ee119a21bee6
Gerrit-PatchSet: 20
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Addshore <[email protected]>
Gerrit-Reviewer: Aude <[email protected]>
Gerrit-Reviewer: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Hoo man <[email protected]>
Gerrit-Reviewer: Thiemo Mättig (WMDE) <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits