From 0b338a2e718ff9218434ced33f5ba294122e0eb9 Mon Sep 17 00:00:00 2001 From: Nico Hoffmann Date: Wed, 1 May 2024 17:41:02 +0200 Subject: [PATCH] Merge `Api` classes --- config/fields/mixins/upload.php | 2 +- src/Api/Api.php | 230 +++++- src/Api/Model.php | 8 +- src/Cms/Api.php | 260 ------- src/Cms/App.php | 1 + tests/Api/ApiTest.php | 721 +++++++++++++++++- .../CollectionTestCase.php} | 5 +- .../ModelTestCase.php} | 5 +- .../collections/ChildrenCollectionTest.php} | 6 +- .../collections/FilesCollectionTest.php} | 8 +- .../collections/LanguagesCollectionTest.php} | 6 +- .../collections/PagesCollectionTest.php} | 7 +- .../collections/RolesCollectionTest.php} | 6 +- .../TranslationsCollectionTest.php | 18 + .../collections/UsersCollectionTest.php} | 6 +- .../models/FileBlueprintModelTest.php} | 8 +- .../models/FileModelTest.php} | 6 +- .../models/FileVersionModelTest.php} | 8 +- .../models/PageBlueprintModelTest.php} | 7 +- .../models/PageModelTest.php} | 6 +- .../models/SiteModelTest.php} | 6 +- .../models/UserBlueprintModelTest.php} | 7 +- .../models/UserModelTest.php} | 6 +- .../Api/routes/AccountRoutesTest.php | 5 +- tests/{Cms => }/Api/routes/AuthRoutesTest.php | 3 +- .../Api/routes/LanguagesRoutesTest.php | 3 +- .../{Cms => }/Api/routes/PagesRoutesTest.php | 3 +- .../{Cms => }/Api/routes/RolesRoutesTest.php | 3 +- tests/{Cms => }/Api/routes/SiteRoutesTest.php | 3 +- .../{Cms => }/Api/routes/SystemRoutesTest.php | 3 +- .../Api/routes/TranslationsRoutesTest.php | 3 +- .../{Cms => }/Api/routes/UsersRoutesTest.php | 5 +- .../{Cms => }/Api/routes/fixtures/avatar.jpg | Bin tests/Cms/Api/ApiTest.php | 717 ----------------- .../TranslationsApiCollectionTest.php | 17 - vendor/composer/autoload_classmap.php | 1 - vendor/composer/autoload_static.php | 1 - 37 files changed, 1027 insertions(+), 1083 deletions(-) delete mode 100644 src/Cms/Api.php rename tests/{Cms/Api/ApiCollectionTestCase.php => Api/CollectionTestCase.php} (79%) rename tests/{Cms/Api/ApiModelTestCase.php => Api/ModelTestCase.php} (87%) rename tests/{Cms/Api/collections/ChildrenApiCollectionTest.php => Api/collections/ChildrenCollectionTest.php} (78%) rename tests/{Cms/Api/collections/FilesApiCollectionTest.php => Api/collections/FilesCollectionTest.php} (79%) rename tests/{Cms/Api/collections/LanguagesApiCollectionTest.php => Api/collections/LanguagesCollectionTest.php} (84%) rename tests/{Cms/Api/collections/PagesApiCollectionTest.php => Api/collections/PagesCollectionTest.php} (76%) rename tests/{Cms/Api/collections/RolesApiCollectionTest.php => Api/collections/RolesCollectionTest.php} (84%) create mode 100644 tests/Api/collections/TranslationsCollectionTest.php rename tests/{Cms/Api/collections/UsersApiCollectionTest.php => Api/collections/UsersCollectionTest.php} (88%) rename tests/{Cms/Api/models/FileBlueprintApiModelTest.php => Api/models/FileBlueprintModelTest.php} (89%) rename tests/{Cms/Api/models/FileApiModelTest.php => Api/models/FileModelTest.php} (88%) rename tests/{Cms/Api/models/FileVersionApiModelTest.php => Api/models/FileVersionModelTest.php} (89%) rename tests/{Cms/Api/models/PageBlueprintApiModelTest.php => Api/models/PageBlueprintModelTest.php} (94%) rename tests/{Cms/Api/models/PageApiModelTest.php => Api/models/PageModelTest.php} (96%) rename tests/{Cms/Api/models/SiteApiModelTest.php => Api/models/SiteModelTest.php} (94%) rename tests/{Cms/Api/models/UserBlueprintApiModelTest.php => Api/models/UserBlueprintModelTest.php} (91%) rename tests/{Cms/Api/models/UserApiModelTest.php => Api/models/UserModelTest.php} (88%) rename tests/{Cms => }/Api/routes/AccountRoutesTest.php (98%) rename tests/{Cms => }/Api/routes/AuthRoutesTest.php (95%) rename tests/{Cms => }/Api/routes/LanguagesRoutesTest.php (98%) rename tests/{Cms => }/Api/routes/PagesRoutesTest.php (99%) rename tests/{Cms => }/Api/routes/RolesRoutesTest.php (95%) rename tests/{Cms => }/Api/routes/SiteRoutesTest.php (99%) rename tests/{Cms => }/Api/routes/SystemRoutesTest.php (93%) rename tests/{Cms => }/Api/routes/TranslationsRoutesTest.php (94%) rename tests/{Cms => }/Api/routes/UsersRoutesTest.php (99%) rename tests/{Cms => }/Api/routes/fixtures/avatar.jpg (100%) delete mode 100644 tests/Cms/Api/ApiTest.php delete mode 100644 tests/Cms/Api/collections/TranslationsApiCollectionTest.php diff --git a/config/fields/mixins/upload.php b/config/fields/mixins/upload.php index 7cfcf5de02..6ea3ce391b 100644 --- a/config/fields/mixins/upload.php +++ b/config/fields/mixins/upload.php @@ -1,6 +1,6 @@ authentication = $props['authentication'] ?? null; $this->data = $props['data'] ?? []; + $this->debug = $props['debug'] ?? false; + $this->kirby = $props['kirby'] ?? App::instance(); $this->routes = $props['routes'] ?? []; - $this->debug = $props['debug'] ?? false; if ($collections = $props['collections'] ?? null) { $this->collections = array_change_key_case($collections); @@ -147,6 +161,14 @@ public function call( $this->setRequestMethod($method); $this->setRequestData($requestData); + $this->kirby->setCurrentLanguage($this->language()); + + $impersonate = $this->kirby()->option('api.allowImpersonation', false); + + $translation = $this->kirby->user(null, $impersonate)?->language(); + $translation ??= $this->kirby->panelLanguage(); + $this->kirby->setCurrentTranslation($translation); + $this->router = new Router($this->routes()); $this->route = $this->router->find($path, $method); $auth = $this->route?->attributes()['auth'] ?? true; @@ -212,13 +234,14 @@ public function clone(array $props = []): static { return new static([ 'autentication' => $this->authentication, - 'data' => $this->data, - 'routes' => $this->routes, - 'debug' => $this->debug, 'collections' => $this->collections, - 'models' => $this->models, + 'data' => $this->data, + 'debug' => $this->debug, + 'kirby' => $this->kirby, + 'models' => $this->models, 'requestData' => $this->requestData, 'requestMethod' => $this->requestMethod, + 'routes' => $this->routes, ...$props ]); } @@ -284,6 +307,53 @@ public function debug(): bool return $this->debug; } + /** + * @throws \Kirby\Exception\NotFoundException if the field type cannot be found or the field cannot be loaded + */ + public function fieldApi( + ModelWithContent $model, + string $name, + string|null $path = null + ): mixed { + $field = Form::for($model)->field($name); + + $fieldApi = $this->clone([ + 'data' => [...$this->data(), 'field' => $field], + 'routes' => $field->api(), + ]); + + return $fieldApi->call( + $path, + $this->requestMethod(), + $this->requestData() + ); + } + + /** + * Returns the file object for the given + * parent path and filename + * + * @param string $path Path to file's parent model + * @throws \Kirby\Exception\NotFoundException if the file cannot be found + */ + public function file( + string $path, + string $filename + ): File|null { + return Find::file($path, $filename); + } + + /** + * Returns the all readable files for the parent + * + * @param string $path Path to file's parent model + * @throws \Kirby\Exception\NotFoundException if the file cannot be found + */ + public function files(string $path): Files + { + return $this->parent($path)->files()->filter('isAccessible', true); + } + /** * Checks if injected data exists for the given key */ @@ -292,6 +362,24 @@ public function hasData(string $key): bool return isset($this->data[$key]) === true; } + /** + * Returns the Kirby instance + */ + public function kirby(): App + { + return $this->kirby; + } + + /** + * Returns the language request header + */ + public function language(): string|null + { + return + $this->requestQuery('language') ?? + $this->requestHeaders('x-language'); + } + /** * Matches an object with an array item * based on the `type` field @@ -341,6 +429,51 @@ public function models(): array return $this->models; } + /** + * Returns the page object for the given id + * + * @param string $id Page's id + * @throws \Kirby\Exception\NotFoundException if the page cannot be found + */ + public function page(string $id): Page|null + { + return Find::page($id); + } + + /** + * Returns the subpages for the given + * parent. The subpages can be filtered + * by status (draft, listed, unlisted, published, all) + */ + public function pages( + string|null $parentId = null, + string|null $status = null + ): Pages { + $parent = $parentId === null ? $this->site() : $this->page($parentId); + $pages = match ($status) { + 'all' => $parent->childrenAndDrafts(), + 'draft', 'drafts' => $parent->drafts(), + 'listed' => $parent->children()->listed(), + 'unlisted' => $parent->children()->unlisted(), + 'published' => $parent->children(), + default => $parent->children() + }; + + return $pages->filter('isAccessible', true); + } + + /** + * Returns the model's object for the given path + * + * @param string $path Path to parent model + * @throws \Kirby\Exception\InvalidArgumentException if the model type is invalid + * @throws \Kirby\Exception\NotFoundException if the model cannot be found + */ + public function parent(string $path): ModelWithContent|null + { + return Find::parent($path); + } + /** * Getter for request data * Can either get all the data @@ -577,6 +710,57 @@ public function responseForException(Throwable $e): array return $result; } + /** + * Search for direct subpages of the + * given parent + */ + public function searchPages(string|null $parent = null): Pages + { + $pages = $this->pages($parent, $this->requestQuery('status')); + + if ($this->requestMethod() === 'GET') { + return $pages->search($this->requestQuery('q')); + } + + return $pages->query($this->requestBody()); + } + + /** + * @throws \Kirby\Exception\NotFoundException if the section type cannot be found or the section cannot be loaded + */ + public function sectionApi( + ModelWithContent $model, + string $name, + string|null $path = null + ): mixed { + if (!$section = $model->blueprint()?->section($name)) { + throw new NotFoundException( + message: 'The section "' . $name . '" could not be found' + ); + } + + $sectionApi = $this->clone([ + 'data' => [...$this->data(), 'section' => $section], + 'routes' => $section->api(), + ]); + + return $sectionApi->call( + $path, + $this->requestMethod(), + $this->requestData() + ); + } + + /** + * Returns the current Session instance + * + * @param array $options Additional options, see the session component + */ + public function session(array $options = []): Session + { + return $this->kirby->session(['detect' => true, ...$options]); + } + /** * Setter for the request data * @return $this @@ -604,6 +788,14 @@ protected function setRequestMethod( return $this; } + /** + * Returns the site object + */ + public function site(): Site + { + return $this->kirby->site(); + } + /** * Upload helper method * @@ -619,4 +811,32 @@ public function upload( ): array { return (new Upload($this, $single, $debug))->process($callback); } + + /** + * Returns the user object for the given id or + * returns the current authenticated user if no + * id is passed + * + * @throws \Kirby\Exception\NotFoundException if the user for the given id cannot be found + */ + public function user(string|null $id = null): User|null + { + try { + return Find::user($id); + } catch (NotFoundException $e) { + if ($id === null) { + return null; + } + + throw $e; + } + } + + /** + * Returns the users collection + */ + public function users(): Users + { + return $this->kirby->users(); + } } diff --git a/src/Api/Model.php b/src/Api/Model.php index fa3a000f2e..407ecaa078 100644 --- a/src/Api/Model.php +++ b/src/Api/Model.php @@ -63,7 +63,9 @@ public function __construct( null => 'null', default => $this->data::class, }; - throw new Exception(sprintf('Invalid model type "%s" expected: "%s"', $class, $schema['type'])); + throw new Exception( + message: sprintf('Invalid model type "%s" expected: "%s"', $class, $schema['type']) + ); } } @@ -218,7 +220,9 @@ public function view(string $name): static // try to fall back to the default view at least if (isset($this->views[$name]) === false) { - throw new Exception(sprintf('The view "%s" does not exist', $name)); + throw new Exception( + message: sprintf('The view "%s" does not exist', $name) + ); } } diff --git a/src/Cms/Api.php b/src/Cms/Api.php deleted file mode 100644 index d7945b4ac2..0000000000 --- a/src/Cms/Api.php +++ /dev/null @@ -1,260 +0,0 @@ - - * @link https://getkirby.com - * @copyright Bastian Allgeier - * @license https://getkirby.com/license - */ -class Api extends BaseApi -{ - protected App $kirby; - - public function __construct(array $props) - { - $this->kirby = $props['kirby']; - parent::__construct($props); - } - - /** - * Execute an API call for the given path, - * request method and optional request data - */ - public function call( - string|null $path = null, - string $method = 'GET', - array $requestData = [] - ): mixed { - $this->setRequestMethod($method); - $this->setRequestData($requestData); - - $this->kirby->setCurrentLanguage($this->language()); - - $allowImpersonation = $this->kirby()->option('api.allowImpersonation', false); - - $translation = $this->kirby->user(null, $allowImpersonation)?->language(); - $translation ??= $this->kirby->panelLanguage(); - $this->kirby->setCurrentTranslation($translation); - - return parent::call($path, $method, $requestData); - } - - /** - * Creates a new instance while - * merging initial and new properties - */ - public function clone(array $props = []): static - { - return parent::clone([ - 'kirby' => $this->kirby, - ...$props - ]); - } - - /** - * @throws \Kirby\Exception\NotFoundException if the field type cannot be found or the field cannot be loaded - */ - public function fieldApi( - ModelWithContent $model, - string $name, - string|null $path = null - ): mixed { - $field = Form::for($model)->field($name); - - $fieldApi = $this->clone([ - 'data' => [...$this->data(), 'field' => $field], - 'routes' => $field->api(), - ]); - - return $fieldApi->call( - $path, - $this->requestMethod(), - $this->requestData() - ); - } - - /** - * Returns the file object for the given - * parent path and filename - * - * @param string $path Path to file's parent model - * @throws \Kirby\Exception\NotFoundException if the file cannot be found - */ - public function file( - string $path, - string $filename - ): File|null { - return Find::file($path, $filename); - } - - /** - * Returns the all readable files for the parent - * - * @param string $path Path to file's parent model - * @throws \Kirby\Exception\NotFoundException if the file cannot be found - */ - public function files(string $path): Files - { - return $this->parent($path)->files()->filter('isAccessible', true); - } - - /** - * Returns the model's object for the given path - * - * @param string $path Path to parent model - * @throws \Kirby\Exception\InvalidArgumentException if the model type is invalid - * @throws \Kirby\Exception\NotFoundException if the model cannot be found - */ - public function parent(string $path): ModelWithContent|null - { - return Find::parent($path); - } - - /** - * Returns the Kirby instance - */ - public function kirby(): App - { - return $this->kirby; - } - - /** - * Returns the language request header - */ - public function language(): string|null - { - return - $this->requestQuery('language') ?? - $this->requestHeaders('x-language'); - } - - /** - * Returns the page object for the given id - * - * @param string $id Page's id - * @throws \Kirby\Exception\NotFoundException if the page cannot be found - */ - public function page(string $id): Page|null - { - return Find::page($id); - } - - /** - * Returns the subpages for the given - * parent. The subpages can be filtered - * by status (draft, listed, unlisted, published, all) - */ - public function pages( - string|null $parentId = null, - string|null $status = null - ): Pages { - $parent = $parentId === null ? $this->site() : $this->page($parentId); - $pages = match ($status) { - 'all' => $parent->childrenAndDrafts(), - 'draft', 'drafts' => $parent->drafts(), - 'listed' => $parent->children()->listed(), - 'unlisted' => $parent->children()->unlisted(), - 'published' => $parent->children(), - default => $parent->children() - }; - - return $pages->filter('isAccessible', true); - } - - /** - * Search for direct subpages of the - * given parent - */ - public function searchPages(string|null $parent = null): Pages - { - $pages = $this->pages($parent, $this->requestQuery('status')); - - if ($this->requestMethod() === 'GET') { - return $pages->search($this->requestQuery('q')); - } - - return $pages->query($this->requestBody()); - } - - /** - * @throws \Kirby\Exception\NotFoundException if the section type cannot be found or the section cannot be loaded - */ - public function sectionApi( - ModelWithContent $model, - string $name, - string|null $path = null - ): mixed { - if (!$section = $model->blueprint()?->section($name)) { - throw new NotFoundException( - message: 'The section "' . $name . '" could not be found' - ); - } - - $sectionApi = $this->clone([ - 'data' => [...$this->data(), 'section' => $section], - 'routes' => $section->api(), - ]); - - return $sectionApi->call( - $path, - $this->requestMethod(), - $this->requestData() - ); - } - - /** - * Returns the current Session instance - * - * @param array $options Additional options, see the session component - */ - public function session(array $options = []): Session - { - return $this->kirby->session(['detect' => true, ...$options]); - } - - /** - * Returns the site object - */ - public function site(): Site - { - return $this->kirby->site(); - } - - /** - * Returns the user object for the given id or - * returns the current authenticated user if no - * id is passed - * - * @throws \Kirby\Exception\NotFoundException if the user for the given id cannot be found - */ - public function user(string|null $id = null): User|null - { - try { - return Find::user($id); - } catch (NotFoundException $e) { - if ($id === null) { - return null; - } - - throw $e; - } - } - - /** - * Returns the users collection - */ - public function users(): Users - { - return $this->kirby->users(); - } -} diff --git a/src/Cms/App.php b/src/Cms/App.php index 67980b3d7d..9325707aba 100644 --- a/src/Cms/App.php +++ b/src/Cms/App.php @@ -4,6 +4,7 @@ use Closure; use Generator; +use Kirby\Api\Api; use Kirby\Content\VersionId; use Kirby\Data\Data; use Kirby\Email\Email as BaseEmail; diff --git a/tests/Api/ApiTest.php b/tests/Api/ApiTest.php index fa2b9543ed..62aeccaafe 100644 --- a/tests/Api/ApiTest.php +++ b/tests/Api/ApiTest.php @@ -3,12 +3,17 @@ namespace Kirby\Api; use Exception; -use Kirby\Cms\Response; +use Kirby\Cms\App; +use Kirby\Cms\Auth; use Kirby\Cms\User; +use Kirby\Exception\AuthException; +use Kirby\Exception\InvalidArgumentException; use Kirby\Exception\NotFoundException; -use Kirby\Http\Response as HttpResponse; +use Kirby\Filesystem\Dir; +use Kirby\Http\Response; use Kirby\TestCase; use Kirby\Toolkit\Collection; +use Kirby\Toolkit\I18n; use Kirby\Toolkit\Obj; use stdClass; @@ -22,9 +27,67 @@ class ExtendedModel extends stdClass class ApiTest extends TestCase { + public const TMP = KIRBY_TMP_DIR . '/Cms.Api'; + + protected $api; + protected $locale; + + public function setUp(): void + { + $this->app = new App([ + 'roots' => [ + 'index' => static::TMP + ], + 'site' => [ + 'children' => [ + [ + 'slug' => 'a', + 'children' => [ + ['slug' => 'aa'], + ['slug' => 'ab'] + ], + 'files' => [ + ['filename' => 'a-regular-file.jpg'], + ['filename' => 'a filename with spaces.jpg'] + ] + ], + [ + 'slug' => 'b' + ] + ] + ], + 'options' => [ + 'api' => [ + 'allowImpersonation' => true, + 'authentication' => fn () => true, + 'routes' => [ + [ + 'pattern' => 'foo', + 'method' => 'GET', + 'action' => fn () => 'something' + ] + ] + ], + 'locale' => 'de_DE.UTF-8' + ], + ]); + + $this->app->impersonate('kirby'); + $this->api = $this->app->api(); + + $this->locale = setlocale(LC_ALL, 0); + Dir::make(static::TMP); + } + + public function tearDown(): void + { + Dir::remove(static::TMP); + setlocale(LC_ALL, $this->locale); + } + public function testConstruct() { - $api = new Api([]); + $api = new Api(['kirby' => $this->app]); $this->assertNull($api->authentication()); $this->assertSame([], $api->collections()); @@ -64,6 +127,47 @@ public function testAuthentication() $api->authenticate(); } + public function testAuthenticationWithoutCsrf() + { + $auth = $this->createMock(Auth::class); + $auth->method('type')->willReturn('session'); + $auth->method('csrf')->willReturn(false); + + $kirby = $this->createMock(App::class); + $kirby->method('auth')->willReturn($auth); + + $this->expectException(AuthException::class); + $this->expectExceptionMessage('Unauthenticated'); + + $function = require $this->app->root('kirby') . '/config/api/authentication.php'; + + $api = new Api([ + 'kirby' => $kirby + ]); + + $function->call($api); + } + + public function testAuthenticationWithoutUser() + { + $auth = $this->createMock(Auth::class); + $auth->method('user')->willReturn(null); + + $kirby = $this->createMock(App::class); + $kirby->method('auth')->willReturn($auth); + + $this->expectException(AuthException::class); + $this->expectExceptionMessage('Unauthenticated'); + + $function = require $this->app->root('kirby') . '/config/api/authentication.php'; + + $api = new Api([ + 'kirby' => $kirby + ]); + + $function->call($api); + } + public function testCall() { $api = new Api([ @@ -115,8 +219,7 @@ public function testCall() public function testCallLocale() { $originalLocale = setlocale(LC_CTYPE, 0); - - $language = 'de'; + $language = 'de'; $api = new Api([ 'routes' => [ @@ -131,20 +234,216 @@ public function testCallLocale() } ]); + $de = ['de', 'de_DE', 'de_DE.UTF-8', 'de_DE.UTF8', 'de_DE.ISO8859-1']; $this->assertSame('something', $api->call('foo')); - $this->assertTrue(in_array(setlocale(LC_MONETARY, 0), ['de', 'de_DE', 'de_DE.UTF-8', 'de_DE.UTF8', 'de_DE.ISO8859-1'])); - $this->assertTrue(in_array(setlocale(LC_NUMERIC, 0), ['de', 'de_DE', 'de_DE.UTF-8', 'de_DE.UTF8', 'de_DE.ISO8859-1'])); - $this->assertTrue(in_array(setlocale(LC_TIME, 0), ['de', 'de_DE', 'de_DE.UTF-8', 'de_DE.UTF8', 'de_DE.ISO8859-1'])); + $this->assertTrue(in_array(setlocale(LC_MONETARY, 0), $de)); + $this->assertTrue(in_array(setlocale(LC_NUMERIC, 0), $de)); + $this->assertTrue(in_array(setlocale(LC_TIME, 0), $de)); $this->assertSame($originalLocale, setlocale(LC_CTYPE, 0)); $language = 'pt_BR'; + $pt = ['pt', 'pt_BR', 'pt_BR.UTF-8', 'pt_BR.UTF8', 'pt_BR.ISO8859-1']; $this->assertSame('something', $api->call('foo')); - $this->assertTrue(in_array(setlocale(LC_MONETARY, 0), ['pt', 'pt_BR', 'pt_BR.UTF-8', 'pt_BR.UTF8', 'pt_BR.ISO8859-1'])); - $this->assertTrue(in_array(setlocale(LC_NUMERIC, 0), ['pt', 'pt_BR', 'pt_BR.UTF-8', 'pt_BR.UTF8', 'pt_BR.ISO8859-1'])); - $this->assertTrue(in_array(setlocale(LC_TIME, 0), ['pt', 'pt_BR', 'pt_BR.UTF-8', 'pt_BR.UTF8', 'pt_BR.ISO8859-1'])); + $this->assertTrue(in_array(setlocale(LC_MONETARY, 0), $pt)); + $this->assertTrue(in_array(setlocale(LC_NUMERIC, 0), $pt)); + $this->assertTrue(in_array(setlocale(LC_TIME, 0), $pt)); $this->assertSame($originalLocale, setlocale(LC_CTYPE, 0)); } + public function testCallLocaleSingleLang1() + { + setlocale(LC_ALL, 'C'); + $this->assertSame('C', setlocale(LC_ALL, 0)); + + $this->assertSame('something', $this->api->call('foo')); + $this->assertSame('de_DE.UTF-8', setlocale(LC_ALL, 0)); + } + + public function testCallLocaleSingleLang2() + { + setlocale(LC_ALL, 'C'); + $this->assertSame('C', setlocale(LC_ALL, 0)); + + $_GET['language'] = 'en'; + + $this->assertSame('something', $this->api->call('foo')); + $this->assertSame('de_DE.UTF-8', setlocale(LC_ALL, 0)); + + $_GET = []; + } + + public function testCallLocaleMultiLang1() + { + setlocale(LC_ALL, 'C'); + $this->assertSame('C', setlocale(LC_ALL, 0)); + + $this->app = $this->app->clone([ + 'languages' => [ + [ + 'code' => 'en', + 'name' => 'English', + 'default' => true, + 'locale' => 'en_US.UTF-8', + 'url' => '/', + ], + [ + 'code' => 'de', + 'name' => 'Deutsch', + 'locale' => 'de_AT.UTF-8', + 'url' => '/de', + ], + ] + ]); + $this->api = $this->app->api(); + + $this->assertSame('something', $this->api->call('foo')); + $this->assertSame('en_US.UTF-8', setlocale(LC_ALL, 0)); + } + + public function testCallLocaleMultiLang2() + { + setlocale(LC_ALL, 'C'); + $this->assertSame('C', setlocale(LC_ALL, 0)); + + $this->app = $this->app->clone([ + 'languages' => [ + [ + 'code' => 'en', + 'name' => 'English', + 'default' => true, + 'locale' => 'en_US.UTF-8', + 'url' => '/', + ], + [ + 'code' => 'de', + 'name' => 'Deutsch', + 'locale' => 'de_AT.UTF-8', + 'url' => '/de', + ], + ] + ]); + $this->api = $this->app->api(); + + $this->assertSame('something', $this->api->call('foo', 'GET', [ + 'query' => ['language' => 'de'] + ])); + $this->assertSame('de_AT.UTF-8', setlocale(LC_ALL, 0)); + + $_GET = []; + } + + public function testCallTranslation() + { + // with logged in user with language + $app = $this->app->clone([ + 'users' => [ + [ + 'email' => 'homer@simpsons.com', + 'language' => 'fr' + ] + ] + ]); + $app->impersonate('homer@simpsons.com'); + + $api = $app->api(); + $this->assertSame('something', $api->call('foo')); + $this->assertSame('fr', I18n::$locale); + + // with logged in user without language + $app = $this->app->clone([ + 'users' => [ + [ + 'email' => 'homer@simpsons.com' + ] + ], + 'languages' => [ + [ + 'code' => 'it-it', + 'default' => true, + ] + ], + 'options' => [ + 'panel.language' => 'de' + ] + ]); + $app->impersonate('homer@simpsons.com'); + + $api = $app->api(); + $this->assertSame('something', $api->call('foo')); + $this->assertSame('de', I18n::$locale); + + // with logged in user without language without Panel language + $app = $this->app->clone([ + 'users' => [ + [ + 'email' => 'homer@simpsons.com' + ] + ], + 'languages' => [ + [ + 'code' => 'it-it', + 'default' => true, + ] + ] + ]); + $app->impersonate('homer@simpsons.com'); + + $api = $app->api(); + $this->assertSame('something', $api->call('foo')); + $this->assertSame('it', I18n::$locale); + + // with logged in user without any configuration + $app = $this->app->clone([ + 'users' => [ + [ + 'email' => 'homer@simpsons.com' + ] + ] + ]); + $app->impersonate('homer@simpsons.com'); + + $api = $app->api(); + $this->assertSame('something', $api->call('foo')); + $this->assertSame('en', I18n::$locale); + + // without logged in user + $app = $this->app->clone([ + 'languages' => [ + [ + 'code' => 'it-it', + 'default' => true, + ] + ], + 'options' => [ + 'panel.language' => 'de' + ] + ]); + + $api = $app->api(); + $this->assertSame('something', $api->call('foo')); + $this->assertSame('de', I18n::$locale); + + // without logged in user without Panel language + $app = $this->app->clone([ + 'languages' => [ + [ + 'code' => 'it-it', + 'default' => true, + ] + ] + ]); + + $api = $app->api(); + $this->assertSame('something', $api->call('foo')); + $this->assertSame('it', I18n::$locale); + + // without logged in user without any configuration + $app = $this->app->clone(); + $api = $app->api(); + $this->assertSame('something', $api->call('foo')); + $this->assertSame('en', I18n::$locale); + } + public function testCollections() { $api = new Api([ @@ -215,6 +514,183 @@ public function testDebug() $this->assertTrue($api->debug()); } + public function testFieldApi() + { + $app = $this->app->clone([ + 'site' => [ + 'children' => [ + [ + 'slug' => 'test', + 'content' => [ + 'title' => 'Test Title', + 'cover' => [ + 'a.jpg' + ] + ], + 'files' => [ + ['filename' => 'a.jpg'], + ['filename' => 'b.jpg'], + ], + 'blueprint' => [ + 'title' => 'Test', + 'name' => 'test', + 'fields' => [ + 'cover' => [ + 'type' => 'files', + ] + ] + ] + ] + ] + ] + ]); + $app->impersonate('kirby'); + + $page = $app->page('test'); + $response = $app->api()->fieldApi($page, 'cover'); + + $this->assertCount(2, $response); + $this->assertArrayHasKey('data', $response); + $this->assertArrayHasKey('pagination', $response); + $this->assertCount(2, $response['data']); + $this->assertSame('a.jpg', $response['data'][0]['filename']); + $this->assertSame('b.jpg', $response['data'][1]['filename']); + } + + public function testFieldApiInvalidField() + { + $app = $this->app->clone([ + 'site' => [ + 'children' => [ + ['slug' => 'test'] + ] + ] + ]); + + $this->expectException(NotFoundException::class); + $this->expectExceptionMessage('The field could not be found'); + + $page = $app->page('test'); + $app->api()->fieldApi($page, 'nonexists'); + } + + public function testFieldApiEmptyField() + { + $app = $this->app->clone([ + 'site' => [ + 'children' => [ + ['slug' => 'test'] + ] + ] + ]); + + $this->expectException(NotFoundException::class); + $this->expectExceptionMessage('No field could be loaded'); + + $page = $app->page('test'); + $app->api()->fieldApi($page, ''); + } + + public function testFile() + { + $app = $this->app->clone([ + 'site' => [ + 'children' => [ + [ + 'slug' => 'a', + 'files' => [ + ['filename' => 'test.jpg'] + ], + 'children' => [ + [ + 'slug' => 'a', + 'files' => [ + ['filename' => 'test.jpg'] + ], + ] + ] + ] + ], + 'files' => [ + ['filename' => 'test.jpg'] + ] + ], + 'users' => [ + [ + 'email' => 'test@getkirby.com', + 'files' => [ + ['filename' => 'test.jpg'] + ] + ] + ] + ]); + + $app->impersonate('kirby'); + $api = $app->api(); + + $this->assertSame('test.jpg', $api->file('site', 'test.jpg')->filename()); + $this->assertSame('test.jpg', $api->file('pages/a', 'test.jpg')->filename()); + $this->assertSame('test.jpg', $api->file('pages/a+a', 'test.jpg')->filename()); + $this->assertSame('test.jpg', $api->file('users/test@getkirby.com', 'test.jpg')->filename()); + } + + public function testFileNotFound() + { + $this->expectException(NotFoundException::class); + $this->expectExceptionMessage('The file "nope.jpg" cannot be found'); + + $this->api->file('site', 'nope.jpg'); + } + + public function testFileNotReadable() + { + $this->expectException(NotFoundException::class); + $this->expectExceptionMessage('The file "protected.jpg" cannot be found'); + + $app = $this->app->clone([ + 'blueprints' => [ + 'files/protected' => [ + 'options' => ['read' => false] + ] + ], + 'site' => [ + 'files' => [ + ['filename' => 'protected.jpg', 'template' => 'protected'] + ] + ] + ]); + + $this->api->file('site', 'protected.jpg'); + } + + public function testFileGetRoute() + { + // regular + $result = $this->api->call('pages/a/files/a-regular-file.jpg', 'GET'); + + $this->assertSame(200, $result['code']); + $this->assertSame('a-regular-file.jpg', $result['data']['filename']); + + // with spaces in filename + $result = $this->api->call('pages/a/files/a filename with spaces.jpg', 'GET'); + + $this->assertSame(200, $result['code']); + $this->assertSame('a filename with spaces.jpg', $result['data']['filename']); + } + + public function testLanguage() + { + $api = $this->api->clone([ + 'requestData' => [ + 'headers' => [ + 'x-language' => 'de' + ] + ] + ]); + + $this->assertSame('de', $api->language()); + } + public function testModels() { $api = new Api([ @@ -276,6 +752,143 @@ public function testModelResolverWithMissingModel() $api->resolve(new MockModel()); } + public function testPage() + { + $a = $this->app->page('a'); + $aa = $this->app->page('a/aa'); + + $this->assertSame($a, $this->api->page('a')); + $this->assertSame($aa, $this->api->page('a+aa')); + + $this->expectException(NotFoundException::class); + $this->expectExceptionMessage('The page "does-not-exist" cannot be found'); + $this->api->page('does-not-exist'); + } + + public function testPages() + { + $this->assertSame(['a/aa', 'a/ab'], $this->api->pages('a')->keys()); + } + + public function testPagesNotAccessible() + { + $app = $this->app->clone([ + 'blueprints' => [ + 'pages/api-protected' => [ + 'options' => ['access' => false] + ] + ], + 'site' => [ + 'children' => [ + [ + 'slug' => 'a' + ], + [ + 'slug' => 'b', + 'template' => 'api-protected' + ], + [ + 'slug' => 'c' + ] + ] + ], + 'users' => [ + ['id' => 'bastian', 'role' => 'admin'] + ] + ]); + + $app->impersonate('bastian'); + + $this->assertSame(['a', 'c'], $app->api()->pages()->keys()); + } + + public function testParent() + { + $app = $this->app->clone([ + 'site' => [ + 'files' => [ + ['filename' => 'sitefile.jpg'] + ] + ], + 'users' => [ + [ + 'email' => 'current@getkirby.com', + 'role' => 'admin' + ], + [ + 'email' => 'test@getkirby.com', + 'files' => [ + ['filename' => 'userfile.jpg'] + ] + ] + ], + ]); + + $app->impersonate('current@getkirby.com'); + + $api = $app->api(); + + $this->assertIsUser($api->parent('account')); + $this->assertIsUser($api->parent('users/test@getkirby.com')); + $this->assertIsSite($api->parent('site')); + $this->assertIsPage($api->parent('pages/a+aa')); + $this->assertIsFile($api->parent('site/files/sitefile.jpg')); + $this->assertIsFile($api->parent('pages/a/files/a-regular-file.jpg')); + $this->assertIsFile($api->parent('users/test@getkirby.com/files/userfile.jpg')); + + // model type is not recognized + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid model type: something'); + $this->assertNull($api->parent('something/something')); + + // model cannot be found + $this->expectException(NotFoundException::class); + $this->expectExceptionMessage('The page cannot be found'); + $this->assertNull($api->parent('pages/does-not-exist')); + } + + public function testRenderExceptionWithDebugging() + { + // simulate the document root to test relative file paths + $app = $this->app->clone([ + 'server' => [ + 'DOCUMENT_ROOT' => __DIR__ + ] + ]); + + $api = new Api([ + 'debug' => true, + 'kirby' => $app, + 'routes' => [ + [ + 'pattern' => 'test', + 'method' => 'POST', + 'action' => function () { + throw new \Exception('nope'); + } + ] + ] + ]); + + $result = $api->render('test', 'POST'); + + $expected = [ + 'status' => 'error', + 'message' => 'nope', + 'code' => 500, + 'exception' => 'Exception', + 'key' => null, + 'file' => '/' . basename(__FILE__), + 'line' => __LINE__ - 15, + 'details' => [], + 'route' => 'test' + ]; + + $this->assertInstanceOf(Response::class, $result); + $this->assertSame(json_encode($expected), $result->body()); + } + + public function testRequestData() { $api = new Api([ @@ -347,7 +960,7 @@ public function testRenderArray() $result = $api->render('test', 'POST'); - $this->assertInstanceOf(HttpResponse::class, $result); + $this->assertInstanceOf(Response::class, $result); $this->assertSame(json_encode(['a' => 'A']), $result->body()); } @@ -371,7 +984,7 @@ public function testRenderTrue() 'code' => 200 ]; - $this->assertInstanceOf(HttpResponse::class, $result); + $this->assertInstanceOf(Response::class, $result); $this->assertSame(json_encode($expected), $result->body()); } @@ -395,7 +1008,7 @@ public function testRenderFalse() 'code' => 400 ]; - $this->assertInstanceOf(HttpResponse::class, $result); + $this->assertInstanceOf(Response::class, $result); $this->assertSame(json_encode($expected), $result->body()); } @@ -419,7 +1032,7 @@ public function testRenderNull() 'code' => 404 ]; - $this->assertInstanceOf(HttpResponse::class, $result); + $this->assertInstanceOf(Response::class, $result); $this->assertSame(json_encode($expected), $result->body()); } @@ -447,11 +1060,11 @@ public function testRenderException() 'details' => [] ]; - $this->assertInstanceOf(HttpResponse::class, $result); + $this->assertInstanceOf(Response::class, $result); $this->assertSame(json_encode($expected), $result->body()); } - public function testRenderExceptionWithDebugging() + public function testRenderExceptionWithDebugging2() { $api = new Api([ 'debug' => true, @@ -477,13 +1090,13 @@ public function testRenderExceptionWithDebugging() 'code' => 500, 'exception' => 'Exception', 'key' => null, - 'file' => '/' . basename(__FILE__), + 'file' => basename(__FILE__), 'line' => __LINE__ - 18, 'details' => [], 'route' => 'test' ]; - $this->assertInstanceOf(HttpResponse::class, $result); + $this->assertInstanceOf(Response::class, $result); $this->assertSame(json_encode($expected), $result->body()); unset($_SERVER['DOCUMENT_ROOT']); @@ -517,7 +1130,7 @@ public function testRenderKirbyException() 'details' => ['a' => 'A'], ]; - $this->assertInstanceOf(HttpResponse::class, $result); + $this->assertInstanceOf(Response::class, $result); $this->assertSame(json_encode($expected), $result->body()); } @@ -552,12 +1165,12 @@ public function testRenderKirbyExceptionWithDebugging() 'exception' => NotFoundException::class, 'key' => 'error.test', 'file' => '/' . basename(__FILE__), - 'line' => __LINE__ - 22, + 'line' => __LINE__ - 24, 'details' => ['a' => 'A'], 'route' => 'test', ]; - $this->assertInstanceOf(HttpResponse::class, $result); + $this->assertInstanceOf(Response::class, $result); $this->assertSame(json_encode($expected), $result->body()); unset($_SERVER['DOCUMENT_ROOT']); @@ -605,6 +1218,70 @@ public function testRoutes() $this->assertSame($routes, $api->routes()); } + public function testSectionApi() + { + $app = $this->app->clone([ + 'sections' => [ + 'test' => [ + 'api' => function () { + return [ + [ + 'pattern' => '/message', + 'action' => function () { + return [ + 'message' => 'Test' + ]; + } + ] + ]; + } + ] + ], + 'blueprints' => [ + 'pages/test' => [ + 'title' => 'Test', + 'name' => 'test', + 'sections' => [ + 'test' => [ + 'type' => 'test', + ] + ] + ] + ], + 'site' => [ + 'children' => [ + [ + 'slug' => 'test', + 'template' => 'test', + ] + ] + ] + ]); + + $app->impersonate('kirby'); + $page = $app->page('test'); + + $response = $app->api()->sectionApi($page, 'test', 'message'); + $this->assertSame('Test', $response['message']); + } + + public function testSectionApiWithInvalidSection() + { + $app = $this->app->clone([ + 'site' => [ + 'children' => [ + ['slug' => 'test'] + ] + ] + ]); + + $this->expectException(NotFoundException::class); + $this->expectExceptionMessage('The field could not be found'); + + $page = $app->page('test'); + $app->api()->sectionApi($page, 'nonexists'); + } + public function testUpload() { $api = new Api([ diff --git a/tests/Cms/Api/ApiCollectionTestCase.php b/tests/Api/CollectionTestCase.php similarity index 79% rename from tests/Cms/Api/ApiCollectionTestCase.php rename to tests/Api/CollectionTestCase.php index da902f127a..a75317eb2f 100644 --- a/tests/Cms/Api/ApiCollectionTestCase.php +++ b/tests/Api/CollectionTestCase.php @@ -1,12 +1,11 @@ api->collection( + 'translations', + $this->app->translations()->filter('id', 'en') + ); + $result = $collection->toArray(); + + $this->assertCount(1, $result); + $this->assertSame('en', $result[0]['id']); + } +} diff --git a/tests/Cms/Api/collections/UsersApiCollectionTest.php b/tests/Api/collections/UsersCollectionTest.php similarity index 88% rename from tests/Cms/Api/collections/UsersApiCollectionTest.php rename to tests/Api/collections/UsersCollectionTest.php index 2809299b14..d1f91ee608 100644 --- a/tests/Cms/Api/collections/UsersApiCollectionTest.php +++ b/tests/Api/collections/UsersCollectionTest.php @@ -1,11 +1,11 @@ app = new App([ - 'roots' => [ - 'index' => static::TMP - ], - 'site' => [ - 'children' => [ - [ - 'slug' => 'a', - 'children' => [ - [ - 'slug' => 'aa' - ], - [ - 'slug' => 'ab' - ] - ], - 'files' => [ - [ - 'filename' => 'a-regular-file.jpg', - ], - [ - 'filename' => 'a filename with spaces.jpg', - ] - ] - ], - [ - 'slug' => 'b' - ] - ] - ], - 'options' => [ - 'api' => [ - 'allowImpersonation' => true, - 'authentication' => fn () => true, - 'routes' => [ - [ - 'pattern' => 'foo', - 'method' => 'GET', - 'action' => fn () => 'something' - ] - ] - ], - 'locale' => 'de_DE.UTF-8' - ], - ]); - - $this->app->impersonate('kirby'); - $this->api = $this->app->api(); - - $this->locale = setlocale(LC_ALL, 0); - Dir::make(static::TMP); - } - - public function tearDown(): void - { - Dir::remove(static::TMP); - setlocale(LC_ALL, $this->locale); - } - - public function testCallLocaleSingleLang1() - { - setlocale(LC_ALL, 'C'); - $this->assertSame('C', setlocale(LC_ALL, 0)); - - $this->assertSame('something', $this->api->call('foo')); - $this->assertSame('de_DE.UTF-8', setlocale(LC_ALL, 0)); - } - - public function testCallLocaleSingleLang2() - { - setlocale(LC_ALL, 'C'); - $this->assertSame('C', setlocale(LC_ALL, 0)); - - $_GET['language'] = 'en'; - - $this->assertSame('something', $this->api->call('foo')); - $this->assertSame('de_DE.UTF-8', setlocale(LC_ALL, 0)); - - $_GET = []; - } - - public function testCallLocaleMultiLang1() - { - setlocale(LC_ALL, 'C'); - $this->assertSame('C', setlocale(LC_ALL, 0)); - - $this->app = $this->app->clone([ - 'languages' => [ - [ - 'code' => 'en', - 'name' => 'English', - 'default' => true, - 'locale' => 'en_US.UTF-8', - 'url' => '/', - ], - [ - 'code' => 'de', - 'name' => 'Deutsch', - 'locale' => 'de_AT.UTF-8', - 'url' => '/de', - ], - ] - ]); - $this->api = $this->app->api(); - - $this->assertSame('something', $this->api->call('foo')); - $this->assertSame('en_US.UTF-8', setlocale(LC_ALL, 0)); - } - - public function testCallLocaleMultiLang2() - { - setlocale(LC_ALL, 'C'); - $this->assertSame('C', setlocale(LC_ALL, 0)); - - $this->app = $this->app->clone([ - 'languages' => [ - [ - 'code' => 'en', - 'name' => 'English', - 'default' => true, - 'locale' => 'en_US.UTF-8', - 'url' => '/', - ], - [ - 'code' => 'de', - 'name' => 'Deutsch', - 'locale' => 'de_AT.UTF-8', - 'url' => '/de', - ], - ] - ]); - $this->api = $this->app->api(); - - $this->assertSame('something', $this->api->call('foo', 'GET', [ - 'query' => ['language' => 'de'] - ])); - $this->assertSame('de_AT.UTF-8', setlocale(LC_ALL, 0)); - - $_GET = []; - } - - public function testCallTranslation() - { - // with logged in user with language - $app = $this->app->clone([ - 'users' => [ - [ - 'email' => 'homer@simpsons.com', - 'language' => 'fr' - ] - ] - ]); - $app->impersonate('homer@simpsons.com'); - - $api = $app->api(); - $this->assertSame('something', $api->call('foo')); - $this->assertSame('fr', I18n::$locale); - - // with logged in user without language - $app = $this->app->clone([ - 'users' => [ - [ - 'email' => 'homer@simpsons.com' - ] - ], - 'languages' => [ - [ - 'code' => 'it-it', - 'default' => true, - ] - ], - 'options' => [ - 'panel.language' => 'de' - ] - ]); - $app->impersonate('homer@simpsons.com'); - - $api = $app->api(); - $this->assertSame('something', $api->call('foo')); - $this->assertSame('de', I18n::$locale); - - // with logged in user without language without Panel language - $app = $this->app->clone([ - 'users' => [ - [ - 'email' => 'homer@simpsons.com' - ] - ], - 'languages' => [ - [ - 'code' => 'it-it', - 'default' => true, - ] - ] - ]); - $app->impersonate('homer@simpsons.com'); - - $api = $app->api(); - $this->assertSame('something', $api->call('foo')); - $this->assertSame('it', I18n::$locale); - - // with logged in user without any configuration - $app = $this->app->clone([ - 'users' => [ - [ - 'email' => 'homer@simpsons.com' - ] - ] - ]); - $app->impersonate('homer@simpsons.com'); - - $api = $app->api(); - $this->assertSame('something', $api->call('foo')); - $this->assertSame('en', I18n::$locale); - - // without logged in user - $app = $this->app->clone([ - 'languages' => [ - [ - 'code' => 'it-it', - 'default' => true, - ] - ], - 'options' => [ - 'panel.language' => 'de' - ] - ]); - - $api = $app->api(); - $this->assertSame('something', $api->call('foo')); - $this->assertSame('de', I18n::$locale); - - // without logged in user without Panel language - $app = $this->app->clone([ - 'languages' => [ - [ - 'code' => 'it-it', - 'default' => true, - ] - ] - ]); - - $api = $app->api(); - $this->assertSame('something', $api->call('foo')); - $this->assertSame('it', I18n::$locale); - - // without logged in user without any configuration - $app = $this->app->clone(); - $api = $app->api(); - $this->assertSame('something', $api->call('foo')); - $this->assertSame('en', I18n::$locale); - } - - public function testLanguage() - { - $api = $this->api->clone([ - 'requestData' => [ - 'headers' => [ - 'x-language' => 'de' - ] - ] - ]); - - $this->assertSame('de', $api->language()); - } - - public function testFile() - { - $app = $this->app->clone([ - 'site' => [ - 'children' => [ - [ - 'slug' => 'a', - 'files' => [ - ['filename' => 'test.jpg'] - ], - 'children' => [ - [ - 'slug' => 'a', - 'files' => [ - ['filename' => 'test.jpg'] - ], - ] - ] - ] - ], - 'files' => [ - ['filename' => 'test.jpg'] - ] - ], - 'users' => [ - [ - 'email' => 'test@getkirby.com', - 'files' => [ - ['filename' => 'test.jpg'] - ] - ] - ] - ]); - - $app->impersonate('kirby'); - $api = $app->api(); - - $this->assertSame('test.jpg', $api->file('site', 'test.jpg')->filename()); - $this->assertSame('test.jpg', $api->file('pages/a', 'test.jpg')->filename()); - $this->assertSame('test.jpg', $api->file('pages/a+a', 'test.jpg')->filename()); - $this->assertSame('test.jpg', $api->file('users/test@getkirby.com', 'test.jpg')->filename()); - } - - public function testFileNotFound() - { - $this->expectException(NotFoundException::class); - $this->expectExceptionMessage('The file "nope.jpg" cannot be found'); - - $this->api->file('site', 'nope.jpg'); - } - - public function testFileNotReadable() - { - $this->expectException(NotFoundException::class); - $this->expectExceptionMessage('The file "protected.jpg" cannot be found'); - - $app = $this->app->clone([ - 'blueprints' => [ - 'files/protected' => [ - 'options' => ['read' => false] - ] - ], - 'site' => [ - 'files' => [ - ['filename' => 'protected.jpg', 'template' => 'protected'] - ] - ] - ]); - - $this->api->file('site', 'protected.jpg'); - } - - public function testPage() - { - $a = $this->app->page('a'); - $aa = $this->app->page('a/aa'); - - $this->assertSame($a, $this->api->page('a')); - $this->assertSame($aa, $this->api->page('a+aa')); - - $this->expectException(NotFoundException::class); - $this->expectExceptionMessage('The page "does-not-exist" cannot be found'); - $this->api->page('does-not-exist'); - } - - public function testPages() - { - $this->assertSame(['a/aa', 'a/ab'], $this->api->pages('a')->keys()); - } - - public function testPagesNotAccessible() - { - $app = $this->app->clone([ - 'blueprints' => [ - 'pages/api-protected' => [ - 'options' => ['access' => false] - ] - ], - 'site' => [ - 'children' => [ - [ - 'slug' => 'a' - ], - [ - 'slug' => 'b', - 'template' => 'api-protected' - ], - [ - 'slug' => 'c' - ] - ] - ], - 'users' => [ - ['id' => 'bastian', 'role' => 'admin'] - ] - ]); - - $app->impersonate('bastian'); - - $this->assertSame(['a', 'c'], $app->api()->pages()->keys()); - } - - public function testUser() - { - $app = $this->app->clone([ - 'users' => [ - [ - 'email' => 'current@getkirby.com', - ], - [ - 'email' => 'test@getkirby.com', - ] - ], - ]); - - $app->impersonate('current@getkirby.com'); - $api = $app->api(); - - $this->assertSame('current@getkirby.com', $api->user()->email()); - $this->assertSame('test@getkirby.com', $api->user('test@getkirby.com')->email()); - - $this->expectException(NotFoundException::class); - $this->expectExceptionMessage('The user "nope@getkirby.com" cannot be found'); - $this->api->user('nope@getkirby.com'); - } - - public function testUsers() - { - $this->assertSame($this->app->users(), $this->api->users()); - } - - public function testFileGetRoute() - { - // regular - $result = $this->api->call('pages/a/files/a-regular-file.jpg', 'GET'); - - $this->assertSame(200, $result['code']); - $this->assertSame('a-regular-file.jpg', $result['data']['filename']); - - // with spaces in filename - $result = $this->api->call('pages/a/files/a filename with spaces.jpg', 'GET'); - - $this->assertSame(200, $result['code']); - $this->assertSame('a filename with spaces.jpg', $result['data']['filename']); - } - - public function testAuthenticationWithoutCsrf() - { - $auth = $this->createMock(Auth::class); - $auth->method('type')->willReturn('session'); - $auth->method('csrf')->willReturn(false); - - $kirby = $this->createMock(App::class); - $kirby->method('auth')->willReturn($auth); - - $this->expectException(AuthException::class); - $this->expectExceptionMessage('Unauthenticated'); - - $function = require $this->app->root('kirby') . '/config/api/authentication.php'; - - $api = new Api([ - 'kirby' => $kirby - ]); - - $function->call($api); - } - - public function testAuthenticationWithoutUser() - { - $auth = $this->createMock(Auth::class); - $auth->method('user')->willReturn(null); - - $kirby = $this->createMock(App::class); - $kirby->method('auth')->willReturn($auth); - - $this->expectException(AuthException::class); - $this->expectExceptionMessage('Unauthenticated'); - - $function = require $this->app->root('kirby') . '/config/api/authentication.php'; - - $api = new Api([ - 'kirby' => $kirby - ]); - - $function->call($api); - } - - public function testParent() - { - $app = $this->app->clone([ - 'site' => [ - 'files' => [ - ['filename' => 'sitefile.jpg'] - ] - ], - 'users' => [ - [ - 'email' => 'current@getkirby.com', - 'role' => 'admin' - ], - [ - 'email' => 'test@getkirby.com', - 'files' => [ - ['filename' => 'userfile.jpg'] - ] - ] - ], - ]); - - $app->impersonate('current@getkirby.com'); - - $api = $app->api(); - - $this->assertIsUser($api->parent('account')); - $this->assertIsUser($api->parent('users/test@getkirby.com')); - $this->assertIsSite($api->parent('site')); - $this->assertIsPage($api->parent('pages/a+aa')); - $this->assertIsFile($api->parent('site/files/sitefile.jpg')); - $this->assertIsFile($api->parent('pages/a/files/a-regular-file.jpg')); - $this->assertIsFile($api->parent('users/test@getkirby.com/files/userfile.jpg')); - - // model type is not recognized - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid model type: something'); - $this->assertNull($api->parent('something/something')); - - // model cannot be found - $this->expectException(NotFoundException::class); - $this->expectExceptionMessage('The page cannot be found'); - $this->assertNull($api->parent('pages/does-not-exist')); - } - - public function testFieldApi() - { - $app = $this->app->clone([ - 'site' => [ - 'children' => [ - [ - 'slug' => 'test', - 'content' => [ - 'title' => 'Test Title', - 'cover' => [ - 'a.jpg' - ] - ], - 'files' => [ - ['filename' => 'a.jpg'], - ['filename' => 'b.jpg'], - ], - 'blueprint' => [ - 'title' => 'Test', - 'name' => 'test', - 'fields' => [ - 'cover' => [ - 'type' => 'files', - ] - ] - ] - ] - ] - ] - ]); - $app->impersonate('kirby'); - - $page = $app->page('test'); - $response = $app->api()->fieldApi($page, 'cover'); - - $this->assertCount(2, $response); - $this->assertArrayHasKey('data', $response); - $this->assertArrayHasKey('pagination', $response); - $this->assertCount(2, $response['data']); - $this->assertSame('a.jpg', $response['data'][0]['filename']); - $this->assertSame('b.jpg', $response['data'][1]['filename']); - } - - public function testFieldApiInvalidField() - { - $app = $this->app->clone([ - 'site' => [ - 'children' => [ - ['slug' => 'test'] - ] - ] - ]); - - $this->expectException(NotFoundException::class); - $this->expectExceptionMessage('The field could not be found'); - - $page = $app->page('test'); - $app->api()->fieldApi($page, 'nonexists'); - } - - public function testFieldApiEmptyField() - { - $app = $this->app->clone([ - 'site' => [ - 'children' => [ - ['slug' => 'test'] - ] - ] - ]); - - $this->expectException(NotFoundException::class); - $this->expectExceptionMessage('The field could not be found'); - - $page = $app->page('test'); - $app->api()->fieldApi($page, ''); - } - - public function testRenderExceptionWithDebugging() - { - // simulate the document root to test relative file paths - $app = $this->app->clone([ - 'server' => [ - 'DOCUMENT_ROOT' => __DIR__ - ] - ]); - - $api = new Api([ - 'debug' => true, - 'kirby' => $app, - 'routes' => [ - [ - 'pattern' => 'test', - 'method' => 'POST', - 'action' => function () { - throw new Exception('nope'); - } - ] - ] - ]); - - $result = $api->render('test', 'POST'); - - $expected = [ - 'status' => 'error', - 'message' => 'nope', - 'code' => 500, - 'exception' => 'Exception', - 'key' => null, - 'file' => '/' . basename(__FILE__), - 'line' => __LINE__ - 15, - 'details' => [], - 'route' => 'test' - ]; - - $this->assertInstanceOf(Response::class, $result); - $this->assertSame(json_encode($expected), $result->body()); - } - - public function testSectionApi() - { - $app = $this->app->clone([ - 'sections' => [ - 'test' => [ - 'api' => fn () => [ - [ - 'pattern' => '/message', - 'action' => fn () => [ - 'message' => 'Test' - ] - ] - ] - ] - ], - 'blueprints' => [ - 'pages/test' => [ - 'title' => 'Test', - 'name' => 'test', - 'sections' => [ - 'test' => [ - 'type' => 'test', - ] - ] - ] - ], - 'site' => [ - 'children' => [ - [ - 'slug' => 'test', - 'template' => 'test', - ] - ] - ] - ]); - - $app->impersonate('kirby'); - $page = $app->page('test'); - - $response = $app->api()->sectionApi($page, 'test', 'message'); - $this->assertSame('Test', $response['message']); - } - - public function testSectionApiWithInvalidSection() - { - $app = $this->app->clone([ - 'site' => [ - 'children' => [ - ['slug' => 'test'] - ] - ] - ]); - - $this->expectException(NotFoundException::class); - $this->expectExceptionMessage('The section "nonexists" could not be found'); - - $page = $app->page('test'); - $app->api()->sectionApi($page, 'nonexists'); - } -} diff --git a/tests/Cms/Api/collections/TranslationsApiCollectionTest.php b/tests/Cms/Api/collections/TranslationsApiCollectionTest.php deleted file mode 100644 index 9538cda2c5..0000000000 --- a/tests/Cms/Api/collections/TranslationsApiCollectionTest.php +++ /dev/null @@ -1,17 +0,0 @@ -api->collection('translations', $this->app->translations()->filter('id', 'en')); - $result = $collection->toArray(); - - $this->assertCount(1, $result); - $this->assertSame('en', $result[0]['id']); - } -} diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 765ee12590..8ecaef5971 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -44,7 +44,6 @@ 'Kirby\\Cache\\NullCache' => $baseDir . '/src/Cache/NullCache.php', 'Kirby\\Cache\\RedisCache' => $baseDir . '/src/Cache/RedisCache.php', 'Kirby\\Cache\\Value' => $baseDir . '/src/Cache/Value.php', - 'Kirby\\Cms\\Api' => $baseDir . '/src/Cms/Api.php', 'Kirby\\Cms\\App' => $baseDir . '/src/Cms/App.php', 'Kirby\\Cms\\AppCaches' => $baseDir . '/src/Cms/AppCaches.php', 'Kirby\\Cms\\AppErrors' => $baseDir . '/src/Cms/AppErrors.php', diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 1ef5089861..794d4ab2f2 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -164,7 +164,6 @@ class ComposerStaticInit0bf5c8a9cfa251a218fc581ac888fe35 'Kirby\\Cache\\NullCache' => __DIR__ . '/../..' . '/src/Cache/NullCache.php', 'Kirby\\Cache\\RedisCache' => __DIR__ . '/../..' . '/src/Cache/RedisCache.php', 'Kirby\\Cache\\Value' => __DIR__ . '/../..' . '/src/Cache/Value.php', - 'Kirby\\Cms\\Api' => __DIR__ . '/../..' . '/src/Cms/Api.php', 'Kirby\\Cms\\App' => __DIR__ . '/../..' . '/src/Cms/App.php', 'Kirby\\Cms\\AppCaches' => __DIR__ . '/../..' . '/src/Cms/AppCaches.php', 'Kirby\\Cms\\AppErrors' => __DIR__ . '/../..' . '/src/Cms/AppErrors.php',