Allow sorting collections dynamically; sort desc by default
This commit is contained in:
parent
5f985c36c0
commit
b608e08661
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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'] );
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user