jenkins-bot has submitted this change and it was merged. Change subject: Add editentity claims support ......................................................................
Add editentity claims support This allows the addition and removal of claims New claims can contain references and qualifiers Multiple claims can be changed at once This now allows everything in an entity to change in a single edit! Bug: 46709 Change-Id: Id8586795c34eed24f899858b48dec7f299636db9 --- M lib/includes/serializers/ClaimSerializer.php M repo/includes/api/ApiWikibase.php M repo/includes/api/EditEntity.php M repo/tests/phpunit/includes/api/EditEntityTest.php M repo/tests/phpunit/includes/api/WikibaseApiTestCase.php 5 files changed, 234 insertions(+), 7 deletions(-) Approvals: Tobias Gritschacher: Looks good to me, approved jenkins-bot: Verified diff --git a/lib/includes/serializers/ClaimSerializer.php b/lib/includes/serializers/ClaimSerializer.php index 489e8ed..ec81dcd 100644 --- a/lib/includes/serializers/ClaimSerializer.php +++ b/lib/includes/serializers/ClaimSerializer.php @@ -161,7 +161,6 @@ } $requiredElements = array( - 'id', 'mainsnak', ); @@ -187,7 +186,9 @@ $claim = new $claimClass( $snakUnserializer->newFromSerialization( $serialization['mainsnak'] ) ); assert( $claim instanceof Claim ); - $claim->setGuid( $serialization['id'] ); + if( array_key_exists( 'id', $serialization ) ){ + $claim->setGuid( $serialization['id'] ); + } $claim->setQualifiers( $this->unserializeQualifiers( $serialization, $snakUnserializer ) ); diff --git a/repo/includes/api/ApiWikibase.php b/repo/includes/api/ApiWikibase.php index 8e74fc5..214426a 100644 --- a/repo/includes/api/ApiWikibase.php +++ b/repo/includes/api/ApiWikibase.php @@ -3,9 +3,11 @@ namespace Wikibase\Api; use User, Status, ApiBase; +use Wikibase\Claims; use Wikibase\DataModel\SimpleSiteLink; use Wikibase\Entity; use Wikibase\EntityContent; +use Wikibase\Lib\Serializers\ClaimsSerializer; use Wikibase\Settings; use Wikibase\EditEntity; use Wikibase\SiteLink; @@ -244,6 +246,33 @@ } /** + * Get serialized claims and add them to result + * + * @since 0.5 + * + * @param array $claims the labels to set in the result + * @param array|string $path where the data is located + * @param string $name name used for the entry + * @param string $tag tag used for indexed entries in xml formats and similar + * + */ + protected function addClaimsToResult( array $claims, $path, $name = 'claims', $tag = 'claim' ) { + $options = new MultiLangSerializationOptions(); + $options->setUseKeys( $this->getUsekeys() ); + $claimSerializer = new ClaimsSerializer( $options ); + + $value = $claimSerializer->getSerialized( new Claims( $claims ) ); + + if ( $value !== array() ) { + if ( !$this->getUsekeys() ) { + $this->getResult()->setIndexedTagName( $value, $tag ); + } + + $this->getResult()->addValue( $path, $name, $value ); + } + } + + /** * Returns the permissions that are required to perform the operation specified by * the parameters. * diff --git a/repo/includes/api/EditEntity.php b/repo/includes/api/EditEntity.php index 028172c..f4858f0 100644 --- a/repo/includes/api/EditEntity.php +++ b/repo/includes/api/EditEntity.php @@ -2,6 +2,10 @@ namespace Wikibase\Api; +use DataValues\IllegalValueException; +use MWException; +use Wikibase\ChangeOpClaim; +use Wikibase\Claim; use Wikibase\EntityContentFactory; use InvalidArgumentException; use Wikibase\ChangeOps; @@ -15,9 +19,11 @@ use Wikibase\Entity; use Wikibase\EntityContent; use Wikibase\Item; +use Wikibase\Lib\ClaimGuidGenerator; use Wikibase\Property; use Wikibase\QueryContent; use Wikibase\Utils; +use WikiPage; /** * Derived class for API modules modifying a single entity identified by id xor a combination of site and page title. @@ -28,6 +34,7 @@ * @author John Erling Blad < jeb...@gmail.com > * @author Daniel Kinzler * @author Tobias Gritschacher < tobias.gritschac...@wikimedia.de > + * @author Adam Shorland */ class EditEntity extends ModifyEntity { @@ -156,10 +163,19 @@ if ( array_key_exists( 'sitelinks', $data ) ) { if ( $entity->getType() !== Item::ENTITY_TYPE ) { wfProfileOut( __METHOD__ ); - $this->dieUsage( "key can't be handled: sitelinks", 'not-recognized' ); + $this->dieUsage( "Non Items can not have sitelinks", 'not-recognized' ); } $changeOps->add( $this->getSiteLinksChangeOps( $data['sitelinks'], $status ) ); + } + + if( array_key_exists( 'claims', $data ) ) { + $changeOps->add( + $this->getClaimsChangeOps( + $data['claims'], + new ClaimGuidGenerator( $entity->getId() ) + ) + ); } if ( !$status->isOk() ) { @@ -180,6 +196,7 @@ if ( $entity->getType() === Item::ENTITY_TYPE ) { $this->addSiteLinksToResult( $entity->getSimpleSiteLinks(), 'entity' ); } + $this->addClaimsToResult( $entity->getClaims(), 'entity' ); wfProfileOut( __METHOD__ ); return $summary; @@ -353,6 +370,53 @@ } /** + * @since 0.5 + * + * @param array $claims + * + * @param ClaimGuidGenerator $guidGenerator + * @return ChangeOpClaim[] + */ + protected function getClaimsChangeOps( $claims, $guidGenerator ) { + $claimChangeOps = array(); + + if ( !is_array( $claims ) ) { + $this->dieUsage( "List of claims must be an array", 'not-recognized-array' ); + } + + $serializerFactory = new \Wikibase\Lib\Serializers\SerializerFactory(); + $unserializer = $serializerFactory->newUnserializerForClass( 'Wikibase\Claim' ); + + foreach ( $claims as $claimArray ) { + + try { + $claim = $unserializer->newFromSerialization( $claimArray ); + assert( $claim instanceof Claim ); + } catch ( IllegalValueException $illegalValueException ) { + $this->dieUsage( $illegalValueException->getMessage(), 'invalid-claim' ); + } catch ( MWException $mwException ) { + $this->dieUsage( $mwException->getMessage(), 'invalid-claim' ); + } + + if ( array_key_exists( 'remove', $claimArray ) ) { + if( array_key_exists( 'id', $claimArray ) ){ + $claimChangeOps[] = new ChangeOpClaim( $claim, 'remove', $guidGenerator ); + } else { + $this->dieUsage( 'Cannot remove a claim with no GUID', 'invalid-claim' ); + } + } else { + + if( array_key_exists( 'id', $claimArray ) ){ + $claimChangeOps[] = new ChangeOpClaim( $claim, 'remove', $guidGenerator ); + } + $claimChangeOps[] = new ChangeOpClaim( $claim, 'add', $guidGenerator ); + } + } + + return $claimChangeOps; + } + + /** * @since 0.4 * * @param array $data @@ -382,6 +446,7 @@ 'descriptions', 'aliases', 'sitelinks', + 'claims', 'datatype' ); if ( is_null( $data ) ) { @@ -398,7 +463,7 @@ } if ( !in_array( $prop, $allowedProps ) ) { - $this->dieUsage( "unknown key: $prop", 'not-recognized' ); + $this->dieUsage( "Unknown key in json: $prop", 'not-recognized' ); } } @@ -434,7 +499,6 @@ array( 'code' => 'no-external-page', 'info' => $this->msg( 'wikibase-api-no-external-page' )->text() ), array( 'code' => 'invalid-json', 'info' => $this->msg( 'wikibase-api-invalid-json' )->text() ), array( 'code' => 'not-recognized-string', 'info' => $this->msg( 'wikibase-api-not-recognized-string' )->text() ), - array( 'code' => 'not-recognized', 'info' => $this->msg( 'wikibase-api-not-recognized' )->text() ), array( 'code' => 'param-illegal', 'info' => $this->msg( 'wikibase-api-param-illegal' )->text() ), array( 'code' => 'param-missing', 'info' => $this->msg( 'wikibase-api-param-missing' )->text() ), array( 'code' => 'inconsistent-language', 'info' => $this->msg( 'wikibase-api-inconsistent-language' )->text() ), @@ -517,6 +581,12 @@ => 'Create a new property containing the json data, returns extended with the item structure', 'api.php?action=wbeditentity&id=Q42&data={"sitelinks":{"nowiki":{"site":"nowiki","title":"København"}}}' => 'Sets sitelink for nowiki, overwriting it if it already exists', + 'api.php?action=wbeditentity&id=Q42&data={"claims":[{"mainsnak":{"snaktype":"value","property":"P56","datavalue":{"value":"ExampleString","type":"string"}},"type":"statement","rank":"normal"}]}' + => 'Creates a new claim on the item for the property P56 and a value of "ExampleString"', + 'api.php?action=wbeditentity&id=Q42&data={"claims":[{"id":"Q42$D8404CDA-25E4-4334-AF13-A3290BCD9C0F","mainsnak":{"snaktype":"value","property":"P56","datavalue":{"value":"FirstClaim","type":"string"}},"remove":""},{"id":"Q42$GH678DSA-01PQ-28XC-HJ90-DDFD9990126X","mainsnak":{"snaktype":"value","property":"P56","datavalue":{"value":"SecondClaim","type":"string"}},"remove":""}]}' + => 'Removes the claims from the item with the guids q42$D8404CDA-25E4-4334-AF13-A3290BCD9C0F and q42$GH678DSA-01PQ-28XC-HJ90-DDFD9990126X', + 'api.php?action=wbeditentity&id=Q42&data={"claims":[{"id":"Q42$GH678DSA-01PQ-28XC-HJ90-DDFD9990126X","mainsnak":{"snaktype":"value","property":"P56","datavalue":{"value":"ChangedString","type":"string"}},"type":"statement","rank":"normal"}]}' + => 'Sets the claim with the GUID to the value of the claim', ); } diff --git a/repo/tests/phpunit/includes/api/EditEntityTest.php b/repo/tests/phpunit/includes/api/EditEntityTest.php index e1ac907..9a8d3cd 100644 --- a/repo/tests/phpunit/includes/api/EditEntityTest.php +++ b/repo/tests/phpunit/includes/api/EditEntityTest.php @@ -2,10 +2,13 @@ namespace Wikibase\Test\Api; use ApiTestCase; +use Wikibase\DataModel\Entity\EntityId; +use Wikibase\DataModel\Entity\PropertyId; +use Wikibase\PropertyContent; /** * Tests for the ApiWikibase class. - + * * @since 0.1 * * @ingroup WikibaseRepoTest @@ -32,14 +35,21 @@ class EditEntityTest extends WikibaseApiTestCase { private static $testEntityId; + private static $testClaimGuid; private static $hasSetup; static public $id = null; public function setup() { parent::setup(); + $prop56 = PropertyId::newFromNumber( 56 ); + if( !isset( self::$hasSetup ) ){ $this->initTestEntities( array( 'Berlin' ) ); + $prop = PropertyContent::newEmpty(); + $prop->getEntity()->setId( $prop56 ); + $prop->getEntity()->setDataTypeId( 'string' ); + $prop->save( 'EditEntityTest' ); } self::$hasSetup = true; } @@ -92,6 +102,89 @@ array( //14 unset a sitelink using the other sitelink 'p' => array( 'site' => 'svwiki', 'title' => 'FOO', 'data' => '{"sitelinks":{"dewiki":{"site":"dewiki","title":""}}}' ), 'e' => array( 'sitelinks' => array( 'svwiki' => 'FOO' ) ) ), + + array( //15 add a claim + 'p' => array( 'data' => '{"claims":[{"mainsnak":{"snaktype":"value","property":"P56","datavalue":{"value":"imastring","type":"string"}},"type":"statement","rank":"normal"}]}' ), + 'e' => array( 'claims' => array( + 'P56' => array( + 'mainsnak' => array( 'snaktype' => 'value', 'property' => 'P56', + 'datavalue' => array( + 'value' => 'imastring', + 'type' => 'string' ) ), + 'type' => 'statement', + 'rank' => 'normal' ) ) ) ), + + array( //15 change the claim + 'p' => array( 'data' => '{"claims":[{"id":"GUID","mainsnak":{"snaktype":"value","property":"P56","datavalue":{"value":"diffstring","type":"string"}},"type":"statement","rank":"normal"}]}' ), + 'e' => array( 'claims' => array( + 'P56' => array( + 'mainsnak' => array( 'snaktype' => 'value', 'property' => 'P56', + 'datavalue' => array( + 'value' => 'diffstring', + 'type' => 'string' ) ), + 'type' => 'statement', + 'rank' => 'normal' ) ) ) ), + + array( //15 remove the claim + 'p' => array( 'data' => '{"claims":[{"id":"GUID","mainsnak":{"snaktype":"value","property":"P56","datavalue":{"value":"diffstring","type":"string"}},"type":"statement","rank":"normal","remove":""}]}' ), + 'e' => array( 'claims' => array() ) ), + + array( //15 add multiple claims + 'p' => array( + 'data' => '{"claims":[{"mainsnak":{"snaktype":"value","property":"P56","datavalue":{"value":"imastring1","type":"string"}},"type":"statement","rank":"normal"},'. + '{"mainsnak":{"snaktype":"value","property":"P56","datavalue":{"value":"imastring2","type":"string"}},"type":"statement","rank":"normal"}]}' ), + 'e' => array( 'claims' => array( + 'P56' => array( + 'mainsnak' => array( + 'snaktype' => 'value', 'property' => 'P56', + 'datavalue' => array( + 'value' => 'imastring1', + 'type' => 'string' ) ), + 'type' => 'statement', + 'rank' => 'normal' ), + array( + 'mainsnak' => array( + 'snaktype' => 'value', 'property' => 'P56', + 'datavalue' => array( + 'value' => 'imastring2', + 'type' => 'string' ) ), + 'type' => 'statement', + 'rank' => 'normal' ) + ) ) + ), + + array( //15 clear and add complex claim with qualifiers and references + 'p' => array( 'clear' => '', 'data' => '{"claims": [{"mainsnak": {"snaktype": "value", "property": "P56", "datavalue": { "value": "str", "type": "string" } },'. + '"qualifiers": { "P56": [ { "snaktype": "value", "property": "P56", "datavalue": { "value": "qual", "type": "string" } } ] }, "type": "statement", "rank": "normal",'. + '"references": [ { "snaks": { "P56": [ { "snaktype": "value", "property": "P56", "datavalue": { "value": "src", "type": "string" } } ] } } ]}]}' ), + 'e' => array( 'claims' => array( + 'P56' => array( + 'mainsnak' => array( + 'snaktype' => 'value', 'property' => 'P56', + 'datavalue' => array( + 'value' => 'str', + 'type' => 'string' ) ), + 'qualifiers' => array( + 'P56' => array( + 'snaktype' => 'value', 'property' => 'P56', + 'datavalue' => array( + 'value' => 'qual', + 'type' => 'string' ) ), + ), + 'type' => 'statement', + 'rank' => 'normal', + 'references' => array( + 'snaks' => array( + 'P56' => array( + 'snaktype' => 'value', 'property' => 'P56', + 'datavalue' => array( + 'value' => 'src', + 'type' => 'string' ) ), + ), + ), + ) + ) ) + ), ); } @@ -107,13 +200,23 @@ && !array_key_exists( 'title', $params) ){ $params['id'] = self::$testEntityId; } + if( array_key_exists( 'data', $params ) && strstr( $params['data'], 'GUID' ) ){ + $params['data'] = str_replace( 'GUID', self::$testClaimGuid, $params['data'] ); + } // -- do the request -------------------------------------------------- list($result,,) = $this->doApiRequestWithToken( $params ); - // -- steal stuff for later tests ------------------------------------- + // -- steal ids for later tests ------------------------------------- if( array_key_exists( 'new', $params ) && stristr( $params['new'], 'item' ) ){ self::$testEntityId = $result['entity']['id']; + } + if( array_key_exists( 'claims', $result['entity'] ) && array_key_exists( 'P56', $result['entity']['claims'] ) ){ + foreach( $result['entity']['claims']['P56'] as $claim ){ + if( array_key_exists( 'id', $claim ) ){ + self::$testClaimGuid = $claim['id']; + } + } } // -- check the result ------------------------------------------------ @@ -199,6 +302,12 @@ 'data' => '{"descriptions":{"en":{"language":"en","value":"'.LangAttributeTestHelper::makeOverlyLongString().'"}}}'), 'e' => array( 'exception' => array( 'type' => 'UsageException' ) ) ), //@todo add check for Bug:52731 once fixed + array( //19 removing invalid claim fails + 'p' => array( 'site' => 'enwiki', 'title' => 'Berlin' , 'data' => '{"claims":[{"remove":""}]}'), + 'e' => array( 'exception' => array( 'type' => 'UsageException', 'code' => 'invalid-claim', 'message' => 'Invalid claim type specified' ) ) ), + array( //20 removing valid claim with no guid fails + 'p' => array( 'site' => 'enwiki', 'title' => 'Berlin' , 'data' => '{"remove":"","claims":[{"mainsnak":{"snaktype":"value","property":"P56","datavalue":{"value":"imastring","type":"string"}},"type":"statement","rank":"normal"}]}'), + 'e' => array( 'exception' => array( 'type' => 'UsageException', 'code' => 'not-recognized', 'message' => 'Unknown key in json: remove' ) ) ), ); } diff --git a/repo/tests/phpunit/includes/api/WikibaseApiTestCase.php b/repo/tests/phpunit/includes/api/WikibaseApiTestCase.php index 067128d..deb5cf8 100644 --- a/repo/tests/phpunit/includes/api/WikibaseApiTestCase.php +++ b/repo/tests/phpunit/includes/api/WikibaseApiTestCase.php @@ -252,6 +252,24 @@ // keys are significant in flat form $this->assertArrayEquals( $exp, $data, false, true ); } + + if ( isset( $expected['claims'] ) ) { + if( empty( $expected['claims'] ) ){ + $this->assertArrayNotHasKey( 'claims', $actual ); + } else { + $data = self::flattenArray( $actual['claims'], 'mainsnak', 'value', true ); + $exp = self::flattenArray( $expected['claims'], 'language', 'value', true ); + for( $i = 0; $i < count( $expected['claims'] ); $i++ ){ + $this->assertArrayHasKey( $i, $data['id'] ); + $this->assertGreaterThanOrEqual( 39 , strlen( $data['id'][$i] ) ); + } + //unset stuff we dont actually want to compare + unset( $data['id'] ); + unset( $data['hash'] ); + unset( $data['qualifiers-order'] ); + $this->assertArrayEquals( $exp, $data, false, true ); + } + } } /** -- To view, visit https://gerrit.wikimedia.org/r/81671 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: Id8586795c34eed24f899858b48dec7f299636db9 Gerrit-PatchSet: 23 Gerrit-Project: mediawiki/extensions/Wikibase Gerrit-Branch: master Gerrit-Owner: Addshore <addshorew...@gmail.com> Gerrit-Reviewer: Addshore <addshorew...@gmail.com> Gerrit-Reviewer: Aude <aude.w...@gmail.com> Gerrit-Reviewer: Daniel Kinzler <daniel.kinz...@wikimedia.de> Gerrit-Reviewer: Hoo man <h...@online.de> Gerrit-Reviewer: Jeroen De Dauw <jeroended...@gmail.com> Gerrit-Reviewer: Legoktm <legoktm.wikipe...@gmail.com> Gerrit-Reviewer: Tobias Gritschacher <tobias.gritschac...@wikimedia.de> Gerrit-Reviewer: Umherirrender <umherirrender_de...@web.de> Gerrit-Reviewer: jenkins-bot _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits