diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbf1033..ea3d848 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - php_version: [8.0, 8.1, 8.2] + php_version: [8.1, 8.2] steps: - name: Checkout uses: actions/checkout@v2 @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - php_version: [8.0, 8.1, 8.2] + php_version: [8.1, 8.2] steps: - name: Checkout uses: actions/checkout@v2 diff --git a/README.md b/README.md index fa43e88..c88f558 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ If you would like to help, check out the [Contributing](#contributing) section b ## Requirements -This package requires PHP `8.0` or above. +This package requires PHP `8.1` or above. ## Installation diff --git a/composer.json b/composer.json index 3c8900d..3dc5864 100644 --- a/composer.json +++ b/composer.json @@ -11,8 +11,8 @@ ], "minimum-stability": "stable", "require": { - "php": ">=8.0", - "symfony/http-client": "^6.2" + "php": ">=8.1", + "symfony/http-client": "^6.2.7" }, "require-dev": { "phpunit/phpunit": "^10.0", diff --git a/src/Api/Module/CmsEndpoints.php b/src/Api/Module/CmsEndpoints.php index fad4b04..8bfecfa 100644 --- a/src/Api/Module/CmsEndpoints.php +++ b/src/Api/Module/CmsEndpoints.php @@ -48,11 +48,22 @@ public function getCollection(string $collectionId): Collection /** * Get all items for a collection * + * @param string $sortBy Defines the field by which to sort the results. + * Either a `CollectionItem::SORT_BY_` constant or the slug of a field from the collection. + * @param string $sortDirection Either `ASC` or `DESC`. * @return PaginatedList */ - public function listCollectionItems(string|Collection $collectionId): PaginatedList + public function listCollectionItems(string|Collection $collectionId, string $sortBy = CollectionItem::SORT_BY_CREATED_ON, string $sortDirection = 'ASC'): PaginatedList { - return $this->requestWithPagination(CollectionItem::class, "/collections/{$collectionId}/items"); + $sortPrefix = strtoupper($sortDirection) === 'DESC' ? '-' : ''; + + return $this->requestWithPagination( + CollectionItem::class, + "/collections/{$collectionId}/items", + [ + 'sort[]' => $sortPrefix . $sortBy, + ] + ); } /** diff --git a/src/Api/Module/MembershipEndpoints.php b/src/Api/Module/MembershipEndpoints.php index 0e15787..a817e13 100644 --- a/src/Api/Module/MembershipEndpoints.php +++ b/src/Api/Module/MembershipEndpoints.php @@ -21,11 +21,22 @@ trait MembershipEndpoints /** * Get a list of users for a site * + * @param string $sortBy Defines the field by which to sort the results. + * Must be a `User::SORT_BY_` constant. + * @param string $sortDirection Either `ASC` or `DESC`. * @return PaginatedList */ - public function listUsers(string|Site $siteId): PaginatedList + public function listUsers(string|Site $siteId, string $sortBy = User::SORT_BY_CREATED_ON, string $sortDirection = 'ASC'): PaginatedList { - return $this->requestWithPagination(User::class, "/sites/{$siteId}/users"); + $sortPrefix = strtoupper($sortDirection) === 'DESC' ? '-' : ''; + + return $this->requestWithPagination( + User::class, + "/sites/{$siteId}/users", + [ + 'sort' => $sortPrefix . $sortBy, + ] + ); } /** @@ -104,6 +115,8 @@ public function inviteUser(string|Site $siteId, string $email, array $accessGrou */ public function listAccessGroups(string|Site $siteId): PaginatedList { - return $this->requestWithPagination(AccessGroup::class, "/sites/{$siteId}/accessgroups"); + return $this->requestWithPagination(AccessGroup::class, "/sites/{$siteId}/accessgroups", [ + 'sort' => 'CreatedOn', + ]); } } diff --git a/src/Api/SiteClient.php b/src/Api/SiteClient.php index 8523899..9abc7bf 100644 --- a/src/Api/SiteClient.php +++ b/src/Api/SiteClient.php @@ -158,11 +158,14 @@ public function getCollection(string $collectionId): Collection /** * Get all items for a collection * + * @param string $sortBy Defines the field by which to sort the results. + * Either a `CollectionItem::SORT_BY_` constant or the slug of a field from the collection. + * @param string $sortDirection Either `ASC` or `DESC`. * @return PaginatedList */ - public function listCollectionItems(Collection|string $collectionId): PaginatedList + public function listCollectionItems(Collection|string $collectionId, string $sortBy = 'created-on', string $sortDirection = 'ASC'): PaginatedList { - return $this->client->listCollectionItems($collectionId); + return $this->client->listCollectionItems($collectionId, $sortBy, $sortDirection); } /** @@ -228,11 +231,14 @@ public function publishCollectionItems(Collection|string $collectionId, array $i /** * Get a list of users for a site * + * @param string $sortBy Defines the field by which to sort the results. + * Must be a `User::SORT_BY_` constant. + * @param string $sortDirection Either `ASC` or `DESC`. * @return PaginatedList */ - public function listUsers(): PaginatedList + public function listUsers(string $sortBy = 'CreatedOn', string $sortDirection = 'ASC'): PaginatedList { - return $this->client->listUsers($this->siteId); + return $this->client->listUsers($this->siteId, $sortBy, $sortDirection); } /** diff --git a/src/Model/Cms/CollectionItem.php b/src/Model/Cms/CollectionItem.php index b95d129..3db75aa 100644 --- a/src/Model/Cms/CollectionItem.php +++ b/src/Model/Cms/CollectionItem.php @@ -14,6 +14,14 @@ */ class CollectionItem extends AbstractWebflowModel { + public const SORT_BY_CREATED_ON = 'created-on'; + + public const SORT_BY_UPDATED_ON = 'updated-on'; + + public const SORT_BY_PUBLISHED_ON = 'published-on'; + + public const SORT_BY_SLUG = 'slug'; + /** * Date on which the item was created */ diff --git a/src/Model/Membership/User.php b/src/Model/Membership/User.php index d390f22..017a453 100644 --- a/src/Model/Membership/User.php +++ b/src/Model/Membership/User.php @@ -13,6 +13,16 @@ */ class User extends AbstractWebflowModel { + public const SORT_BY_STATUS = 'Status'; + + public const SORT_BY_EMAIL = 'Email'; + + public const SORT_BY_CREATED_ON = 'CreatedOn'; + + public const SORT_BY_UPDATED_ON = 'UpdatedOn'; + + public const SORT_BY_LAST_LOGIN = 'LastLogin'; + public const STATUS_INVITED = 'invited'; public const STATUS_UNVERIFIED = 'unverified'; diff --git a/src/Util/PaginatedList.php b/src/Util/PaginatedList.php index dbc872a..acc4b01 100644 --- a/src/Util/PaginatedList.php +++ b/src/Util/PaginatedList.php @@ -26,6 +26,12 @@ class PaginatedList implements Iterator */ public const LIMIT = 100; + /** + * Total number of items in the list. + * This includes items that haven't been loaded yet. + */ + public readonly int $total; + private int $currentItemIndex = 0; /** @@ -35,11 +41,6 @@ class PaginatedList implements Iterator */ private array $pages = []; - /** - * Total number of pages in the list. - */ - private int $total = 0; - /** * The key that contains the list of data in raw responses. */ diff --git a/tests/Api/ClientTest.php b/tests/Api/ClientTest.php index 5e61d1a..c689de6 100644 --- a/tests/Api/ClientTest.php +++ b/tests/Api/ClientTest.php @@ -22,9 +22,9 @@ protected function setUp(): void // Mock the API client's inner HttpClient $mockResponses = include(__DIR__ . '/../sample_data.php'); - $mockResponses['/sites/paginationtest/accessgroups'] = $this->generateRandomAccessGroupDataForPaginationTest(0, 205); - $mockResponses['/sites/paginationtest/accessgroups?offset=100'] = $this->generateRandomAccessGroupDataForPaginationTest(100, 205); - $mockResponses['/sites/paginationtest/accessgroups?offset=200'] = $this->generateRandomAccessGroupDataForPaginationTest(200, 205); + $mockResponses['/sites/paginationtest/accessgroups?sort=CreatedOn'] = $this->generateRandomAccessGroupDataForPaginationTest(0, 205); + $mockResponses['/sites/paginationtest/accessgroups?sort=CreatedOn&offset=100'] = $this->generateRandomAccessGroupDataForPaginationTest(100, 205); + $mockResponses['/sites/paginationtest/accessgroups?sort=CreatedOn&offset=200'] = $this->generateRandomAccessGroupDataForPaginationTest(200, 205); $responseFactory = function (string $method, string $url) use ($mockResponses) { $url = str_replace('https://api.webflow.com', '', $url); return new MockResponse(json_encode($mockResponses[$url][$method])); diff --git a/tests/real_use_ci_test.php b/tests/real_use_ci_test.php index 2918238..b987996 100644 --- a/tests/real_use_ci_test.php +++ b/tests/real_use_ci_test.php @@ -21,6 +21,13 @@ $collections = $client->listCollections(); $testCollection = $client->getCollection($collections[0]->getId()); +// Loop over items to test paginated list +$items = $client->listCollectionItems($testCollection); +foreach ($items as $i => $item) { + $item->name = 'Test item #' . ($i + 1); + $client->updateCollectionItem($testCollection, $item, false); +} + // Create a test item $newItem = new CollectionItem( name: 'Test item', @@ -35,15 +42,9 @@ ] ); $createdItem = $client->createCollectionItem($testCollection, $newItem, true); -$createdItem->name .= ' :)'; +$createdItem->name .= ' #' . ($items->total + 1); $client->updateCollectionItem($testCollection, $createdItem, true); -// Loop over items to test paginated list -$items = $client->listCollectionItems($testCollection); -foreach ($items as $item) { - // Nothing to do -} - // Create a duplicate, then delete it $deletionTestItem = new CollectionItem( name: 'Deletion Test Item', diff --git a/tests/sample_data.php b/tests/sample_data.php index d55a14c..3b3ffab 100644 --- a/tests/sample_data.php +++ b/tests/sample_data.php @@ -382,6 +382,39 @@ ], ], + '/collections/580e63fc8c9a982ac9b8b745/items?sort[]=created-on' => [ + 'GET' => [ + 'items' => [ + [ + '_archived' => false, + '_draft' => false, + 'color' => '#a98080', + 'name' => 'Exciting blog post title', + 'post-body' => '

Blog post contents...

', + 'post-summary' => 'Summary of exciting blog post', + 'main-image' => [ + 'fileId' => '580e63fe8c9a982ac9b8b749', + 'url' => 'https://d1otoma47x30pg.cloudfront.net/580e63fc8c9a982ac9b8b744/580e63fe8c9a982ac9b8b749_1477338110257-image20.jpg', + ], + 'slug' => 'exciting-post', + 'author' => '580e640c8c9a982ac9b8b778', + 'updated-on' => '2016-11-15T22:45:32.647Z', + 'updated-by' => 'Person_5660c5338e9d3b0bee3b86aa', + 'created-on' => '2016-11-15T22:45:32.647Z', + 'created-by' => 'Person_5660c5338e9d3b0bee3b86aa', + 'published-on' => null, + 'published-by' => null, + '_cid' => '580e63fc8c9a982ac9b8b745', + '_id' => '580e64008c9a982ac9b8b754', + ], + ], + 'count' => 1, + 'limit' => 1, + 'offset' => 0, + 'total' => 1, + ], + ], + '/collections/580e63fc8c9a982ac9b8b745/items' => [ 'GET' => [ 'items' => [ @@ -2337,7 +2370,7 @@ ], ], - '/sites/580e63e98c9a982ac9b8b741/users' => [ + '/sites/580e63e98c9a982ac9b8b741/users?sort=CreatedOn' => [ 'GET' => [ 'users' => [ [ @@ -2466,25 +2499,7 @@ ], ], - '/sites/580e63e98c9a982ac9b8b741/accessgroups' => [ - 'GET' => [ - 'accessGroups' => [ - [ - '_id' => '62be58d404be8a6cc900c081', - 'name' => 'Webflowers', - 'shortId' => 'jo', - 'slug' => 'webflowers', - 'createdOn' => '2022-08-01T19:41:48.349Z', - ], - ], - 'count' => 1, - 'limit' => 10, - 'offset' => 0, - 'total' => 1, - ], - ], - - '/sites/paginationtest/accessgroups' => [ + '/sites/580e63e98c9a982ac9b8b741/accessgroups?sort=CreatedOn' => [ 'GET' => [ 'accessGroups' => [ [