This commit is contained in:
Dave M. 2023-03-30 18:26:21 +00:00
commit 40f9c246b2
24 changed files with 166 additions and 85 deletions

View File

@ -6,7 +6,7 @@
"authors": [
{
"name": "Dave Mc Nicoll",
"email": "mcndave@gmail.com"
"email": "info@mcnd.ca"
}
],
"require": {

View File

@ -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;
},
];

View File

@ -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;
},
];

View File

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

View File

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

View File

@ -5,7 +5,7 @@ use function DI\autowire, DI\create, DI\get;
use Laminas\Diactoros\Response\HtmlResponse;
use Picea\{ Picea, Caching\Cache, Caching\Opcache, Compiler, Compiler\Context, Compiler\BaseContext, FileFetcher, Language\DefaultRegistrations, Method\Request };
use Picea\Extension\{ LanguageHandler, LanguageExtension, TitleExtension, MoneyExtension, UrlExtension };
use Picea\Extension\{ LanguageHandler, LanguageExtension, TitleExtension, NumberExtension, UrlExtension };
use Picea\Ui\{ Method, Ui };
return [
@ -21,7 +21,7 @@ return [
return new Compiler(new class(array_merge([
$c->get(LanguageExtension::class),
$c->get(TitleExtension::class),
$c->get(MoneyExtension::class),
$c->get(NumberExtension::class),
$c->get(UrlExtension::class),
$c->get(Method\Form::class),
$c->get(Method\Pagination::class),
@ -63,7 +63,7 @@ return [
TitleExtension::class => autowire(TitleExtension::class),
MoneyExtension::class => autowire(MoneyExtension::class),
NumberExtension::class => autowire(NumberExtension::class),
UrlExtension::class => create(UrlExtension::class)->constructor(getenv("URL_BASE"), get('git.commit')),

View File

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

View File

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

View File

@ -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', '' ]),
],
],
],

View File

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

View File

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

View File

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

View File

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

View File

@ -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",
];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

26
view/lean/error/403.phtml Normal file
View File

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

View File

@ -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;max-height:300px;overflow-y:auto}
{% endsection %}