- Added a new Email defintion (which will soon see it's components changed)

- Added a new post-install script for composer which adds a project skeleton "ready to use".
- Error 500 are now handled properly in production; waiting to see if adjustements will be required before adding other code pages.
This commit is contained in:
Dave M. 2021-03-01 16:01:27 +00:00
parent 09b3e47c19
commit 01bb926ca7
30 changed files with 887 additions and 2 deletions

View File

@ -0,0 +1,24 @@
<?php
use function DI\autowire, DI\create, DI\get;
use TheBugs\Email\EmailConfiguration,
TheBugs\Email\MailerInterface,
TheBugs\Email\SwiftMailer;
return [
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;
},
MailerInterface::class => autowire(SwiftMailer::class),
];

View File

@ -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"
]
}
}

View File

@ -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"
]
}
}

View File

@ -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"
}
}

43
skeleton/.env Normal file
View File

@ -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%"

40
skeleton/meta/config.php Normal file
View File

@ -0,0 +1,40 @@
<?php
return [
'lean' => [
'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"),
],
],
]
]
];

26
skeleton/meta/crontab.php Normal file
View File

@ -0,0 +1,26 @@
<?php
use Psr\Http\Message\ResponseInterface,
Psr\Http\Message\ServerRequestInterface;
/*
Min Hour Day Mon Weekday
* * * * *
└─ Weekday (0=Sun .. 6=Sat)
└────── Month (1..12)
└─────────── Day (1..31)
└──────────────── Hour (0..23)
└───────────────────── Minute (0..59)
ex:
0 * * * * Chaque heure
15 2 * * 0 Tout les dimanches soir a 2h15 AM
*/
return [
"* * * * *" => function(ServerRequestInterface $request, ResponseInterface $response) : ResponseInterface
{
},
];

View File

@ -0,0 +1,52 @@
<?php
use function DI\autowire, DI\create, DI\get;
use %NAMESPACE%\Entity;
use Ulmus\Entity\Field\Datetime,
Ulmus\User\Lib\Authenticate;
use Storage\{ Session, Cookie };
use Laminas\Diactoros\Response\{ RedirectResponse, HtmlResponse };
use Notes\Security\SecurityHandler;
use Picea\Picea;
use TheBugs\Email\{ EmailConfiguration, MailerInterface, SwiftMailer };
return [
Entity\User::class => 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;
},
];

View File

@ -0,0 +1,36 @@
<?php
use function DI\autowire, DI\create, DI\get;
$dir = getenv("META_PATH") . "/definitions";
return array_merge(
Lean\Lean::definitions(),
Lean\Console\Lean::definitions(),
[
'%APPKEY%' => [
'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"); } ]
);

14
skeleton/meta/definitions/env/dev.php vendored Normal file
View File

@ -0,0 +1,14 @@
<?php
use function DI\create, DI\autowire, DI\get;
use Dump\DumpMiddleware;
if ($_GET['sql'] ?? false) {
\Ulmus\Common\PdoObject::$dump = "dump";
}
return [
"dump" => create(DumpMiddleware::class),
"errorHandler" => create(Middlewares\Whoops::class),
];

42
skeleton/meta/definitions/env/prod.php vendored Normal file
View File

@ -0,0 +1,42 @@
<?php
use Picea\Picea;
use CSLSJ\Debogueur\DebogueurMiddleware;
use Laminas\Diactoros\Response\HtmlResponse;
use Psr\Http\Server\MiddlewareInterface,
Psr\Http\Message\ServerRequestInterface,
Psr\Http\Server\RequestHandlerInterface,
Psr\Http\Message\ResponseInterface;
use function DI\{ create, get, autowire };
error_reporting(getenv('ERROR_REPORTING') ?: ( E_ALL & ~E_USER_DEPRECATED & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE ));
return [
/* do nothing in production !*/
"dump" => 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,
]));
};
},
];

View File

@ -0,0 +1,25 @@
<?php
use Psr\Container\ContainerInterface;
use Ulmus\ConnectionAdapter,
Ulmus\Container\AdapterProxy;
use LdapRecord\Connection;
return [
ConnectionAdapter::class => 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)
);
}
];

View File

@ -0,0 +1,8 @@
<IfModule mod_rewrite.c>
RewriteEngine On
# Remove trailing slashes from request URL
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [R=301,L]
</IfModule>

View File

@ -0,0 +1,3 @@
<?php
require_once("../src/Kernel.php");

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Remove trailing slash" stopProcessing="true">
<match url="(.*)/$" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Redirect" redirectType="Permanent" url="{R:1}" />
</rule>
<rule name="Framework Routing" stopProcessing="true">
<match url="." ignoreCase="false" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
</conditions>
<action type="Rewrite" url="index.php" />
</rule>
</rules>
</rewrite>
<staticContent>
<remove fileExtension=".js" />
<mimeMap fileExtension=".js" mimeType="application/x-javascript; charset=UTF-8" />
<remove fileExtension=".css" />
<mimeMap fileExtension=".css" mimeType="text/css; charset=UTF-8" />
<remove fileExtension=".woff" />
<remove fileExtension=".eot" />
<remove fileExtension=".ttf" />
<remove fileExtension=".svg" />
<mimeMap fileExtension=".eot" mimeType="application/vnd.ms-fontobject" />
<mimeMap fileExtension=".ttf" mimeType="application/font-sfnt" />
<mimeMap fileExtension=".svg" mimeType="image/svg+xml" />
<mimeMap fileExtension=".woff" mimeType="application/font-woff" />
</staticContent>
<handlers>
<remove name="PHP7.3" />
<add name="PHP7.3" path="*.php" verb="*" modules="FastCgiModule" scriptProcessor="C:\php\7.4.0\php-cgi.exe" resourceType="File" requireAccess="Script" />
</handlers>
<httpErrors errorMode="Detailed" />
</system.webServer>
</configuration>

View File

@ -0,0 +1,26 @@
<?php
namespace %NAMESPACE%\Controller;
use Psr\Http\Message\{ServerRequestInterface, ResponseInterface };
use %NAMESPACE%\{ Lib, Entity, Form, };
use function %NAMESPACE%\View\{ _, lang, url, route, form };
/**
* @Language("%APPKEY%.home")
*/
class Home {
use Lib\ControllerTrait;
/**
* @Route("/", "name" => "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');
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace %NAMESPACE%\Entity;
use Ulmus\Entity\Field\Datetime;
use %NAMESPACE%\Lib;
/**
* # Table('name' => "user")
*/
class User extends \Ulmus\User\Entity\User implements \JsonSerializable
{
use Lib\EntityTrait;
}

View File

@ -0,0 +1,30 @@
<?php
namespace %NAMESPACE%\Form;
use %NAMESPACE%\{ Lib, Entity };
use Picea\Ui\Method\{ FormContextInterface, Message\ErrorMessage };
use Psr\Http\Message\ServerRequestInterface;
use function %NAMESPACE%\View\{ lang };
class Form implements \Picea\Ui\Method\FormInterface {
public function initialize(FormContextInterface $context) : void
{
}
public function validate(FormContextInterface $context) : bool
{
return $context->valid();
}
public function execute(FormContextInterface $context) : void
{
$context->pushMessage(Message::generateSuccess(
lang("form.success")
));
}
}

36
skeleton/src/Kernel.php Normal file
View File

@ -0,0 +1,36 @@
<?php declare(strict_types=1);
namespace %NAMESPACE%;
use Ulmus\ConnectionAdapter;
require_once dirname(__DIR__) . '/vendor/autoload.php';
new class(dirname(__DIR__)) extends \Lean\Kernel {
public array $paths = [
'CACHE_PATH' => "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();
}
};

View File

@ -0,0 +1,33 @@
<?php
namespace %NAMESPACE%\Lib;
use Picea\Picea;
use Storage\Session;
use Ulmus\User\Entity\User;
use Ulmus\User\Lib\Authenticate;
use %NAMESPACE%\Entity;
/**
* @Security("locked" => 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();
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace %NAMESPACE%\Lib;
trait EntityTrait {
use \Ulmus\EntityTrait;
}

View File

@ -0,0 +1,5 @@
<?php
namespace %NAMESPACE%\Lib;
class FormContext extends \Picea\Ui\Method\FormContext { }

View File

@ -0,0 +1,103 @@
<?php declare(strict_types=1);
namespace %NAMESPACE%\Lib;
use Picea\Ui\Method\FormMessage;
class Message implements FormMessage {
const MESSAGE_TYPE = [
'success' => [
'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 <<<HTML
<article class="message is-{$this->class}" role="alert">
<div class="message-body">
<strong>{$this->header}</strong>
<span>{$this->message}</span>
</div>
</article>
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);
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace %NAMESPACE%\Middleware;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Storage\Session;
class Authentication implements MiddlewareInterface {
public Session $session;
public string $sessionUserVariable = 'user';
public ResponseInterface $loginRedirect;
public function __construct(ResponseInterface $loginRedirect, Session $session)
{
$this->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;
}
}

View File

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="fr">
{% section "head" %}
<head>
<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 %}
{% section "body" %}
<body>
{% section "body.header" %}{% endsection %}
{% section "header" %}{% endsection %}
{% section "message" %}
{% view "lean/widget/message" %}
{% endsection %}
<main role="main" class="content">{% section "main" %}{% endsection %}</main>
<footer>{% section "footer" %}{% endsection %}</footer>
{% section "body.footer" %}{% endsection %}
</body>
{% endsection %}
</html>

9
skeleton/view/home.phtml Normal file
View File

@ -0,0 +1,9 @@
{% extends 'base/layout/default' %}
{% title "Home" %}
{% section "main" %}
<div class="main">
Hello World :) !
</div>
{% endsection %}

92
src/Composer.php Normal file
View File

@ -0,0 +1,92 @@
<?php
namespace Lean;
use Composer\EventDispatcher\Event;
class Composer
{
public static function postInstall(Event $event) : void
{
$event->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));
}
}

View File

@ -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);

View File

@ -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"),

24
view/lean/error/500.phtml Normal file
View File

@ -0,0 +1,24 @@
{% extends "lean/layout/error" %}
{% language.set "lean.error.500" %}
{% title _('page-title') %}
{% section "content-right" %}
<div>
<div class="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>
</div>
{% endsection %}
{% section "content-left" %}
<img class="picto-login" src="{% asset 'static/img/bugs.svg' %}">
{% endsection %}
{% section "head.css" %}
.title {font-size:2rem}
.subtitle {font-size:1.25rem; padding-top: 1rem;}
.content {padding-top:1rem}
{% endsection %}