jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/334749 )

Change subject: New Wikidata Build - 2017-01-28T10:00:02+0000
......................................................................


New Wikidata Build - 2017-01-28T10:00:02+0000

Change-Id: I38256934302440e4d906f29fa09a05c191a085a1
---
M composer.lock
A extensions/Wikibase/CREDITS
M extensions/Wikibase/client/WikibaseClient.php
M extensions/Wikibase/client/i18n/hu.json
M extensions/Wikibase/client/i18n/lzh.json
M extensions/Wikibase/client/i18n/my.json
M extensions/Wikibase/client/includes/DispatchingServiceFactory.php
M extensions/Wikibase/client/includes/Store/RepositoryServiceContainer.php
A 
extensions/Wikibase/client/includes/Store/RepositoryServiceContainerFactory.php
M extensions/Wikibase/client/includes/WikibaseClient.php
M 
extensions/Wikibase/client/tests/phpunit/includes/DispatchingServiceFactoryTest.php
M 
extensions/Wikibase/client/tests/phpunit/includes/DispatchingServiceWiringTest.php
A 
extensions/Wikibase/client/tests/phpunit/includes/Store/RepositoryServiceContainerFactoryTest.php
M 
extensions/Wikibase/client/tests/phpunit/includes/Store/RepositoryServiceContainerTest.php
M 
extensions/Wikibase/client/tests/phpunit/includes/Store/Sql/DirectSqlStoreTest.php
M extensions/Wikibase/docs/change-op-serializations.wiki
M extensions/Wikibase/docs/extending-entities.wiki
M extensions/Wikibase/lib/WikibaseLib.php
M extensions/Wikibase/lib/includes/Store/CacheAwarePropertyInfoStore.php
M extensions/Wikibase/lib/tests/phpunit/MockRepository.php
M extensions/Wikibase/lib/tests/phpunit/Store/EntityInfoBuilderTest.php
M extensions/Wikibase/lib/tests/phpunit/Store/Sql/SqlEntityInfoBuilderTest.php
M extensions/Wikibase/repo/Wikibase.php
M extensions/Wikibase/repo/i18n/bn.json
M extensions/Wikibase/repo/i18n/de.json
M extensions/Wikibase/repo/i18n/es.json
M extensions/Wikibase/repo/i18n/ml.json
M extensions/Wikibase/repo/i18n/my.json
M extensions/Wikibase/repo/includes/Api/ParseValue.php
M extensions/Wikibase/repo/includes/Content/EntityContent.php
M extensions/Wikibase/repo/includes/Search/Elastic/Fields/LabelCountField.php
M 
extensions/Wikibase/repo/includes/Validators/LabelDescriptionUniquenessValidator.php
M extensions/Wikibase/repo/includes/Validators/LabelUniquenessValidator.php
M extensions/Wikibase/repo/tests/phpunit/includes/Api/ParseValueTest.php
M 
extensions/Wikibase/repo/tests/phpunit/includes/Api/StatementModificationHelperTest.php
M extensions/Wikibase/repo/tests/phpunit/includes/ChangeOp/ChangeOpLabelTest.php
M extensions/Wikibase/repo/tests/phpunit/includes/Content/EntityContentTest.php
M 
extensions/Wikibase/repo/tests/phpunit/includes/Specials/SpecialGoToLinkedPageTest.php
M 
extensions/Wikibase/repo/tests/phpunit/includes/Specials/SpecialModifyTermTestCase.php
M vendor/composer/autoload_classmap.php
M vendor/composer/installed.json
41 files changed, 1,042 insertions(+), 278 deletions(-)

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



diff --git a/composer.lock b/composer.lock
index 7770ba1..22953e8 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1606,12 +1606,12 @@
             "source": {
                 "type": "git",
                 "url": 
"https://github.com/wikimedia/mediawiki-extensions-Wikibase.git";,
-                "reference": "429ab38c5ddf34438425a4012b08fcba806fb54b"
+                "reference": "5c39fd44085adf7797e5084a7754b7631c2f2e1b"
             },
             "dist": {
                 "type": "zip",
-                "url": 
"https://api.github.com/repos/wikimedia/mediawiki-extensions-Wikibase/zipball/429ab38c5ddf34438425a4012b08fcba806fb54b";,
-                "reference": "429ab38c5ddf34438425a4012b08fcba806fb54b",
+                "url": 
"https://api.github.com/repos/wikimedia/mediawiki-extensions-Wikibase/zipball/5c39fd44085adf7797e5084a7754b7631c2f2e1b";,
+                "reference": "5c39fd44085adf7797e5084a7754b7631c2f2e1b",
                 "shasum": ""
             },
             "require": {
@@ -1686,7 +1686,7 @@
                 "wikibaserepo",
                 "wikidata"
             ],
-            "time": "2017-01-26 15:15:12"
+            "time": "2017-01-27 16:09:49"
         },
         {
             "name": "wikibase/wikimedia-badges",
diff --git a/extensions/Wikibase/CREDITS b/extensions/Wikibase/CREDITS
new file mode 100644
index 0000000..d9f8505
--- /dev/null
+++ b/extensions/Wikibase/CREDITS
@@ -0,0 +1,156 @@
+<!--
+Wikibase is a collaborative project released under the
+GNU General Public License v2. We would like to recognize
+the following names for their contribution to the product.
+
+The following list can be found parsed under:
+ * Special:Version/Credits/Wikibase_Repository
+ * Special:Version/Credits/Wikibase_Client
+ * Special:Version/Credits/WikibaseLib
+-->
+== Team members ==
+
+This includes current and previous members.
+
+* Abraham Taherivand
+* addshore
+* Adrian Heine
+* Aleksey Bekh-Ivanov
+* Amir Sarabadani
+* Anja Jentzsch
+* Bene
+* Christopher Johnson
+* Daniel Kinzler
+* Daniel Werner
+* Denny Vrandečić
+* H. Snater
+* Jakob Warkotsch
+* Jan Dittrich
+* Jan Zerebecki
+* Jens Ohlig
+* Jeroen De Dauw
+* John Erling Blad
+* Jonas Kress
+* Katie Filbert
+* Leszek Manicki
+* Lucie-Aimée Kaffee
+* Lydia Pintscher
+* Marius Hoch
+* Markus Krötzsch
+* Nikola Smolenski
+* Stanislav Malyshev
+* Thiemo Mättig
+* Tobias Gritschacher
+
+== Patch Contributors ==
+* 1ec5
+* Aaron Schulz
+* Aftab
+* Alangi Derick
+* albe
+* Alexander Lehmann
+* Alex Monk
+* Amir E. Aharoni
+* Antoine Musso
+* Arkanosis
+* atlight
+* ayush_garg
+* Bartosz Dziewoński
+* Beta16
+* blotmandroid
+* Brad Jorsch
+* ch3nkula
+* Chad Horohoe
+* dachary
+* danmichaelo
+* Dan Michael O. Heggø
+* Dereckson
+* Ebrahim Byagowi
+* ecotg
+* Erik Bernhardson
+* Federico Leva
+* feelfreelinux
+* Florian Schmidt
+* Fomafix
+* GEOFBOT
+* Geoffrey Mon
+* Gergő Tisza
+* Gilles Dubuc
+* gladoscc
+* Granjow
+* Hostmaster of the Day
+* Jack Phoenix
+* JadeMaveric
+* Jared Flores
+* jcf2000
+* Joan Creus
+* Julia Himmel
+* julius
+* Justin Du
+* Jérémie Roquet
+* karan
+* karan10
+* Kevin Israel
+* kghbln
+* Kunal Mehta
+* kushal124
+* Kushal Khandelwal
+* lalexf
+* LB-42
+* Leon Liesener
+* Liangent
+* Loic Dachary
+* lokal-profil
+* mary-kate
+* Matěj Suchánek
+* mayankmadan
+* mdex192837
+* Michał Łazowik
+* Minh Nguyễn
+* mjbmr
+* mlazowik
+* MtDu
+* multichill
+* Mushroom
+* Ori Livneh
+* Paladox
+* Pavel Selitskas
+* plstand
+* pragunbhutani
+* Purodha
+* Raimond Spekking
+* Raymond
+* raymondde
+* Ricordisamoa
+* rlot
+* Ryan Kaldari
+* Sam Reed
+* Schnark
+* se4598
+* shirayuki
+* Siebrand Mazeland
+* Simon A. Eugster
+* Sn1per
+* Southparkfan
+* S Page
+* subins2000
+* Subin Siby
+* tholam
+* Timo Tijhof
+* Tim Starling
+* Tom Arrow
+* Tony Thomas
+* Tpt
+* TTO
+* Umherirrender
+* Urbanecm
+* vikassy
+* vldandrew
+* wizardist
+* Yuki Shira
+* [[mw:User:GZWDer]]
+* Željko Filipin
+
+== Translators ==
+
+* [https://translatewiki.net/wiki/Translating:MediaWiki/Credits Translators on 
translatewiki.net and others]
diff --git a/extensions/Wikibase/client/WikibaseClient.php 
b/extensions/Wikibase/client/WikibaseClient.php
index 6a95bfe..07df902 100644
--- a/extensions/Wikibase/client/WikibaseClient.php
+++ b/extensions/Wikibase/client/WikibaseClient.php
@@ -78,7 +78,7 @@
                'name' => 'Wikibase Client',
                'version' => WBC_VERSION,
                'author' => array(
-                       'The Wikidata team', // TODO: link?
+                       'The Wikidata team',
                ),
                'url' => 
'https://www.mediawiki.org/wiki/Extension:Wikibase_Client',
                'descriptionmsg' => 'wikibase-client-desc',
diff --git a/extensions/Wikibase/client/i18n/hu.json 
b/extensions/Wikibase/client/i18n/hu.json
index 163c29a..d5fcce2 100644
--- a/extensions/Wikibase/client/i18n/hu.json
+++ b/extensions/Wikibase/client/i18n/hu.json
@@ -81,5 +81,7 @@
        "notification-header-page-connection": "A(z) <strong>$3</strong> 
{{GENDER:$2|össze lett kapcsolva}} egy {{WBREPONAME}}-elemmel",
        "notification-bundle-header-page-connection": "<strong>$3</strong> és 
{{PLURAL:$4|még egy|$4 másik|100=99+ másik}} lap {{GENDER:$2|össze lett 
kapcsolva}} {{WBREPONAME}}-elemekkel.",
        "notification-link-text-view-item": "Elem {{GENDER:$1|megtekintése}}",
-       "notification-subject-page-connection": "A(z) {{SITENAME}} wikin 
{{GENDER:$3|létrehozott}} oldaladat {{GENDER:$2|összekapcsolták}} egy 
{{WBREPONAME}}-elemmel"
+       "notification-subject-page-connection": "A(z) {{SITENAME}} wikin 
{{GENDER:$3|létrehozott}} oldaladat {{GENDER:$2|összekapcsolták}} egy 
{{WBREPONAME}}-elemmel",
+       "unresolved-property-category": "Lapok feloldhatatlan tulajdonságokkal",
+       "unresolved-property-category-desc": "Ez a kategória azokat a lapokat 
listázza, amik olyan {{WBREPONAME}}-tulajdonságokat próbálnak lekérdezni, 
amiket sem az azonosítójuk, sem a címkéjük alapján nem sikerült megtalálni."
 }
diff --git a/extensions/Wikibase/client/i18n/lzh.json 
b/extensions/Wikibase/client/i18n/lzh.json
index fbf8b4c..418ed0f 100644
--- a/extensions/Wikibase/client/i18n/lzh.json
+++ b/extensions/Wikibase/client/i18n/lzh.json
@@ -7,6 +7,7 @@
                        "SolidBlock"
                ]
        },
+       "wikibase-after-page-move": "斯頁自有相繫者,在於{{WBREPONAME}}。君既改其題,亦宜改其[$1 
相繫之頁]。然後諸語之間,方能不斷連結。",
        "wikibase-comment-update": "{{WBREPONAME}}項變",
        "wikibase-editlinks": "纂鏈",
        "wikibase-editlinkstitle": "修跨語鏈",
diff --git a/extensions/Wikibase/client/i18n/my.json 
b/extensions/Wikibase/client/i18n/my.json
index 237b1c6..841dfc9 100644
--- a/extensions/Wikibase/client/i18n/my.json
+++ b/extensions/Wikibase/client/i18n/my.json
@@ -5,6 +5,7 @@
                        "9.sinistra"
                ]
        },
+       "wikibase-comment-update": "{{WBREPONAME}} item ပြောင်းလဲခဲ့သည်",
        "wikibase-dataitem": "{{WBREPONAME}} item",
        "wikibase-editlinks": "လင့်ခ်များကို တည်းဖြတ်ရန်",
        "wikibase-editlinkstitle": "ဘာသာစကားလင့်ခ်များကို တည်းဖြတ်ရန်",
diff --git a/extensions/Wikibase/client/includes/DispatchingServiceFactory.php 
b/extensions/Wikibase/client/includes/DispatchingServiceFactory.php
index 28ca917..546b00d 100644
--- a/extensions/Wikibase/client/includes/DispatchingServiceFactory.php
+++ b/extensions/Wikibase/client/includes/DispatchingServiceFactory.php
@@ -4,23 +4,36 @@
 
 use MediaWiki\Services\ServiceContainer;
 use Wikibase\Client\Store\RepositoryServiceContainer;
-use Wikibase\DataModel\Services\EntityId\PrefixMappingEntityIdParserFactory;
+use Wikibase\Client\Store\RepositoryServiceContainerFactory;
+use Wikibase\DataModel\Services\Lookup\UnknownForeignRepositoryException;
+use Wikibase\DataModel\Entity\EntityId;
+use Wikibase\DataModel\Entity\EntityRedirect;
 use Wikibase\DataModel\Services\Term\TermBuffer;
-use Wikibase\Lib\Serialization\RepositorySpecificDataValueDeserializerFactory;
+use Wikibase\EntityRevision;
 use Wikibase\Lib\Store\EntityRevisionLookup;
+use Wikibase\Lib\Store\EntityStoreWatcher;
 use Wikibase\Lib\Store\PropertyInfoLookup;
-use Wikibase\SettingsArray;
 
 /**
  * A factory/locator of services dispatching the action to services configured 
for the
  * particular input, based on the repository the particular input entity 
belongs to.
  * Dispatching services provide a way of using entities from multiple 
repositories.
  *
- * Services are defined by loading a wiring array(s), or by using 
defineService method.
+ * Services are defined by loading wiring arrays, or by using defineService 
method.
  *
  * @license GPL-2.0+
  */
-class DispatchingServiceFactory extends ServiceContainer implements 
EntityDataRetrievalServiceFactory {
+class DispatchingServiceFactory extends ServiceContainer implements 
EntityDataRetrievalServiceFactory, EntityStoreWatcher {
+
+       /**
+        * @var string[]
+        */
+       private $repositoryNames;
+
+       /**
+        * @var RepositoryServiceContainerFactory
+        */
+       private $repositoryServiceContainerFactory;
 
        /**
         * @var RepositoryServiceContainer[]
@@ -28,83 +41,17 @@
        private $repositoryServiceContainers = [];
 
        /**
-        * FIXME: injecting of the top-level factory (WikibaseClient) here is 
only a temporary solution.
-        * This class uses top-level factory to access settings and several 
services provided by the top-level
-        * factory. Also, the instance of the top-level factory is being passed 
to instantiators of services
-        * stored in the RepositoryServiceContainer in order to get service 
they depend on.
-        *
-        * This approach is not clean, the class should not depend on the 
top-level factory.
-        * This should be changed after some refactoring: this factory should 
be able to instantiate
-        * services it now gets from the client. Config should be also passed 
in properly, without
-        * a need to inject WikibaseClient instance to access relevant 
settings. Instantiators
-        * in RepositoryServiceWiring should rather be getting some other 
service container, not the
-        * whole top-level factory.
-        *
-        * @param WikibaseClient $client
+        * @param RepositoryServiceContainerFactory 
$repositoryServiceContainerFactory
+        * @param string[] $repositoryNames
         */
-       public function __construct( WikibaseClient $client ) {
+       public function __construct(
+               RepositoryServiceContainerFactory 
$repositoryServiceContainerFactory,
+               array $repositoryNames
+       ) {
                parent::__construct();
 
-               $this->initRepositoryServiceContainers( $client );
-       }
-
-       private function initRepositoryServiceContainers( WikibaseClient 
$client ) {
-               $repositoryNames = array_merge(
-                       [ '' ],
-                       array_keys( $client->getSettings()->getSetting( 
'foreignRepositories' ) )
-               );
-
-               $idParserFactory = new PrefixMappingEntityIdParserFactory(
-                       $client->getEntityIdParser(), // TODO: this should be 
moved to this class; see T153427
-                       $this->getIdPrefixMaps( 
$client->getSettings()->getSetting( 'foreignRepositories' ) )
-               );
-               $dataValueDeserializerFactory = new 
RepositorySpecificDataValueDeserializerFactory( $idParserFactory );
-
-               foreach ( $repositoryNames as $repositoryName ) {
-                       $container = new RepositoryServiceContainer(
-                               $this->getRepositoryDatabaseName( 
$repositoryName, $client->getSettings() ),
-                               $repositoryName,
-                               $idParserFactory->getIdParser( $repositoryName 
),
-                               $dataValueDeserializerFactory->getDeserializer( 
$repositoryName ),
-                               $client
-                       );
-                       $container->loadWiringFiles( 
$client->getSettings()->getSetting( 'repositoryServiceWiringFiles' ) );
-
-                       $this->repositoryServiceContainers[$repositoryName] = 
$container;
-               }
-       }
-
-       /**
-        * Returns a map of id prefix mappings defined for configured foreign 
repositories.
-        *
-        * @param array[] $settings Repository definitions mapping repository 
names to settings
-        *
-        * @return array[] Associative array mapping repository names to 
repository-specific prefix
-        *  mapping.
-        */
-       private function getIdPrefixMaps( array $settings ) {
-               $mappings = [];
-               foreach ( $settings as $repositoryName => $repositorySettings ) 
{
-                       if ( array_key_exists( 'prefixMapping', 
$repositorySettings ) ) {
-                               $mappings[$repositoryName] = 
$repositorySettings['prefixMapping'];
-                       }
-               }
-               return $mappings;
-       }
-
-       /**
-        * @param string $repositoryName
-        * @param SettingsArray $settings
-        *
-        * @return string|false
-        */
-       private function getRepositoryDatabaseName( $repositoryName, 
SettingsArray $settings ) {
-               if ( $repositoryName === '' ) {
-                       return $settings->getSetting( 'repoDatabase' );
-               }
-
-               $foreignRepoSettings = $settings->getSetting( 
'foreignRepositories' );
-               return $foreignRepoSettings[$repositoryName]['repoDatabase'];
+               $this->repositoryServiceContainerFactory = 
$repositoryServiceContainerFactory;
+               $this->repositoryNames = $repositoryNames;
        }
 
        /**
@@ -113,13 +60,78 @@
         */
        public function getServiceMap( $service ) {
                $serviceMap = [];
-               foreach ( $this->repositoryServiceContainers as $repositoryName 
=> $container ) {
+               foreach ( $this->repositoryNames as $repositoryName ) {
+                       $container = $this->getContainerForRepository( 
$repositoryName );
+                       if ( $container !== null ) {
                                $serviceMap[$repositoryName] = 
$container->getService( $service );
+                       }
                }
                return $serviceMap;
        }
 
        /**
+        * @param string $repositoryName
+        *
+        * @return RepositoryServiceContainer|null
+        */
+       private function getContainerForRepository( $repositoryName ) {
+               if ( !array_key_exists( $repositoryName, 
$this->repositoryServiceContainers ) ) {
+                       try {
+                               
$this->repositoryServiceContainers[$repositoryName] =
+                                       
$this->repositoryServiceContainerFactory->newContainer( $repositoryName );
+                       } catch ( UnknownForeignRepositoryException $exception 
) {
+                               
$this->repositoryServiceContainers[$repositoryName] = null;
+                       }
+               }
+
+               return $this->repositoryServiceContainers[$repositoryName];
+       }
+
+       /**
+        * @see EntityStoreWatcher::entityUpdated
+        *
+        * @param EntityRevision $entityRevision
+        */
+       public function entityUpdated( EntityRevision $entityRevision ) {
+               $container = $this->getContainerForRepository(
+                       
$entityRevision->getEntity()->getId()->getRepositoryName()
+               );
+
+               if ( $container !== null ) {
+                       $container->entityUpdated( $entityRevision );
+               }
+       }
+
+       /**
+        * @see EntityStoreWatcher::entityDeleted
+        *
+        * @param EntityId $entityId
+        */
+       public function entityDeleted( EntityId $entityId ) {
+               $container = $this->getContainerForRepository( 
$entityId->getRepositoryName() );
+
+               if ( $container !== null ) {
+                       $container->entityDeleted( $entityId );
+               }
+       }
+
+       /**
+        * @see EntityStoreWatcher::redirectUpdated
+        *
+        * @param EntityRedirect $entityRedirect
+        * @param int $revisionId
+        */
+       public function redirectUpdated( EntityRedirect $entityRedirect, 
$revisionId ) {
+               $container = $this->getContainerForRepository(
+                       $entityRedirect->getEntityId()->getRepositoryName()
+               );
+
+               if ( $container !== null ) {
+                       $container->redirectUpdated( $entityRedirect, 
$revisionId );
+               }
+       }
+
+       /**
         * @return EntityRevisionLookup
         */
        public function getEntityRevisionLookup() {
diff --git 
a/extensions/Wikibase/client/includes/Store/RepositoryServiceContainer.php 
b/extensions/Wikibase/client/includes/Store/RepositoryServiceContainer.php
index 36c7b5b..74c8799 100644
--- a/extensions/Wikibase/client/includes/Store/RepositoryServiceContainer.php
+++ b/extensions/Wikibase/client/includes/Store/RepositoryServiceContainer.php
@@ -8,8 +8,12 @@
 use MediaWiki\Services\ServiceContainer;
 use Wikibase\Client\WikibaseClient;
 use Wikibase\DataModel\DeserializerFactory;
+use Wikibase\DataModel\Entity\EntityId;
 use Wikibase\DataModel\Entity\EntityIdParser;
+use Wikibase\DataModel\Entity\EntityRedirect;
+use Wikibase\EntityRevision;
 use Wikibase\InternalSerialization\DeserializerFactory as 
InternalDeserializerFactory;
+use Wikibase\Lib\Store\EntityStoreWatcher;
 
 /**
  * A service locator for services configured for a particular repository.
@@ -17,7 +21,7 @@
  *
  * @license GPL-2.0+
  */
-class RepositoryServiceContainer extends ServiceContainer {
+class RepositoryServiceContainer extends ServiceContainer implements 
EntityStoreWatcher {
 
        /**
         * @var string|false
@@ -121,4 +125,47 @@
                return $internalDeserializerFactory->newEntityDeserializer();
        }
 
+       /**
+        * @see EntityStoreWatcher::entityUpdated
+        *
+        * @param EntityRevision $entityRevision
+        */
+       public function entityUpdated( EntityRevision $entityRevision ) {
+               foreach ( $this->getServiceNames() as $serviceName ) {
+                       $service = $this->peekService( $serviceName );
+                       if ( $service instanceof EntityStoreWatcher ) {
+                               $service->entityUpdated( $entityRevision );
+                       }
+               }
+       }
+
+       /**
+        * @see EntityStoreWatcher::entityDeleted
+        *
+        * @param EntityId $entityId
+        */
+       public function entityDeleted( EntityId $entityId ) {
+               foreach ( $this->getServiceNames() as $serviceName ) {
+                       $service = $this->peekService( $serviceName );
+                       if ( $service instanceof EntityStoreWatcher ) {
+                               $service->entityDeleted( $entityId );
+                       }
+               }
+       }
+
+       /**
+        * @see EntityStoreWatcher::redirectUpdated
+        *
+        * @param EntityRedirect $entityRedirect
+        * @param int $revisionId
+        */
+       public function redirectUpdated( EntityRedirect $entityRedirect, 
$revisionId ) {
+               foreach ( $this->getServiceNames() as $serviceName ) {
+                       $service = $this->peekService( $serviceName );
+                       if ( $service instanceof EntityStoreWatcher ) {
+                               $service->redirectUpdated( $entityRedirect, 
$revisionId );
+                       }
+               }
+       }
+
 }
diff --git 
a/extensions/Wikibase/client/includes/Store/RepositoryServiceContainerFactory.php
 
b/extensions/Wikibase/client/includes/Store/RepositoryServiceContainerFactory.php
new file mode 100644
index 0000000..128f367
--- /dev/null
+++ 
b/extensions/Wikibase/client/includes/Store/RepositoryServiceContainerFactory.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Wikibase\Client\Store;
+
+use Wikibase\Client\WikibaseClient;
+use Wikibase\DataModel\Services\EntityId\PrefixMappingEntityIdParserFactory;
+use Wikibase\DataModel\Services\Lookup\UnknownForeignRepositoryException;
+use Wikibase\Lib\Serialization\RepositorySpecificDataValueDeserializerFactory;
+
+/**
+ * A factory providing RepositoryServiceContainer objects configured for given 
repository.
+ * RepositoryServiceContainers are initialized using wiring files provided in 
the constructor.
+ *
+ * @license GPL-2.0+
+ */
+class RepositoryServiceContainerFactory {
+
+       /**
+        * @var PrefixMappingEntityIdParserFactory
+        */
+       private $idParserFactory;
+
+       /**
+        * @var RepositorySpecificDataValueDeserializerFactory
+        */
+       private $dataValueDeserializerFactory;
+
+       /**
+        * Associative array mapping repository names to database names (string 
or false)
+        *
+        * @var array
+        */
+       private $databaseNames;
+
+       /**
+        * @var string[]
+        */
+       private $wiringFiles;
+
+       /**
+        * @var WikibaseClient
+        */
+       private $client;
+
+       /**
+        * FIXME: injecting of the top-level factory (WikibaseClient) here is 
only a temporary solution.
+        * The instance of the top-level factory is being passed to 
instantiators of services
+        * stored in the RepositoryServiceContainer so they can get the service 
they depend on.
+        *
+        * This approach is not clean, the class should not depend on the 
top-level factory.
+        * This should be changed after some refactoring: Instantiators in 
RepositoryServiceWiring should
+        * rather be getting some other service container, not the whole 
top-level factory.
+        *
+        * @param PrefixMappingEntityIdParserFactory $idParserFactory
+        * @param RepositorySpecificDataValueDeserializerFactory 
$dataValueDeserializerFactory
+        * @param array $repositoryDatabaseNames
+        * @param string[] $wiringFiles
+        * @param WikibaseClient $client
+        */
+       public function __construct(
+               PrefixMappingEntityIdParserFactory $idParserFactory,
+               RepositorySpecificDataValueDeserializerFactory 
$dataValueDeserializerFactory,
+               array $repositoryDatabaseNames,
+               array $wiringFiles,
+               WikibaseClient $client
+       ) {
+               $this->idParserFactory = $idParserFactory;
+               $this->dataValueDeserializerFactory = 
$dataValueDeserializerFactory;
+               $this->databaseNames = $repositoryDatabaseNames;
+               $this->wiringFiles = $wiringFiles;
+               $this->client = $client;
+       }
+
+       /**
+        * @param string $repositoryName
+        *
+        * @return RepositoryServiceContainer
+        *
+        * @throws UnknownForeignRepositoryException
+        */
+       public function newContainer( $repositoryName ) {
+               if ( !array_key_exists( $repositoryName, $this->databaseNames ) 
) {
+                       throw new UnknownForeignRepositoryException( 
$repositoryName );
+               }
+
+               $container = new RepositoryServiceContainer(
+                       $this->databaseNames[$repositoryName],
+                       $repositoryName,
+                       $this->idParserFactory->getIdParser( $repositoryName ),
+                       $this->dataValueDeserializerFactory->getDeserializer( 
$repositoryName ),
+                       $this->client
+               );
+               $container->loadWiringFiles( $this->wiringFiles );
+
+               return $container;
+       }
+
+}
diff --git a/extensions/Wikibase/client/includes/WikibaseClient.php 
b/extensions/Wikibase/client/includes/WikibaseClient.php
index 2f07c6b..ef8f447 100644
--- a/extensions/Wikibase/client/includes/WikibaseClient.php
+++ b/extensions/Wikibase/client/includes/WikibaseClient.php
@@ -40,8 +40,10 @@
 use Wikibase\Client\ParserOutput\ClientParserOutputDataUpdater;
 use Wikibase\Client\RecentChanges\RecentChangeFactory;
 use Wikibase\Client\Serializer\ForbiddenSerializer;
+use Wikibase\Client\Store\RepositoryServiceContainerFactory;
 use Wikibase\DataModel\Entity\EntityIdValue;
 use Wikibase\DataModel\SerializerFactory;
+use Wikibase\DataModel\Services\EntityId\PrefixMappingEntityIdParserFactory;
 use Wikibase\DataModel\Services\Lookup\RestrictedEntityLookup;
 use Wikibase\Client\DataAccess\SnaksFinder;
 use Wikibase\Client\Hooks\LanguageLinkBadgeDisplay;
@@ -72,6 +74,7 @@
 use Wikibase\Lib\EntityIdComposer;
 use Wikibase\Lib\EntityTypeDefinitions;
 use Wikibase\Lib\FormatterLabelDescriptionLookupFactory;
+use Wikibase\Lib\Serialization\RepositorySpecificDataValueDeserializerFactory;
 use Wikibase\Lib\Store\LanguageFallbackLabelDescriptionLookupFactory;
 use Wikibase\Lib\LanguageNameLookup;
 use Wikibase\Lib\MediaWikiContentLanguages;
@@ -374,7 +377,14 @@
         */
        private function getEntityDataRetrievalServiceFactory() {
                if ( $this->entityDataRetrievalServiceFactory === null ) {
-                       $factory = new DispatchingServiceFactory( $this );
+                       $factory = new DispatchingServiceFactory(
+                               $this->getRepositoryServiceContainerFactory(),
+                               // FIXME: array_merge trick will no longer be 
needed once repositry settings are unified, see: T153767.
+                               array_merge(
+                                       [ '' ],
+                                       array_keys( 
$this->getSettings()->getSetting( 'foreignRepositories' ) )
+                               )
+                       );
                        $factory->loadWiringFiles( $this->settings->getSetting( 
'dispatchingServiceWiringFiles' ) );
 
                        $this->entityDataRetrievalServiceFactory = $factory;
@@ -383,6 +393,59 @@
                return $this->entityDataRetrievalServiceFactory;
        }
 
+       private function getRepositoryServiceContainerFactory() {
+               $idParserFactory = new PrefixMappingEntityIdParserFactory(
+                       $this->getEntityIdParser(),
+                       $this->getIdPrefixMaps()
+               );
+
+               return new RepositoryServiceContainerFactory(
+                       $idParserFactory,
+                       new RepositorySpecificDataValueDeserializerFactory( 
$idParserFactory ),
+                       $this->getRepositoryDatabaseNames(),
+                       $this->getSettings()->getSetting( 
'repositoryServiceWiringFiles' ),
+                       $this
+               );
+       }
+
+       /**
+        * Returns an associative array mapping names of configured 
repositories to respective database names
+        * (either strings or false for local wiki's database).
+        * Returned map contains an empty string key for a local repository.
+        *
+        * @return array
+        */
+       private function getRepositoryDatabaseNames() {
+               // FIXME: t no longer be needed to check different settings 
(repoDatabase vs foreignRepositories
+               // once repositry settings are unified, see: T153767.
+               $databaseNames = [ '' => $this->getSettings()->getSetting( 
'repoDatabase' ) ];
+
+               foreach ( $this->getSettings()->getSetting( 
'foreignRepositories' )
+                       as $repositoryName => $repositorySettings
+               ) {
+                       $databaseNames[$repositoryName] = 
$repositorySettings['repoDatabase'];
+               }
+
+               return $databaseNames;
+       }
+
+       /**
+        * Returns a map of id prefix mappings defined for configured foreign 
repositories.
+        *
+        * @return array[] Associative array mapping repository names to 
repository-specific prefix mapping.
+        */
+       private function getIdPrefixMaps() {
+               $mappings = [];
+               foreach ( $this->getSettings()->getSetting( 
'foreignRepositories' )
+                       as $repositoryName => $repositorySettings
+               ) {
+                       if ( array_key_exists( 'prefixMapping', 
$repositorySettings ) ) {
+                               $mappings[$repositoryName] = 
$repositorySettings['prefixMapping'];
+                       }
+               }
+               return $mappings;
+       }
+
        /**
         * @return EntityLookup
         */
diff --git 
a/extensions/Wikibase/client/tests/phpunit/includes/DispatchingServiceFactoryTest.php
 
b/extensions/Wikibase/client/tests/phpunit/includes/DispatchingServiceFactoryTest.php
index f55acf9..3e49b22 100644
--- 
a/extensions/Wikibase/client/tests/phpunit/includes/DispatchingServiceFactoryTest.php
+++ 
b/extensions/Wikibase/client/tests/phpunit/includes/DispatchingServiceFactoryTest.php
@@ -2,8 +2,16 @@
 
 namespace Wikibase\Client\Tests;
 
+use DataValues\Deserializers\DataValueDeserializer;
 use Wikibase\Client\DispatchingServiceFactory;
+use Wikibase\Client\Store\RepositoryServiceContainer;
+use Wikibase\Client\Store\RepositoryServiceContainerFactory;
 use Wikibase\Client\WikibaseClient;
+use Wikibase\DataModel\Entity\BasicEntityIdParser;
+use Wikibase\DataModel\Entity\EntityRedirect;
+use Wikibase\DataModel\Entity\Item;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\EntityRevision;
 use Wikibase\Lib\Store\EntityRevisionLookup;
 
 /**
@@ -17,14 +25,43 @@
 class DispatchingServiceFactoryTest extends \PHPUnit_Framework_TestCase {
 
        /**
+        * @return RepositoryServiceContainerFactory
+        */
+       private function getRepositoryServiceContainerFactory() {
+               $entityRevisionLookup = $this->getMock( 
EntityRevisionLookup::class );
+
+               $container = $this->getMockBuilder( 
RepositoryServiceContainer::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $container->expects( $this->any() )
+                       ->method( 'getService' )
+                       ->will(
+                               $this->returnCallback( function ( $service ) 
use ( $entityRevisionLookup ) {
+                                       return $service === 
'EntityRevisionLookup' ? $entityRevisionLookup : null;
+                               } )
+                       );
+
+               $containerFactory = $this->getMockBuilder( 
RepositoryServiceContainerFactory::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $containerFactory->expects( $this->any() )
+                       ->method( 'newContainer' )
+                       ->will( $this->returnValue( $container ) );
+
+               return $containerFactory;
+       }
+
+       /**
+        * @param RepositoryServiceContainerFactory $containerFactory
+        *
         * @return DispatchingServiceFactory
         */
-       private function getDispatchingServiceFactory() {
+       private function getDispatchingServiceFactory( 
RepositoryServiceContainerFactory $containerFactory ) {
                $client = WikibaseClient::getDefaultInstance();
                $settings = $client->getSettings();
                $settings->setSetting( 'foreignRepositories', [ 'foo' => [ 
'repoDatabase' => 'foowiki' ] ] );
 
-               $factory = new DispatchingServiceFactory( $client );
+               $factory = new DispatchingServiceFactory( $containerFactory, [ 
'', 'foo' ] );
 
                $factory->defineService( 'EntityRevisionLookup', function() {
                        return $this->getMock( EntityRevisionLookup::class );
@@ -34,7 +71,7 @@
        }
 
        public function testGetServiceNames() {
-               $factory = $this->getDispatchingServiceFactory();
+               $factory = $this->getDispatchingServiceFactory( 
$this->getRepositoryServiceContainerFactory() );
 
                $this->assertEquals(
                        [ 'EntityRevisionLookup' ],
@@ -43,7 +80,7 @@
        }
 
        public function testGetServiceMap() {
-               $factory = $this->getDispatchingServiceFactory();
+               $factory = $this->getDispatchingServiceFactory( 
$this->getRepositoryServiceContainerFactory() );
 
                $serviceMap = $factory->getServiceMap( 'EntityRevisionLookup' );
 
@@ -55,7 +92,7 @@
        }
 
        public function testGetService() {
-               $factory = $this->getDispatchingServiceFactory();
+               $factory = $this->getDispatchingServiceFactory( 
$this->getRepositoryServiceContainerFactory() );
 
                $serviceOne = $factory->getService( 'EntityRevisionLookup' );
                $serviceTwo = $factory->getService( 'EntityRevisionLookup' );
@@ -65,4 +102,75 @@
                $this->assertSame( $serviceOne, $serviceTwo );
        }
 
+       /**
+        * @param string|false $dbName
+        * @param string $repositoryName
+        *
+        * @return RepositoryServiceContainer
+        */
+       private function getRepositoryServiceContainer( $dbName, 
$repositoryName ) {
+               return new RepositoryServiceContainer(
+                       $dbName,
+                       $repositoryName,
+                       new BasicEntityIdParser(),
+                       new DataValueDeserializer( [] ),
+                       WikibaseClient::getDefaultInstance()
+               );
+       }
+
+       /**
+        * @param string $event
+        *
+        * @return RepositoryServiceContainerFactory
+        */
+       private function getRepositoryServiceContainerFactoryForEventTest( 
$event ) {
+               $localServiceContainer = $this->getMockBuilder( 
RepositoryServiceContainer::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $localServiceContainer->expects( $this->never() )->method( 
$event );
+
+               $fooServiceContainer = $this->getMockBuilder( 
RepositoryServiceContainer::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $fooServiceContainer->expects( $this->atLeastOnce() )->method( 
$event );
+
+               $containerFactory = $this->getMockBuilder( 
RepositoryServiceContainerFactory::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $containerFactory->expects( $this->any() )
+                       ->method( 'newContainer' )
+                       ->will(
+                               $this->returnCallback( function ( $container ) 
use ( $localServiceContainer, $fooServiceContainer ) {
+                                       return $container === '' ? 
$localServiceContainer : $fooServiceContainer;
+                               } )
+                       );
+
+               return $containerFactory;
+       }
+
+       public function 
testEntityUpdatedDelegatesEventToContainerOfRelevantRepository() {
+               $factory = $this->getDispatchingServiceFactory(
+                       
$this->getRepositoryServiceContainerFactoryForEventTest( 'entityUpdated' )
+               );
+
+               $factory->entityUpdated( new EntityRevision( new Item( new 
ItemId( 'foo:Q123' ) ) ) );
+       }
+
+       public function 
testEntityDeletedDelegatesEventToContainerOfRelevantRepository() {
+               $factory = $this->getDispatchingServiceFactory(
+                       
$this->getRepositoryServiceContainerFactoryForEventTest( 'entityDeleted' )
+               );
+
+               $factory->entityDeleted( new ItemId( 'foo:Q123' ) );
+       }
+
+       public function 
testRedirectUpdatedDelegatesEventToContainerOfRelevantRepository() {
+               $factory = $this->getDispatchingServiceFactory(
+                       
$this->getRepositoryServiceContainerFactoryForEventTest( 'redirectUpdated' )
+               );
+
+               $factory->redirectUpdated( new EntityRedirect( new ItemId( 
'foo:Q123' ), new ItemId( 'foo:Q321' ) ), 100 );
+       }
+
 }
diff --git 
a/extensions/Wikibase/client/tests/phpunit/includes/DispatchingServiceWiringTest.php
 
b/extensions/Wikibase/client/tests/phpunit/includes/DispatchingServiceWiringTest.php
index eb61f06..8433c4e 100644
--- 
a/extensions/Wikibase/client/tests/phpunit/includes/DispatchingServiceWiringTest.php
+++ 
b/extensions/Wikibase/client/tests/phpunit/includes/DispatchingServiceWiringTest.php
@@ -3,8 +3,12 @@
 namespace Wikibase\Client\Tests;
 
 use Wikibase\Client\DispatchingServiceFactory;
+use Wikibase\Client\Store\RepositoryServiceContainerFactory;
 use Wikibase\Client\WikibaseClient;
+use Wikibase\DataModel\Entity\BasicEntityIdParser;
+use Wikibase\DataModel\Services\EntityId\PrefixMappingEntityIdParserFactory;
 use Wikibase\DataModel\Services\Term\TermBuffer;
+use Wikibase\Lib\Serialization\RepositorySpecificDataValueDeserializerFactory;
 use Wikibase\Lib\Store\EntityRevisionLookup;
 use Wikibase\Lib\Store\PropertyInfoLookup;
 
@@ -17,10 +21,27 @@
 class DispatchingServiceWiringTest extends \PHPUnit_Framework_TestCase {
 
        /**
+        * @return RepositoryServiceContainerFactory
+        */
+       private function getRepositoryServiceContainerFactory() {
+               $idParser = new PrefixMappingEntityIdParserFactory(
+                       new BasicEntityIdParser(), []
+               );
+
+               return new RepositoryServiceContainerFactory(
+                       $idParser,
+                       new RepositorySpecificDataValueDeserializerFactory( 
$idParser ),
+                       [ '' => false ],
+                       [ __DIR__ . 
'/../../../includes/Store/RepositoryServiceWiring.php' ],
+                       WikibaseClient::getDefaultInstance()
+               );
+       }
+
+       /**
         * @return DispatchingServiceFactory
         */
        private function getDispatchingServiceFactory() {
-               $factory = new DispatchingServiceFactory( 
WikibaseClient::getDefaultInstance() );
+               $factory = new DispatchingServiceFactory( 
$this->getRepositoryServiceContainerFactory(), [ '' ] );
                $factory->loadWiringFiles( [ __DIR__ . 
'/../../../includes/DispatchingServiceWiring.php' ] );
                return $factory;
        }
diff --git 
a/extensions/Wikibase/client/tests/phpunit/includes/Store/RepositoryServiceContainerFactoryTest.php
 
b/extensions/Wikibase/client/tests/phpunit/includes/Store/RepositoryServiceContainerFactoryTest.php
new file mode 100644
index 0000000..5aec4f6
--- /dev/null
+++ 
b/extensions/Wikibase/client/tests/phpunit/includes/Store/RepositoryServiceContainerFactoryTest.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Wikibase\Client\Tests\Store;
+
+use Wikibase\Client\Store\RepositoryServiceContainer;
+use Wikibase\Client\Store\RepositoryServiceContainerFactory;
+use Wikibase\Client\WikibaseClient;
+use Wikibase\DataModel\Entity\ItemIdParser;
+use Wikibase\DataModel\Services\EntityId\PrefixMappingEntityIdParserFactory;
+use Wikibase\DataModel\Services\Lookup\UnknownForeignRepositoryException;
+use Wikibase\Lib\Serialization\RepositorySpecificDataValueDeserializerFactory;
+
+/**
+ * @covers Wikibase\Client\Store\RepositoryServiceContainerFactory
+ *
+ * @group Wikibase
+ * @group WikibaseClient
+ *
+ * @license GPL-2.0+
+ */
+class RepositoryServiceContainerFactoryTest extends 
\PHPUnit_Framework_TestCase {
+
+       private function getRepositoryServiceContainerFactory() {
+               $idParserFactory = new PrefixMappingEntityIdParserFactory(
+                       new ItemIdParser(), []
+               );
+
+               return new RepositoryServiceContainerFactory(
+                       $idParserFactory,
+                       new RepositorySpecificDataValueDeserializerFactory( 
$idParserFactory ),
+                       [ '' => false ],
+                       [],
+                       WikibaseClient::getDefaultInstance()
+               );
+       }
+
+       public function 
testGivenKnownRepository_newContainerReturnsContainerForThisRepository() {
+               $factory = $this->getRepositoryServiceContainerFactory();
+
+               $container = $factory->newContainer( '' );
+
+               $this->assertInstanceOf( RepositoryServiceContainer::class, 
$container );
+               $this->assertSame( '', $container->getRepositoryName() );
+       }
+
+       public function 
testGivenUnknownRepository_newContainerThrowsException() {
+               $factory = $this->getRepositoryServiceContainerFactory();
+
+               $this->setExpectedException( 
UnknownForeignRepositoryException::class );
+
+               $factory->newContainer( 'foo' );
+       }
+
+       public function testNewContainerReturnsAFreshInstanceOnEachCall() {
+               $factory = $this->getRepositoryServiceContainerFactory();
+
+               $containerOne = $factory->newContainer( '' );
+               $containerTwo = $factory->newContainer( '' );
+
+               $this->assertNotSame( $containerOne, $containerTwo );
+       }
+
+}
diff --git 
a/extensions/Wikibase/client/tests/phpunit/includes/Store/RepositoryServiceContainerTest.php
 
b/extensions/Wikibase/client/tests/phpunit/includes/Store/RepositoryServiceContainerTest.php
index 2673d1a..3f17bb2 100644
--- 
a/extensions/Wikibase/client/tests/phpunit/includes/Store/RepositoryServiceContainerTest.php
+++ 
b/extensions/Wikibase/client/tests/phpunit/includes/Store/RepositoryServiceContainerTest.php
@@ -5,13 +5,19 @@
 use DataValues\Deserializers\DataValueDeserializer;
 use HashSiteStore;
 use Language;
+use stdClass;
 use Wikibase\Client\Store\RepositoryServiceContainer;
 use Wikibase\Client\WikibaseClient;
 use Wikibase\DataModel\Entity\EntityIdParser;
+use Wikibase\DataModel\Entity\EntityRedirect;
+use Wikibase\DataModel\Entity\Item;
+use Wikibase\DataModel\Entity\ItemId;
 use Wikibase\DataModel\Services\EntityId\PrefixMappingEntityIdParser;
+use Wikibase\EntityRevision;
 use Wikibase\Lib\DataTypeDefinitions;
 use Wikibase\Lib\EntityTypeDefinitions;
 use Wikibase\Lib\Store\EntityRevisionLookup;
+use Wikibase\Lib\Store\EntityStoreWatcher;
 use Wikibase\SettingsArray;
 
 /**
@@ -45,23 +51,30 @@
        /**
         * @return RepositoryServiceContainer
         */
-       private function getRepositoryServiceContainer() {
+       private function newRepositoryServiceContainer() {
                /** @var EntityIdParser $idParser */
                $idParser = $this->getMock( EntityIdParser::class );
 
-               $services = new RepositoryServiceContainer(
+               return new RepositoryServiceContainer(
                        'foowiki',
                        'foo',
                        new PrefixMappingEntityIdParser( [ '' => 'foo' ], 
$idParser ),
                        new DataValueDeserializer( [] ),
                        $this->getWikibaseClient()
                );
+       }
 
-               $services->defineService( 'EntityRevisionLookup', function() {
+       /**
+        * @return RepositoryServiceContainer
+        */
+       private function getRepositoryServiceContainer() {
+               $container = $this->newRepositoryServiceContainer();
+
+               $container->defineService( 'EntityRevisionLookup', function() {
                        return $this->getMock( EntityRevisionLookup::class );
                } );
 
-               return $services;
+               return $container;
        }
 
        public function testGetService() {
@@ -97,4 +110,69 @@
                $this->assertEquals( 'foowiki', 
$repositoryServiceContainer->getDatabaseName() );
        }
 
+       /**
+        * @param $event
+        *
+        * Returns a RepositoryServiceContainer with the following services 
defined:
+        *  - 'watcherService' - dummy service implementing EntityStoreWatcher 
interface,
+        *  - 'anotherWatcherService' - dummy service implementing 
EntityStoreWatcher interface,
+        *  - 'unusedWatcherService' - dummy service implementing 
EntityStoreWatcher interface,
+        *  - 'nonWatcherService' - dummy service not implementing 
EntityStoreWatcher interface,
+        * All services but 'unusedWatcherService' are initialized by default.
+        * This methods provides a set up for testing that 
RepositoryServiceContainer propagates entity change
+        * event to all of its watcher services but not to those that have not 
been used yet (in which case
+        * it makes no sense to pass the event to them).
+        *
+        * @return RepositoryServiceContainer
+        */
+       private function getRepositoryServiceContainerForEventTest( $event ) {
+               $watcherService = $this->getMock( EntityStoreWatcher::class );
+               $watcherService->expects( $this->atLeastOnce() )->method( 
$event );
+
+               $unusedWatcherService = $this->getMock( 
EntityStoreWatcher::class );
+               $unusedWatcherService->expects( $this->never() )->method( 
$event );
+
+               $nonWatcherService = $this->getMock( stdClass::class );
+               $nonWatcherService->expects( $this->never() )->method( $event );
+
+               $container = $this->newRepositoryServiceContainer();
+               $container->defineService( 'watcherService', function () use ( 
$watcherService ) {
+                       return $watcherService;
+               } );
+               $container->defineService( 'anotherWatcherService', function () 
use ( $watcherService ) {
+                       return $watcherService;
+               } );
+               $container->defineService( 'unusedWatcherService', function () 
use ( $unusedWatcherService ) {
+                       return $unusedWatcherService;
+               } );
+               $container->defineService( 'nonWatcherService', function () use 
( $nonWatcherService ) {
+                       return $nonWatcherService;
+               } );
+
+               // Instantiate services relevant for the check
+               $container->getService( 'watcherService' );
+               $container->getService( 'anotherWatcherService' );
+               $container->getService( 'nonWatcherService' );
+
+               return $container;
+       }
+
+       public function 
testEntityUpdatedDelegatesEventToAllWatchersThatHaveAlreadyBeenUsed() {
+               $container = $this->getRepositoryServiceContainerForEventTest( 
'entityUpdated' );
+
+               $container->entityUpdated( new EntityRevision( new Item( new 
ItemId( 'foo:Q123' ) ) ) );
+       }
+
+       public function 
testEntityDeletedDelegatesEventToAllWatchersThatHaveAlreadyBeenUsed() {
+               $container = $this->getRepositoryServiceContainerForEventTest( 
'entityDeleted' );
+
+               $container->entityDeleted( new ItemId( 'foo:Q123' ) );
+       }
+
+       public function 
testRedirectUpdatedDelegatesEventToAllWatchersThatHaveAlreadyBeenUsed() {
+               $container = $this->getRepositoryServiceContainerForEventTest( 
'redirectUpdated' );
+
+               $container->redirectUpdated( new EntityRedirect( new ItemId( 
'foo:Q123' ), new ItemId( 'foo:Q321' ) ), 100 );
+       }
+
 }
diff --git 
a/extensions/Wikibase/client/tests/phpunit/includes/Store/Sql/DirectSqlStoreTest.php
 
b/extensions/Wikibase/client/tests/phpunit/includes/Store/Sql/DirectSqlStoreTest.php
index ef146e5..ec0b5a5 100644
--- 
a/extensions/Wikibase/client/tests/phpunit/includes/Store/Sql/DirectSqlStoreTest.php
+++ 
b/extensions/Wikibase/client/tests/phpunit/includes/Store/Sql/DirectSqlStoreTest.php
@@ -4,6 +4,7 @@
 
 use Wikibase\Client\DispatchingServiceFactory;
 use Wikibase\Client\RecentChanges\RecentChangesDuplicateDetector;
+use Wikibase\Client\Store\RepositoryServiceContainerFactory;
 use Wikibase\Client\Usage\SubscriptionManager;
 use Wikibase\Client\Usage\UsageLookup;
 use Wikibase\Client\Usage\UsageTracker;
@@ -43,7 +44,13 @@
 
                $client = WikibaseClient::getDefaultInstance();
 
-               $dispatchingServiceFactory = new DispatchingServiceFactory( 
$client );
+               /** @var RepositoryServiceContainerFactory $containerFactory */
+               $containerFactory = $this->getMockBuilder( 
RepositoryServiceContainerFactory::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $dispatchingServiceFactory = new DispatchingServiceFactory( 
$containerFactory, [] );
+
                $dispatchingServiceFactory->defineService( 
'EntityRevisionLookup', function() {
                        return $this->getMock( EntityRevisionLookup::class );
                } );
diff --git a/extensions/Wikibase/docs/change-op-serializations.wiki 
b/extensions/Wikibase/docs/change-op-serializations.wiki
index 63d83a5..93d5205 100644
--- a/extensions/Wikibase/docs/change-op-serializations.wiki
+++ b/extensions/Wikibase/docs/change-op-serializations.wiki
@@ -36,19 +36,19 @@
 ==== "labels" and "descriptions" ====
 * To add or edit a label or description, simply provide it's "language" and 
"value".
 * There are two ways to remove a label or description:
-** You can add the key "remove", e.g. <code>{ "language": "en", "remove": 1 
}</code>. The content of the "remove" element can be whatever you want, 
typically 1.
+** You can add the key "remove", e.g. <code>{ "language": "en", "remove": "" 
}</code>. The content of the "remove" element can be whatever you want, 
typically an empty string.
 ** You can set the "value" to an empty string, e.g. <code>{ "language": "en", 
"value": "" }</code>.
 
 ==== "aliases" ====
 * You can either add or remove aliases individually, or send the full set of 
aliases for a language.
-* To remove an alias, provide the old "language" and "value" of the alias you 
want to remove, as well as the key "remove", e.g. <code>{ "language": "en", 
"value": "…", "remove": 1 }</code>. The content of the "remove" element can be 
whatever you want, typically 1.
-* To add an alias without touching the existing ones, provide a single alias 
and add the key "add", e.g. <code>{ "language": "en", "value": "…", "add": 1 
}</code>. The content of the "add" element can be whatever you want, typically 
1.
+* To remove an alias, provide the old "language" and "value" of the alias you 
want to remove, as well as the key "remove", e.g. <code>{ "language": "en", 
"value": "…", "remove": "" }</code>. The content of the "remove" element can be 
whatever you want, typically an empty string.
+* To add an alias without touching the existing ones, provide a single alias 
and add the key "add", e.g. <code>{ "language": "en", "value": "…", "add": "" 
}</code>. The content of the "add" element can be whatever you want, typically 
an empty string.
 
 === Statements ("claims") ===
 Statements must be provided via the element key "claims". This is for 
compatibility with older versions of the Wikibase software.
 * To add a statement, provide a full statement serialization as supported by 
the StatementDeserializer in the 
[//github.com/wmde/WikibaseDataModelSerialization Wikibase DataModel 
Serialization component]. See docs/json.wiki.
 * To edit an existing statement, do as above and make sure to include the "id" 
of the existing statement.
-* To remove a statement, you must provide it's "id" and the key "remove". The 
content of the "remove" element can be whatever you want, typically 1.
+* To remove a statement, you must provide it's "id" and the key "remove". The 
content of the "remove" element can be whatever you want, typically an empty 
string.
 
 == Property specific elements ==
 The only element specific for properties, their data type, can not be edited.
@@ -60,7 +60,7 @@
 * Editing the "site" ID of an existing sitelink is not possible. You must 
remove the old sitelink and add the new sitelink instead.
 * To edit the page name of an existing sitelink, provide the "site" ID with 
the new "title", e.g. <code>{ "site": "enwiki", "title": "…" }</code>.
 * There are three ways to remove a sitelink:
-** You can add the key "remove". The "title" is optional in this case. The 
content of the "remove" element can be whatever you want, typically 1.
+** You can add the key "remove". The "title" is optional in this case. The 
content of the "remove" element can be whatever you want, typically an empty 
string.
 ** You can provide a sitelink with no title and no badges, e.g. <code>{ 
"site": "enwiki" }</code>.
 ** You can set the "title" to an empty string, e.g. <code>{ "site": "enwiki", 
"title": "" }</code>.
 
diff --git a/extensions/Wikibase/docs/extending-entities.wiki 
b/extensions/Wikibase/docs/extending-entities.wiki
index 369e0bc..71b04fa 100644
--- a/extensions/Wikibase/docs/extending-entities.wiki
+++ b/extensions/Wikibase/docs/extending-entities.wiki
@@ -1,4 +1,5 @@
 This is a checklist of things that need doing when adding a new field to an 
existing entity type.
+For example if you want to add "NewThingy" part to entity type called "Foo":
 
 * Add a provider interface, such as NewThingyProvider, for the new field.
 * Add to entity as an attribute and add getter and setter (implement the 
provider interface)
@@ -11,13 +12,14 @@
 * Add support to FooPatcher
 * Add support to FooDiffer
 * Add support in FooView (extends EntityView)
+* Add a ChangeOp for the new field, e.g. NewThingyChangeOp
+* Add support in FooValidatorFactory
+* Add support in FooChangeOpDeserializer
 
-CAVEAT (as of November 2016): For the below parts of Wikibase, there are no 
extension
+CAVEAT (as of January 2017): For the below parts of Wikibase, there are no 
extension
 interfaces yet for handling additional fields of entities. Fields of entity 
types known
 to Wikibase itself can be hardcoded here, but for supporting entity types 
defined in
 other extensions, plug-in interfaces still need to be added.
 
 * Add to RDF mapping (not currently pluggable!)
-* Add Validator (not currently pluggable)
-* Add ChangeOp for updating (not currently pluggable) (must work with 
wbeditentity)
 * Add handling (or suppression) to EntityChangeFactory (not currently 
pluggable)
diff --git a/extensions/Wikibase/lib/WikibaseLib.php 
b/extensions/Wikibase/lib/WikibaseLib.php
index 69853b9..7d00f27 100644
--- a/extensions/Wikibase/lib/WikibaseLib.php
+++ b/extensions/Wikibase/lib/WikibaseLib.php
@@ -61,7 +61,7 @@
                'name' => 'WikibaseLib',
                'version' => WBL_VERSION,
                'author' => array(
-                       'The Wikidata team', // TODO: link?
+                       'The Wikidata team',
                ),
                'url' => 'https://www.mediawiki.org/wiki/Extension:WikibaseLib',
                'descriptionmsg' => 'wikibase-lib-desc',
diff --git 
a/extensions/Wikibase/lib/includes/Store/CacheAwarePropertyInfoStore.php 
b/extensions/Wikibase/lib/includes/Store/CacheAwarePropertyInfoStore.php
index 7808d70..fbb946b 100644
--- a/extensions/Wikibase/lib/includes/Store/CacheAwarePropertyInfoStore.php
+++ b/extensions/Wikibase/lib/includes/Store/CacheAwarePropertyInfoStore.php
@@ -83,17 +83,10 @@
                // update primary store
                $this->store->setPropertyInfo( $propertyId, $info );
 
-               // NOTE: Even if we don't have the propertyInfo locally, we 
still need to
-               //       fully load it to update memcached.
-
-               // Get local cached version.
-               // NOTE: this may be stale at this point, if it was already 
loaded
                $propertyInfo = $this->cache->get( $this->cacheKey );
                $id = $propertyId->getSerialization();
 
-               // update local cache
                $propertyInfo[$id] = $info;
-               $this->propertyInfo = $propertyInfo;
 
                // update external cache
                wfDebugLog( __CLASS__, __FUNCTION__ . ': updating cache after 
updating property ' . $id );
@@ -118,14 +111,8 @@
                        return false;
                }
 
-               // NOTE: Even if we don't have the propertyInfo locally, we 
still need to
-               //       fully load it to update memcached.
-
-               // Get local cached version.
-               // NOTE: this may be stale at this point, if it was already 
loaded
                $propertyInfo = $this->cache->get( $this->cacheKey );
 
-               // update local cache
                unset( $propertyInfo[$id] );
 
                // update external cache
diff --git a/extensions/Wikibase/lib/tests/phpunit/MockRepository.php 
b/extensions/Wikibase/lib/tests/phpunit/MockRepository.php
index 94aefaf..09b4c22 100644
--- a/extensions/Wikibase/lib/tests/phpunit/MockRepository.php
+++ b/extensions/Wikibase/lib/tests/phpunit/MockRepository.php
@@ -20,7 +20,7 @@
 use Wikibase\DataModel\Services\Lookup\PropertyDataTypeLookup;
 use Wikibase\DataModel\Services\Lookup\PropertyDataTypeLookupException;
 use Wikibase\DataModel\SiteLink;
-use Wikibase\DataModel\Term\FingerprintProvider;
+use Wikibase\DataModel\Term\LabelsProvider;
 use Wikibase\EntityRevision;
 use Wikibase\Lib\Store\EntityInfoBuilderFactory;
 use Wikibase\Lib\Store\EntityRevisionLookup;
@@ -413,11 +413,11 @@
 
                        $property = $this->getEntity( $propertyId );
 
-                       if ( !( $property instanceof FingerprintProvider ) ) {
+                       if ( !( $property instanceof LabelsProvider ) ) {
                                continue;
                        }
 
-                       $labels = $property->getFingerprint()->getLabels();
+                       $labels = $property->getLabels();
 
                        if ( $labels->hasTermForLanguage( $languageCode )
                                && $labels->getByLanguage( $languageCode 
)->getText() === $propertyLabel
diff --git 
a/extensions/Wikibase/lib/tests/phpunit/Store/EntityInfoBuilderTest.php 
b/extensions/Wikibase/lib/tests/phpunit/Store/EntityInfoBuilderTest.php
index 9e3c5ad..ab77c08 100644
--- a/extensions/Wikibase/lib/tests/phpunit/Store/EntityInfoBuilderTest.php
+++ b/extensions/Wikibase/lib/tests/phpunit/Store/EntityInfoBuilderTest.php
@@ -2,7 +2,6 @@
 
 namespace Wikibase\Lib\Tests\Store;
 
-use Wikibase\DataModel\Entity\EntityDocument;
 use Wikibase\DataModel\Entity\EntityId;
 use Wikibase\DataModel\Entity\Item;
 use Wikibase\DataModel\Entity\ItemId;
@@ -31,7 +30,7 @@
        }
 
        /**
-        * @return EntityDocument[]
+        * @return Item[]|Property[]
         */
        protected function getKnownEntities() {
                $q1 = new Item( new ItemId( 'Q1' ) );
diff --git 
a/extensions/Wikibase/lib/tests/phpunit/Store/Sql/SqlEntityInfoBuilderTest.php 
b/extensions/Wikibase/lib/tests/phpunit/Store/Sql/SqlEntityInfoBuilderTest.php
index 58bbf0b..d2ce9ea 100644
--- 
a/extensions/Wikibase/lib/tests/phpunit/Store/Sql/SqlEntityInfoBuilderTest.php
+++ 
b/extensions/Wikibase/lib/tests/phpunit/Store/Sql/SqlEntityInfoBuilderTest.php
@@ -35,59 +35,68 @@
                $this->tablesUsed[] = 'wb_terms';
                $this->tablesUsed[] = 'wb_entity_per_page';
 
-               $termRows = array();
-               $infoRows = array();
-               $eppRows = array();
+               $termRows = [];
+               $infoRows = [];
+               $eppRows = [];
 
                $pageId = 1000;
 
                foreach ( $this->getKnownEntities() as $entity ) {
-                       $eppRows[] = array(
+                       $id = $entity->getId();
+
+                       $eppRows[] = [
                                $entity->getType(),
-                               $entity->getId()->getNumericId(),
+                               $id->getNumericId(),
                                $pageId++,
                                null
-                       );
+                       ];
 
-                       $labels = 
$entity->getFingerprint()->getLabels()->toTextArray();
-                       $descriptions = 
$entity->getFingerprint()->getDescriptions()->toTextArray();
-                       $aliases = 
$entity->getFingerprint()->getAliasGroups()->toTextArray();
+                       $labels = $entity->getLabels()->toTextArray();
+                       $descriptions = 
$entity->getDescriptions()->toTextArray();
+                       $aliases = $entity->getAliasGroups()->toTextArray();
 
-                       $termRows = array_merge( $termRows, $this->getTermRows( 
$entity->getId(), 'label', $labels ) );
-                       $termRows = array_merge( $termRows, $this->getTermRows( 
$entity->getId(), 'description', $descriptions ) );
-                       $termRows = array_merge( $termRows, $this->getTermRows( 
$entity->getId(), 'alias', $aliases ) );
+                       $termRows = array_merge( $termRows, $this->getTermRows( 
$id, 'label', $labels ) );
+                       $termRows = array_merge( $termRows, $this->getTermRows( 
$id, 'description', $descriptions ) );
+                       $termRows = array_merge( $termRows, $this->getTermRows( 
$id, 'alias', $aliases ) );
 
                        if ( $entity instanceof Property ) {
-                               $infoRows[] = array(
-                                       $entity->getId()->getNumericId(),
+                               $infoRows[] = [
+                                       $id->getNumericId(),
                                        $entity->getDataTypeId(),
                                        '{"type":"' . $entity->getDataTypeId() 
. '"}'
-                               );
+                               ];
                        }
                }
 
                foreach ( $this->getKnownRedirects() as $from => $toId ) {
                        $fromId = new ItemId( $from );
 
-                       $eppRows[] = array(
+                       $eppRows[] = [
                                $fromId->getEntityType(),
                                $fromId->getNumericId(),
                                $pageId++,
                                $toId->getSerialization()
-                       );
+                       ];
                }
 
                $this->insertRows(
                        'wb_terms',
-                       array( 'term_entity_type', 'term_entity_id', 
'term_type', 'term_language', 'term_text', 'term_search_key' ),
+                       [
+                               'term_entity_type',
+                               'term_entity_id',
+                               'term_type',
+                               'term_language',
+                               'term_text',
+                               'term_search_key'
+                       ],
                        $termRows );
 
                $this->insertRows(
                        'wb_property_info',
-                       array( 'pi_property_id', 'pi_type', 'pi_info' ),
+                       [ 'pi_property_id', 'pi_type', 'pi_info' ],
                        $infoRows );
 
-               $eppColumns = array( 'epp_entity_type', 'epp_entity_id', 
'epp_page_id', 'epp_redirect_target' );
+               $eppColumns = [ 'epp_entity_type', 'epp_entity_id', 
'epp_page_id', 'epp_redirect_target' ];
 
                $this->insertRows(
                        'wb_entity_per_page',
@@ -96,19 +105,20 @@
        }
 
        private function getTermRows( EntityId $id, $termType, $terms ) {
-               $rows = array();
+               $rows = [];
 
                foreach ( $terms as $lang => $langTerms ) {
                        $langTerms = (array)$langTerms;
 
                        foreach ( $langTerms as $term ) {
-                               $rows[] = array(
+                               $rows[] = [
                                        $id->getEntityType(),
                                        $id->getNumericId(),
                                        $termType,
                                        $lang,
                                        $term,
-                                       $term );
+                                       $term
+                               ];
                        }
                }
 
@@ -128,7 +138,7 @@
                                // Just ignore insertation errors... if similar 
data already is in the DB
                                // it's probably good enough for the tests (as 
this is only testing for UNIQUE
                                // fields anyway).
-                               array( 'IGNORE' )
+                               [ 'IGNORE' ]
                        );
                }
        }
diff --git a/extensions/Wikibase/repo/Wikibase.php 
b/extensions/Wikibase/repo/Wikibase.php
index 4c42317..08c4182 100644
--- a/extensions/Wikibase/repo/Wikibase.php
+++ b/extensions/Wikibase/repo/Wikibase.php
@@ -239,7 +239,24 @@
        $wgAPIModules['wbsetqualifier'] = Wikibase\Repo\Api\SetQualifier::class;
        $wgAPIModules['wbmergeitems'] = Wikibase\Repo\Api\MergeItems::class;
        $wgAPIModules['wbformatvalue'] = 
Wikibase\Repo\Api\FormatSnakValue::class;
-       $wgAPIModules['wbparsevalue'] = Wikibase\Repo\Api\ParseValue::class;
+       $wgAPIModules['wbparsevalue'] = [
+               'class' => Wikibase\Repo\Api\ParseValue::class,
+               'factory' => function( ApiMain $mainModule, $moduleName ) {
+                       $wikibaseRepo = 
Wikibase\Repo\WikibaseRepo::getDefaultInstance();
+                       $apiHelperFactory = $wikibaseRepo->getApiHelperFactory( 
$mainModule->getContext() );
+
+                       return new Wikibase\Repo\Api\ParseValue(
+                               $mainModule,
+                               $moduleName,
+                               $wikibaseRepo->getDataTypeFactory(),
+                               $wikibaseRepo->getValueParserFactory(),
+                               $wikibaseRepo->getDataTypeValidatorFactory(),
+                               $wikibaseRepo->getExceptionLocalizer(),
+                               $wikibaseRepo->getValidatorErrorLocalizer(),
+                               $apiHelperFactory->getErrorReporter( 
$mainModule )
+                       );
+               }
+       ];
        $wgAPIModules['wbavailablebadges'] = 
Wikibase\Repo\Api\AvailableBadges::class;
        $wgAPIModules['wbcreateredirect'] = [
                'class' => Wikibase\Repo\Api\CreateRedirect::class,
diff --git a/extensions/Wikibase/repo/i18n/bn.json 
b/extensions/Wikibase/repo/i18n/bn.json
index 4b2f148..5de27b1 100644
--- a/extensions/Wikibase/repo/i18n/bn.json
+++ b/extensions/Wikibase/repo/i18n/bn.json
@@ -5,7 +5,8 @@
                        "Bellayet",
                        "Leemon2010",
                        "Aftabuzzaman",
-                       "Nasir8891"
+                       "Nasir8891",
+                       "Elias Ahmmad"
                ]
        },
        "wikibase-desc": "কাঠামোবদ্ধ তথ্য ভান্ডার",
@@ -243,6 +244,7 @@
        "wikibase-entitydata-bad-id": "অবৈধ আইডি: $1।",
        "wikibase-entitydata-storage-error": "$1 সত্তা লোড করতে ব্যর্থ।",
        "wikibase-entitydata-title": "সত্তার উপাত্ত",
+       "special-entitypage": "সত্তা পাতা",
        "special-redirectentity": "একটি সত্তা পুনঃনির্দেশ করুন",
        "wikibase-redirectentity-success": "$1 $2-এর দিকে পুনঃনির্দেশিত 
হয়েছে।",
        "wikibase-redirectentity-submit": "পুনঃনির্দেশ",
diff --git a/extensions/Wikibase/repo/i18n/de.json 
b/extensions/Wikibase/repo/i18n/de.json
index 2d3a288..e7b227c 100644
--- a/extensions/Wikibase/repo/i18n/de.json
+++ b/extensions/Wikibase/repo/i18n/de.json
@@ -307,6 +307,7 @@
        "wikibase-entitydata-storage-error": "Das Objekt $1 konnte nicht 
geladen werden.",
        "wikibase-entitydata-title": "Objektdaten",
        "wikibase-entitydata-text": "Diese Seite liefert eine verlinkte 
Datenschnittstelle zu Objektwerten. Bitte gib die Objektkennung in der URL an, 
indem du Unterseitensyntax verwendest.\n* Inhaltsvereinbarungsanfragen 
basierend auf deinem Client-Accept-Header. Das bedeutet, dass die Objektdaten 
im Format angegeben wird, das von deinem Client bevorzugt wird. Für einen 
Webbrowser wird dies HTML sein, was bewirkt, dass dein Browser zur normalen 
Objektseite weiterleitet.\n* Du kannst ein spezielles Datenformat ausdrücklich 
durch das Hinzufügen der passenden Dateierweiterung zur Objektkennung 
anfordern: Q23.json gibt Daten im JSON-Format zurück, Q23.ttl gibt RDF/Turtle 
zurück und so weiter.",
+       "special-entitypage": "Objektseite",
        "wikibase-entitypage-title": "Objektseite",
        "wikibase-entitypage-text": "Diese Seite leitet auf die Objektseite auf 
dem Repositorium weiter, zu der sie gehört. Bitte gib mithilfe der 
Unterseitensyntax die Objektkennung in der URL an.",
        "wikibase-entitypage-bad-id": "Ungültige Kennung: $1.",
diff --git a/extensions/Wikibase/repo/i18n/es.json 
b/extensions/Wikibase/repo/i18n/es.json
index b5b4b84..c8b36b8 100644
--- a/extensions/Wikibase/repo/i18n/es.json
+++ b/extensions/Wikibase/repo/i18n/es.json
@@ -393,6 +393,7 @@
        "apihelp-wbformatvalue-param-datatype": "El tipo de dato del valor. 
Esto es distinto del tipo del valor",
        "apihelp-wbformatvalue-param-property": "ID de la propiedad el valor de 
los datos pertenece, debe ser usado en lugar del tipo de datos del parámetro.",
        "apihelp-wbgetentities-description": "Obtiene los datos de múltiples 
entidades de Wikibase.",
+       "apihelp-wbgetentities-param-ids": "Los identificadores de las 
entidades de las que obtener los datos",
        "apihelp-wbgetentities-param-languages": "Por defecto, los valores 
internacionalizados se devuelven en todos los idiomas disponibles. Este 
parámetro permite filtrarlos a uno o más idiomas. Para ello, hay que introducir 
los códigos de idioma correspondientes.",
        "apihelp-wbgetentities-param-languagefallback": "Aplicar idioma de 
reserva para los idiomas definidos en el parámetro <var>languages</var>, en el 
contexto actual de la llamada a la API.",
        "apihelp-wbgetentities-param-normalize": "Tratar de normalizar el 
título de la página en función del sitio en el que se encuentra.\nEsto solo 
funciona si se proporciona exactamente un sitio y una página.",
@@ -423,10 +424,18 @@
        "apihelp-wbparsevalue-param-values": "Los valores que analizar",
        "apihelp-wbparsevalue-param-options": "Opciones que el analizador debe 
usar. Se proporciona como un objeto JSON.",
        "apihelp-wbparsevalue-example-1": "Analizar una cadena sin formato en 
un objeto StringValue.",
+       "apihelp-wbremoveclaims-param-baserevid": "El identificador numérico de 
la revisión en la que basar la modificación.\nEste dato se utiliza para 
detectar conflictos al guardar.",
+       "apihelp-wbremoveclaims-param-bot": "Marca esta edición como realizada 
por un bot. Esta marca de URL solo se respetará si el usuario pertenece al 
grupo \"bot\".",
        "apihelp-wbremovequalifiers-description": "Elimina un calificador de 
una reclamación.",
+       "apihelp-wbremovequalifiers-param-baserevid": "El identificador 
numérico de la revisión en la que basar la modificación.\nEste dato se utiliza 
para detectar conflictos al guardar.",
+       "apihelp-wbremovequalifiers-param-bot": "Marca esta edición como 
realizada por un bot. Esta marca de URL solo se respetará si el usuario 
pertenece al grupo \"bot\".",
+       "apihelp-wbremovereferences-param-baserevid": "El identificador 
numérico de la revisión en la que basar la modificación.\nEste dato se utiliza 
para detectar conflictos al guardar.",
+       "apihelp-wbremovereferences-param-bot": "Marca esta edición como 
realizada por un bot. Esta marca de URL solo se respetará si el usuario 
pertenece al grupo \"bot\".",
+       "apihelp-wbremovereferences-param-summary": "Resumen de la 
edición.\nSerá precedido por un comentario generado automáticamente. La 
longitud máxima del autocomentario junto con el resumen es de 260 caracteres. 
Ten en cuenta que todo lo que sobrepase el límite se cortará.",
        "apihelp-wbsearchentities-description": "Busca entidades que usen 
etiquetas y alias.\nDevuelve una etiqueta y una descripción de la entidad en el 
idioma del usuario, si es posible.\nDevuelve detalles del término 
coincidente.\nEl texto del término coincidente también está presente en la 
clave de alias si es diferente de la etiqueta de visualización.",
        "apihelp-wbsearchentities-param-search": "Buscar este texto.",
        "apihelp-wbsearchentities-param-language": "Buscar en este idioma.",
+       "apihelp-wbsearchentities-param-strictlanguage": "Indica si se 
deshabilita el idioma de reserva.",
        "apihelp-wbsearchentities-param-type": "Buscar este tipo de entidad.",
        "apihelp-wbsearchentities-param-limit": "Número máximo de resultados",
        "apihelp-wbsearchentities-param-continue": "Desplazamiento desde donde 
continuar una búsqueda",
@@ -436,6 +445,7 @@
        "apihelp-query+wbsearch-description": "Busca entidades que usan 
etiquetas y alias.\nEsto se puede utilizar como generador de otras 
consultas.\nDevuelve el término encontrado que debe mostrarse.",
        "apihelp-query+wbsearch-param-search": "Buscar este texto.",
        "apihelp-query+wbsearch-param-language": "Buscar en este idioma.",
+       "apihelp-query+wbsearch-param-strictlanguage": "Indica si se 
deshabilita el idioma de reserva.",
        "apihelp-query+wbsearch-param-type": "Buscar este tipo de entidad.",
        "apihelp-query+wbsearch-param-limit": "Número máximo de resultados",
        "apihelp-query+wbsearch-example-2": "Buscar \"abc\" en el idioma inglés 
con un límite de 50",
@@ -445,6 +455,8 @@
        "apihelp-query+wbsubscribers-param-limit": "Número máximo de 
resultados",
        "apihelp-wbsetaliases-description": "Establece los alias de una entidad 
de Wikibase.",
        "apihelp-wbsetaliases-param-new": "Si se establece, se creará una 
entidad nueva.\nEstablece esto al tipo de entidad que quieres crear.",
+       "apihelp-wbsetaliases-param-baserevid": "El identificador numérico de 
la revisión en la que basar la modificación.\nEste dato se utiliza para 
detectar conflictos al guardar.",
+       "apihelp-wbsetaliases-param-summary": "Resumen de la edición.\nSerá 
precedido por un comentario generado automáticamente. La longitud máxima del 
autocomentario junto con el resumen es de 260 caracteres. Ten en cuenta que 
todo lo que sobrepase el límite se cortará.",
        "apihelp-wbsetaliases-param-bot": "Marcar esta edición como hecha por 
un robot. Este parámetro funcionará solo si el usuario pertenece al grupo 
«bot».",
        "apihelp-wbsetaliases-param-add": "Lista de alias que añadir (se puede 
combinar con <var>remove</var>)",
        "apihelp-wbsetaliases-param-remove": "Lista de alias que eliminar (se 
puede combinar con <var>add</var>)",
@@ -455,21 +467,35 @@
        "apihelp-wbsetaliases-example-3": "Eliminar Foo y Bar de la lista de 
alias en inglés para la entidad con el identificador Q1",
        "apihelp-wbsetaliases-example-4": "Eliminar Foo de la lista de alias en 
inglés para la entidad con el identificador Q1, y añadirle Bar",
        "apihelp-wbsetclaim-description": "Crea o actualiza toda una 
Declaración o Reclamación.",
+       "apihelp-wbsetclaim-param-summary": "Resumen de la edición.\nSerá 
precedido por un comentario generado automáticamente. La longitud máxima del 
autocomentario junto con el resumen es de 260 caracteres. Ten en cuenta que 
todo lo que sobrepase el límite se cortará.",
+       "apihelp-wbsetclaim-param-baserevid": "El identificador numérico de la 
revisión en la que basar la modificación.\nEste dato se utiliza para detectar 
conflictos al guardar.",
        "apihelp-wbsetclaim-param-bot": "Marcar esta edición como hecha por un 
robot. Este parámetro funcionará solo si el usuario pertenece al grupo «bot».",
+       "apihelp-wbsetclaimvalue-param-summary": "Resumen de la edición.\nSerá 
precedido por un comentario generado automáticamente. La longitud máxima del 
autocomentario junto con el resumen es de 260 caracteres. Ten en cuenta que 
todo lo que sobrepase el límite se cortará.",
+       "apihelp-wbsetclaimvalue-param-baserevid": "El identificador numérico 
de la revisión en la que basar la modificación.\nEste dato se utiliza para 
detectar conflictos al guardar.",
        "apihelp-wbsetclaimvalue-param-bot": "Marcar esta edición como hecha 
por un robot. Este parámetro funcionará solo si el usuario pertenece al grupo 
«bot».",
        "apihelp-wbsetdescription-param-new": "Si se establece, se creará una 
entidad nueva.\nEstablece esto al tipo de entidad que quieres crear.",
+       "apihelp-wbsetdescription-param-baserevid": "El identificador numérico 
de la revisión en la que basar la modificación.\nEste dato se utiliza para 
detectar conflictos al guardar.",
+       "apihelp-wbsetdescription-param-summary": "Resumen de la edición.\nSerá 
precedido por un comentario generado automáticamente. La longitud máxima del 
autocomentario junto con el resumen es de 260 caracteres. Ten en cuenta que 
todo lo que sobrepase el límite se cortará.",
        "apihelp-wbsetdescription-param-bot": "Marcar esta edición como hecha 
por un robot. Este parámetro funcionará solo si el usuario pertenece al grupo 
«bot».",
        "apihelp-wbsetdescription-param-language": "Idioma de la descripción",
        "apihelp-wbsetdescription-param-value": "El valor que establecer como 
descripción",
        "apihelp-wbsetlabel-param-new": "Si se establece, se creará una entidad 
nueva.\nEstablece esto al tipo de entidad que quieres crear.",
+       "apihelp-wbsetlabel-param-baserevid": "El identificador numérico de la 
revisión en la que basar la modificación.\nEste dato se utiliza para detectar 
conflictos al guardar.",
+       "apihelp-wbsetlabel-param-summary": "Resumen de la edición.\nSerá 
precedido por un comentario generado automáticamente. La longitud máxima del 
autocomentario junto con el resumen es de 260 caracteres. Ten en cuenta que 
todo lo que sobrepase el límite se cortará.",
        "apihelp-wbsetlabel-param-bot": "Marcar esta edición como hecha por un 
robot. Este parámetro funcionará solo si el usuario pertenece al grupo «bot».",
        "apihelp-wbsetlabel-param-language": "Idioma de la etiqueta",
        "apihelp-wbsetlabel-param-value": "El valor de la etiqueta",
+       "apihelp-wbsetqualifier-param-summary": "Resumen de la edición.\nSerá 
precedido por un comentario generado automáticamente. La longitud máxima del 
autocomentario junto con el resumen es de 260 caracteres. Ten en cuenta que 
todo lo que sobrepase el límite se cortará.",
+       "apihelp-wbsetqualifier-param-baserevid": "El identificador numérico de 
la revisión en la que basar la modificación.\nEste dato se utiliza para 
detectar conflictos al guardar.",
        "apihelp-wbsetqualifier-param-bot": "Marcar esta edición como hecha por 
un robot. Este parámetro funcionará solo si el usuario pertenece al grupo 
«bot».",
        "apihelp-wbsetreference-description": "Crea una referencia o establece 
el valor de una referencia existente.",
        "apihelp-wbsetreference-param-statement": "Un GUID que identifica la 
declaración para la cual se está estableciendo una referencia",
+       "apihelp-wbsetreference-param-summary": "Resumen de la edición.\nSerá 
precedido por un comentario generado automáticamente. La longitud máxima del 
autocomentario junto con el resumen es de 260 caracteres. Ten en cuenta que 
todo lo que sobrepase el límite se cortará.",
+       "apihelp-wbsetreference-param-baserevid": "El identificador numérico de 
la revisión en la que basar la modificación.\nEste dato se utiliza para 
detectar conflictos al guardar.",
        "apihelp-wbsetreference-param-bot": "Marcar esta edición como hecha por 
un robot. Este parámetro funcionará solo si el usuario pertenece al grupo 
«bot».",
        "apihelp-wbsetsitelink-param-new": "Si se establece, se creará una 
entidad nueva.\nEstablece esto al tipo de entidad que quieres crear.",
+       "apihelp-wbsetsitelink-param-baserevid": "El identificador numérico de 
la revisión en la que basar la modificación.\nEste dato se utiliza para 
detectar conflictos al guardar.",
+       "apihelp-wbsetsitelink-param-summary": "Resumen de la edición.\nSerá 
precedido por un comentario generado automáticamente. La longitud máxima del 
autocomentario junto con el resumen es de 260 caracteres. Ten en cuenta que 
todo lo que sobrepase el límite se cortará.",
        "apihelp-wbsetsitelink-param-bot": "Marcar esta edición como hecha por 
un robot. Este parámetro funcionará solo si el usuario pertenece al grupo 
«bot».",
        "apihelp-wbsetsitelink-param-linktitle": "El título del artículo que se 
enlazará. Si este parámetro es una cadena vacía o no se establecen ni el título 
del enlace ni las insignias , el enlace se eliminará.",
        "apihelp-wbsetsitelink-param-badges": "Los identificadores de los 
elementos que se establecerán como insignias. Reemplazarán a los actuales. Si 
este parámetro no está definido, las insignias no se cambiarán",
diff --git a/extensions/Wikibase/repo/i18n/ml.json 
b/extensions/Wikibase/repo/i18n/ml.json
index 199e2fa..fe13cb2 100644
--- a/extensions/Wikibase/repo/i18n/ml.json
+++ b/extensions/Wikibase/repo/i18n/ml.json
@@ -6,7 +6,8 @@
                        "Praveenp",
                        "Santhosh.thottingal",
                        "Vssun",
-                       "Macofe"
+                       "Macofe",
+                       "Jameela P."
                ]
        },
        "wikibase-desc": "വിന്യസിത ഡേറ്റാ റെപ്പോസിറ്ററി",
@@ -33,6 +34,7 @@
        "wikibase-description-empty": "വിവരണമൊന്നും നിർവചിച്ചിട്ടില്ല",
        "wikibase-description-edit-placeholder": "വിവരണം നൽകുക",
        "wikibase-description-edit-placeholder-language-aware": "$1 ഭാഷയിൽ 
വിവരണം നൽകുക",
+       "wikibase-content-language-edit-label": "ഭാഷ:",
        "wikibase-diffview-reference": "അവലംബം",
        "wikibase-diffview-rank": "റാങ്ക്",
        "wikibase-diffview-rank-preferred": "ഉദ്ദേശിക്കുന്ന റാങ്ക്",
@@ -43,6 +45,7 @@
        "wikibase-diffview-alias": "അപരനാമങ്ങൾ",
        "wikibase-diffview-description": "വിവരണം",
        "wikibase-diffview-link": "കണ്ണികൾ",
+       "wikibase-diffview-link-name": "പേര്‌",
        "wikibase-sitelink-site-edit-placeholder": "സൈറ്റ്",
        "wikibase-sitelink-page-edit-placeholder": "താൾ",
        "wikibase-alias-edit-placeholder": "ഒരു അപരനാമം നൽകുക",
@@ -69,7 +72,6 @@
        "wikibase-disambiguation-title": "\"$1\" എന്നതിനുള്ള വിവക്ഷകൾ",
        "wb-special-newitem-new-item-notification": "പുതിയ ഇനം $1 
സൃഷ്ടിക്കപ്പെട്ടു, അതിന്റെ താളിലേയ്ക്ക് തിരിച്ചുവിടപ്പെട്ടു. $2 എന്നതിലേക്ക് 
മടങ്ങുക.",
        "wikibase-aliases-input-help-message": "ഈ ഡേറ്റാ ഗണം ഒന്നിലധികം 
പേരുകളിൽ അറിയപ്പെടുന്നുണ്ടെങ്കിൽ, താങ്കൾക്കവ ഇവിടെ പര്യായങ്ങളായോ അപരനാമങ്ങളായോ 
ചേർക്കാവുന്നതാണ്, അങ്ങനെ ചെയ്യുമ്പോൾ അവയുടെ ഇതരനാമങ്ങൾ ഉപയോഗിച്ചും അവ 
കണ്ടെത്താനാവും.",
-       "wikibase-aliases-empty": "അപരനാമങ്ങളൊന്നും കണ്ടെത്താനായില്ല.",
        "wikibase-propertypage-datatype": "ഡേറ്റാ തരം",
        "wikibase-statementview-rank-normal": "സാധാരണ റാങ്ക്",
        "wikibase-statementview-references-counter": 
"$1{{PLURAL:$2|0=|$3+$2$4}} {{PLURAL:$1|സ്രോതസ്സ്|സ്രോതസ്സുകൾ}}",
@@ -139,6 +141,7 @@
        "wikibase-entitieswithoutlabel-label-alltypes": "എല്ലാം",
        "wikibase-entitieswithoutlabel-submit": "കണ്ടെത്തുക",
        "wikibase-entitieswithoutlabel-invalid-language": "\"$1\" എന്നത് 
സാധുവായ ഭാഷാ കോഡ് അല്ല.",
+       "wikibase-entitypage-bad-id": "അസാധുവായ ഉപയോക്തൃനാമം: $1",
        "wikibase-restoreold": "പുനഃസ്ഥാപിക്കുക",
        "wikibase-no-direct-editing": "$1  എന്ന നാമമേഖലയിൽ നേരിട്ടുള്ള തിരുത്ത് 
പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു",
        "wikibase-noentity-createone": "ഒപ്പം താങ്കൾക്ക് [[$1|പുതിയൊരെണ്ണം 
സൃഷ്ടിക്കാവുന്നതുമാണ്]].",
diff --git a/extensions/Wikibase/repo/i18n/my.json 
b/extensions/Wikibase/repo/i18n/my.json
index 0b5885a..136d0d9 100644
--- a/extensions/Wikibase/repo/i18n/my.json
+++ b/extensions/Wikibase/repo/i18n/my.json
@@ -7,6 +7,7 @@
        },
        "wikibase-edit": "တည်းဖြတ်",
        "wikibase-add": "ပေါင်းထည့်",
+       "wikibase-label-empty": "နာမည်ညွှန်း မပေးထားပါ",
        "wikibase-description-empty": "ဖော်ပြချက် ပေးမထားပါ",
        "wikibase-sitelink-page-edit-placeholder": "စာမျက်နှာ",
        "wikibase-sitelinks-special": "အခြား ဆိုဒ်များ",
diff --git a/extensions/Wikibase/repo/includes/Api/ParseValue.php 
b/extensions/Wikibase/repo/includes/Api/ParseValue.php
index 8524e92..37fea83 100644
--- a/extensions/Wikibase/repo/includes/Api/ParseValue.php
+++ b/extensions/Wikibase/repo/includes/Api/ParseValue.php
@@ -65,29 +65,20 @@
        private $errorReporter;
 
        /**
+        * @see ApiBase::__construct
+        *
         * @param ApiMain $mainModule
         * @param string $moduleName
-        * @param string $modulePrefix
-        *
-        * @see ApiBase::__construct
+        * @param DataTypeFactory $dataTypeFactory
+        * @param ValueParserFactory $valueParserFactory
+        * @param DataTypeValidatorFactory $dataTypeValidatorFactory
+        * @param ExceptionLocalizer $exceptionLocalizer
+        * @param ValidatorErrorLocalizer $validatorErrorLocalizer
+        * @param ApiErrorReporter $errorReporter
         */
-       public function __construct( ApiMain $mainModule, $moduleName, 
$modulePrefix = '' ) {
-               parent::__construct( $mainModule, $moduleName, $modulePrefix );
-
-               $wikibaseRepo = WikibaseRepo::getDefaultInstance();
-               $apiHelperFactory = $wikibaseRepo->getApiHelperFactory( 
$this->getContext() );
-
-               $this->setServices(
-                       $wikibaseRepo->getDataTypeFactory(),
-                       $wikibaseRepo->getValueParserFactory(),
-                       $wikibaseRepo->getDataTypeValidatorFactory(),
-                       $wikibaseRepo->getExceptionLocalizer(),
-                       $wikibaseRepo->getValidatorErrorLocalizer(),
-                       $apiHelperFactory->getErrorReporter( $this )
-               );
-       }
-
-       public function setServices(
+       public function __construct(
+               ApiMain $mainModule,
+               $moduleName,
                DataTypeFactory $dataTypeFactory,
                ValueParserFactory $valueParserFactory,
                DataTypeValidatorFactory $dataTypeValidatorFactory,
@@ -95,6 +86,7 @@
                ValidatorErrorLocalizer $validatorErrorLocalizer,
                ApiErrorReporter $errorReporter
        ) {
+               parent::__construct( $mainModule, $moduleName );
                $this->dataTypeFactory = $dataTypeFactory;
                $this->valueParserFactory = $valueParserFactory;
                $this->dataTypeValidatorFactory = $dataTypeValidatorFactory;
diff --git a/extensions/Wikibase/repo/includes/Content/EntityContent.php 
b/extensions/Wikibase/repo/includes/Content/EntityContent.php
index 7dd54b1..4df825b 100644
--- a/extensions/Wikibase/repo/includes/Content/EntityContent.php
+++ b/extensions/Wikibase/repo/includes/Content/EntityContent.php
@@ -26,6 +26,7 @@
 use Wikibase\DataModel\Entity\EntityDocument;
 use Wikibase\DataModel\Entity\EntityId;
 use Wikibase\DataModel\Entity\EntityRedirect;
+use Wikibase\DataModel\Term\DescriptionsProvider;
 use Wikibase\DataModel\Term\FingerprintProvider;
 use Wikibase\Repo\Content\EntityContentDiff;
 use Wikibase\Repo\Content\EntityHandler;
@@ -412,13 +413,12 @@
                global $wgLang;
                $entity = $this->getEntity();
 
-               // TODO use DescriptionProvider
-               if ( $entity instanceof FingerprintProvider ) {
-                       $fingerprint = $entity->getFingerprint();
+               if ( $entity instanceof DescriptionsProvider ) {
+                       $descriptions = $entity->getDescriptions();
                        $languageCode = $wgLang->getCode();
 
-                       if ( $fingerprint->hasDescription( $languageCode ) ) {
-                               $description = $fingerprint->getDescription( 
$languageCode )->getText();
+                       if ( $descriptions->hasTermForLanguage( $languageCode ) 
) {
+                               $description = $descriptions->getByLanguage( 
$languageCode )->getText();
                                return substr( $description, 0, $maxLength );
                        }
                }
diff --git 
a/extensions/Wikibase/repo/includes/Search/Elastic/Fields/LabelCountField.php 
b/extensions/Wikibase/repo/includes/Search/Elastic/Fields/LabelCountField.php
index 3ad28b3..12f8879 100644
--- 
a/extensions/Wikibase/repo/includes/Search/Elastic/Fields/LabelCountField.php
+++ 
b/extensions/Wikibase/repo/includes/Search/Elastic/Fields/LabelCountField.php
@@ -3,7 +3,7 @@
 namespace Wikibase\Repo\Search\Elastic\Fields;
 
 use Wikibase\DataModel\Entity\EntityDocument;
-use Wikibase\DataModel\Term\FingerprintProvider;
+use Wikibase\DataModel\Term\LabelsProvider;
 
 /**
  * @license GPL-2.0+
@@ -30,8 +30,8 @@
         * @return int
         */
        public function getFieldData( EntityDocument $entity ) {
-               if ( $entity instanceof FingerprintProvider ) {
-                       return $entity->getFingerprint()->getLabels()->count();
+               if ( $entity instanceof LabelsProvider ) {
+                       return $entity->getLabels()->count();
                }
 
                return 0;
diff --git 
a/extensions/Wikibase/repo/includes/Validators/LabelDescriptionUniquenessValidator.php
 
b/extensions/Wikibase/repo/includes/Validators/LabelDescriptionUniquenessValidator.php
index 3907974..2c930ef 100644
--- 
a/extensions/Wikibase/repo/includes/Validators/LabelDescriptionUniquenessValidator.php
+++ 
b/extensions/Wikibase/repo/includes/Validators/LabelDescriptionUniquenessValidator.php
@@ -5,8 +5,9 @@
 use ValueValidators\Result;
 use Wikibase\DataModel\Entity\EntityDocument;
 use Wikibase\DataModel\Entity\EntityId;
+use Wikibase\DataModel\Term\DescriptionsProvider;
 use Wikibase\DataModel\Term\Fingerprint;
-use Wikibase\DataModel\Term\FingerprintProvider;
+use Wikibase\DataModel\Term\LabelsProvider;
 use Wikibase\LabelDescriptionDuplicateDetector;
 
 /**
@@ -39,11 +40,11 @@
         * @return Result
         */
        public function validateEntity( EntityDocument $entity ) {
-               if ( $entity instanceof FingerprintProvider ) {
+               if ( $entity instanceof LabelsProvider && $entity instanceof 
DescriptionsProvider ) {
                        return 
$this->duplicateDetector->detectLabelDescriptionConflicts(
                                $entity->getType(),
-                               
$entity->getFingerprint()->getLabels()->toTextArray(),
-                               
$entity->getFingerprint()->getDescriptions()->toTextArray(),
+                               $entity->getLabels()->toTextArray(),
+                               $entity->getDescriptions()->toTextArray(),
                                $entity->getId()
                        );
                }
diff --git 
a/extensions/Wikibase/repo/includes/Validators/LabelUniquenessValidator.php 
b/extensions/Wikibase/repo/includes/Validators/LabelUniquenessValidator.php
index 437bed3..8bb2ffc 100644
--- a/extensions/Wikibase/repo/includes/Validators/LabelUniquenessValidator.php
+++ b/extensions/Wikibase/repo/includes/Validators/LabelUniquenessValidator.php
@@ -6,7 +6,7 @@
 use Wikibase\DataModel\Entity\EntityDocument;
 use Wikibase\DataModel\Entity\EntityId;
 use Wikibase\DataModel\Term\Fingerprint;
-use Wikibase\DataModel\Term\FingerprintProvider;
+use Wikibase\DataModel\Term\LabelsProvider;
 use Wikibase\LabelDescriptionDuplicateDetector;
 
 /**
@@ -38,10 +38,10 @@
         * @return Result
         */
        public function validateEntity( EntityDocument $entity ) {
-               if ( $entity instanceof FingerprintProvider ) {
+               if ( $entity instanceof LabelsProvider ) {
                        return $this->duplicateDetector->detectLabelConflicts(
                                $entity->getType(),
-                               
$entity->getFingerprint()->getLabels()->toTextArray(),
+                               $entity->getLabels()->toTextArray(),
                                // insert again when T104393 is resolved
                                null, 
//$entity->getFingerprint()->getAliasGroups()->toTextArray(),
                                $entity->getId()
diff --git 
a/extensions/Wikibase/repo/tests/phpunit/includes/Api/ParseValueTest.php 
b/extensions/Wikibase/repo/tests/phpunit/includes/Api/ParseValueTest.php
index 8b97373..ce7b3cb 100644
--- a/extensions/Wikibase/repo/tests/phpunit/includes/Api/ParseValueTest.php
+++ b/extensions/Wikibase/repo/tests/phpunit/includes/Api/ParseValueTest.php
@@ -40,14 +40,12 @@
                $request = new FauxRequest( $params, true );
                $main = new ApiMain( $request );
 
-               $module = new ParseValue( $main, 'wbparsevalue' );
-
                $wikibaseRepo = WikibaseRepo::getDefaultInstance();
                $exceptionLocalizer = $wikibaseRepo->getExceptionLocalizer();
                $validatorErrorLocalizer = 
$wikibaseRepo->getValidatorErrorLocalizer();
 
                $errorReporter = new ApiErrorReporter(
-                       $module,
+                       $main,
                        $exceptionLocalizer,
                        Language::factory( 'qqq' )
                );
@@ -70,7 +68,9 @@
                        'url' => array( $this, 'newArrayWithStringValidator' ),
                ) );
 
-               $module->setServices(
+               return new ParseValue(
+                       $main,
+                       'wbparsevalue',
                        $dataTypeFactory,
                        $valueParserFactory,
                        $validatorFactory,
@@ -78,8 +78,6 @@
                        $validatorErrorLocalizer,
                        $errorReporter
                );
-
-               return $module;
        }
 
        public function newArrayWithStringValidator() {
diff --git 
a/extensions/Wikibase/repo/tests/phpunit/includes/Api/StatementModificationHelperTest.php
 
b/extensions/Wikibase/repo/tests/phpunit/includes/Api/StatementModificationHelperTest.php
index fa3f268..088ace9 100644
--- 
a/extensions/Wikibase/repo/tests/phpunit/includes/Api/StatementModificationHelperTest.php
+++ 
b/extensions/Wikibase/repo/tests/phpunit/includes/Api/StatementModificationHelperTest.php
@@ -50,6 +50,7 @@
 
                try {
                        $helper->getEntityIdFromString( $invalidEntityIdString 
);
+                       $this->fail( 'Expected exception was not thrown' );
                } catch ( ApiUsageException $ex ) {
                        $this->assertMessage( 'invalid-entity-id', $ex );
                }
@@ -96,6 +97,7 @@
 
                try {
                        $helper->getStatementFromEntity( 'foo', $entity );
+                       $this->fail( 'Expected exception was not thrown' );
                } catch ( ApiUsageException $ex ) {
                        $this->assertMessage( 'no-such-claim', $ex );
                }
@@ -108,6 +110,7 @@
 
                try {
                        $helper->getStatementFromEntity( 'unknown', $entity );
+                       $this->fail( 'Expected exception was not thrown' );
                } catch ( ApiUsageException $ex ) {
                        $this->assertMessage( 'no-such-claim', $ex );
                }
@@ -141,6 +144,7 @@
 
                try {
                        $helper->applyChangeOp( $changeOp, new Item() );
+                       $this->fail( 'Expected exception was not thrown' );
                } catch ( ApiUsageException $ex ) {
                        $this->assertMessage( 'modification-failed', $ex );
                }
@@ -160,6 +164,7 @@
 
                try {
                        $helper->applyChangeOp( $changeOp, new Item() );
+                       $this->fail( 'Expected exception was not thrown' );
                } catch ( ApiUsageException $ex ) {
                        $this->assertMessage( 'modification-failed', $ex );
                }
diff --git 
a/extensions/Wikibase/repo/tests/phpunit/includes/ChangeOp/ChangeOpLabelTest.php
 
b/extensions/Wikibase/repo/tests/phpunit/includes/ChangeOp/ChangeOpLabelTest.php
index 263a3d3..1a007ad 100644
--- 
a/extensions/Wikibase/repo/tests/phpunit/includes/ChangeOp/ChangeOpLabelTest.php
+++ 
b/extensions/Wikibase/repo/tests/phpunit/includes/ChangeOp/ChangeOpLabelTest.php
@@ -8,7 +8,6 @@
 use Wikibase\DataModel\Entity\EntityDocument;
 use Wikibase\DataModel\Entity\Item;
 use Wikibase\DataModel\Entity\ItemId;
-use Wikibase\DataModel\Term\FingerprintProvider;
 use Wikibase\Summary;
 
 /**
@@ -102,7 +101,7 @@
        }
 
        /**
-        * @return FingerprintProvider|EntityDocument
+        * @return Item
         */
        private function provideNewEntity() {
                $item = new Item( new ItemId( 'Q23' ) );
diff --git 
a/extensions/Wikibase/repo/tests/phpunit/includes/Content/EntityContentTest.php 
b/extensions/Wikibase/repo/tests/phpunit/includes/Content/EntityContentTest.php
index 1f5d0b3..1666268 100644
--- 
a/extensions/Wikibase/repo/tests/phpunit/includes/Content/EntityContentTest.php
+++ 
b/extensions/Wikibase/repo/tests/phpunit/includes/Content/EntityContentTest.php
@@ -5,13 +5,14 @@
 use Diff\DiffOp\Diff\Diff;
 use Diff\DiffOp\DiffOpChange;
 use Diff\Patcher\PatcherException;
+use InvalidArgumentException;
 use ParserOutput;
 use Title;
 use Wikibase\DataModel\Entity\EntityDocument;
 use Wikibase\DataModel\Entity\EntityId;
 use Wikibase\DataModel\Entity\EntityRedirect;
 use Wikibase\DataModel\Services\Diff\EntityDiff;
-use Wikibase\DataModel\Term\FingerprintProvider;
+use Wikibase\DataModel\Term\LabelsProvider;
 use Wikibase\EntityContent;
 use Wikibase\Lib\Store\EntityStore;
 use Wikibase\Repo\Content\EntityContentDiff;
@@ -86,12 +87,12 @@
                $this->assertSame( $expected, 
$entityContent->getTextForSearchIndex() );
        }
 
-       private function setLabel( EntityDocument $entity, $lang, $text ) {
-               if ( $entity instanceof FingerprintProvider ) {
-                       $entity->getFingerprint()->setLabel( $lang, $text );
-               } else {
-                       throw new \InvalidArgumentException( 
'FingerprintProvider expected!' );
+       private function setLabel( EntityDocument $entity, $languageCode, $text 
) {
+               if ( !( $entity instanceof LabelsProvider ) ) {
+                       throw new InvalidArgumentException( '$entity must be a 
LabelsProvider' );
                }
+
+               $entity->getLabels()->setTextForLanguage( $languageCode, $text 
);
        }
 
        public function getTextForSearchIndexProvider() {
diff --git 
a/extensions/Wikibase/repo/tests/phpunit/includes/Specials/SpecialGoToLinkedPageTest.php
 
b/extensions/Wikibase/repo/tests/phpunit/includes/Specials/SpecialGoToLinkedPageTest.php
index 8f9fed1..e012819 100644
--- 
a/extensions/Wikibase/repo/tests/phpunit/includes/Specials/SpecialGoToLinkedPageTest.php
+++ 
b/extensions/Wikibase/repo/tests/phpunit/includes/Specials/SpecialGoToLinkedPageTest.php
@@ -29,6 +29,11 @@
  */
 class SpecialGoToLinkedPageTest extends SpecialPageTestBase {
 
+       use HtmlAssertionHelpers;
+
+       /** @see \LanguageQqx */
+       const DUMMY_LANGUAGE = 'qqx';
+
        /**
         * @return SiteLinkLookup
         */
@@ -147,32 +152,13 @@
         */
        public function testExecuteWithoutRedirect( $sub, $target, $site, 
$item, $error ) {
                /* @var FauxResponse $response */
-               list( $output, $response ) = $this->executeSpecialPage( $sub, 
null, 'qqx' );
+               list( $output, $response ) = $this->executeSpecialPage( $sub, 
null, self::DUMMY_LANGUAGE );
 
                $this->assertEquals( $target, $response->getheader( 'Location' 
), 'Redirect' );
 
-               $matchers = array();
-               $matchers['site'] = array(
-                       'tag' => 'input',
-                       'attributes' => array(
-                               'name' => 'site',
-                               'value' => $site
-                       ) );
-               $matchers['itemid'] = array(
-                       'tag' => 'input',
-                       'attributes' => array(
-                               'name' => 'itemid',
-                               'value' => $item
-                       ) );
-               $matchers['submit'] = array(
-                       'tag' => 'button',
-                       'attributes' => array(
-                               'type' => 'submit',
-                       )
-               );
-               foreach ( $matchers as $key => $matcher ) {
-                       $this->assertTag( $matcher, $output, "Failed to match 
html output for: " . $key );
-               }
+               $this->assertHtmlContainsInputWithNameAndValue( $output, 
'site', $site );
+               $this->assertHtmlContainsInputWithNameAndValue( $output, 
'itemid', $item );
+               $this->assertHtmlContainsSubmitControl( $output );
 
                if ( !empty( $error ) ) {
                        $this->assertContains( '<p class="error">' . $error . 
'</p>', $output,
diff --git 
a/extensions/Wikibase/repo/tests/phpunit/includes/Specials/SpecialModifyTermTestCase.php
 
b/extensions/Wikibase/repo/tests/phpunit/includes/Specials/SpecialModifyTermTestCase.php
index fe165a3..a992462 100644
--- 
a/extensions/Wikibase/repo/tests/phpunit/includes/Specials/SpecialModifyTermTestCase.php
+++ 
b/extensions/Wikibase/repo/tests/phpunit/includes/Specials/SpecialModifyTermTestCase.php
@@ -16,17 +16,29 @@
  */
 abstract class SpecialModifyTermTestCase extends SpecialPageTestBase {
 
+       const USER_LANGUAGE = 'en';
+
+       protected function setUp() {
+
+               parent::setUp();
+
+               $this->setMwGlobals( 'wgGroupPermissions', [ '*' => [ 'edit' => 
true, 'item-term' => true ] ] );
+       }
+
        /**
         * Creates a new item and returns its id.
         *
+        * @param string $language
+        * @param string $termValue
         * @return string
         */
-       private function createNewItem() {
+       private function createNewItemWithTerms( $language, $termValue ) {
+
                $item = new Item();
                // add data and check if it is shown in the form
-               $item->setLabel( 'de', 'foo' );
-               $item->setDescription( 'de', 'foo' );
-               $item->setAliases( 'de', array( 'foo' ) );
+               $item->setLabel( $language, $termValue );
+               $item->setDescription( $language, $termValue );
+               $item->setAliases( $language, [ $termValue ] );
 
                // save the item
                $store = WikibaseRepo::getDefaultInstance()->getEntityStore();
@@ -36,102 +48,165 @@
                return $item->getId()->getSerialization();
        }
 
-       public function testExecute() {
-               $id = $this->createNewItem();
-
-               $this->setMwGlobals( 'wgGroupPermissions', array( '*' => array( 
'edit' => true, 'item-term' => true ) ) );
-
+       public function testRenderWithoutSubPage_AllInputFieldsPresent() {
                $page = $this->newSpecialPage();
 
-               $matchers['id'] = array(
+               $matchers['id'] = [
                        'tag' => 'input',
-                       'attributes' => array(
+                       'attributes' => [
                                'id' => 'wb-modifyentity-id',
                                'class' => 'wb-input',
                                'name' => 'id',
-                       ) );
-               $matchers['language'] = array(
+                       ] ];
+               $matchers['language'] = [
                        'tag' => 'input',
-                       'attributes' => array(
+                       'attributes' => [
                                'id' => 'wb-modifyterm-language',
                                'class' => 'wb-input',
                                'name' => 'language',
-                               'value' => 'en',
-                       ) );
-               $matchers['value'] = array(
+                               'value' => self::USER_LANGUAGE,
+                       ] ];
+               $matchers['value'] = [
                        'tag' => 'input',
-                       'attributes' => array(
+                       'attributes' => [
                                'id' => 'wb-modifyterm-value',
                                'class' => 'wb-input',
                                'name' => 'value',
-                       ) );
-               $matchers['submit'] = array(
+                       ] ];
+               $matchers['submit'] = [
                        'tag' => 'input',
-                       'attributes' => array(
+                       'attributes' => [
                                'id' => 'wb-' . strtolower( $page->getName() ) 
. '-submit',
                                'class' => 'wb-button',
                                'type' => 'submit',
                                'name' => 'wikibase-' . strtolower( 
$page->getName() ) . '-submit',
-                       ) );
+                       ] ];
 
                // execute with no subpage value
-               list( $output, ) = $this->executeSpecialPage( '', null, 'en' );
+               list( $output, ) = $this->executeSpecialPage( '', null, 
self::USER_LANGUAGE );
                foreach ( $matchers as $key => $matcher ) {
                        $this->assertTag( $matcher, $output, "Failed to match 
html output with tag '{$key}'" );
                }
+       }
 
-               // execute with one subpage value
-               list( $output, ) = $this->executeSpecialPage( $id, null, 'en' );
-               $matchers['id']['attributes'] = array(
+       public function 
testRenderWithOneSubpageValue_TreatsValueAsItemIdAndShowsOnlyTermInputField() {
+               $notUserLanguage = 'de';
+               $id = $this->createNewItemWithTerms( $notUserLanguage, 
'some-term-value' );
+
+               $page = $this->newSpecialPage();
+
+               $matchers['id'] = [
+                       'tag' => 'input',
+                       'attributes' => [
                                'type' => 'hidden',
                                'name' => 'id',
                                'value' => $id,
-                       );
-               $matchers['language']['attributes'] = array(
-                               'type' => 'hidden',
-                               'name' => 'language',
-                               'value' => 'en',
-                       );
-               $matchers['remove'] = array(
+                       ] ];
+               $matchers['language'] = [
                        'tag' => 'input',
-                       'attributes' => array(
+                       'attributes' => [
+                               'name' => 'language',
+                               'value' => self::USER_LANGUAGE,
+                               'type' => 'hidden',
+                       ] ];
+               $matchers['value'] = [
+                       'tag' => 'input',
+                       'attributes' => [
+                               'id' => 'wb-modifyterm-value',
+                               'class' => 'wb-input',
+                               'name' => 'value',
+                       ] ];
+               $matchers['submit'] = [
+                       'tag' => 'input',
+                       'attributes' => [
+                               'id' => 'wb-' . strtolower( $page->getName() ) 
. '-submit',
+                               'class' => 'wb-button',
+                               'type' => 'submit',
+                               'name' => 'wikibase-' . strtolower( 
$page->getName() ) . '-submit',
+                       ] ];
+               $matchers['remove'] = [
+                       'tag' => 'input',
+                       'attributes' => [
                                'type' => 'hidden',
                                'name' => 'remove',
                                'value' => 'remove',
-                       ) );
+                       ] ];
+
+               // execute with one subpage value
+               list( $output, ) = $this->executeSpecialPage( $id, null, 
self::USER_LANGUAGE );
 
                foreach ( $matchers as $key => $matcher ) {
                        $this->assertTag( $matcher, $output, "Failed to match 
html output with tag '{$key}' passing one subpage value" );
                }
+       }
+
+       public function 
testRenderWithTwoSubpageValues_TreatsSecondValueAsLanguageAndShowsOnlyTermInputField()
 {
+               $id = $this->createNewItemWithTerms( $itemTermLanguage = 'de', 
$termValue = 'foo' );
+
+               $page = $this->newSpecialPage();
+
+               $matchers['id'] = [
+                       'tag' => 'input',
+                       'attributes' => [
+                               'type' => 'hidden',
+                               'name' => 'id',
+                               'value' => $id,
+                       ] ];
+               $matchers['language'] = [
+                       'tag' => 'input',
+                       'attributes' => [
+                               'name' => 'language',
+                               'value' => $itemTermLanguage,
+                               'type' => 'hidden',
+                       ] ];
+               $matchers['value'] = [
+                       'tag' => 'input',
+                       'attributes' => [
+                               'id' => 'wb-modifyterm-value',
+                               'class' => 'wb-input',
+                               'name' => 'value',
+                               'value' => $termValue
+                       ] ];
+               $matchers['submit'] = [
+                       'tag' => 'input',
+                       'attributes' => [
+                               'id' => 'wb-' . strtolower( $page->getName() ) 
. '-submit',
+                               'class' => 'wb-button',
+                               'type' => 'submit',
+                               'name' => 'wikibase-' . strtolower( 
$page->getName() ) . '-submit',
+                       ] ];
+               $matchers['remove'] = [
+                       'tag' => 'input',
+                       'attributes' => [
+                               'type' => 'hidden',
+                               'name' => 'remove',
+                               'value' => 'remove',
+                       ] ];
 
                // execute with two subpage values
-               list( $output, ) = $this->executeSpecialPage( $id . '/de', 
null, 'en' );
-               $matchers['language']['attributes']['value'] = 'de';
-               $matchers['value']['attributes']['value'] = 'foo';
+               list( $output, ) = $this->executeSpecialPage( $id . '/' . 
$itemTermLanguage, null, self::USER_LANGUAGE );
 
                foreach ( $matchers as $key => $matcher ) {
-                       $this->assertTag( $matcher, $output, "Failed to match 
html output with tag '{$key}' passing two subpage values" );
+                       $this->assertTag( $matcher, $output, "Failed to match 
html output with tag '{$key}' passing two subpage values" . PHP_EOL . $output );
                }
        }
 
        public function testValuePreservesWhenNothingEntered() {
-               $id = $this->createNewItem();
+               $id = $this->createNewItemWithTerms( $language = 'de', 
$termValue = 'foo' );
 
-               $this->setMwGlobals( 'wgGroupPermissions', array( '*' => array( 
'edit' => true, 'item-term' => true ) ) );
-
-               $request = new FauxRequest( array( 'id' => $id, 'language' => 
'de', 'value' => '' ), true );
+               $request = new FauxRequest( [ 'id' => $id, 'language' => 
$language, 'value' => '' ], true );
 
                list( $output, ) = $this->executeSpecialPage( '', $request );
 
-               $this->assertTag( array(
+               $this->assertTag( [
                        'tag' => 'input',
-                       'attributes' => array(
+                       'attributes' => [
                                'id' => 'wb-modifyterm-value',
                                'class' => 'wb-input',
                                'name' => 'value',
-                               'value' => 'foo',
-                       )
-               ), $output, 'Value still preserves when no value was entered in 
the big form' );
+                               'value' => $termValue,
+                       ]
+               ], $output, 'Value still preserves when no value was entered in 
the big form' );
        }
 
 }
diff --git a/vendor/composer/autoload_classmap.php 
b/vendor/composer/autoload_classmap.php
index 5dcc9d8..e346a27 100644
--- a/vendor/composer/autoload_classmap.php
+++ b/vendor/composer/autoload_classmap.php
@@ -466,6 +466,7 @@
     'Wikibase\\Client\\Specials\\SpecialUnconnectedPages' => $baseDir . 
'/extensions/Wikibase/client/includes/Specials/SpecialUnconnectedPages.php',
     'Wikibase\\Client\\Store\\AddUsagesForPageJob' => $baseDir . 
'/extensions/Wikibase/client/includes/Store/AddUsagesForPageJob.php',
     'Wikibase\\Client\\Store\\RepositoryServiceContainer' => $baseDir . 
'/extensions/Wikibase/client/includes/Store/RepositoryServiceContainer.php',
+    'Wikibase\\Client\\Store\\RepositoryServiceContainerFactory' => $baseDir . 
'/extensions/Wikibase/client/includes/Store/RepositoryServiceContainerFactory.php',
     'Wikibase\\Client\\Store\\Sql\\BulkSubscriptionUpdater' => $baseDir . 
'/extensions/Wikibase/client/includes/Store/Sql/BulkSubscriptionUpdater.php',
     'Wikibase\\Client\\Store\\Sql\\PagePropsEntityIdLookup' => $baseDir . 
'/extensions/Wikibase/client/includes/Store/Sql/PagePropsEntityIdLookup.php',
     'Wikibase\\Client\\Store\\TitleFactory' => $baseDir . 
'/extensions/Wikibase/client/includes/Store/TitleFactory.php',
@@ -551,6 +552,7 @@
     'Wikibase\\Client\\Tests\\Specials\\SpecialPagesWithBadgesTest' => 
$baseDir . 
'/extensions/Wikibase/client/tests/phpunit/includes/Specials/SpecialPagesWithBadgesTest.php',
     'Wikibase\\Client\\Tests\\Specials\\SpecialUnconnectedPagesTest' => 
$baseDir . 
'/extensions/Wikibase/client/tests/phpunit/includes/Specials/SpecialUnconnectedPagesTest.php',
     'Wikibase\\Client\\Tests\\Store\\AddUsagesForPageJobTest' => $baseDir . 
'/extensions/Wikibase/client/tests/phpunit/includes/Store/AddUsagesForPageJobTest.php',
+    'Wikibase\\Client\\Tests\\Store\\RepositoryServiceContainerFactoryTest' => 
$baseDir . 
'/extensions/Wikibase/client/tests/phpunit/includes/Store/RepositoryServiceContainerFactoryTest.php',
     'Wikibase\\Client\\Tests\\Store\\RepositoryServiceContainerTest' => 
$baseDir . 
'/extensions/Wikibase/client/tests/phpunit/includes/Store/RepositoryServiceContainerTest.php',
     'Wikibase\\Client\\Tests\\Store\\RepositoryServiceWiringTest' => $baseDir 
. 
'/extensions/Wikibase/client/tests/phpunit/includes/Store/RepositoryServiceWiringTest.php',
     'Wikibase\\Client\\Tests\\Store\\Sql\\BulkSubscriptionUpdaterTest' => 
$baseDir . 
'/extensions/Wikibase/client/tests/phpunit/includes/Store/Sql/BulkSubscriptionUpdaterTest.php',
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index deef418..68d14a0 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -1409,12 +1409,12 @@
         "source": {
             "type": "git",
             "url": 
"https://github.com/wikimedia/mediawiki-extensions-Wikibase.git";,
-            "reference": "429ab38c5ddf34438425a4012b08fcba806fb54b"
+            "reference": "5c39fd44085adf7797e5084a7754b7631c2f2e1b"
         },
         "dist": {
             "type": "zip",
-            "url": 
"https://api.github.com/repos/wikimedia/mediawiki-extensions-Wikibase/zipball/429ab38c5ddf34438425a4012b08fcba806fb54b";,
-            "reference": "429ab38c5ddf34438425a4012b08fcba806fb54b",
+            "url": 
"https://api.github.com/repos/wikimedia/mediawiki-extensions-Wikibase/zipball/5c39fd44085adf7797e5084a7754b7631c2f2e1b";,
+            "reference": "5c39fd44085adf7797e5084a7754b7631c2f2e1b",
             "shasum": ""
         },
         "require": {
@@ -1447,7 +1447,7 @@
             "jakub-onderka/php-parallel-lint": "0.9.2",
             "mediawiki/mediawiki-codesniffer": "0.4.0|0.5.0"
         },
-        "time": "2017-01-26 15:15:12",
+        "time": "2017-01-27 16:09:49",
         "type": "mediawiki-extension",
         "installation-source": "dist",
         "autoload": {

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I38256934302440e4d906f29fa09a05c191a085a1
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Wikidata
Gerrit-Branch: master
Gerrit-Owner: WikidataBuilder <wikidata-servi...@wikimedia.de>
Gerrit-Reviewer: Aude <aude.w...@gmail.com>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to