From b5f68cc05b0b4608c7482df5effd77a8f56cc41f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ewilan=20Rivi=C3=A8re?= Date: Thu, 14 Sep 2023 16:30:41 +0200 Subject: [PATCH 1/2] v1.0.2 - Update `OpdsJsonEngine` to use these options for OPDS 2.0 output - @todo OPDS 1.2 was not adapted in this PR by [@mikespub](https://github.com/mikespub) - Add `properties` option for `OpdsEntryNavigation` to include extra properties (like numberOfItems for facets) by [@mikespub](https://github.com/mikespub) - Add `relation` option for `OpdsEntryNavigation` to specify the relation to use (instead of `current`) by [@mikespub](https://github.com/mikespub) - Add `identifier` option for `OpdsEntryBook` to specify the actual identifier to use (instead of `urn:isbn:...`) by [@mikespub](https://github.com/mikespub) - Fix XML links `type` attribute - Add `paginationQuery` property to `OpdsConfig` to specify the query parameter for pagination (default: `page`) - Fix bug with paginator when using `page` query parameter --- CHANGELOG.md | 152 ++++++++++++++++------------------ composer.json | 2 +- src/Engine/OpdsEngine.php | 11 ++- src/Engine/OpdsJsonEngine.php | 20 ++--- src/Engine/OpdsPaginator.php | 13 +-- src/OpdsConfig.php | 13 +++ tests/Datasets.php | 2 + tests/OpdsConfigTest.php | 2 + tests/OpdsEntryTest.php | 11 +++ tests/OpdsPaginationTest.php | 19 +++++ tests/OpdsTest.php | 11 ++- tests/Pest.php | 3 + 12 files changed, 156 insertions(+), 103 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb0875c..efb0fbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,16 +2,9 @@ All notable changes to `php-opds` will be documented in this file. -## v1.x.x - 2023-09-13 - -- Update `OpdsJsonEngine` to use these options for OPDS 2.0 output - @todo OPDS 1.2 was not adapted in this PR -- Add `properties` option for `OpdsEntryNavigation` to include extra properties (like numberOfItems for facets) -- Add `relation` option for `OpdsEntryNavigation` to specify the relation to use (instead of `current`) -- Add `identifier` option for `OpdsEntryBook` to specify the actual identifier to use (instead of `urn:isbn:...`) - ## v1.0.1 - 2023-09-07 -- Add `useAutoPagination` option for `OpdsConfig` to enable/disable auto pagination (works only for `OpdsEntryBook`) +- Add `useAutoPagination` option for `OpdsConfig` to enable/disable auto pagination (works only for `OpdsEntryBook`) ## v1.0.0 - 2023-09-06 @@ -21,7 +14,7 @@ This version rework completely the library, it's not compatible with previous ve #### `Opds::class` -- static method `response()` removed, now static method is `Opds::make()` and `get()` is a arrow method: +- static method `response()` removed, now static method is `Opds::make()` and `get()` is a arrow method: ```php $opds = Opds::make() @@ -31,28 +24,28 @@ return $opds->send(); // `never` because send response ``` -- To add `entries`, you have to use `feeds()` arrow method - - - `feeds()` accept `OpdsEntryBook[]` or `OpdsEntryNavigation[]` but also `OpdsEntryNavigation` or `OpdsEntryBook` - -- To add `isSearch`, you have to use `isSearch()` arrow method - -- To add `title`, you have to use `title()` arrow method - -- To add `url`, you have to use `url()` arrow method (only for testing, URL is automatically generated) - -- OPDS version can be handle by query param `version`: `?version=2.0` or `?version=1.2` - -- To get generate response and keep `Opds::class` instance, you can use `get()` arrow method - -- To get response as XML or JSON, you can use `send()` arrow method - -- `asString` param removed, now you can use `get()` arrow method to debug response - -- To get response after `get()` you can use `getResponse()` arrow method (different that `send()` will return full content as `never` with headers) - -- Add fallback for old OPDS versions to v1.2 - + +- To add `entries`, you have to use `feeds()` arrow method + + - `feeds()` accept `OpdsEntryBook[]` or `OpdsEntryNavigation[]` but also `OpdsEntryNavigation` or `OpdsEntryBook` + +- To add `isSearch`, you have to use `isSearch()` arrow method + +- To add `title`, you have to use `title()` arrow method + +- To add `url`, you have to use `url()` arrow method (only for testing, URL is automatically generated) + +- OPDS version can be handle by query param `version`: `?version=2.0` or `?version=1.2` + +- To get generate response and keep `Opds::class` instance, you can use `get()` arrow method + +- To get response as XML or JSON, you can use `send()` arrow method + +- `asString` param removed, now you can use `get()` arrow method to debug response + +- To get response after `get()` you can use `getResponse()` arrow method (different that `send()` will return full content as `never` with headers) + +- Add fallback for old OPDS versions to v1.2 ```php use Kiwilan\Opds\Opds; @@ -70,87 +63,86 @@ $opds = Opds::make(new OpdsConfig()) // Accept `OpdsConfig::class` ``` + #### Misc -- `OpdsConfig` - - - `usePagination` is now default to `false` - - `forceJson` param allow to skip OPDS 1.2 - - `searchQuery` removed from `OpdsConfig` because query parameter is statically defined (`q` for OPDS 1.2, `query` for OPDS 2.0) - -- `OpdsEntry` is now `OpdsEntryNavigation` - -- `OpdsEngine` rewrite completely - -- `OpdsResponse` can be debug with `getContents()` method to inspect response (accessible if you use `get()` method) - -- `OpdsEntry` items have now `get` prefix for all getter - -- remove modules system - -- `OpdsEngine` property `xml` is now `contents` - - - `getXml()` is now `getContents()` - - add setter `setContents()` - +- `OpdsConfig` + + - `usePagination` is now default to `false` + - `forceJson` param allow to skip OPDS 1.2 + - `searchQuery` removed from `OpdsConfig` because query parameter is statically defined (`q` for OPDS 1.2, `query` for OPDS 2.0) + +- `OpdsEntry` is now `OpdsEntryNavigation` + +- `OpdsEngine` rewrite completely + +- `OpdsResponse` can be debug with `getContents()` method to inspect response (accessible if you use `get()` method) + +- `OpdsEntry` items have now `get` prefix for all getter + +- remove modules system + +- `OpdsEngine` property `xml` is now `contents` + + - `getXml()` is now `getContents()` + - add setter `setContents()` ### Added -- add ODPS 2.0 support partially -- add setters to `OpdsEntry` -- XML pagination is now supported -- JSON pagination is now supported -- rewrite documentation -- `Opds` improve `send()` with new parameter `mock`, a boolean to send the response or not. -- Move all `enum` to `Kiwilan\Opds\Enums` namespace. -- Add new `OpdsPaginator` class to handle pagination -- more tests +- add ODPS 2.0 support partially +- add setters to `OpdsEntry` +- XML pagination is now supported +- JSON pagination is now supported +- rewrite documentation +- `Opds` improve `send()` with new parameter `mock`, a boolean to send the response or not. +- Move all `enum` to `Kiwilan\Opds\Enums` namespace. +- Add new `OpdsPaginator` class to handle pagination +- more tests ## 0.3.12 - 2023-05-09 -- Change default pagination from `15` to `32` -- Add more characters for `content` property of `OpdsEntryBook` +- Change default pagination from `15` to `32` +- Add more characters for `content` property of `OpdsEntryBook` ## 0.3.11 - 2023-05-09 -- `media` mime type fix +- `media` mime type fix ## 0.3.10 - 2023-05-09 -- `OpdsEntryBook` add `content` property with HTML +- `OpdsEntryBook` add `content` property with HTML ## 0.3.0 - 2023-05-09 -- add `OpdsVersionEnum` for `version` -- `OpdsVersionOneDotTwoModule` is now `Opds1Dot2Module` +- add `OpdsVersionEnum` for `version` +- `OpdsVersionOneDotTwoModule` is now `Opds1Dot2Module` ## 0.2.0 - 2023-05-09 -- `OpdsApp` is now `OpdsConfig` - - - `Opds` property `app` is now `config` - -- `OpdsEntry`, `OpdsEntryBook`, `OpdsEntryBookAuthor` has now namespace `Kiwilan\Opds\Entries` - -- `OpdsXmlConverter` is has now one static method - +- `OpdsApp` is now `OpdsConfig` + + - `Opds` property `app` is now `config` + +- `OpdsEntry`, `OpdsEntryBook`, `OpdsEntryBookAuthor` has now namespace `Kiwilan\Opds\Entries` + +- `OpdsXmlConverter` is has now one static method ## 0.1.30 - 2023-05-09 -- add pagination feature, see `usePagination` and `maxItemsPerPage` in `OpdsConfig` -- add `OpdsConfig` property: `iconUrl` +- add pagination feature, see `usePagination` and `maxItemsPerPage` in `OpdsConfig` +- add `OpdsConfig` property: `iconUrl` ## 0.1.21 - 2023-05-09 -- search template fixing +- search template fixing ## 0.1.20 - 2023-05-09 -- add `OpdsEntryBookAuthor` +- add `OpdsEntryBookAuthor` ## 0.1.10 - 2023-05-09 -- Add documentation +- Add documentation ## 0.1.0 - 2023-05-09 diff --git a/composer.json b/composer.json index bef98e5..dd1f2a1 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "kiwilan/php-opds", "description": "PHP package to create OPDS feed for eBooks.", - "version": "1.0.1", + "version": "1.0.2", "keywords": [ "php", "ebook", diff --git a/src/Engine/OpdsEngine.php b/src/Engine/OpdsEngine.php index 61344d1..89ce122 100644 --- a/src/Engine/OpdsEngine.php +++ b/src/Engine/OpdsEngine.php @@ -208,8 +208,13 @@ public static function addXmlLink( string $href = null, string $title = null, string $rel = null, - string $type = 'application/atom+xml;profile=opds-catalog;kind=acquisition', + string $type = 'application/atom+xml;profile=opds-catalog;kind=navigation', + bool $acquisition = false, ): array { + if ($acquisition) { + $type = 'application/atom+xml;profile=opds-catalog;kind=acquisition'; + } + return [ '_attributes' => [ 'rel' => $rel, @@ -242,6 +247,10 @@ protected function route(?string $route): ?string */ protected function paginate(array &$content, array &$feeds): void { + if (! $this->getOpds()->getConfig()->isUsePagination() && ! $this->getOpds()->getConfig()->isUseAutoPagination()) { + return; + } + $this->paginator = OpdsPaginator::make($this)->paginate($content, $feeds); } } diff --git a/src/Engine/OpdsJsonEngine.php b/src/Engine/OpdsJsonEngine.php index c3fdeac..5ddd53a 100644 --- a/src/Engine/OpdsJsonEngine.php +++ b/src/Engine/OpdsJsonEngine.php @@ -89,24 +89,18 @@ public function search(): self public function addNavigationEntry(OpdsEntryNavigation $entry): array { - $properties = $entry->getProperties(); - - if ($properties) { - return [ - 'href' => $this->route($entry->getRoute()), - 'title' => $entry->getTitle(), - 'type' => 'application/opds+json', - 'rel' => $entry->getRelation() ?? 'current', - 'properties' => $properties, - ]; - } - - return [ + $item = [ 'href' => $this->route($entry->getRoute()), 'title' => $entry->getTitle(), 'type' => 'application/opds+json', 'rel' => $entry->getRelation() ?? 'current', ]; + + if ($property = $entry->getProperties()) { + $item['properties'] = $property; + } + + return $item; } public function addBookEntry(OpdsEntryBook $entry): array diff --git a/src/Engine/OpdsPaginator.php b/src/Engine/OpdsPaginator.php index eb5bb5e..5e8727b 100644 --- a/src/Engine/OpdsPaginator.php +++ b/src/Engine/OpdsPaginator.php @@ -15,6 +15,7 @@ class OpdsPaginator protected function __construct( protected OpdsOutputEnum $output, protected string $versionQuery, + protected string $paginationQuery, protected string $url, protected array $query = [], protected bool $usePagination = false, @@ -43,11 +44,13 @@ public static function make(OpdsEngine $engine): self $output = $engine->getOpds()->getOutput(); $query = $engine->getOpds()->getQuery(); - $page = $query['page'] ?? 1; + $pagination = $engine->getOpds()->getConfig()->getPaginationQuery(); + $page = $query[$pagination] ?? 1; return new self( output: $output, versionQuery: $engine->getOpds()->getConfig()->getVersionQuery(), + paginationQuery: $pagination, url: $url, query: $query, usePagination: $engine->getOpds()->getConfig()->isUsePagination(), @@ -196,24 +199,24 @@ private function json(array &$content): void if ($this->page !== 1) { $content['links'][] = OpdsEngine::addJsonLink( rel: 'first', - href: $this->route($this->url, ['page' => 1]), + href: $this->route($this->url, [$this->paginationQuery => 1]), ); $content['links'][] = OpdsEngine::addJsonLink( rel: 'previous', - href: $this->route($this->url, ['page' => $this->page - 1]), + href: $this->route($this->url, [$this->paginationQuery => $this->page - 1]), ); } if ($this->page !== $this->size) { $content['links'][] = OpdsEngine::addJsonLink( rel: 'next', - href: $this->route($this->url, ['page' => $this->page + 1]), + href: $this->route($this->url, [$this->paginationQuery => $this->page + 1]), ); $content['links'][] = OpdsEngine::addJsonLink( rel: 'last', - href: $this->route($this->url, ['page' => $this->size]), + href: $this->route($this->url, [$this->paginationQuery => $this->size]), ); } } diff --git a/src/OpdsConfig.php b/src/OpdsConfig.php index 0815bb3..b34ad24 100644 --- a/src/OpdsConfig.php +++ b/src/OpdsConfig.php @@ -32,6 +32,7 @@ public function __construct( protected ?string $startUrl = null, protected ?string $searchUrl = null, protected string $versionQuery = 'version', + protected string $paginationQuery = 'page', protected DateTime $updated = new DateTime(), protected bool $usePagination = false, protected bool $useAutoPagination = false, @@ -75,6 +76,11 @@ public function getVersionQuery(): string return $this->versionQuery; } + public function getPaginationQuery(): string + { + return $this->paginationQuery; + } + public function getUpdated(): DateTime { return $this->updated; @@ -149,6 +155,13 @@ public function setVersionQuery(string $versionQuery): self return $this; } + public function setPaginationQuery(string $paginationQuery): self + { + $this->paginationQuery = $paginationQuery; + + return $this; + } + public function setUpdated(DateTime $updated): self { $this->updated = $updated; diff --git a/tests/Datasets.php b/tests/Datasets.php index 7e2f156..a2fb241 100644 --- a/tests/Datasets.php +++ b/tests/Datasets.php @@ -12,6 +12,8 @@ summary: 'Authors, 1 available', media: 'https://user-images.githubusercontent.com/48261459/201463225-0a5a084e-df15-4b11-b1d2-40fafd3555cf.svg', updated: new DateTime(), + relation: 'series', + properties: ['properties'], ), ]); diff --git a/tests/OpdsConfigTest.php b/tests/OpdsConfigTest.php index b0f5a92..f14b872 100644 --- a/tests/OpdsConfigTest.php +++ b/tests/OpdsConfigTest.php @@ -12,6 +12,7 @@ $config->setStartUrl('https://example.com/opds'); $config->setSearchUrl('https://example.com/opds/search'); $config->setVersionQuery('v'); + $config->setPaginationQuery('pagination'); $config->setUpdated(new DateTime()); $config->usePagination(); $config->useAutoPagination(); @@ -25,6 +26,7 @@ expect($config->getStartUrl())->toBe('https://example.com/opds'); expect($config->getSearchUrl())->toBe('https://example.com/opds/search'); expect($config->getVersionQuery())->toBe('v'); + expect($config->getPaginationQuery())->toBe('pagination'); expect($config->getUpdated())->toBeInstanceOf(DateTime::class); expect($config->isUsePagination())->toBeTrue(); expect($config->isUseAutoPagination())->toBeTrue(); diff --git a/tests/OpdsEntryTest.php b/tests/OpdsEntryTest.php index 2affd3c..7f84878 100644 --- a/tests/OpdsEntryTest.php +++ b/tests/OpdsEntryTest.php @@ -12,7 +12,16 @@ expect($entry->getSummary())->toBe('Authors, 1 available'); expect($entry->getMedia())->toBe('https://user-images.githubusercontent.com/48261459/201463225-0a5a084e-df15-4b11-b1d2-40fafd3555cf.svg'); expect($entry->getUpdated())->toBeInstanceOf(DateTime::class); + expect($entry->getRelation())->toBe('series'); + expect($entry->getProperties())->toBeArray(); + expect($entry->getProperties())->toBe(['properties']); expect($entry->toArray())->toBeArray(); + + $entry->relation('new relation'); + $entry->properties(['new properties']); + expect($entry->getRelation())->toBe('new relation'); + expect($entry->getProperties())->toBeArray(); + expect($entry->getProperties())->toBe(['new properties']); })->with('feeds'); it('is OpdsEntryBook', function (OpdsEntryBook $entry) { @@ -62,6 +71,7 @@ $entry->serie('Earth\'s Children'); $entry->language('English'); $entry->isbn('1234567890'); + $entry->identifier('1234567890'); $entry->translator('Translator'); $entry->publisher('Publisher'); @@ -83,6 +93,7 @@ expect($entry->getSerie())->toBe('Earth\'s Children'); expect($entry->getLanguage())->toBe('English'); expect($entry->getIsbn())->toBe('1234567890'); + expect($entry->getIdentifier())->toBe('1234567890'); expect($entry->getTranslator())->toBe('Translator'); expect($entry->getPublisher())->toBe('Publisher'); }); diff --git a/tests/OpdsPaginationTest.php b/tests/OpdsPaginationTest.php index 7b9b1a9..58259e9 100644 --- a/tests/OpdsPaginationTest.php +++ b/tests/OpdsPaginationTest.php @@ -125,3 +125,22 @@ expect($xml)->toBeArray(); expect(count($xml))->toBe(32); }); + +it('can skip json pagination', function () { + $opds = Opds::make(getConfig()->forceJson()) + ->feeds(manyFeeds()) + ->get(); + + $response = json_decode($opds->getResponse()->getContents(), true); + + expect(count($response['publications']))->toBe(100); + + $opds = Opds::make(getConfig()->forceJson()) + ->url('http://localhost:8000/opds?page=2') + ->feeds(manyFeeds()) + ->get(); + + $response = json_decode($opds->getResponse()->getContents(), true); + + expect(count($response['publications']))->toBe(100); +}); diff --git a/tests/OpdsTest.php b/tests/OpdsTest.php index a5a6226..09e8b08 100644 --- a/tests/OpdsTest.php +++ b/tests/OpdsTest.php @@ -5,6 +5,7 @@ use Kiwilan\Opds\Enums\OpdsOutputEnum; use Kiwilan\Opds\Enums\OpdsVersionEnum; use Kiwilan\Opds\Opds; +use Kiwilan\Opds\OpdsConfig; use Kiwilan\Opds\OpdsResponse; use Kiwilan\XmlReader\XmlReader; @@ -45,12 +46,16 @@ expect($opds->getOutput())->toBe(OpdsOutputEnum::xml); expect($opds->getResponse())->toBeInstanceOf(OpdsResponse::class); expect($opds->getUrlParts())->toBeArray(); - expect($opds->getPaginator())->toBeInstanceOf(OpdsPaginator::class); + expect($opds->getPaginator())->toBeNull(); }); it('can use opds paginator', function () { - $opds = Opds::make() - ->title('feed'); + $config = (new OpdsConfig())->usePagination() + ->setVersionQuery('v') + ->setPaginationQuery('pagination'); + $opds = Opds::make($config) + ->title('feed') + ->get(); expect($opds->getPaginator())->toBeInstanceOf(OpdsPaginator::class); }); diff --git a/tests/Pest.php b/tests/Pest.php index 28b001c..18e25fd 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -156,6 +156,9 @@ function navigationEntries(): array content: 'content', media: 'https://raw.githubusercontent.com/kiwilan/php-opds/main/docs/banner.jpg', updated: new DateTime(), + properties: [ + 'numberOfItems' => 1, + ], ), new OpdsEntryNavigation( id: 'authors', From 6bf99ee0c970f9b2aafde5c7f1c1adcf8025ca40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ewilan=20Rivi=C3=A8re?= Date: Thu, 14 Sep 2023 16:35:19 +0200 Subject: [PATCH 2/2] docs --- README.md | 8 +++++++- src/Entries/OpdsEntryBook.php | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c6a8a53..6ae4984 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,7 @@ $config = new OpdsConfig( startUrl: 'https://example.com/opds', // Start URL, will be included in top navigation searchUrl: 'https://example.com/opds/search', // Search URL, will be included in top navigation versionQuery: 'version', // query parameter for version + paginationQuery: 'page', // query parameter for pagination updated: new DateTime(), // Last update of OPDS feed usePagination: false, // To enable pagination, default is false useAutoPagination: false, // To enable auto pagination, default is false, if `usePagination` is true, this option will be ignored @@ -248,6 +249,10 @@ $entry = new OpdsEntryNavigation( summary: 'Authors, 1 available', media: 'https://user-images.githubusercontent.com/48261459/201463225-0a5a084e-df15-4b11-b1d2-40fafd3555cf.svg', updated: new DateTime(), + properties: [ + 'numberOfItems' => 1, + ], // to include extra properties (like numberOfItems for facets) + relation: 'current', // to specify the relation to use (instead of `current`) ); ``` @@ -298,7 +303,8 @@ $entry = new OpdsEntryBook( volume: 1, serie: 'Earth\'s Children', language: 'English', - isbn: '9780553381672', + isbn: '9780553381672', // deprecated, use `identifier` instead + identifier: 'urn:isbn:9780553381672', // to specify the actual identifier to use (instead of `urn:isbn:...`) translator: 'translator', publisher: 'publisher', ); diff --git a/src/Entries/OpdsEntryBook.php b/src/Entries/OpdsEntryBook.php index a9da2ca..12f1451 100644 --- a/src/Entries/OpdsEntryBook.php +++ b/src/Entries/OpdsEntryBook.php @@ -9,6 +9,7 @@ class OpdsEntryBook extends OpdsEntryNavigation /** * @param string[] $categories * @param OpdsEntryBookAuthor[] $authors + * @param ?string $isbn @deprecated Use `identifier` instead */ public function __construct( protected string $id,