diff --git a/composer.json b/composer.json index 696383c3c73b5c47cdec2fd7ff38435527e1c5b2..03104690821eecacafc0bc3769e2b445d7ecf8e2 100644 --- a/composer.json +++ b/composer.json @@ -52,6 +52,7 @@ "symfony/console": "~5.3.16", "symfony/process": "^5.4", "jumbojett/openid-connect-php": "^0.9.2", - "league/oauth2-server": "^8.3" + "league/oauth2-server": "^8.3", + "willdurand/negotiation": "^3.1" } } diff --git a/composer.lock b/composer.lock index 1acf6b621f56dd66b807391420cc71235d908a73..29f2142a9d7a88e42f325040f9f05d816e019aa7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c5b753821c3aa77c5de49e8932b47994", + "content-hash": "7a9043e1984ae4fb02e5872928486f0a", "packages": [ { "name": "algo26-matthias/idna-convert", @@ -4019,6 +4019,62 @@ } ], "time": "2021-09-14T12:46:25+00:00" + }, + { + "name": "willdurand/negotiation", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/willdurand/Negotiation.git", + "reference": "68e9ea0553ef6e2ee8db5c1d98829f111e623ec2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/willdurand/Negotiation/zipball/68e9ea0553ef6e2ee8db5c1d98829f111e623ec2", + "reference": "68e9ea0553ef6e2ee8db5c1d98829f111e623ec2", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Negotiation\\": "src/Negotiation" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "William Durand", + "email": "will+git@drnd.me" + } + ], + "description": "Content Negotiation tools for PHP provided as a standalone library.", + "homepage": "http://williamdurand.fr/Negotiation/", + "keywords": [ + "accept", + "content", + "format", + "header", + "negotiation" + ], + "support": { + "issues": "https://github.com/willdurand/Negotiation/issues", + "source": "https://github.com/willdurand/Negotiation/tree/3.1.0" + }, + "time": "2022-01-30T20:08:53+00:00" } ], "packages-dev": [ diff --git a/lib/classes/JsonApi/Middlewares/Language.php b/lib/classes/JsonApi/Middlewares/Language.php new file mode 100644 index 0000000000000000000000000000000000000000..8843445665d6bfc7b7f44645b3f5c9b779aac479 --- /dev/null +++ b/lib/classes/JsonApi/Middlewares/Language.php @@ -0,0 +1,89 @@ +<?php +namespace JsonApi\Middlewares; + +use Negotiation\LanguageNegotiator; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Server\RequestHandlerInterface as RequestHandler; + +/** + * This class defines a middleware that tries to set the language for Stud.IP + * by analyzing the HTTP header "Accept-Language". + * + * @author Jan-Hendrik Willms <tleilax+studip@gmail.com> + */ +class Language +{ + /** + * @param Request $request das Request-Objekt + * @param RequestHandler $handler der PSR-15 Request Handler + * + * @return ResponseInterface das neue Response-Objekt + */ + public function __invoke(Request $request, RequestHandler $handler) + { + $language = $this->detectValidLanguageFromRequest($request); + + // Set language if detected + if ($language) { + $_SESSION['_language'] = $language; + setTempLanguage(false, $language); + } + + return $handler->handle($request); + } + + /** + * Tries to detect a valid installed language from the request. + * + * @param Request $request + * @return string|null The detected language (if any) + */ + private function detectValidLanguageFromRequest(Request $request): ?string + { + if (!$request->hasHeader('Accept-Language')) { + return null; + } + + $negotiator = new LanguageNegotiator(); + $best_language = $negotiator->getBest( + $request->getHeaderLine('Accept-Language'), + $this->getStudIPLanguagePriorities() + ); + + if (!$best_language) { + return null; + } + + return $this->normalizeLanguageForStudIP($best_language->getType()); + } + + /** + * Returns a list of the normalized installed languages for the Stud.IP + * system. + * + * @return array + */ + private function getStudIPLanguagePriorities(): array + { + return array_map( + function ($language) { + return str_replace('_', '-', $language); + }, + array_keys($GLOBALS['INSTALLED_LANGUAGES']) + ); + } + + /** + * Normalizes the given language string (<language>-<variety>, e.g. de-de) + * for Stud.IP (e.g. de_DE). + * + * @param string $language + * @return string + */ + private function normalizeLanguageForStudIP(string $language): string + { + $tags = explode('-', $language); + return $tags[0] . '_' . strtoupper($tags[1]); + } +} diff --git a/lib/classes/JsonApi/middleware.php b/lib/classes/JsonApi/middleware.php index 7a318d5b7cad34007153df713b579536ffb2b75d..f65231c886184106d15a54f1a4a9b68df63f84cd 100644 --- a/lib/classes/JsonApi/middleware.php +++ b/lib/classes/JsonApi/middleware.php @@ -26,6 +26,9 @@ return function (App $app) { // Add Routing Middleware $app->addRoutingMiddleware(); + // Add language middleware + $app->add(new Middlewares\Language()); + /** @var array|null */ $corsOrigin = \Config::get()->getValue('JSONAPI_CORS_ORIGIN'); if (is_array($corsOrigin) && count($corsOrigin)) {