From f482c19df52512cac37b0aa18230a96f888c6ff9 Mon Sep 17 00:00:00 2001
From: Dave Mc Nicoll <info@mcnd.ca>
Date: Wed, 29 Mar 2023 13:59:59 +0000
Subject: [PATCH] - Splitted cronard definitions - Added TMP_PATH env var -
 Added support for Events from attributes - Added Negundo client support

---
 composer.json                              |  2 +-
 meta/definitions/cronard.php               | 32 ++++++++++++++++++++++
 meta/definitions/event.php                 | 26 ++++++++++++++++++
 meta/definitions/routes.php                |  2 +-
 meta/definitions/software.php              | 18 ------------
 meta/i18n/fr/lean.error.json               |  2 +-
 skeleton/.env                              |  1 +
 skeleton/meta/definitions/definitions.php  |  8 ++++++
 skeleton/meta/definitions/env/dev.php      |  1 +
 skeleton/meta/definitions/env/prod.php     | 10 +++++--
 skeleton/src/Controller/Home.php           |  8 ++----
 skeleton/src/Entity/User.php               |  5 ++--
 skeleton/src/Kernel.php                    |  1 +
 skeleton/src/Lib/ControllerTrait.php       | 20 +++++++++-----
 skeleton/src/Middleware/Authentication.php | 18 ++++--------
 skeleton/view/base/layout/default.phtml    | 21 --------------
 src/Application.php                        |  5 ++++
 src/ControllerTrait.php                    |  7 ++++-
 src/Kernel.php                             |  2 +-
 src/Lean.php                               |  7 +++++
 src/Response/FileDownloadResponse.php      | 13 +++++++--
 view/lean/error/403.phtml                  | 26 ++++++++++++++++++
 view/lean/error/500.phtml                  | 10 +++----
 23 files changed, 163 insertions(+), 82 deletions(-)
 create mode 100644 meta/definitions/cronard.php
 create mode 100644 meta/definitions/event.php
 create mode 100644 view/lean/error/403.phtml

diff --git a/composer.json b/composer.json
index 659f59e..2a9a1fd 100644
--- a/composer.json
+++ b/composer.json
@@ -6,7 +6,7 @@
     "authors": [
         {
             "name": "Dave Mc Nicoll",
-            "email": "mcndave@gmail.com"
+            "email": "info@mcnd.ca"
         }
     ],
     "require": {
diff --git a/meta/definitions/cronard.php b/meta/definitions/cronard.php
new file mode 100644
index 0000000..3fc0ff5
--- /dev/null
+++ b/meta/definitions/cronard.php
@@ -0,0 +1,32 @@
+<?php
+
+use function DI\autowire, DI\create, DI\get;
+
+use Laminas\Diactoros\Response\HtmlResponse;
+
+use Cronard\CronardMiddleware,
+    Notes\Cronard\TaskFetcher;
+
+use Psr\Http\Message\ResponseInterface;
+
+use Lean\Lean;
+
+return [
+    CronardMiddleware::class => function($c) {
+        $cronardMiddleware = new CronardMiddleware($c, getenv('CRON_KEY'), function() : ResponseInterface {
+            return new HtmlResponse(sprintf("%s - cron task begin...", date('Y-m-d H:i:s')));
+        }, [], $c->get(TaskFetcher::class));
+
+        return $cronardMiddleware->fromFile(getenv("META_PATH")."/crontab.php")->fromAnnotations($c->get(TaskFetcher::class));
+    },
+
+    TaskFetcher::class => function($c) {
+        $fetcher = new TaskFetcher(null, null, $c->get('cronard.caching'));
+
+        $fetcher->setFolderList(array_map(function($item) {
+            return $item;
+        }, $c->get(Lean::class)->getCronard()));
+
+        return $fetcher;
+    },
+];
diff --git a/meta/definitions/event.php b/meta/definitions/event.php
new file mode 100644
index 0000000..fa8cb15
--- /dev/null
+++ b/meta/definitions/event.php
@@ -0,0 +1,26 @@
+<?php
+
+use function DI\autowire, DI\create, DI\get;
+
+use Lean\Lean;
+use Mcnd\Event;
+
+return [
+    Event\EventManager::class => autowire(Event\EventManager::class),
+
+    Event\EventMiddleware::class => function($c) {
+        $mw = new Event\EventMiddleware($c, $c->get(Event\EventManager::class));
+
+        return $mw->fromAttributes($c->get(Notes\Event\EventFetcher::class));
+    },
+
+    Notes\Event\EventFetcher::class => function($c) {
+        $fetcher = new Notes\Event\EventFetcher(null, null, $c->get('events.caching'));
+
+        $fetcher->setFolderList(array_map(function($item) {
+            return $item;
+        }, $c->get(Lean::class)->getEvents()));
+
+        return $fetcher;
+    },
+];
diff --git a/meta/definitions/routes.php b/meta/definitions/routes.php
index bbd2d1f..0c9d3ae 100644
--- a/meta/definitions/routes.php
+++ b/meta/definitions/routes.php
@@ -81,7 +81,7 @@ return [
         };
     },
 
-    'routes.middlewares'  => [ "dump", "errorHandler", SessionMiddleware::class, CronardMiddleware::class, HttpBasicAuthentication::class ],
+    'routes.middlewares'  => [ "dump", "errorHandler", SessionMiddleware::class, CronardMiddleware::class, Mcnd\Event\EventMiddleware::class, HttpBasicAuthentication::class ],
 
     'routes.list' => function($c) {
         return function (ContainerInterface $container) {
diff --git a/meta/definitions/software.php b/meta/definitions/software.php
index 0b6a091..3a5c08e 100644
--- a/meta/definitions/software.php
+++ b/meta/definitions/software.php
@@ -66,24 +66,6 @@ return [
 
     Lean::class => autowire(Lean::class),
 
-    CronardMiddleware::class => function($c) {
-        $cronardMiddleware = new CronardMiddleware($c, getenv('CRON_KEY'), function() : ResponseInterface {
-            return new HtmlResponse(sprintf("%s - cron task begin...", date('Y-m-d H:i:s')));
-        }, [], $c->get(TaskFetcher::class));
-
-        return $cronardMiddleware->fromFile(getenv("META_PATH")."/crontab.php")->fromAnnotations($c->get(TaskFetcher::class));
-    },
-
-    TaskFetcher::class => function($c) {
-        $fetcher = new TaskFetcher(null, null, $c->get('cronard.caching'));
-
-        $fetcher->setFolderList(array_map(function($item) {
-            return $item;
-        }, $c->get(Lean::class)->getCronard()));
-
-        return $fetcher;
-    },
-
     JavascriptMiddleware::class => create(JavascriptMiddleware::class),
 
     Cookie::class => create(Cookie::class)->constructor([ 'secure' => true, 'path' => getenv('URL_BASE') ?: '/',  ], getenv("LEAN_RANDOM")),
diff --git a/meta/i18n/fr/lean.error.json b/meta/i18n/fr/lean.error.json
index 04021b7..cced617 100644
--- a/meta/i18n/fr/lean.error.json
+++ b/meta/i18n/fr/lean.error.json
@@ -14,7 +14,7 @@
     "back": "Revenir à la page précédente"
   },
   "500": {
-    "title": "Une erreur semble s'être produite",
+    "title": "Une erreur s'est produite",
     "page-title": "Une erreur semble s'être produite",
     "subtitle": "L'action que vous avez tenté d'effectué semble avoir échoué.",
     "message": "Un message d'erreur a été envoyé au développeur de cette application.",
diff --git a/skeleton/.env b/skeleton/.env
index e11d9d8..57ff9c5 100644
--- a/skeleton/.env
+++ b/skeleton/.env
@@ -13,6 +13,7 @@ META_DIR = "meta"
 LOGS_DIR = "var/logs"
 PUBLIC_DIR = "public"
 PRIVATE_DIR = "private"
+TMP_DIR = "var/tmp"
 VIEW_DIR = "view"
 I18N_DIR = "i18n"
 
diff --git a/skeleton/meta/definitions/definitions.php b/skeleton/meta/definitions/definitions.php
index 4eaf0e4..75be0fb 100644
--- a/skeleton/meta/definitions/definitions.php
+++ b/skeleton/meta/definitions/definitions.php
@@ -26,6 +26,14 @@ return array_merge(
             'routes' => [
                 '%ESCAPED_NAMESPACE%\\Controller' => getenv("PROJECT_PATH") . '/src/Controller/',
             ],
+
+            'events' => [
+                '%ESCAPED_NAMESPACE%\\Controller' => implode(DIRECTORY_SEPARATOR, [ getenv("PROJECT_PATH"), 'src', 'Controller', '' ]),
+            ],
+
+            'cronard' => [
+                '%ESCAPED_NAMESPACE%\\Controller' => implode(DIRECTORY_SEPARATOR, [ getenv("PROJECT_PATH"), 'src', 'Controller', '' ]),
+            ],
         ],
     ],
 
diff --git a/skeleton/meta/definitions/env/dev.php b/skeleton/meta/definitions/env/dev.php
index 8a94859..e49d6b2 100644
--- a/skeleton/meta/definitions/env/dev.php
+++ b/skeleton/meta/definitions/env/dev.php
@@ -15,4 +15,5 @@ return [
     'breadcrumbs.caching' => create(Kash\ArrayCache::class)->constructor( get(Kash\CacheInvalidator::class), "lean.breadcrumbs", 30),
     'ulmus.caching' => create(Kash\ArrayCache::class)->constructor( get(Kash\CacheInvalidator::class), "ulmus.entities", 30),
     'cronard.caching' => create(Kash\ArrayCache::class)->constructor( get(Kash\CacheInvalidator::class), "lean.cronards", 30),
+    'events.caching' => create(Kash\ArrayCache::class)->constructor( get(Kash\CacheInvalidator::class), "lean.events", random_int(3600, 7200)),
 ];
diff --git a/skeleton/meta/definitions/env/prod.php b/skeleton/meta/definitions/env/prod.php
index 8cbe1b1..a4b653e 100644
--- a/skeleton/meta/definitions/env/prod.php
+++ b/skeleton/meta/definitions/env/prod.php
@@ -2,7 +2,10 @@
 
 use Picea\Picea;
 
-use CSLSJ\Debogueur\DebogueurMiddleware;
+#use CSLSJ\Debogueur\DebogueurMiddleware;
+use Negundo\Client\{ NegundoMiddleware, SoftwareConfig };
+
+use
 
 use Laminas\Diactoros\Response\HtmlResponse;
 
@@ -27,7 +30,9 @@ return [
         };
     },
 
-    "errorHandler" => create(DebogueurMiddleware::class)->constructor(getenv('DEBOGUEUR_HASH') ?: "", null, get('app.errorhandler.html')),
+    SoftwareConfig::class => create(SoftwareConfig::class)->constructor(getenv('NEGUNDO_HASH'), getenv('NEGUNDO_SERVER')),
+
+    "errorHandler" => create(NegundoMiddleware::class)->constructor(get(SoftwareConfig::class), null, get('app.errorhandler.html')),
 
     'app.errorhandler.html' => function($c, Picea $picea) {
         return function(\Throwable $exception) use ($picea) {
@@ -46,4 +51,5 @@ return [
     'breadcrumbs.caching' => create(Kash\ApcuCache::class)->constructor(get(Kash\CacheInvalidator::class), "lean.breadcrumbs", random_int(3600, 7200)),
     'ulmus.caching' => create(Kash\ApcuCache::class)->constructor( get(Kash\CacheInvalidator::class), "ulmus.entities", random_int(3600, 7200)),
     'cronard.caching' => create(Kash\ApcuCache::class)->constructor( get(Kash\CacheInvalidator::class), "lean.cronards", random_int(3600, 7200)),
+    'events.caching' => create(Kash\ApcuCache::class)->constructor( get(Kash\CacheInvalidator::class), "lean.events", random_int(3600, 7200)),
 ];
\ No newline at end of file
diff --git a/skeleton/src/Controller/Home.php b/skeleton/src/Controller/Home.php
index 9ceb2d3..73f6fbf 100644
--- a/skeleton/src/Controller/Home.php
+++ b/skeleton/src/Controller/Home.php
@@ -8,15 +8,11 @@ use %NAMESPACE%\{ Lib, Entity, Form, };
 
 use function %NAMESPACE%\View\{ _, lang, url, route, form };
 
-/**
- * @Language("%APPKEY%.home")
- */
+#[Language("%APPKEY%.home")]
 class Home {
     use Lib\ControllerTrait;
 
-    /**
-     * @Route("/", "name" => "home")
-     */
+    #[Route("/", name: "home")]
     public function index(ServerRequestInterface $request, array $arguments) : ResponseInterface
     {
         form(new Form\Form(), $this->pushContext(new Lib\FormContext($request, "form.name")));
diff --git a/skeleton/src/Entity/User.php b/skeleton/src/Entity/User.php
index 25dc2ed..1ef3fb6 100644
--- a/skeleton/src/Entity/User.php
+++ b/skeleton/src/Entity/User.php
@@ -3,12 +3,11 @@
 namespace %NAMESPACE%\Entity;
 
 use Ulmus\Entity\Field\Datetime;
+use Ulmus\{Attribute\Obj\Table;
 
 use %NAMESPACE%\Lib;
 
-/**
- * @Table('name' => "user")
- */
+#[Table(name: "user")]
 class User extends \Ulmus\User\Entity\User implements \JsonSerializable
 {
     use Lib\EntityTrait;
diff --git a/skeleton/src/Kernel.php b/skeleton/src/Kernel.php
index 3436682..adff807 100644
--- a/skeleton/src/Kernel.php
+++ b/skeleton/src/Kernel.php
@@ -14,6 +14,7 @@ new class(dirname(__DIR__)) extends \Lean\Kernel {
         'META_PATH' => "META_DIR",
         'PUBLIC_PATH' => "PUBLIC_DIR",
         'PRIVATE_PATH' => "PRIVATE_DIR",
+        'TMP_PATH' => "TMP_DIR",
         'VIEW_PATH' => "VIEW_DIR",
     ];
 
diff --git a/skeleton/src/Lib/ControllerTrait.php b/skeleton/src/Lib/ControllerTrait.php
index 3fb0279..21e6d1f 100644
--- a/skeleton/src/Lib/ControllerTrait.php
+++ b/skeleton/src/Lib/ControllerTrait.php
@@ -6,12 +6,14 @@ use Picea\Picea;
 use Storage\Session;
 use Ulmus\User\Entity\User;
 use Ulmus\User\Lib\Authenticate;
+use Notes\Route\Attribute\Object\Route;
+use Notes\Security\Attribute\Security;
 use %NAMESPACE%\Entity;
 
-/**
- * @Security("locked" => false)
- * @RouteParam("methods" => [ "GET", "POST" ])
- */
+use Mcnd\Event\EventManager;
+
+#[Security(locked: false)]
+#[Route(method: ['GET', 'POST', ])]
 trait ControllerTrait {
     use \Lean\ControllerTrait;
 
@@ -19,14 +21,18 @@ trait ControllerTrait {
 
     protected Authenticate $authenticate;
 
-    public function __construct(Picea $picea, Session $session, Authenticate $authenticate) {
-        $this->initializeController($picea, $session, $authenticate);
+    protected EventManager $eventManager;
+
+    public function __construct(Picea $picea, Session $session, Authenticate $authenticate, \Notes\Breadcrumb\Breadcrumb $breadcrumb, EventManager $eventManager) {
+        $this->initializeController($picea, $session, $authenticate, $breadcrumb, $eventManager);
     }
 
-    public function initializeController(Picea $picea, Session $session, Authenticate $authenticate) {
+    public function initializeController(Picea $picea, Session $session, Authenticate $authenticate, \Notes\Breadcrumb\Breadcrumb $breadcrumb, EventManager $eventManager) {
         $this->picea = $picea;
         $this->authenticate = $authenticate;
         $this->session = $session;
+        $this->eventManager = $eventManager;
+        $this->breadcrumb = $breadcrumb;
         $this->user = $authenticate->rememberMe( Entity\User::repository() ) ?: new Entity\User();
     }
 
diff --git a/skeleton/src/Middleware/Authentication.php b/skeleton/src/Middleware/Authentication.php
index 328c276..c939356 100644
--- a/skeleton/src/Middleware/Authentication.php
+++ b/skeleton/src/Middleware/Authentication.php
@@ -23,13 +23,9 @@ class Authentication implements MiddlewareInterface {
         $this->session = $session;
     }
 
-    /**
-     * Middleware request handling
-     *
-     * @param  ServerRequestInterface  $request
-     * @param  RequestHandlerInterface $handler
-     * @return ResponseInterface
-     */
+    #[param  ServerRequestInterface  $request]
+    #[param  RequestHandlerInterface $handler]
+    #[return ResponseInterface]
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
     {
         $user = $this->session->get( $this->sessionUserVariable() );
@@ -41,12 +37,8 @@ class Authentication implements MiddlewareInterface {
         return $handler->handle($request);
     }
 
-    /**
-     * Getter / setter of Session User Variable
-     *
-     * @param string|null sessionUserVariable
-     * @return mixed
-     */
+    #[param string|null sessionUserVariable]
+    #[return mixed]
     public function sessionUserVariable(?string $set = null) : string
     {
         return $set !== null ? $this->sessionUserVariable = $set : $this->sessionUserVariable;
diff --git a/skeleton/view/base/layout/default.phtml b/skeleton/view/base/layout/default.phtml
index 32353ae..ebfa87e 100644
--- a/skeleton/view/base/layout/default.phtml
+++ b/skeleton/view/base/layout/default.phtml
@@ -5,28 +5,7 @@
             <meta charset="UTF-8" />
             <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
             <title>{% section "head.title" %}{{ title() }} — {{ lang('application_name') }}{% endsection %}</title>
-            <link rel="stylesheet" href="{% asset 'static/css/main.e39066da.css' %}">
-            <style>
-                @font-face{
-                    font-family:FontAwesome;
-                    src:url({% asset 'static/fonts/fontawesome-webfont.674f50d2.eot' %});
-                    src:url({% asset 'static/fonts/fontawesome-webfont.674f50d2.eot' %}) format("embedded-opentype"),
-                        url({% asset 'static/fonts/fontawesome-webfont.af7ae505.woff2' %}) format("woff2"),
-                        url({% asset 'static/fonts/fontawesome-webfont.fee66e71.woff' %}) format("woff"),
-                        url({% asset 'static/fonts/fontawesome-webfont.b06871f2.ttf' %}) format("truetype"),
-                        url({% asset 'static/images/fontawesome-webfont.912ec66d.svg' %}) format("svg");
-                    font-weight:400;
-                    font-style:normal;
-                }
             </style>
-
-            <link rel="apple-touch-icon" sizes="180x180" href="{% asset 'static/favicon/apple-touch-icon.png' %}">
-            <link rel="icon" type="image/png" sizes="32x32" href="{% asset 'static/favicon/favicon-32x32.png' %}">
-            <link rel="icon" type="image/png" sizes="16x16" href="{% asset 'static/favicon/favicon-16x16.png' %}">
-            <link rel="manifest" href="{% asset 'static/favicon/site.webmanifest' %}">
-            <link rel="mask-icon" href="{% asset 'static/favicon/safari-pinned-tab.svg' %}" color="#5bbad5">
-            <meta name="msapplication-TileColor" content="#2d89ef">
-            <meta name="theme-color" content="#ffffff">
         </head>
     {% endsection %}
 
diff --git a/src/Application.php b/src/Application.php
index e256f1c..c027ecf 100644
--- a/src/Application.php
+++ b/src/Application.php
@@ -20,6 +20,8 @@ class Application
 
     public array $entities;
 
+    public array $events;
+
     public array $tellJson;
 
     public array $tellPhp;
@@ -71,6 +73,9 @@ class Application
         if (is_array($data['cronard'] ?? false)) {
             $this->cronard = $data['cronard'];
         }
+        if (is_array($data['events'] ?? false)) {
+            $this->events = $data['events'];
+        }
 
         return $this;
     }
diff --git a/src/ControllerTrait.php b/src/ControllerTrait.php
index 1b15919..fdca39b 100644
--- a/src/ControllerTrait.php
+++ b/src/ControllerTrait.php
@@ -2,7 +2,7 @@
 
 namespace Lean;
 
-use Lean\Response\{ PdfResponse, ImageResponse, DownloadResponse };
+use Lean\Response\{FileDownloadResponse, PdfResponse, ImageResponse, DownloadResponse};
 
 use Picea,
     Picea\Ui\Method\FormContext;
@@ -112,6 +112,11 @@ trait ControllerTrait {
         return new ImageResponse($data, $code, $headers);
     }
 
+    public static function renderAsset(string $path, int $code = 200, array $headers = []) : ResponseInterface
+    {
+        return new FileDownloadResponse($path, $code, $headers);
+    }
+
     public function fromResponse(ResponseInterface $response)
     {
         if ( $response->getStatusCode() === 200 ) {
diff --git a/src/Kernel.php b/src/Kernel.php
index 68b71d1..d99ebe0 100644
--- a/src/Kernel.php
+++ b/src/Kernel.php
@@ -61,7 +61,7 @@ class Kernel {
         Dotenv::create(getenv("PROJECT_PATH"))->load();
 
         // Override using headers
-        if ( ( $keys = getenv('KEYS') ) && ( $auth = $_SERVER["HTTP_X_DEV_AUTH"] ) && in_array($auth, explode(',', $keys)) ) {
+        if ( ( $keys = getenv('KEYS') ) && ( $auth = $_SERVER["HTTP_X_DEV_AUTH"] ?? null ) && in_array($auth, explode(',', $keys)) ) {
             foreach (['APP_ENV', 'DEBUG',] as $env) {
                 if (null !== $value = $_SERVER["HTTP_X_$env"] ?? null) {
                     static::putenv($env, $value);
diff --git a/src/Lean.php b/src/Lean.php
index 85ad5c5..cfc3d83 100644
--- a/src/Lean.php
+++ b/src/Lean.php
@@ -83,6 +83,11 @@ class Lean
         return array_merge(...array_map(fn($app) => $app->cronard ?? [], $this->applications));
     }
 
+    public function getEvents() : array
+    {
+        return array_merge(...array_map(fn($app) => $app->events ?? [], $this->applications));
+    }
+
     public function getEntities() : array
     {
         return array_merge(...array_map(fn($app) => $app->entities ?? [], $this->applications));
@@ -123,7 +128,9 @@ class Lean
         $path = dirname(__DIR__) . "/meta/definitions/";
 
         return array_merge(
+            require($path . "cronard.php"),
             require($path . "email.php"),
+            require($path . "event.php"),
             require($path . "http.php"),
             require($path . "language.php"),
             require($path . "routes.php"),
diff --git a/src/Response/FileDownloadResponse.php b/src/Response/FileDownloadResponse.php
index b55569b..9bcce2a 100644
--- a/src/Response/FileDownloadResponse.php
+++ b/src/Response/FileDownloadResponse.php
@@ -36,11 +36,20 @@ class FileDownloadResponse extends Response
     public function __construct($filepath, int $status = 200, array $headers = [])
     {
         $body = $this->createBody($filepath);
-        
+
+        if (class_exists(\Mimey\MimeTypes::class)) {
+            $mime = (new \Mimey\MimeTypes())->getMimeType(pathinfo($filepath, PATHINFO_EXTENSION));
+        }
+        else {
+            $finfo = finfo_open(FILEINFO_MIME_TYPE);
+            $mime = finfo_file($finfo, $filepath);
+            finfo_close($finfo);
+        }
+
         parent::__construct(
             $body,
             $status,
-            $this->injectContentType( (new \Mimey\MimeTypes())->getMimeType( pathinfo($filepath, PATHINFO_EXTENSION) ), $headers)
+            $this->injectContentType($mime, $headers)
         );
     }
 
diff --git a/view/lean/error/403.phtml b/view/lean/error/403.phtml
new file mode 100644
index 0000000..f421d07
--- /dev/null
+++ b/view/lean/error/403.phtml
@@ -0,0 +1,26 @@
+{% extends "lean/layout/error" %}
+
+{% language.set "lean.error.401" %}
+
+{% title _('page-title') %}
+
+{% section "content-right" %}
+    <div>
+        <div class="title">{% _ "title" %}</div>
+        <div class="subtitle">{% _ "subtitle" %}</div>
+        <div class="content">{% _ "message" %}</div>
+        <u><a href="#" onclick="history.back()">{% _ "back" %}</a></u>
+    </div>
+{% endsection %}
+
+{% section "content-left" %}
+    <div class="picto-login">
+        {% view "lean/picto/undraw_forbidden" %}
+    </div>
+{% endsection %}
+
+{% section "head.css" %}
+    .title {font-size:2rem}
+    .subtitle {font-size:1.25rem; padding-top: 1rem;}
+    .content {padding-top:1rem}
+{% endsection %}
diff --git a/view/lean/error/500.phtml b/view/lean/error/500.phtml
index dc5c150..1ce3265 100644
--- a/view/lean/error/500.phtml
+++ b/view/lean/error/500.phtml
@@ -6,7 +6,7 @@
 
 {% section "content-right" %}
     <div>
-        <div class="title">{% _ "title" %}</div>
+        <div class="title">{{ $title ?? title() }}</div>
         <div class="subtitle">{{= isset($subtitle) ? nl2br($subtitle) :  _("subtitle") }}</div>
         <div class="content">{{= isset($message) ? nl2br($message) :  _("message") }}</div>
         <u><a href="#" onclick="history.back()">{% _ "back" %}</a></u>
@@ -14,13 +14,13 @@
 {% endsection %}
 
 {% section "content-left" %}
-<div class="picto-login">
-    {% view "lean/picto/undraw_error" %}
-</div>
+    <div class="picto-login">
+        {% view "lean/picto/undraw_error" %}
+    </div>
 {% endsection %}
 
 {% section "head.css" %}
     .title {font-size:2rem}
     .subtitle {font-size:1.25rem; padding-top: 1rem;}
-    .content {padding-top:1rem}
+    .content {background: #f7f7f7;padding: 7px;color: #8c3c3c;}
 {% endsection %}