diff --git a/src/bundle/Resources/config/routing.yml b/src/bundle/Resources/config/routing.yml
index ba95b55..2663e92 100644
--- a/src/bundle/Resources/config/routing.yml
+++ b/src/bundle/Resources/config/routing.yml
@@ -383,6 +383,18 @@ ezpublish_rest_setObjectStatesForContent:
contentId: \d+
+# Languages
+
+ibexa.rest.languages.list:
+ path: /languages
+ methods: [GET]
+ controller: Ibexa\Rest\Server\Controller\Language::listLanguages
+
+ibexa.rest.languages.view:
+ path: /languages/{languageCode}
+ methods: [GET]
+ controller: Ibexa\Rest\Server\Controller\Language::loadLanguage
+
# Locations
diff --git a/src/bundle/Resources/config/services.yml b/src/bundle/Resources/config/services.yml
index 4eede10..fc814a1 100644
--- a/src/bundle/Resources/config/services.yml
+++ b/src/bundle/Resources/config/services.yml
@@ -177,6 +177,10 @@ services:
- "@ezpublish.api.service.location"
tags: [controller.service_arguments]
+ Ibexa\Rest\Server\Controller\Language:
+ autowire: true
+ tags: [ controller.service_arguments ]
+
ezpublish_rest.controller.location:
class: "%ezpublish_rest.controller.location.class%"
parent: ezpublish_rest.controller.base
diff --git a/src/bundle/Resources/config/value_object_visitors.yml b/src/bundle/Resources/config/value_object_visitors.yml
index 1273d15..4c76b2e 100644
--- a/src/bundle/Resources/config/value_object_visitors.yml
+++ b/src/bundle/Resources/config/value_object_visitors.yml
@@ -206,6 +206,11 @@ services:
- { name: ezpublish_rest.output.value_object_visitor, type: Symfony\Component\HttpKernel\Exception\HttpException }
# Language
+ Ibexa\Rest\Server\Output\ValueObjectVisitor\LanguageList:
+ parent: Ibexa\Contracts\Rest\Output\ValueObjectVisitor
+ tags:
+ - { name: ibexa.rest.output.value_object.visitor, type: Ibexa\Rest\Server\Values\LanguageList }
+
ezpublish_rest.output.value_object_visitor.Language:
parent: ezpublish_rest.output.value_object_visitor.base
class: EzSystems\EzPlatformRest\Server\Output\ValueObjectVisitor\Language
diff --git a/src/lib/Server/Controller/Language.php b/src/lib/Server/Controller/Language.php
new file mode 100644
index 0000000..9c005c8
--- /dev/null
+++ b/src/lib/Server/Controller/Language.php
@@ -0,0 +1,41 @@
+languageService = $languageService;
+ }
+
+ public function listLanguages(): LanguageList
+ {
+ $languages = $this->languageService->loadLanguages();
+
+ if ($languages instanceof Traversable) {
+ $languages = iterator_to_array($languages);
+ }
+
+ return new LanguageList($languages);
+ }
+
+ public function loadLanguage(string $languageCode): ApiLanguage
+ {
+ return $this->languageService->loadLanguage($languageCode);
+ }
+}
diff --git a/src/lib/Server/Output/ValueObjectVisitor/Language.php b/src/lib/Server/Output/ValueObjectVisitor/Language.php
index 8e05eee..69272aa 100644
--- a/src/lib/Server/Output/ValueObjectVisitor/Language.php
+++ b/src/lib/Server/Output/ValueObjectVisitor/Language.php
@@ -28,8 +28,15 @@ public function visit(Visitor $visitor, Generator $generator, $data): void
private function visitLanguageAttributes(Visitor $visitor, Generator $generator, LanguageValue $language): void
{
- $generator->valueElement('languageId', $language->id);
- $generator->valueElement('languageCode', $language->languageCode);
- $generator->valueElement('name', $language->name);
+ $generator->attribute(
+ 'href',
+ $this->router->generate(
+ 'ibexa.rest.languages.view',
+ ['languageCode' => $language->getLanguageCode()],
+ ),
+ );
+ $generator->valueElement('languageId', $language->getId());
+ $generator->valueElement('languageCode', $language->getLanguageCode());
+ $generator->valueElement('name', $language->getName());
}
}
diff --git a/src/lib/Server/Output/ValueObjectVisitor/LanguageList.php b/src/lib/Server/Output/ValueObjectVisitor/LanguageList.php
new file mode 100644
index 0000000..c9c82d4
--- /dev/null
+++ b/src/lib/Server/Output/ValueObjectVisitor/LanguageList.php
@@ -0,0 +1,35 @@
+startObjectElement('LanguageList');
+ $visitor->setHeader('Content-Type', $generator->getMediaType('LanguageList'));
+
+ $generator->attribute('href', $this->router->generate('ibexa.rest.languages.list'));
+
+ $generator->startList('Language');
+ foreach ($data->languages as $language) {
+ $visitor->visitValueObject($language);
+ }
+ $generator->endList('Language');
+
+ $generator->endObjectElement('LanguageList');
+ }
+}
diff --git a/src/lib/Server/Values/LanguageList.php b/src/lib/Server/Values/LanguageList.php
new file mode 100644
index 0000000..b8ff3ac
--- /dev/null
+++ b/src/lib/Server/Values/LanguageList.php
@@ -0,0 +1,25 @@
+ $languages
+ */
+ public function __construct(array $languages)
+ {
+ $this->languages = $languages;
+ }
+}
diff --git a/tests/bundle/Functional/JsonSchema/Language.json b/tests/bundle/Functional/JsonSchema/Language.json
new file mode 100644
index 0000000..427ed75
--- /dev/null
+++ b/tests/bundle/Functional/JsonSchema/Language.json
@@ -0,0 +1,38 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type": "object",
+ "properties": {
+ "Language": {
+ "type": "object",
+ "properties": {
+ "_media-type": {
+ "type": "string"
+ },
+ "_href": {
+ "type": "string"
+ },
+ "languageId": {
+ "type": "integer"
+ },
+ "languageCode": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[[:alnum:]_]+"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_media-type",
+ "_href",
+ "languageId",
+ "languageCode",
+ "name"
+ ]
+ }
+ },
+ "required": [
+ "Language"
+ ]
+}
diff --git a/tests/bundle/Functional/JsonSchema/LanguageList.json b/tests/bundle/Functional/JsonSchema/LanguageList.json
new file mode 100644
index 0000000..c26ce25
--- /dev/null
+++ b/tests/bundle/Functional/JsonSchema/LanguageList.json
@@ -0,0 +1,56 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type": "object",
+ "properties": {
+ "LanguageList": {
+ "type": "object",
+ "properties": {
+ "_media-type": {
+ "type": "string"
+ },
+ "_href": {
+ "type": "string"
+ },
+ "Language": {
+ "type":"array",
+ "items": {
+ "properties": {
+ "_media-type": {
+ "type": "string"
+ },
+ "_href": {
+ "type": "string"
+ },
+ "languageId": {
+ "type": "integer"
+ },
+ "languageCode": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[[:alnum:]_]+"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_media-type",
+ "_href",
+ "languageId",
+ "languageCode",
+ "name"
+ ]
+ }
+ }
+ },
+ "required": [
+ "_media-type",
+ "_href",
+ "Language"
+ ]
+ }
+ },
+ "required": [
+ "LanguageList"
+ ]
+}
diff --git a/tests/bundle/Functional/LanguageTest.php b/tests/bundle/Functional/LanguageTest.php
new file mode 100644
index 0000000..f56f1e3
--- /dev/null
+++ b/tests/bundle/Functional/LanguageTest.php
@@ -0,0 +1,64 @@
+createHttpRequest('GET', '/api/ibexa/v2/languages', '', 'LanguageList+json');
+ $response = $this->sendHttpRequest($request);
+
+ self::assertHttpResponseCodeEquals($response, 200);
+ $content = $response->getBody()->getContents();
+ self::assertJson($content);
+
+ self::assertJsonResponseIsValid($content, 'LanguageList');
+ self::assertResponseMatchesJsonSnapshot($content, self::SNAPSHOT_DIR . '/LanguageList.json');
+ }
+
+ public function testLanguageListXml(): void
+ {
+ $request = $this->createHttpRequest('GET', '/api/ibexa/v2/languages');
+ $response = $this->sendHttpRequest($request);
+
+ self::assertHttpResponseCodeEquals($response, 200);
+ $content = $response->getBody()->getContents();
+ self::assertResponseMatchesXmlSnapshot($content, self::SNAPSHOT_DIR . '/LanguageList.xml');
+ }
+
+ public function testLanguageViewJson(): void
+ {
+ $request = $this->createHttpRequest('GET', '/api/ibexa/v2/languages/eng-GB', '', 'LanguageList+json');
+ $response = $this->sendHttpRequest($request);
+
+ self::assertHttpResponseCodeEquals($response, 200);
+ $content = $response->getBody()->getContents();
+ self::assertJson($content);
+
+ self::assertJsonResponseIsValid($content, 'Language');
+ self::assertResponseMatchesJsonSnapshot($content, self::SNAPSHOT_DIR . '/Language.json');
+ }
+
+ public function testLanguageViewXml(): void
+ {
+ $request = $this->createHttpRequest('GET', '/api/ibexa/v2/languages/eng-GB');
+ $response = $this->sendHttpRequest($request);
+
+ self::assertHttpResponseCodeEquals($response, 200);
+ $content = $response->getBody()->getContents();
+ self::assertResponseMatchesXmlSnapshot($content, self::SNAPSHOT_DIR . '/Language.xml');
+ }
+}
diff --git a/tests/bundle/Functional/ResourceAssertionsTrait.php b/tests/bundle/Functional/ResourceAssertionsTrait.php
new file mode 100644
index 0000000..8e55183
--- /dev/null
+++ b/tests/bundle/Functional/ResourceAssertionsTrait.php
@@ -0,0 +1,123 @@
+ 'file://' . self::getSchemaFileLocation($resource),
+ ];
+
+ $validator->validate($decodedData, $schemaReference);
+
+ self::assertTrue($validator->isValid(), self::convertErrorsToString($validator, $data));
+ }
+
+ private static function convertErrorsToString(Validator $validator, string $data): string
+ {
+ $errorMessage = '';
+ foreach ($validator->getErrors() as $error) {
+ $errorMessage .= sprintf(
+ "property: [%s], constraint: %s, error: %s\n",
+ $error['property'],
+ $error['constraint'],
+ $error['message']
+ );
+ }
+
+ $errorMessage .= "\n\nFor data:\n\n" . $data;
+
+ return $errorMessage;
+ }
+
+ private static function getSchemaFileLocation(string $resource): string
+ {
+ return __DIR__ . '/JsonSchema/' . $resource . '.json';
+ }
+
+ private static function checkSnapshotFileExistence(string $file, string $content): void
+ {
+ if (file_exists($file)) {
+ return;
+ }
+
+ if ($_ENV['IBEXA_REST_GENERATE_SNAPSHOTS'] ?? false) {
+ file_put_contents($file, rtrim($content, "\n") . "\n");
+
+ return;
+ }
+
+ self::fail(sprintf(
+ 'File %s does not exist. If it\'s a new REST route, add environment variable "%s" to phpunit.xml '
+ . '(or environment) set to truthy value to enable snapshot generation.',
+ $file,
+ 'IBEXA_REST_GENERATE_SNAPSHOTS',
+ ));
+ }
+
+ private static function getDefaultSnapshotFileLocation(?string $type): string
+ {
+ $classInfo = new \ReflectionClass(static::class);
+ $class = substr(static::class, strrpos(static::class, '\\') + 1);
+ $classFilename = $classInfo->getFileName();
+ self::assertNotFalse($classFilename);
+
+ return dirname($classFilename) . '/_snapshot/' . $class . '.' . ($type ?? 'log');
+ }
+}
diff --git a/tests/bundle/Functional/_snapshot/Language.json b/tests/bundle/Functional/_snapshot/Language.json
new file mode 100644
index 0000000..e82ba70
--- /dev/null
+++ b/tests/bundle/Functional/_snapshot/Language.json
@@ -0,0 +1,9 @@
+{
+ "Language": {
+ "_media-type": "application\/vnd.ibexa.api.Language+json",
+ "_href": "\/api\/ibexa\/v2\/languages\/eng-GB",
+ "languageId": 2,
+ "languageCode": "eng-GB",
+ "name": "English (United Kingdom)"
+ }
+}
diff --git a/tests/bundle/Functional/_snapshot/Language.xml b/tests/bundle/Functional/_snapshot/Language.xml
new file mode 100644
index 0000000..43519f1
--- /dev/null
+++ b/tests/bundle/Functional/_snapshot/Language.xml
@@ -0,0 +1,6 @@
+
+
+ 2
+ eng-GB
+ English (United Kingdom)
+
diff --git a/tests/bundle/Functional/_snapshot/LanguageList.json b/tests/bundle/Functional/_snapshot/LanguageList.json
new file mode 100644
index 0000000..b9eb538
--- /dev/null
+++ b/tests/bundle/Functional/_snapshot/LanguageList.json
@@ -0,0 +1,21 @@
+{
+ "LanguageList": {
+ "_media-type": "application\/vnd.ibexa.api.LanguageList+json",
+ "_href": "\/api\/ibexa\/v2\/languages",
+ "Language": [
+ {
+ "_media-type": "application\/vnd.ibexa.api.Language+json",
+ "_href": "\/api\/ibexa\/v2\/languages\/eng-GB",
+ "languageId": 2,
+ "languageCode": "eng-GB",
+ "name": "English (United Kingdom)"
+ }, {
+ "_href": "/api/ibexa/v2/languages/pol-PL",
+ "_media-type": "application/vnd.ibexa.api.Language+json",
+ "languageCode": "pol-PL",
+ "languageId": 4,
+ "name": "Polish (polski)"
+ }
+ ]
+ }
+}
diff --git a/tests/bundle/Functional/_snapshot/LanguageList.xml b/tests/bundle/Functional/_snapshot/LanguageList.xml
new file mode 100644
index 0000000..f61cfa7
--- /dev/null
+++ b/tests/bundle/Functional/_snapshot/LanguageList.xml
@@ -0,0 +1,13 @@
+
+
+
+ 2
+ eng-GB
+ English (United Kingdom)
+
+
+ 4
+ pol-PL
+ Polish (polski)
+
+