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