From dcf42b6a1f5a1b47d79fa2d2c06e2e4b07a9024f Mon Sep 17 00:00:00 2001
From: Jan-Hendrik Willms <tleilax+studip@gmail.com>
Date: Fri, 9 Sep 2022 11:07:12 +0000
Subject: [PATCH] add language middleware that sets the system's language from
 a given...

Closes #1568

Merge request studip/studip!993
---
 composer.json                                |  3 +-
 composer.lock                                | 58 ++++++++++++-
 lib/classes/JsonApi/Middlewares/Language.php | 89 ++++++++++++++++++++
 lib/classes/JsonApi/middleware.php           |  3 +
 4 files changed, 151 insertions(+), 2 deletions(-)
 create mode 100644 lib/classes/JsonApi/Middlewares/Language.php

diff --git a/composer.json b/composer.json
index 696383c3c73..03104690821 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 1acf6b621f5..29f2142a9d7 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 00000000000..8843445665d
--- /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 7a318d5b7ca..f65231c8861 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)) {
-- 
GitLab