WMDE-leszek has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/389523 )

Change subject: [DNM] Experiment: handle editing a statement on a Form of the 
Lexeme
......................................................................

[DNM] Experiment: handle editing a statement on a Form of the Lexeme

Note: code is not ready for proper review. It is meant to serve
for demonstration/experiment purposes only

Bug: T179662
Bug: T179661

Depends-On: Icb96d76c6fe2420a77eeeddfb3e9c0e9dcbf3cf0
Change-Id: If334ef2ec12570a872ebacbf833541e60e88d023
---
M WikibaseLexeme.entitytypes.php
A src/Api/FormSetClaimRequestParser.php
A src/ChangeOp/ChangeOpFormStatement.php
A tests/phpunit/mediawiki/Api/SetClaimTest.php
4 files changed, 459 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/WikibaseLexeme 
refs/changes/23/389523/1

diff --git a/WikibaseLexeme.entitytypes.php b/WikibaseLexeme.entitytypes.php
index d830937..6e5c390 100644
--- a/WikibaseLexeme.entitytypes.php
+++ b/WikibaseLexeme.entitytypes.php
@@ -18,6 +18,7 @@
 use Wikibase\DataModel\Services\EntityId\EntityIdFormatter;
 use Wikibase\DataModel\Services\Lookup\LabelDescriptionLookup;
 use Wikibase\LanguageFallbackChain;
+use Wikibase\Lexeme\Api\FormSetClaimRequestParser;
 use Wikibase\Lexeme\ChangeOp\Deserialization\LanguageChangeOpDeserializer;
 use Wikibase\Lexeme\ChangeOp\Deserialization\LemmaChangeOpDeserializer;
 use Wikibase\Lexeme\ChangeOp\Deserialization\LexemeChangeOpDeserializer;
@@ -42,6 +43,7 @@
 use Wikibase\Repo\Diff\BasicEntityDiffVisualizer;
 use Wikibase\Repo\Diff\ClaimDiffer;
 use Wikibase\Repo\Diff\ClaimDifferenceVisualizer;
+use Wikibase\Repo\Validators\SnakValidator;
 use Wikibase\Repo\WikibaseRepo;
 use Wikibase\View\EditSectionGenerator;
 use Wikibase\View\EntityTermsView;
@@ -186,6 +188,22 @@
                                $messageLocalizer,
                                $basicEntityDiffVisualizer
                        );
+               },
+               'set-claim-api-request-parser' => function() {
+                       $wikibaseRepo = WikibaseRepo::getDefaultInstance();
+
+                       $snakValidator = new SnakValidator(
+                               $wikibaseRepo->getPropertyDataTypeLookup(),
+                               $wikibaseRepo->getDataTypeFactory(),
+                               $wikibaseRepo->getDataTypeValidatorFactory()
+                       );
+                       return [
+                               new FormSetClaimRequestParser(
+                                       
$wikibaseRepo->getExternalFormatStatementDeserializer(),
+                                       $snakValidator
+                               ),
+                       ];
                }
+
        ]
 ];
diff --git a/src/Api/FormSetClaimRequestParser.php 
b/src/Api/FormSetClaimRequestParser.php
new file mode 100644
index 0000000..bd5a58d
--- /dev/null
+++ b/src/Api/FormSetClaimRequestParser.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Wikibase\Lexeme\Api;
+
+use DataValues\IllegalValueException;
+use ReverseRegex\Exception;
+use Wikibase\DataModel\Deserializers\StatementDeserializer;
+use Wikibase\DataModel\Statement\Statement;
+use Wikibase\DataModel\Statement\StatementGuid;
+use Wikibase\Lexeme\ChangeOp\ChangeOpFormStatement;
+use Wikibase\Lexeme\DataModel\FormId;
+use Wikibase\Lexeme\DataModel\LexemeId;
+use Wikibase\Repo\Api\DispatchableSetClaimRequestParser;
+use Wikibase\Repo\Api\SetClaimRequest;
+use Wikibase\Repo\Validators\SnakValidator;
+
+/**
+ * TEST
+ */
+class FormSetClaimRequestParser implements DispatchableSetClaimRequestParser {
+
+       /**
+        * @var StatementDeserializer
+        */
+       private $statementDeserializer;
+
+       /**
+        * @var SnakValidator
+        */
+       private $snakValidator;
+
+       public function __construct(
+               StatementDeserializer $statementDeserializer,
+               SnakValidator $snakValidator
+       ) {
+               $this->statementDeserializer = $statementDeserializer;
+               $this->snakValidator = $snakValidator;
+       }
+
+       public function parse( array $params ) {
+               $statement = $this->getStatementFromParams( $params );
+
+               $guid = $statement->getGuid();
+
+               list ( $lexemeId, $formId ) = $this->extractLexemeAndFormIds( 
$guid );
+
+               $index = isset( $params['index'] ) ? $params['index'] : null;
+
+               $changeOp = new ChangeOpFormStatement( $statement, $formId, 
$this->snakValidator, $index );
+
+               return new SetClaimRequest( $lexemeId, $statement, $changeOp );
+       }
+
+       public function canParse( array $params ) {
+               if ( !isset( $params['claim'] ) ) {
+                       return false;
+               }
+
+               $claimData = json_decode( $params['claim'], true );
+
+               if ( !$claimData ) {
+                       return false;
+               }
+
+               return isset( $claimData['id'] ) && preg_match( '/^L\d+-F\d+/', 
$claimData['id'] );
+       }
+
+       /**
+        * @param array $params
+        *
+        * @throws IllegalValueException
+        * @return Statement
+        */
+       private function getStatementFromParams( array $params ) {
+               try {
+                       $serializedStatement = json_decode( $params['claim'], 
true );
+                       if ( !is_array( $serializedStatement ) ) {
+                               throw new IllegalValueException( 'Failed to get 
statement from Serialization' );
+                       }
+                       $statement = $this->statementDeserializer->deserialize( 
$serializedStatement );
+                       if ( !( $statement instanceof Statement ) ) {
+                               throw new IllegalValueException( 'Failed to get 
statement from Serialization' );
+                       }
+                       return $statement;
+               } catch ( \InvalidArgumentException $invalidArgumentException ) 
{
+                       // TODO: report errors properly
+                       throw new \Exception(
+                               'Failed to get claim from claim Serialization ' 
. $invalidArgumentException->getMessage()
+                       );
+               }
+       }
+
+       private function extractLexemeAndFormIds( $guid ) {
+               $keyParts = explode( StatementGuid::SEPARATOR, $guid, 2 );
+
+               $key = $keyParts[0];
+
+               $ids = explode( '-', $key );
+
+               if ( count( $ids ) !== 2 ) {
+                       throw new Exception( 'Invalid statement GUID' ); // 
TODO: report errors properly
+               }
+
+               return [ new LexemeId( $ids[0] ), new FormId( $ids[1] ) ];
+       }
+
+}
diff --git a/src/ChangeOp/ChangeOpFormStatement.php 
b/src/ChangeOp/ChangeOpFormStatement.php
new file mode 100644
index 0000000..a54b75c
--- /dev/null
+++ b/src/ChangeOp/ChangeOpFormStatement.php
@@ -0,0 +1,192 @@
+<?php
+
+namespace Wikibase\Lexeme\ChangeOp;
+
+use ValueValidators\Result;
+use Wikibase\DataModel\ByPropertyIdArray;
+use Wikibase\DataModel\Entity\EntityDocument;
+use Wikibase\DataModel\Statement\Statement;
+use Wikibase\DataModel\Statement\StatementList;
+use Wikibase\Lexeme\DataModel\FormId;
+use Wikibase\Lexeme\DataModel\Lexeme;
+use Wikibase\Repo\ChangeOp\ChangeOpBase;
+use Wikibase\Repo\ChangeOp\ChangeOpException;
+use Wikibase\Repo\Validators\SnakValidator;
+use Wikibase\Summary;
+
+/**
+ * TEST
+ */
+class ChangeOpFormStatement extends ChangeOpBase {
+
+       /**
+        * @var Statement
+        */
+       private $statement;
+
+       /**
+        * @var FormId
+        */
+       private $formId;
+
+       /**
+        * @var SnakValidator
+        */
+       private $snakValidator;
+
+       /**
+        * @var int|null
+        */
+       private $index;
+
+       /**
+        * @param Statement $statement
+        * @param FormId $formId
+        * @param SnakValidator $snakValidator
+        * @param int|null $index Where the statement should be placed among 
the other statements.
+        *
+        * @throws \InvalidArgumentException
+        */
+       public function __construct(
+               Statement $statement,
+               FormId $formId,
+               SnakValidator $snakValidator,
+               $index = null
+       ) {
+               if ( $index !== null && ( !is_int( $index ) || $index < 0 ) ) {
+                       throw new \InvalidArgumentException( '$index must be an 
non-negative integer or null' );
+               }
+
+               $this->statement = $statement;
+               $this->formId = $formId;
+               $this->snakValidator = $snakValidator;
+               $this->index = $index;
+       }
+
+       /**
+        * @see ChangeOp::apply
+        *
+        * @param Lexeme $lexeme
+        * @param Summary|null $summary
+        *
+        * @throws \InvalidArgumentException
+        * @throws ChangeOpException
+        */
+       public function apply( EntityDocument $lexeme, Summary $summary = null 
) {
+               if ( !( $lexeme instanceof Lexeme ) ) {
+                       throw new \InvalidArgumentException( '$lexeme must be a 
Lexeme object' );
+               }
+
+               if ( $this->statement->getGuid() === null ) {
+                       // TODO: generate proper GUID
+               }
+
+               // TODO: validate GUID
+
+               // TODO: error when no form with given ID on $lexeme!
+               $form = $lexeme->getForm( $this->formId );
+
+               $entityStatements = $form->getStatements();
+               $oldIndex = $this->removeStatement( $entityStatements );
+
+               if ( $this->index !== null ) {
+                       $statements = $this->addStatementToGroup( 
$entityStatements, $this->index );
+                       $entityStatements->clear();
+                       foreach ( $statements as $statement ) {
+                               $entityStatements->addStatement( $statement );
+                       }
+               } else {
+                       $entityStatements->addStatement( $this->statement, 
$oldIndex );
+               }
+
+               $this->updateSummary( $summary, $oldIndex === null ? 'create' : 
'update' );
+       }
+
+       /**
+        * @param StatementList $statements
+        *
+        * @return int|null
+        */
+       private function removeStatement( StatementList $statements ) {
+               $guid = $this->statement->getGuid();
+               $oldIndex = null;
+               $oldStatement = null;
+
+               foreach ( $statements->toArray() as $index => $statement ) {
+                       if ( $statement->getGuid() === $guid ) {
+                               $oldIndex = $index;
+                               $oldStatement = $statement;
+                               $statements->removeStatementsWithGuid( $guid );
+                               break;
+                       }
+               }
+
+               if ( $oldStatement !== null ) {
+                       $this->checkMainSnakUpdate( $oldStatement );
+               }
+
+               return $oldIndex;
+       }
+
+       /**
+        * Checks that the update of the main snak is permissible.
+        *
+        * This checks that the main snaks of the old and the new statement
+        * refer to the same property.
+        *
+        * @param Statement $oldStatement
+        *
+        * @throws ChangeOpException If the main snak update is illegal.
+        */
+       private function checkMainSnakUpdate( Statement $oldStatement ) {
+               $newMainSnak = $this->statement->getMainSnak();
+               $oldPropertyId = $oldStatement->getPropertyId();
+
+               if ( !$oldPropertyId->equals( $newMainSnak->getPropertyId() ) ) 
{
+                       $guid = $this->statement->getGuid();
+                       throw new ChangeOpException( "Claim with GUID $guid 
uses property "
+                               . $oldPropertyId . ", can't change to "
+                               . $newMainSnak->getPropertyId() );
+               }
+       }
+
+       /**
+        * @param StatementList $statements
+        * @param int $index
+        *
+        * @return Statement[]
+        */
+       private function addStatementToGroup( StatementList $statements, $index 
) {
+               // If we fail with the user supplied index and the index is 
greater than or equal 0
+               // presume the user wants to have the index at the end of the 
list.
+               $indexedStatements = new ByPropertyIdArray( 
$statements->toArray() );
+               $indexedStatements->buildIndex();
+
+               try {
+                       $indexedStatements->addObjectAtIndex( $this->statement, 
$index );
+               } catch ( \OutOfBoundsException $ex ) {
+                       $statements->addStatement( $this->statement );
+                       return $statements->toArray();
+               }
+
+               return $indexedStatements->toFlatArray();
+       }
+
+       /**
+        * @see ChangeOp::validate
+        *
+        * @param Lexeme $lexeme
+        *
+        * @return Result
+        */
+       public function validate( EntityDocument $lexeme ) {
+               if ( !( $lexeme instanceof Lexeme ) ) {
+                       throw new \InvalidArgumentException( '$lexeme must be a 
Lexeme object' );
+               }
+
+               // TODO: some extra case-specific validation here?
+
+               return $this->snakValidator->validateClaimSnaks( 
$this->statement );
+       }
+
+}
diff --git a/tests/phpunit/mediawiki/Api/SetClaimTest.php 
b/tests/phpunit/mediawiki/Api/SetClaimTest.php
new file mode 100644
index 0000000..a896845
--- /dev/null
+++ b/tests/phpunit/mediawiki/Api/SetClaimTest.php
@@ -0,0 +1,142 @@
+<?php
+
+namespace Wikibase\Lexeme\Tests\MediaWiki\Api;
+
+use User;
+use Wikibase\DataModel\Entity\Property;
+use Wikibase\DataModel\Entity\PropertyId;
+use Wikibase\DataModel\Services\Statement\V4GuidGenerator;
+use Wikibase\Lexeme\DataModel\Lexeme;
+use Wikibase\Lexeme\Tests\DataModel\NewForm;
+use Wikibase\Lexeme\Tests\DataModel\NewLexeme;
+use Wikibase\Repo\Tests\Api\WikibaseApiTestCase;
+use Wikibase\Repo\WikibaseRepo;
+
+/**
+ *
+ * @license GPL-2.0+
+ *
+ * @group Database
+ * @group medium
+ * @group WikibaseLexeme
+ */
+class SetClaimTest extends WikibaseApiTestCase {
+
+       const LEXEME_ID = 'L1200';
+
+       const PROPERTY_ID = 'P1';
+
+       public function testAddStatementToTheForm() {
+               $this->saveTestPropertyToDatabase();
+
+               $this->saveLexemeWithFormToDatabase();
+
+               $guidGenerator = new V4GuidGenerator();
+
+               $statementId = 'L1200-F1$' . $guidGenerator->newGuid();
+
+               list ( $result, ) = $this->doApiRequestWithToken( [
+                       'action' => 'wbsetclaim',
+                       'claim' => json_encode( [
+                               'id' => $statementId,
+                               'type' => 'claim',
+                               'mainsnak' => [
+                                       'snaktype' => 'value',
+                                       'property' => self::PROPERTY_ID,
+                                       'datavalue' => [
+                                               'type' => 'string',
+                                               'value' => 'FOO',
+                                       ]
+                               ],
+                       ] )
+               ] );
+
+               $this->assertEquals( 1, $result['success'] );
+               // TODO: assert response fields? Which ones?
+
+               $lexemeData = $this->loadEntity( self::LEXEME_ID );
+
+               $this->assertEmpty( $lexemeData['claims'] );
+
+               $formData = $lexemeData['forms'][0];
+
+               $this->assertCount( 1, $formData['claims'] );
+
+               $this->assertArrayHasKey( self::PROPERTY_ID, 
$formData['claims'] );
+
+               $statementData = $formData['claims'][self::PROPERTY_ID][0];
+
+               $this->assertEquals( $statementId, $statementData['id'] );
+               $this->assertEquals( self::PROPERTY_ID, 
$statementData['mainsnak']['property'] );
+               $this->assertEquals(
+                       [ 'type' => 'string', 'value' => 'FOO' ],
+                       $statementData['mainsnak']['datavalue']
+               );
+       }
+
+       public function testAddStatementToTheLexeme() {
+               $this->saveTestPropertyToDatabase();
+
+               $this->saveLexemeWithFormToDatabase();
+
+               $guidGenerator = new V4GuidGenerator();
+
+               $statementId = self::LEXEME_ID . '$' . 
$guidGenerator->newGuid();
+
+               list ( $result, ) = $this->doApiRequestWithToken( [
+                       'action' => 'wbsetclaim',
+                       'claim' => json_encode( [
+                               'id' => $statementId,
+                               'type' => 'claim',
+                               'mainsnak' => [
+                                       'snaktype' => 'value',
+                                       'property' => self::PROPERTY_ID,
+                                       'datavalue' => [
+                                               'type' => 'string',
+                                               'value' => 'FOO',
+                                       ]
+                               ],
+                       ] )
+               ] );
+
+               $this->assertEquals( 1, $result['success'] );
+               // TODO: assert response fields? Which ones?
+
+               $lexemeData = $this->loadEntity( self::LEXEME_ID );
+
+               $this->assertNotEmpty( $lexemeData['claims'] );
+
+               $formData = $lexemeData['forms'][0];
+
+               $this->assertEmpty( $formData['claims'] );
+
+               $this->assertArrayHasKey( self::PROPERTY_ID, 
$lexemeData['claims'] );
+
+               $statementData = $lexemeData['claims'][self::PROPERTY_ID][0];
+
+               $this->assertEquals( $statementId, $statementData['id'] );
+               $this->assertEquals( self::PROPERTY_ID, 
$statementData['mainsnak']['property'] );
+               $this->assertEquals(
+                       [ 'type' => 'string', 'value' => 'FOO' ],
+                       $statementData['mainsnak']['datavalue']
+               );
+       }
+
+       private function saveLexemeWithFormToDatabase() {
+               $lexeme = NewLexeme::havingForm( NewForm::havingId( 'F1' 
)->build() )
+                       ->withId( self::LEXEME_ID )->build();
+
+               $store = WikibaseRepo::getDefaultInstance()->getEntityStore();
+
+               $store->saveEntity( $lexeme, self::class, $this->getMock( 
User::class ) );
+       }
+
+       private function saveTestPropertyToDatabase() {
+               $property = new Property( new PropertyId( self::PROPERTY_ID ), 
null, 'string' );
+
+               $store = WikibaseRepo::getDefaultInstance()->getEntityStore();
+
+               $store->saveEntity( $property, self::class, $GLOBALS['wgUser'] 
);
+       }
+
+}

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: If334ef2ec12570a872ebacbf833541e60e88d023
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/WikibaseLexeme
Gerrit-Branch: master
Gerrit-Owner: WMDE-leszek <[email protected]>

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

Reply via email to