Allow sorting collections dynamically; sort desc by default

This commit is contained in:
Jeremy Dormitzer 2019-03-30 11:27:03 -04:00
parent 5f985c36c0
commit b608e08661
3 changed files with 277 additions and 54 deletions

View File

@ -87,9 +87,18 @@ class CollectionsService
ActivityPubObject $collection,
Closure $filterFunc )
{
$sort = 'desc';
if ( $request->query->has( 'sort' ) && $request->query->get( 'sort' ) == 'asc' ) {
$sort = 'asc';
}
if ( $request->query->has( 'offset' ) ) {
return $this->getCollectionPage(
$collection, $request, intval( $request->query->get( 'offset' ) ), $this->pageSize, $filterFunc
$collection,
$request,
intval( $request->query->get( 'offset' ) ),
$this->pageSize,
$filterFunc,
$sort
);
}
$colArr = array();
@ -103,7 +112,7 @@ class CollectionsService
}
}
$firstPage = $this->getCollectionPage(
$collection, $request, 0, $this->pageSize, $filterFunc
$collection, $request, 0, $this->pageSize, $filterFunc, $sort
);
$colArr['first'] = $firstPage;
return $colArr;
@ -113,8 +122,10 @@ class CollectionsService
Request $request,
$offset,
$pageSize,
Closure $filterFunc )
Closure $filterFunc,
$sort )
{
$asc = $sort == 'asc';
$itemsKey = 'items';
$pageType = 'CollectionPage';
$isOrdered = $this->isOrdered( $collection );
@ -129,7 +140,11 @@ class CollectionsService
}
$collectionItems = $collection->getFieldValue( $itemsKey );
$pageItems = array();
$idx = $offset;
if ( $asc ) {
$idx = $offset;
} else {
$idx = $this->getCollectionSize( $collection ) - $offset - 1;
}
$count = 0;
while ( $count < $pageSize ) {
$item = $collectionItems->getFieldValue( $idx );
@ -143,22 +158,29 @@ class CollectionsService
$pageItems[] = $item->asArray( 1 );
$count++;
}
$idx++;
if ( $asc ) {
$idx++;
} else {
$idx--;
}
}
if ( $count === 0 ) {
throw new NotFoundHttpException();
}
$page = array(
'@context' => $this->contextProvider->getContext(),
'id' => $collection['id'] . "?offset=$offset",
'id' => $collection['id'] . "?offset=$offset&sort=$sort",
'type' => $pageType,
$itemsKey => $pageItems,
'partOf' => $collection['id'],
);
// TODO set 'first' and 'last' on the page
$nextIdx = $this->hasNextItem( $request, $collectionItems, $idx );
if ( $nextIdx ) {
$page['next'] = $collection['id'] . "?offset=$nextIdx";
$nextIdx = $this->hasNextItem( $request, $collectionItems, $idx, $sort );
if ( is_numeric( $nextIdx ) ) {
if ( ! $asc ) {
$nextIdx = $this->getCollectionSize( $collection ) - $nextIdx - 1;
}
$page['next'] = $collection['id'] . "?offset=$nextIdx&sort=$sort";
}
if ( $isOrdered ) {
$page['startIndex'] = $offset;
@ -179,15 +201,20 @@ class CollectionsService
}
}
private function hasNextItem( Request $request, ActivityPubObject $collectionItems, $idx )
private function hasNextItem( Request $request, ActivityPubObject $collectionItems, $idx, $sort )
{
$asc = $sort == 'asc';
$next = $collectionItems->getFieldValue( $idx );
while ( $next ) {
if ( is_string( $next ) ||
$this->authService->isAuthorized( $request, $next ) ) {
return $idx;
}
$idx++;
if ( $asc ) {
$idx++;
} else {
$idx--;
}
$next = $collectionItems->getFieldValue( $idx );
}
return false;
@ -383,5 +410,31 @@ class CollectionsService
$this->entityManager->persist( $collection );
$this->entityManager->flush();
}
public function getCollectionSize( ActivityPubObject &$collection )
{
if ( $collection->hasField( 'totalItems' ) && is_numeric( $collection['totalItems'] ) ) {
return intval( $collection['totalItems'] );
} else {
$itemsField = 'items';
if ( $collection->hasField( 'type' ) && $collection['type'] == 'OrderedCollection' ) {
$itemsField = 'orderedItems';
}
if ( ! ( $collection->hasField( $itemsField ) && $collection[$itemsField] instanceof ActivityPubObject ) ) {
return 0;
}
$items = $collection[$itemsField];
$count = 0;
$idx = 0;
$currentItem = $items[$idx];
while ( $currentItem ) {
$count++;
$idx++;
$currentItem = $items[$idx];
}
$collection = $this->objectsService->update( $collection['id'], array( 'totalItems' => strval( $count ) ) );
return $count;
}
}
}

View File

@ -42,6 +42,14 @@ class GetControllerTest extends APTestCase
return null;
} )
);
$objectsService->method( 'update' )->will(
$this->returnCallback( function ( $uri ) {
if ( array_key_exists( $uri, $this->objects ) ) {
return $this->objects[$uri];
}
return null;
} )
);
$authService = new AuthService();
$contextProvider = new ContextProvider();
$httpClient = $this->getMock( Client::class );
@ -235,7 +243,7 @@ class GetControllerTest extends APTestCase
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
),
'id' => 'https://example.com/actors/1/inbox?offset=0',
'id' => 'https://example.com/actors/1/inbox?offset=0&sort=desc',
'type' => 'OrderedCollectionPage',
'orderedItems' => array(
array(

View File

@ -95,19 +95,19 @@ class CollectionsServiceTest extends APTestCase
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
),
'id' => 'https://example.com/objects/1?offset=0',
'id' => 'https://example.com/objects/1?offset=0&sort=desc',
'type' => 'OrderedCollectionPage',
'partOf' => 'https://example.com/objects/1',
'startIndex' => 0,
'orderedItems' => array(
array(
'id' => 'https://example.com/objects/2',
'id' => 'https://example.com/objects/4',
),
array(
'id' => 'https://example.com/objects/3',
),
array(
'id' => 'https://example.com/objects/4',
'id' => 'https://example.com/objects/2',
),
),
),
@ -156,23 +156,23 @@ class CollectionsServiceTest extends APTestCase
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
),
'id' => 'https://example.com/objects/1?offset=0',
'id' => 'https://example.com/objects/1?offset=0&sort=desc',
'type' => 'OrderedCollectionPage',
'partOf' => 'https://example.com/objects/1',
'startIndex' => 0,
'next' => 'https://example.com/objects/1?offset=4',
'next' => 'https://example.com/objects/1?offset=4&sort=desc',
'orderedItems' => array(
array(
'id' => 'https://example.com/objects/2',
'id' => 'https://example.com/objects/6',
),
array(
'id' => 'https://example.com/objects/3',
'id' => 'https://example.com/objects/5',
),
array(
'id' => 'https://example.com/objects/4',
),
array(
'id' => 'https://example.com/objects/5',
'id' => 'https://example.com/objects/3',
),
),
),
@ -214,46 +214,19 @@ class CollectionsServiceTest extends APTestCase
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
),
'id' => 'https://example.com/objects/1?offset=3',
'id' => 'https://example.com/objects/1?offset=3&sort=desc',
'type' => 'OrderedCollectionPage',
'partOf' => 'https://example.com/objects/1',
'startIndex' => 3,
'orderedItems' => array(
array(
'id' => 'https://example.com/objects/5',
),
array(
'id' => 'https://example.com/objects/6',
),
),
),
),
array(
'id' => 'nonExistentPage',
'collection' => array(
'@context' => array(
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
),
'id' => 'https://example.com/objects/1',
'type' => 'OrderedCollection',
'orderedItems' => array(
array(
'id' => 'https://example.com/objects/2',
),
array(
'id' => 'https://example.com/objects/3',
),
array(
'id' => 'https://example.com/objects/4',
'id' => 'https://example.com/objects/2',
),
)
),
),
'request' => Request::create(
'https://example.com/objects/1?offset=3',
Request::METHOD_GET
),
'expectedException' => NotFoundHttpException::class,
),
array(
'id' => 'authFilteringPublic',
@ -301,20 +274,20 @@ class CollectionsServiceTest extends APTestCase
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
),
'id' => 'https://example.com/objects/1?offset=0',
'id' => 'https://example.com/objects/1?offset=0&sort=desc',
'type' => 'OrderedCollectionPage',
'partOf' => 'https://example.com/objects/1',
'startIndex' => 0,
'orderedItems' => array(
array(
'id' => 'https://example.com/objects/3',
'id' => 'https://example.com/objects/5',
),
array(
'id' => 'https://example.com/objects/4',
'to' => 'https://www.w3.org/ns/activitystreams#Public',
),
array(
'id' => 'https://example.com/objects/5',
'id' => 'https://example.com/objects/3',
),
),
),
@ -369,7 +342,144 @@ class CollectionsServiceTest extends APTestCase
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
),
'id' => 'https://example.com/objects/1?offset=0',
'id' => 'https://example.com/objects/1?offset=0&sort=desc',
'type' => 'OrderedCollectionPage',
'partOf' => 'https://example.com/objects/1',
'startIndex' => 0,
'orderedItems' => array(
array(
'id' => 'https://example.com/objects/6',
'to' => 'https://example.com/actors/2',
),
array(
'id' => 'https://example.com/objects/5',
),
array(
'id' => 'https://example.com/objects/4',
'to' => 'https://www.w3.org/ns/activitystreams#Public',
),
array(
'id' => 'https://example.com/objects/3',
),
),
),
),
),
array(
'id' => 'sortAsc',
'collection' => array(
'@context' => array(
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
),
'id' => 'https://example.com/objects/1',
'type' => 'OrderedCollection',
'orderedItems' => array(
array(
'id' => 'https://example.com/objects/2',
),
array(
'id' => 'https://example.com/objects/3',
),
array(
'id' => 'https://example.com/objects/4',
),
array(
'id' => 'https://example.com/objects/5',
),
array(
'id' => 'https://example.com/objects/6',
),
)
),
'request' => Request::create(
'https://example.com/objects/1?sort=asc',
Request::METHOD_GET
),
'expectedResult' => array(
'@context' => array(
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
),
'id' => 'https://example.com/objects/1',
'type' => 'OrderedCollection',
'first' => array(
'@context' => array(
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
),
'id' => 'https://example.com/objects/1?offset=0&sort=asc',
'type' => 'OrderedCollectionPage',
'partOf' => 'https://example.com/objects/1',
'startIndex' => 0,
'next' => 'https://example.com/objects/1?offset=4&sort=asc',
'orderedItems' => array(
array(
'id' => 'https://example.com/objects/2',
),
array(
'id' => 'https://example.com/objects/3',
),
array(
'id' => 'https://example.com/objects/4',
),
array(
'id' => 'https://example.com/objects/5',
),
),
),
),
),
array(
'id' => 'authFilteringSpecificActorSortAsc',
'collection' => array(
'@context' => array(
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
),
'id' => 'https://example.com/objects/1',
'type' => 'OrderedCollection',
'orderedItems' => array(
array(
'id' => 'https://example.com/objects/2',
'to' => 'https://example.com/actors/1',
),
array(
'id' => 'https://example.com/objects/3',
),
array(
'id' => 'https://example.com/objects/4',
'to' => 'https://www.w3.org/ns/activitystreams#Public',
),
array(
'id' => 'https://example.com/objects/5',
),
array(
'id' => 'https://example.com/objects/6',
'to' => 'https://example.com/actors/2',
),
)
),
'request' => Request::create(
'https://example.com/objects/1?sort=asc',
Request::METHOD_GET
),
'requestAttributes' => array(
'actor' => 'https://example.com/actors/2',
),
'expectedResult' => array(
'@context' => array(
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
),
'id' => 'https://example.com/objects/1',
'type' => 'OrderedCollection',
'first' => array(
'@context' => array(
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
),
'id' => 'https://example.com/objects/1?offset=0&sort=asc',
'type' => 'OrderedCollectionPage',
'partOf' => 'https://example.com/objects/1',
'startIndex' => 0,
@ -392,8 +502,60 @@ class CollectionsServiceTest extends APTestCase
),
),
),
array(
'id' => 'nonExistentPage',
'collection' => array(
'@context' => array(
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
),
'id' => 'https://example.com/objects/1',
'type' => 'OrderedCollection',
'orderedItems' => array(
array(
'id' => 'https://example.com/objects/2',
),
array(
'id' => 'https://example.com/objects/3',
),
array(
'id' => 'https://example.com/objects/4',
),
)
),
'request' => Request::create(
'https://example.com/objects/1?offset=3',
Request::METHOD_GET
),
'expectedException' => NotFoundHttpException::class,
),
);
foreach ( $testCases as $testCase ) {
$this->authService = new AuthService();
$contextProvider = new ContextProvider();
$httpClient = $this->getMock( Client::class );
$httpClient->method( 'send' )->willReturn(
new Psr7Response( 200, array(), json_encode( array(
'type' => 'OrderedCollectionPage',
'orderedItems' => array(
'item3',
'item4',
),
) ) )
);
$entityManager = $this->getMock( EntityManager::class );
$collection = $testCase['collection'];
$objectsService = $this->getMock( ObjectsService::class );
$objectsService->method( 'update' )->willReturn( TestActivityPubObject::fromArray( $collection ) );
$this->collectionsService = new CollectionsService(
4,
$this->authService,
$contextProvider,
$httpClient,
new SimpleDateTimeProvider(),
$entityManager,
$objectsService
);
if ( array_key_exists( 'expectedException', $testCase ) ) {
$this->setExpectedException( $testCase['expectedException'] );
}