diff --git a/meta/definitions/email.php b/meta/definitions/email.php
new file mode 100644
index 0000000..22901ab
--- /dev/null
+++ b/meta/definitions/email.php
@@ -0,0 +1,24 @@
+ function($c) {
+ $email = new EmailConfiguration( EmailConfiguration::AUTH_TYPE_SMTP );
+ $email->smtpHost = getenv('SMTP_HOST');
+ $email->smtpPort = getenv('SMTP_PORT');
+ $email->smtpUsername = getenv('SMTP_USERNAME');
+ $email->smtpPassword = getenv('SMTP_PASSWORD');
+ $email->smtpUseTLS = getenv('SMTP_TLS');
+ $email->toAddress = getenv("TO_EMAIL");
+ $email->fromAddress = getenv("FROM_EMAIL");
+ $email->fromName = getenv("FROM_NAME");
+ return $email;
+ },
+
+ MailerInterface::class => autowire(SwiftMailer::class),
+];
diff --git a/meta/i18n/en/lean.date.json b/meta/i18n/en/lean.date.json
new file mode 100644
index 0000000..2b61bd2
--- /dev/null
+++ b/meta/i18n/en/lean.date.json
@@ -0,0 +1,15 @@
+{
+ "month": {
+ "list": [
+ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "Décember"
+ ],
+
+ "short": [
+ "Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec"
+ ],
+
+ "letter": [
+ "J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/meta/i18n/fr/lean.date.json b/meta/i18n/fr/lean.date.json
new file mode 100644
index 0000000..952dd05
--- /dev/null
+++ b/meta/i18n/fr/lean.date.json
@@ -0,0 +1,15 @@
+{
+ "month": {
+ "list": [
+ "Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"
+ ],
+
+ "short": [
+ "Jan", "Fév", "Mars", "Avr", "Mai", "Juin", "Jui", "Août", "Sept", "Oct", "Nov", "Déc"
+ ],
+
+ "letter": [
+ "J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/meta/i18n/fr/lean.error.json b/meta/i18n/fr/lean.error.json
index 45d1118..96329fe 100644
--- a/meta/i18n/fr/lean.error.json
+++ b/meta/i18n/fr/lean.error.json
@@ -5,5 +5,12 @@
"subtitle": "Vous avez peut-être suivi un lien expiré ou invalide...",
"message": "Il semblerait que l'action que vous avez tentez à mener vers une page qui n'existe pas / plus. Une notification a été envoyé au développeur de l'application.",
"back": "Revenir à la page précédente"
+ },
+ "500": {
+ "title": "Une erreur semble s'être 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.",
+ "back": "Revenir à la page précédente"
}
}
\ No newline at end of file
diff --git a/skeleton/.env b/skeleton/.env
new file mode 100644
index 0000000..e11d9d8
--- /dev/null
+++ b/skeleton/.env
@@ -0,0 +1,43 @@
+APP_ENV = dev
+DEBUG = 1
+KEYS = "dev:dev"
+CRON_KEY = dev
+
+# Route
+URL_BASE = ""
+APP_URL = "dev.cslsj.qc.ca"
+
+# Path
+CACHE_DIR = "var/cache"
+META_DIR = "meta"
+LOGS_DIR = "var/logs"
+PUBLIC_DIR = "public"
+PRIVATE_DIR = "private"
+VIEW_DIR = "view"
+I18N_DIR = "i18n"
+
+DEFAULT_TIMEZONE = "America/Toronto"
+DEFAULT_LOCAL = "fr_CA.UTF-8"
+DEFAULT_TIME = "fr.UTF-8"
+DEFAULT_TIME_FALLBACK = "french.UTF-8"
+
+# MS Authentication
+# MS_OAUTH_CLIENT_ID = ""
+# MS_OAUTH_CLIENT_SECRET = ""
+
+# MS Graph
+# MS_GRAPH_CLIENT_ID = ""
+# MS_GRAPH_CLIENT_SECRET = ""
+# MS_GRAPH_ACCESS_TOKEN_URL = ""
+
+# Database
+DATABASE_PORT = "3306"
+DATABASE_HOST = "appsdb.cslsj.qc.ca"
+DATABASE_NAME = ""
+DATABASE_USERNAME = ""
+DATABASE_PASSWORD = ""
+DATABASE_CHARSET = "utf8"
+DATABASE_ADAPTER = "MariaDB"
+
+LEAN_RANDOM = "%RAND64%"
+
diff --git a/skeleton/meta/config.php b/skeleton/meta/config.php
new file mode 100644
index 0000000..a80eff1
--- /dev/null
+++ b/skeleton/meta/config.php
@@ -0,0 +1,40 @@
+ [
+ 'autoload' => [
+ 'lean.default',
+ getenv('DEBUG') ? 'lean.console' : null,
+ '%APPKEY%',
+ ],
+ ],
+
+ 'keys' => (function() {
+ foreach(explode(',', getenv('KEYS') ?? "") as $item) {
+ list($var, $value) = explode(":", trim($item));
+ $list[$var] = $value;
+ }
+
+ return $list;
+ })(),
+
+ 'meta' => [
+ 'application_name' => "",
+ ],
+
+ 'ulmus' => [
+ 'connections' => [
+ 'default' => [
+ 'adapter' => getenv("DATABASE_ADAPTER"),
+ 'host' => getenv("DATABASE_HOST"),
+ 'port' => getenv("DATABASE_PORT"),
+ 'database' => getenv("DATABASE_NAME"),
+ 'username' => getenv("DATABASE_USERNAME"),
+ 'password' => getenv("DATABASE_PASSWORD"),
+ 'settings' => [
+ 'charset' => getenv("DATABASE_CHARSET"),
+ ],
+ ],
+ ]
+ ]
+];
diff --git a/skeleton/meta/crontab.php b/skeleton/meta/crontab.php
new file mode 100644
index 0000000..6f3d417
--- /dev/null
+++ b/skeleton/meta/crontab.php
@@ -0,0 +1,26 @@
+ function(ServerRequestInterface $request, ResponseInterface $response) : ResponseInterface
+ {
+
+ },
+];
diff --git a/skeleton/meta/definitions/auth.php b/skeleton/meta/definitions/auth.php
new file mode 100644
index 0000000..aa5a32e
--- /dev/null
+++ b/skeleton/meta/definitions/auth.php
@@ -0,0 +1,52 @@
+ autowire(Entity\User::class),
+
+ Authenticate::class => create(Authenticate::class)->constructor(get(Session::class), get(Cookie::class), get('authentication.method')),
+
+ SecurityHandler::class => create(SecurityHandler::class)->constructor(function() {
+ return new RedirectResponse(getenv("URL_BASE")."/connexion");
+ }),
+
+ 'authentication.error' => function($c, Picea $picea) {
+ return function($message) use ($picea) {
+ return new HtmlResponse($picea->renderHtml('lean/error/500', [
+ 'title' => "",
+ 'subtitle' => "",
+ 'message' => $message,
+ ]));
+ };
+ },
+
+ EmailConfiguration::class => function($c) {
+ $email = new EmailConfiguration( EmailConfiguration::AUTH_TYPE_SMTP );
+ $email->smtpHost = getenv('SMTP_HOST');
+ $email->smtpPort = getenv('SMTP_PORT');
+ $email->smtpUsername = getenv('SMTP_USERNAME');
+ $email->smtpPassword = getenv('SMTP_PASSWORD');
+ $email->smtpUseTLS = getenv('SMTP_TLS');
+ $email->toAddress = getenv("TO_EMAIL");
+ $email->fromAddress = getenv("FROM_EMAIL");
+ $email->fromName = getenv("FROM_NAME");
+
+ return $email;
+ },
+];
diff --git a/skeleton/meta/definitions/definitions.php b/skeleton/meta/definitions/definitions.php
new file mode 100644
index 0000000..601e853
--- /dev/null
+++ b/skeleton/meta/definitions/definitions.php
@@ -0,0 +1,36 @@
+ [
+ 'picea' => [
+ 'context' => "%ESCAPED_NAMESPACE%\\View",
+
+ 'extensions' => [
+ ],
+
+ ],
+
+ 'ulmus' => [
+ 'entities' => [ '%ESCAPED_NAMESPACE%\\Entity' => getenv("PROJECT_PATH") . '/src/Entity/' ],
+ ],
+
+ 'routes' => [
+ '%ESCAPED_NAMESPACE%\\Controller' => getenv("PROJECT_PATH") . '/src/Controller/',
+ ],
+ ],
+ ],
+
+ require("$dir/auth.php"),
+ require("$dir/storage.php"),
+ require("$dir/env/" . getenv('APP_ENV') . ".php"),
+ [ 'config' => function () { return require(getenv("META_PATH")."/config.php"); } ]
+);
diff --git a/skeleton/meta/definitions/env/dev.php b/skeleton/meta/definitions/env/dev.php
new file mode 100644
index 0000000..7859e15
--- /dev/null
+++ b/skeleton/meta/definitions/env/dev.php
@@ -0,0 +1,14 @@
+ create(DumpMiddleware::class),
+ "errorHandler" => create(Middlewares\Whoops::class),
+];
diff --git a/skeleton/meta/definitions/env/prod.php b/skeleton/meta/definitions/env/prod.php
new file mode 100644
index 0000000..28da8e9
--- /dev/null
+++ b/skeleton/meta/definitions/env/prod.php
@@ -0,0 +1,42 @@
+ function($c) {
+ if (! function_exists('dump') ) {
+ function dump(...$what) {}
+ }
+
+ return new class implements MiddlewareInterface {
+ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface { return $handler->handle($request); }
+ };
+ },
+
+ "errorHandler" => create(DebogueurMiddleware::class)->constructor(getenv('DEBOGUEUR_HASH') ?: "", null, get('app.errorhandler.html')),
+
+ 'app.errorhandler.html' => function($c, Picea $picea) {
+ return function(\Throwable $exception) use ($picea) {
+ return new HtmlResponse($picea->renderHtml('lean/error/500', [
+ 'title' => "Une erreur s'est produite lors de l'exécution du script.",
+ 'subtitle' => "Êtes-vous connecté avec le bon compte ?",
+ 'message' => $exception->getMessage(),
+ 'exception' => $exception,
+ ]));
+ };
+ },
+];
diff --git a/skeleton/meta/definitions/storage.php b/skeleton/meta/definitions/storage.php
new file mode 100644
index 0000000..c5011fc
--- /dev/null
+++ b/skeleton/meta/definitions/storage.php
@@ -0,0 +1,25 @@
+ function($c) {
+ $adapter = new ConnectionAdapter('default', $c->get('config')['ulmus'], true);
+ $adapter->resolveConfiguration();
+
+ $adapter->connect();
+
+ return $adapter;
+ },
+
+ AdapterProxy::class => function (ContainerInterface $c) {
+ return new AdapterProxy(
+ $c->get(ConnectionAdapter::class)
+ );
+ }
+];
diff --git a/skeleton/public/.htaccess b/skeleton/public/.htaccess
new file mode 100644
index 0000000..b3bd338
--- /dev/null
+++ b/skeleton/public/.htaccess
@@ -0,0 +1,8 @@
+
+ RewriteEngine On
+
+ # Remove trailing slashes from request URL
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteCond %{REQUEST_URI} (.+)/$
+ RewriteRule ^ %1 [R=301,L]
+
diff --git a/skeleton/public/index.php b/skeleton/public/index.php
new file mode 100644
index 0000000..eb8a548
--- /dev/null
+++ b/skeleton/public/index.php
@@ -0,0 +1,3 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/skeleton/src/Controller/Home.php b/skeleton/src/Controller/Home.php
new file mode 100644
index 0000000..9ceb2d3
--- /dev/null
+++ b/skeleton/src/Controller/Home.php
@@ -0,0 +1,26 @@
+ "home")
+ */
+ public function index(ServerRequestInterface $request, array $arguments) : ResponseInterface
+ {
+ form(new Form\Form(), $this->pushContext(new Lib\FormContext($request, "form.name")));
+
+ return $this->renderView('home');
+ }
+}
\ No newline at end of file
diff --git a/skeleton/src/Entity/User.php b/skeleton/src/Entity/User.php
new file mode 100644
index 0000000..ead134f
--- /dev/null
+++ b/skeleton/src/Entity/User.php
@@ -0,0 +1,15 @@
+ "user")
+ */
+class User extends \Ulmus\User\Entity\User implements \JsonSerializable
+{
+ use Lib\EntityTrait;
+}
diff --git a/skeleton/src/Form/Form.php b/skeleton/src/Form/Form.php
new file mode 100644
index 0000000..4463be2
--- /dev/null
+++ b/skeleton/src/Form/Form.php
@@ -0,0 +1,30 @@
+valid();
+ }
+
+ public function execute(FormContextInterface $context) : void
+ {
+ $context->pushMessage(Message::generateSuccess(
+ lang("form.success")
+ ));
+ }
+}
diff --git a/skeleton/src/Kernel.php b/skeleton/src/Kernel.php
new file mode 100644
index 0000000..3436682
--- /dev/null
+++ b/skeleton/src/Kernel.php
@@ -0,0 +1,36 @@
+ "CACHE_DIR",
+ 'LOGS_PATH' => "LOGS_DIR",
+ 'META_PATH' => "META_DIR",
+ 'PUBLIC_PATH' => "PUBLIC_DIR",
+ 'PRIVATE_PATH' => "PRIVATE_DIR",
+ 'VIEW_PATH' => "VIEW_DIR",
+ ];
+
+ protected function initializeEngine() : self
+ {
+ $this->errorLogPath = getenv("LOGS_PATH") . DIRECTORY_SEPARATOR. date("Y-m").".log";
+
+ $this->definitionFilePath = implode(DIRECTORY_SEPARATOR, [ getenv('META_PATH'), 'definitions', 'definitions.php' ]);
+
+ return parent::initializeEngine();
+ }
+
+ protected function serviceContainer() : self
+ {
+ # $this->container->get(ConnectionAdapter::class);
+
+ return parent::serviceContainer();
+ }
+
+};
diff --git a/skeleton/src/Lib/ControllerTrait.php b/skeleton/src/Lib/ControllerTrait.php
new file mode 100644
index 0000000..00e9439
--- /dev/null
+++ b/skeleton/src/Lib/ControllerTrait.php
@@ -0,0 +1,33 @@
+ false)
+ * @RouteParam("methods" => [ "GET", "POST" ])
+ */
+trait ControllerTrait {
+ use \Lean\ControllerTrait;
+
+ public ? \Ulmus\User\Entity\User $user;
+
+ protected Authenticate $authenticate;
+
+ public function __construct(Picea $picea, Session $session, Authenticate $authenticate) {
+ $this->initializeController($picea, $session, $authenticate);
+ }
+
+ public function initializeController(Picea $picea, Session $session, Authenticate $authenticate) {
+ $this->picea = $picea;
+ $this->authenticate = $authenticate;
+ $this->session = $session;
+ $this->user = $authenticate->rememberMe( Entity\User::repository() ) ?: new User();
+ }
+
+}
\ No newline at end of file
diff --git a/skeleton/src/Lib/EntityTrait.php b/skeleton/src/Lib/EntityTrait.php
new file mode 100644
index 0000000..0fd1d8d
--- /dev/null
+++ b/skeleton/src/Lib/EntityTrait.php
@@ -0,0 +1,7 @@
+ [
+ 'class' => 'success',
+ 'header' => 'Succès !',
+ 'message' => ''
+ ],
+
+ 'error' => [
+ 'class' => 'danger',
+ 'header' => 'Error !',
+ 'message' => ''
+ ],
+
+ 'warning' => [
+ 'class' => 'warning',
+ 'header' => 'Attention !',
+ 'message' => ''
+ ],
+ ];
+
+ public string $type;
+
+ public string $message;
+
+ public string $class;
+
+ public ? string $header = null;
+
+ public function __construct(string $message, string $type = "error", ? string $header = null, ? string $class = null)
+ {
+ $this->message = $message;
+
+ $this->type = $type;
+
+ if ( $header !== null ) {
+ $this->header = $header;
+ }
+ else {
+ $this->header = static::MESSAGE_TYPE[$type]['header'];
+ }
+
+ if ( $class !== null ) {
+ $this->class = $class;
+ }
+ else {
+ $this->class = static::MESSAGE_TYPE[$type]['class'];
+ }
+ }
+
+ public function render() : string
+ {
+ return <<
+
+ {$this->header}
+ {$this->message}
+
+
+ HTML;
+ }
+
+ public function renderJson() : array
+ {
+ return [
+ 'name' => 'show-notification',
+ 'type' => [
+ 'error' => 'danger',
+ 'success' => 'success',
+ 'warning' => 'warning',
+ ][$this->type],
+ 'shake' => '.wrapper',
+ 'message' => $this->message,
+ ];
+ }
+
+ public function isError() : bool
+ {
+ return $this->type === "error";
+ }
+
+ public static function generateSuccess(string $message, ? string $header = null, ? string $class = null) : self
+ {
+ return new static($message, 'success', $header, $class);
+ }
+
+ public static function generateError(string $message, ? string $header = null, ? string $class = null) : self
+ {
+ return new static($message, 'error', $header, $class);
+ }
+
+ public static function generateWarning(string $message, ? string $header = null, ? string $class = null) : self
+ {
+ return new static($message, 'warning', $header, $class);
+ }
+}
diff --git a/skeleton/src/Middleware/Authentication.php b/skeleton/src/Middleware/Authentication.php
new file mode 100644
index 0000000..328c276
--- /dev/null
+++ b/skeleton/src/Middleware/Authentication.php
@@ -0,0 +1,54 @@
+loginRedirect = $loginRedirect;
+ $this->session = $session;
+ }
+
+ /**
+ * Middleware request handling
+ *
+ * @param ServerRequestInterface $request
+ * @param RequestHandlerInterface $handler
+ * @return ResponseInterface
+ */
+ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
+ {
+ $user = $this->session->get( $this->sessionUserVariable() );
+
+ if ( ! $user ) {
+ return $this->loginRedirect;
+ }
+
+ return $handler->handle($request);
+ }
+
+ /**
+ * Getter / setter of Session User Variable
+ *
+ * @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
new file mode 100644
index 0000000..32353ae
--- /dev/null
+++ b/skeleton/view/base/layout/default.phtml
@@ -0,0 +1,50 @@
+
+
+ {% section "head" %}
+
+
+
+ {% section "head.title" %}{{ title() }} — {{ lang('application_name') }}{% endsection %}
+
+
+
+
+
+
+
+
+
+
+
+ {% endsection %}
+
+ {% section "body" %}
+
+ {% section "body.header" %}{% endsection %}
+
+ {% section "header" %}{% endsection %}
+
+ {% section "message" %}
+ {% view "lean/widget/message" %}
+ {% endsection %}
+
+ {% section "main" %}{% endsection %}
+
+
+
+ {% section "body.footer" %}{% endsection %}
+
+ {% endsection %}
+
\ No newline at end of file
diff --git a/skeleton/view/home.phtml b/skeleton/view/home.phtml
new file mode 100644
index 0000000..1d7e5c9
--- /dev/null
+++ b/skeleton/view/home.phtml
@@ -0,0 +1,9 @@
+{% extends 'base/layout/default' %}
+
+{% title "Home" %}
+
+{% section "main" %}
+
+ Hello World :) !
+
+{% endsection %}
\ No newline at end of file
diff --git a/src/Composer.php b/src/Composer.php
new file mode 100644
index 0000000..a4a0e22
--- /dev/null
+++ b/src/Composer.php
@@ -0,0 +1,92 @@
+getIO()->write("post-install script executing ...");
+
+ if ( $event->getIO()->askConfirmation("Do you want to install Lean's Project Skeleton ? ( 'yes' or 'no' (default) ): ", false) ) {
+ $ns = trim(static::getNamespaceFromAutoload($event), '\\');
+
+ $replace = [
+ '%NAMESPACE%' => $ns,
+ '%ESCAPED_NAMESPACE%' => addslashes($ns),
+ '%APPKEY%' => strtolower(str_replace('\\', '.', $ns)),
+ '%RAND64%' => substr(base64_encode(random_bytes(100)), 0, 64),
+ ];
+
+ $dir = static::createPath();
+
+ if ( is_writable($dir) ) {
+ $source = static::createPath('vendor', 'mcnd', 'lean', 'skeleton');
+ $dest = static::createPath('skeleton');
+
+ `cp -a $source $dest`;
+
+ $iterator = new \RecursiveDirectoryIterator($dest);
+ $iterator->setFlags(\RecursiveDirectoryIterator::SKIP_DOTS);
+
+ foreach (new \RecursiveIteratorIterator($iterator) as $file) {
+ if ( in_array($file->getExtension(), [ 'php', 'phtml', 'env' ]) ) {
+ $content = str_replace(array_keys($replace), array_values($replace), file_get_contents($file->getPathname()));
+
+ file_put_contents($file->getPathname(), $content);
+ }
+ }
+
+ foreach(scandir(static::createPath('skeleton')) as $item) {
+ if ( in_array($item, [ '.', '..' ]) ) continue;
+
+ rename(static::createPath('skeleton', $item), static::createPath($item));
+ }
+
+ rmdir($dest);
+
+ $event->getIO()->write("Installation completed.");
+ }
+ else {
+ $event->getIO()->writeError(sprintf("The user you are running this script with doesn't seem to have a writable permission on directory %s", static::createPath()));
+ }
+ }
+ }
+
+ protected static function readComposerJson(Event $event) : ? array
+ {
+ $path = static::createPath('composer.json');
+
+ return file_exists($path) ? json_decode(file_get_contents($path), true) : null;
+ }
+
+ protected static function getNamespaceFromAutoload(Event $event) : ? string
+ {
+ if ( null !== $composerJson = static::readComposerJson($event) ) {
+ if ( $psr4 = $composerJson['autoload']['psr-4'] ?? false ) {
+ foreach($psr4 as $ns => $directory) {
+ if ($directory === 'src/') {
+ return $ns;
+ }
+ }
+ }
+ else {
+ $event->getIO()->writeError("Your composer file do not seem to contains a valid psr-4 project of it's autoload array.");
+ }
+ }
+ else {
+ $event->getIO()->writeError("Composer.json file not found. Have this script been launch from the Composer CLI at the root of your project ?");
+ }
+
+ $event->getIO()->writeError("Your composer file do not seem to contains a valid psr-4 project pointing to the src folder.");
+
+ return null;
+ }
+
+ protected static function createPath(... $path) : string
+ {
+ return implode(DIRECTORY_SEPARATOR, array_merge([ $_SERVER['PWD'] ], $path));
+ }
+}
\ No newline at end of file
diff --git a/src/ControllerTrait.php b/src/ControllerTrait.php
index d600b77..5be7d54 100644
--- a/src/ControllerTrait.php
+++ b/src/ControllerTrait.php
@@ -2,8 +2,8 @@
namespace Lean;
-use CSLSJ\Common\RequestResponse\DownloadResponse;
-use CSLSJ\Common\RequestResponse\ImageResponse;
+use CSLSJ\Common\RequestResponse\{ PdfResponse, ImageResponse, DownloadResponse };
+
use Picea,
Picea\Ui\Method\FormContext;
@@ -63,6 +63,11 @@ trait ControllerTrait {
return new RedirectResponse($url, $code, $headers);
}
+ public function renderPdf($rawdata, int $status = 200, array $headers = []) : PdfResponse
+ {
+ return new PdfResponse($rawdata, $status, $headers);
+ }
+
public static function renderText(string $html, int $code = 200, array $headers = []) : ResponseInterface
{
return new TextResponse($html, $code, $headers);
diff --git a/src/Lean.php b/src/Lean.php
index 7c8f64b..84d1560 100644
--- a/src/Lean.php
+++ b/src/Lean.php
@@ -107,6 +107,7 @@ class Lean
$path = dirname(__DIR__) . "/meta/definitions/";
return array_merge(
+ require($path . "email.php"),
require($path . "http.php"),
require($path . "language.php"),
require($path . "routes.php"),
diff --git a/view/lean/error/500.phtml b/view/lean/error/500.phtml
new file mode 100644
index 0000000..2e531e4
--- /dev/null
+++ b/view/lean/error/500.phtml
@@ -0,0 +1,24 @@
+{% extends "lean/layout/error" %}
+
+{% language.set "lean.error.500" %}
+
+{% title _('page-title') %}
+
+{% section "content-right" %}
+
+
{% _ "title" %}
+
{{= isset($subtitle) ? nl2br($subtitle) : _("subtitle") }}
+
{{= isset($message) ? nl2br($message) : _("message") }}
+
{% _ "back" %}
+
+{% endsection %}
+
+{% section "content-left" %}
+
+{% endsection %}
+
+{% section "head.css" %}
+ .title {font-size:2rem}
+ .subtitle {font-size:1.25rem; padding-top: 1rem;}
+ .content {padding-top:1rem}
+{% endsection %}