diff --git a/src/Entities/Field.php b/src/Entities/Field.php index 00f221f..997514d 100644 --- a/src/Entities/Field.php +++ b/src/Entities/Field.php @@ -96,7 +96,7 @@ class Field } $field = new Field( $time ); $field->setObject( $object, $time ); - $field->setName( $name ); + $field->setName( $name, $time ); $field->setValue( $value, $time ); return $field; } @@ -121,7 +121,7 @@ class Field } $field = new Field( $time ); $field->setObject( $object, $time ); - $field->setName( $name ); + $field->setName( $name, $time ); $field->setTargetObject( $targetObject, $time ); return $field; } @@ -218,9 +218,13 @@ class Field return $this->name; } - protected function setName( $name ) + public function setName( $name, DateTime $time = null ) { + if ( ! $time ) { + $time = new DateTime( "now" ); + } $this->name = $name; + $this->setLastUpdated( $time ); } /** diff --git a/src/Objects/CollectionsService.php b/src/Objects/CollectionsService.php index 4895eaf..285ff3d 100644 --- a/src/Objects/CollectionsService.php +++ b/src/Objects/CollectionsService.php @@ -262,6 +262,9 @@ class CollectionsService */ public function addItem( ActivityPubObject $collection, $item ) { + if ( ! in_array( $collection['type'], array( 'Collection', 'OrderedCollection') ) ) { + return; + } if ( $collection['type'] === 'Collection' ) { $itemsFieldName = 'items'; } else if ( $collection['type'] === 'OrderedCollection' ) { @@ -323,5 +326,56 @@ class CollectionsService $this->entityManager->persist( $collection ); $this->entityManager->flush(); } + + /** + * Remove the item with id $itemId from $collection, if such an item is present in the collection + * + * This is O(n) with the size of the collection. + * + * @param ActivityPubObject $collection + * @param string $itemId + */ + public function removeItem( ActivityPubObject $collection, $itemId ) + { + if ( ! in_array( $collection['type'], array( 'Collection', 'OrderedCollection' ) ) ) { + return; + } + if ( $collection['type'] === 'Collection' ) { + $itemsFieldName = 'items'; + } else if ( $collection['type'] === 'OrderedCollection' ) { + $itemsFieldName = 'orderedItems'; + } + if ( ! $collection->hasField( $itemsFieldName ) ) { + return; + } + $itemsObj = $collection[$itemsFieldName]; + foreach ( $itemsObj->getFields() as $arrayField ) { + if ( $arrayField->hasTargetObject() && + $arrayField->getTargetObject()->getFieldValue( 'id' ) === $itemId ) { + $foundItemField = $arrayField; + $foundItemIndex = intval( $arrayField->getName() ); + break; + } + } + if ( ! isset( $foundItemField ) ) { + return; + } + $itemsObj->removeField( + $foundItemField, $this->dateTimeProvider->getTime( 'collections-service.remove' ) + ); + $this->entityManager->persist( $itemsObj ); + $this->entityManager->remove( $foundItemField ); + while ( $itemsObj->hasField( $foundItemIndex + 1) ) { + $nextItemField = $itemsObj->getField( $foundItemIndex + 1 ); + $nextItemField->setName( + $foundItemIndex, $this->dateTimeProvider->getTime( 'collections-service.remove' ) + ); + $this->entityManager->persist( $nextItemField ); + $foundItemIndex = $foundItemIndex + 1; + } + $collection->setLastUpdated( $this->dateTimeProvider->getTime( 'collections-service.remove' ) ); + $this->entityManager->persist( $collection ); + $this->entityManager->flush(); + } } diff --git a/test/Objects/CollectionsServiceDbTest.php b/test/Objects/CollectionsServiceDbTest.php index 2454376..f30edc4 100644 --- a/test/Objects/CollectionsServiceDbTest.php +++ b/test/Objects/CollectionsServiceDbTest.php @@ -63,6 +63,7 @@ class CollectionsServiceDbTest extends SQLiteTestCase 'objects-service.create' => new DateTime( "12:00" ), 'objects-service.update' => new DateTime( "12:01" ), 'collections-service.add' => new DateTime( "12:03" ), + 'collections-service.remove' => new DateTime( "12:04" ), ) ); $this->httpClient = $this->getMock( Client::class ); $this->httpClient->method( 'send' ) @@ -419,6 +420,146 @@ class CollectionsServiceDbTest extends SQLiteTestCase } } + public function testRemoveItem() + { + $testCases = array( + array( + 'id' => 'basicRemoveTest', + 'collection' => array( + 'id' => 'https://example.com/collections/1', + 'type' => 'Collection', + 'items' => array( + array( 'id' => 'https://example.com/items/1' ), + array( 'id' => 'https://example.com/items/2' ), + array( 'id' => 'https://example.com/items/3' ), + ), + ), + 'itemIdToRemove' => 'https://example.com/items/2', + 'expectedDataSet' => array( + 'objects' => array( + array( + 'id' => 1, + 'created' => $this->getTime( 'objects-service.create' ), + 'lastUpdated' => $this->getTime( 'collections-service.remove' ), + ), + array( + 'id' => 2, + 'created' => $this->getTime( 'objects-service.create' ), + 'lastUpdated' => $this->getTime( 'collections-service.remove' ), + ), + array( + 'id' => 3, + 'created' => $this->getTime( 'objects-service.create' ), + 'lastUpdated' => $this->getTime( 'objects-service.create' ), + ), + array( + 'id' => 4, + 'created' => $this->getTime( 'objects-service.create' ), + 'lastUpdated' => $this->getTime( 'objects-service.create' ), + ), + array( + 'id' => 5, + 'created' => $this->getTime( 'objects-service.create' ), + 'lastUpdated' => $this->getTime( 'objects-service.create' ), + ), + ), + 'fields' => array( + array( + 'id' => 1, + 'object_id' => 1, + 'name' => 'id', + 'value' => 'https://example.com/collections/1', + 'created' => $this->getTime( 'objects-service.create' ), + 'lastUpdated' => $this->getTime( 'objects-service.create' ), + 'targetObject_id' => null, + ), + array( + 'id' => 2, + 'object_id' => 1, + 'name' => 'type', + 'value' => 'Collection', + 'created' => $this->getTime( 'objects-service.create' ), + 'lastUpdated' => $this->getTime( 'objects-service.create' ), + 'targetObject_id' => null, + ), + array( + 'id' => 3, + 'object_id' => 3, + 'name' => 'id', + 'value' => 'https://example.com/items/1', + 'created' => $this->getTime( 'objects-service.create' ), + 'lastUpdated' => $this->getTime( 'objects-service.create' ), + 'targetObject_id' => null, + ), + array( + 'id' => 4, + 'object_id' => 2, + 'name' => 0, + 'value' => null, + 'created' => $this->getTime( 'objects-service.create' ), + 'lastUpdated' => $this->getTime( 'objects-service.create' ), + 'targetObject_id' => 3, + ), + array( + 'id' => 5, + 'object_id' => 4, + 'name' => 'id', + 'value' => 'https://example.com/items/2', + 'created' => $this->getTime( 'objects-service.create' ), + 'lastUpdated' => $this->getTime( 'objects-service.create' ), + 'targetObject_id' => null, + ), + array( + 'id' => 7, + 'object_id' => 5, + 'name' => 'id', + 'value' => 'https://example.com/items/3', + 'created' => $this->getTime( 'objects-service.create' ), + 'lastUpdated' => $this->getTime( 'objects-service.create' ), + 'targetObject_id' => null, + ), + array( + 'id' => 8, + 'object_id' => 2, + 'name' => 1, + 'value' => null, + 'created' => $this->getTime( 'objects-service.create' ), + 'lastUpdated' => $this->getTime( 'collections-service.remove' ), + 'targetObject_id' => 5, + ), + array( + 'id' => 9, + 'object_id' => 1, + 'name' => 'items', + 'value' => null, + 'created' => $this->getTime( 'objects-service.create' ), + 'lastUpdated' => $this->getTime( 'objects-service.create' ), + 'targetObject_id' => 2, + ), + ), + ), + ), + ); + foreach ( $testCases as $testCase ) + { + $this->setUp(); + $collection = $this->objectsService->persist( $testCase['collection'] ); + $this->collectionsService->removeItem( $collection, $testCase['itemIdToRemove'] ); + $expectedDataSet = new ArrayDataSet( $testCase['expectedDataSet'] ); + $expectedObjects = $expectedDataSet->getTable( 'objects' ); + $expectedFields = $expectedDataSet->getTable( 'fields' ); + $actualObjects = $this->getConnection()->createQueryTable( + 'objects', 'SELECT * FROM objects' + ); + $actualFields = $this->getConnection()->createQueryTable( + 'fields', 'SELECT * FROM fields' + ); + $this->assertTablesEqual( $expectedObjects, $actualObjects, "Error on test $testCase[id]"); + $this->assertTablesEqual( $expectedFields, $actualFields, "Error on test $testCase[id]"); + $this->tearDown(); + } + } + /** * Returns the test dataset. *