diff --git a/composer.json b/composer.json index 3383551..a57421e 100644 --- a/composer.json +++ b/composer.json @@ -16,54 +16,26 @@ "ext-posix": "*", "ext-xmlreader": "*", "ext-fileinfo": "*", - "league/route": "^5.0.0-dev", - "laminas/laminas-diactoros": "2.24.x-dev", - "laminas/laminas-httphandlerrunner": "2.5.x-dev", + "league/route": "^7.0.0-dev", + "laminas/laminas-diactoros": "3.9.x-dev", + "laminas/laminas-httphandlerrunner": "2.14.x-dev", "vlucas/phpdotenv": "^3.4@dev", "middlewares/whoops": "dev-master", - "mcnd/storage": "dev-master", - "mcnd/lean-console": "dev-master", - "mcnd/ulmus": "dev-master", - "mcnd/tell": "dev-master", - "mcnd/dump": "dev-master", - "mcnd/event": "dev-master", - "mcnd/thebugs": "dev-master", - "mcnd/taxus": "dev-master", - "mcnd/notes": "dev-master", + "mcnd/dump": "~1.0.0", + "mcnd/storage": "~1.0.0", + "mcnd/event": "~1.0.0", + "mcnd/tell": "~1.0.0", + "mcnd/ulmus": "^1.0 || ^2.0", + "mcnd/kash": "~1.0.0", + "mcnd/taxus": "1.1.0", + "mcnd/notes": "^2.1.1", + "mcnd/thebugs": "^1.0.0", "psr/simple-cache": "*" }, "repositories": [ { - "type": "vcs", - "url": "https://git.mcnd.ca/mcndave/event.git" - }, - { - "type": "vcs", - "url": "https://git.mcnd.ca/mcndave/the-bugs.git" - }, - { - "type": "vcs", - "url": "https://git.mcnd.ca/mcndave/storage.git" - }, - { - "type": "vcs", - "url": "https://git.mcnd.ca/mcndave/ulmus.git" - }, - { - "type": "vcs", - "url": "https://git.mcnd.ca/mcndave/notes.git" - }, - { - "type": "vcs", - "url": "https://git.mcnd.ca/mcndave/tell.git" - }, - { - "type": "vcs", - "url": "https://git.mcnd.ca/mcndave/lean-console.git" - }, - { - "type": "vcs", - "url": "https://git.mcnd.ca/mcndave/dump.git" + "type": "composer", + "url": "https://git.mcnd.ca/api/packages/mcndave/composer" } ], "autoload": { diff --git a/meta/definitions/authorize.php b/meta/definitions/authorize.php index 6332712..324d50b 100644 --- a/meta/definitions/authorize.php +++ b/meta/definitions/authorize.php @@ -1,9 +1,17 @@ create(Authenticate::class)->constructor(get(Entity\UserInterface::class), get(Session::class), get(Cookie::class), get('authentication.method')), - Authorize::class => create(Authorize::class)->constructor(get(Entity\UserInterface::class), get(Session::class), get(Cookie::class), get('authentication.method')), - HeaderAuthenticationMiddleware::class => create(HeaderAuthenticationMiddleware::class)->constructor(get(Authorize::class), get(Entity\UserInterface::class), get('authorize.error')), + Authenticate::class => create(Authenticate::class)->constructor(get(Entity\UserInterface::class), get(Session::class), get(Cookie::class), get('authentication.method')), - PostRequestAuthentication::class => create(PostRequestAuthentication::class)->constructor(get(Authenticate::class), "email", "email", "password"), + AuthenticationMiddleware::class => create(AuthenticationMiddleware::class)->constructor(get(Authorize::class), get('authorize.error')), - PostRequestAuthenticationMiddleware::class => create(PostRequestAuthenticationMiddleware::class)->constructor(get(Entity\UserInterface::class), get('authentication.error'), get(PostRequestAuthentication::class)), + HeaderAuthentication::class => autowire(HeaderAuthentication::class), + + HeaderAuthenticationMiddleware::class => create(HeaderAuthenticationMiddleware::class)->constructor(get(HeaderAuthentication::class)), + + CookieAuthentication::class => autowire(CookieAuthentication::class), + + CookieAuthenticationMiddleware::class => create(CookieAuthenticationMiddleware::class)->constructor(get(CookieAuthentication::class), get(Cookie::class)), + + PostRequestAuthentication::class => create(PostRequestAuthentication::class)->constructor("email", "email", "password"), + + PostRequestAuthenticationMiddleware::class => create(PostRequestAuthenticationMiddleware::class)->constructor(get(PostRequestAuthentication::class)), 'authentication.method' => null, - 'authorize.error' => function($c) { + 'authorize.error' => function(ContainerInterface $c) { return function(array $errorData) use ($c) { - return $c->get(\Lean\Factory\HttpFactoryInterface::class)::createJsonResponse($errorData + [ - 'api.error' => "Authorization failed", - 'api.datetime' => (new \DateTime)->format(\DateTime::ATOM), - ], 403); + return $c->get(HttpFactoryInterface::class)::createJsonResponse($errorData + [ + 'api.error' => "Authorization failed", + 'api.datetime' => (new \DateTime)->format(\DateTime::ATOM), + ], 403); }; }, 'authentication.error' => function($c, Picea $picea) { - return function($message) use ($picea, $c) { - return $c->get(\Lean\Factory\HttpFactoryInterface::class)::createHtmlResponse($picea->renderHtml('lean/error/500', [ + return function($message) use ($c, $picea) { + return $c->get(HttpFactoryInterface::class)::createHtmlResponse($picea->renderHtml('lean/error/500', [ + 'title' => "Authentication failed", 'subtitle' => "", 'message' => $message, diff --git a/meta/definitions/event.php b/meta/definitions/event.php index 5cfa378..0d8ccec 100644 --- a/meta/definitions/event.php +++ b/meta/definitions/event.php @@ -37,7 +37,7 @@ return [ public function execute(Routing $routing, Route $attribute) : void { if (null !== ($name = $attribute->name ?? null)) { - $this->extension->registerRoute($name, $attribute->getRoute(), $attribute->class, $attribute->classMethod, (array) ( $attribute->method ?? $attribute->methods )); + $this->extension->registerRoute($name, $attribute->getRoute(), $attribute->class, $attribute->classMethod, (array) ( $attribute->method )); } } }, @@ -52,7 +52,6 @@ return [ { $class = $attribute->class; $method = $attribute->classMethod; - $object = $container->get($class); $request = $request->withAttribute('lean.route', $attribute); diff --git a/meta/definitions/http.php b/meta/definitions/http.php index 1ae4f7d..cbfab68 100644 --- a/meta/definitions/http.php +++ b/meta/definitions/http.php @@ -1,11 +1,17 @@ function ($c) { @@ -15,6 +21,38 @@ return [ }, EmitterInterface::class => create(SapiEmitter::class), + ThrowableHandlerInterface::class => autowire(ThrowableHandler::class), NotFoundDecoratorInterface::class => autowire(NotFoundDecorator::class), + # MethodNotAllowedInterface::class => autowire(MethodNotAllowedDecorator::class), + + HttpFactoryInterface::class => create(HttpFactory::class), + + Cookie::class => create(Cookie::class)->constructor([ + 'secure' => true, + 'path' => getenv('URL_BASE') ?: '/', + 'httponly' => true, + 'samesite'=> "Lax", + ], getenv("LEAN_RANDOM")), + + Session::class => create(Session::class)->constructor(get(Cookie::class), [ 'path' => getenv('URL_BASE') ?: '/', ]), + + SessionMiddleware::class => create(SessionMiddleware::class)->constructor(get(Cookie::class), [ + 'name' => "lean_sess_" . substr(md5(getenv("LEAN_RANDOM")), 0, 12), + 'path' => getenv('URL_BASE') ?: '/', + 'samesite' => getenv("LEAN_COOKIE_SAMESITE") ?? "Lax", + 'secure' => (bool) getenv('LEAN_COOKIE_SECURE') ?? false + ]), + + 'error.401' => function(ContainerInterface $c, Picea $picea) { + return $c->get(HttpFactoryInterface::class)::createHtmlResponse($picea->renderHtml("lean/error/401", []), 401); + }, + + 'error.404' => function(ContainerInterface $c, Picea $picea) { + return $c->get(HttpFactoryInterface::class)::createHtmlResponse($picea->renderHtml("lean/error/404", []), 404); + }, + + 'error.500' => function(ContainerInterface $c, Picea $picea) { + return $c->get(HttpFactoryInterface::class)::createHtmlResponse($picea->renderHtml("lean/error/500", []), 500); + }, ]; diff --git a/meta/definitions/language.php b/meta/definitions/language.php index 07afe90..9a7942a 100644 --- a/meta/definitions/language.php +++ b/meta/definitions/language.php @@ -2,7 +2,6 @@ use Tell\I18n; use Psr\Container\ContainerInterface; -use Notes\Tell\LanguageHandler; use function DI\autowire, DI\create, DI\get, DI\add; diff --git a/meta/definitions/routes.php b/meta/definitions/routes.php index a2460b9..5d0140f 100644 --- a/meta/definitions/routes.php +++ b/meta/definitions/routes.php @@ -59,7 +59,7 @@ return [ $router = $container->get(Router::class); foreach([ 'routes.middlewares', 'app.middlewares' ] as $key) { - if ( $container->has($key) ) { + if ($container->has($key)) { foreach ($container->get($key) as $i => $middleware) { if ($container->has($middleware)) { $router->middleware($container->get($middleware)); @@ -68,7 +68,8 @@ return [ } } - $router->addPatternMatcher('email', '[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+'); + $router->addPatternMatcher('email', '[^@ \t\r\n/]+@[^@ \t\r\n/]+\.[^@ \t\r\n/]+'); + $router->addPatternMatcher('ids', '\b\d[\d,]*\b'); $routing = $container->get(Lean\Routing::class); $routing->registerRoute($container, getenv('URL_BASE')); @@ -76,4 +77,4 @@ return [ return $router; }; }, -]; +]; \ No newline at end of file diff --git a/meta/definitions/software.php b/meta/definitions/software.php index aaff563..48c2278 100644 --- a/meta/definitions/software.php +++ b/meta/definitions/software.php @@ -6,9 +6,6 @@ use TheBugs\JavascriptMiddleware; use Lean\Lean; -use Storage\Cookie, - Storage\Session, - Storage\SessionMiddleware; use function DI\autowire, DI\create, DI\get, DI\add; @@ -26,7 +23,7 @@ return [ ], [ 'path' => implode(DIRECTORY_SEPARATOR, [ $dir, "view", '' ]), - 'order' => 99, + 'order' => 1000, ], ], @@ -78,12 +75,6 @@ return [ JavascriptMiddleware::class => create(JavascriptMiddleware::class), - Cookie::class => create(Cookie::class)->constructor([ 'secure' => true, 'path' => getenv('URL_BASE') ?: '/', ], getenv("LEAN_RANDOM")), - - Session::class => create(Session::class)->constructor(get(Cookie::class), [ 'path' => getenv('URL_BASE') ?: '/', ]), - - SessionMiddleware::class => create(SessionMiddleware::class)->constructor(get(Cookie::class), [ 'name' => "lean_sess_" . substr(md5(getenv("LEAN_RANDOM")), 0, 12), 'path' => getenv('URL_BASE') ?: '/' ]), - 'git.commit' => function($c) { if ( getenv("DEBUG") ) { return uniqid("debug_"); diff --git a/meta/i18n/en/lean.date.json b/meta/i18n/en/lean.date.json index 2fe6844..c30924c 100644 --- a/meta/i18n/en/lean.date.json +++ b/meta/i18n/en/lean.date.json @@ -5,6 +5,15 @@ "week": "week" }, + "interval": { + "years": "{$years} year(s)", + "months": "{$months} month(s)", + "days": "{$days} day(s)", + "hours": "{$hours} hour(s)", + "minutes": "{$minutes} minutes(s)", + "seconds": "{$seconds} second(s)" + }, + "month": { "list": [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "Décember" diff --git a/meta/i18n/fr/lean.date.json b/meta/i18n/fr/lean.date.json index f236ce0..d6655c9 100644 --- a/meta/i18n/fr/lean.date.json +++ b/meta/i18n/fr/lean.date.json @@ -4,6 +4,15 @@ "week": "semaine" }, + "interval": { + "years": "{$years} années(s)", + "months": "{$months} mois", + "days": "{$days} jour(s)", + "hours": "{$hours} heure(s)", + "minutes": "{$minutes} minutes(s)", + "seconds": "{$seconds} seconde(s)" + }, + "month": { "list": [ "Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre" diff --git a/skeleton/.env b/skeleton/.env index 7d73d77..85e71b6 100644 --- a/skeleton/.env +++ b/skeleton/.env @@ -5,7 +5,8 @@ CRON_KEY = dev # Route URL_BASE = "" -APP_URL = "dev.mcnd.ca" +APP_URL = "myapp.ca" +ASSET_PATH = "static/" # Path CACHE_DIR = "var/cache" @@ -23,19 +24,18 @@ DEFAULT_TIME = "fr.UTF-8" DEFAULT_TIME_FALLBACK = "french.UTF-8" # Database -#SQLITE_PATH = "var/home.sqlite3" -#SQLITE_ADAPTER = "SQLite" -#SQLITE_PRAGMA_BEGIN = "foreign_keys=ON,synchronous=NORMAL" -#SQLITE_PRAGMA_DEBUG_BEGIN = "journal_mode=WAL" -#SQLITE_PRAGMA_CLOSE = "analysis_limit=500,optimize" +SQLITE_PATH = "var/myapp.sqlite3" +SQLITE_ADAPTER = "SQLite" +SQLITE_PRAGMA_BEGIN = "foreign_keys=ON,synchronous=NORMAL" +SQLITE_PRAGMA_DEBUG_BEGIN = "journal_mode=WAL" +SQLITE_PRAGMA_CLOSE = "analysis_limit=500,optimize" -DATABASE_PORT = "" -DATABASE_HOST = "" -DATABASE_NAME = "" -DATABASE_USERNAME = "" -DATABASE_PASSWORD = "" -DATABASE_CHARSET = "utf8" -DATABASE_ADAPTER = "MariaDB" - -LEAN_RANDOM = "%RAND64%" +#DATABASE_PORT = "" +#DATABASE_HOST = "" +#DATABASE_NAME = "" +#DATABASE_USERNAME = "" +#DATABASE_PASSWORD = "" +#DATABASE_CHARSET = "utf8" +#DATABASE_ADAPTER = "MariaDB" +LEAN_RANDOM = "%RAND64%" \ No newline at end of file diff --git a/skeleton/lean b/skeleton/cli old mode 100755 new mode 100644 similarity index 100% rename from skeleton/lean rename to skeleton/cli diff --git a/skeleton/composer.json.dist b/skeleton/composer.json.dist index 3492572..7ec3365 100644 --- a/skeleton/composer.json.dist +++ b/skeleton/composer.json.dist @@ -8,26 +8,37 @@ ], "minimum-stability": "dev", "require": { - "php": "^8.2", + "php": "^8.4", "ext-apcu": "*", + "ext-sodium": "*", + "ext-bcmath": "*", "ext-json": "*", + "ext-readline": "*", "league/route": "^5.0.0-dev", "laminas/laminas-diactoros": "2.24.x-dev", "laminas/laminas-httphandlerrunner": "2.5.x-dev", "vlucas/phpdotenv": "^3.4@dev", "middlewares/whoops": "dev-master", - "ralouphie/getallheaders": "dev-master", + "ralouphie/getallheaders": "3.x", + "guzzlehttp/guzzle": "7.9.x-dev", + "symfony/mailer": "6.3.x-dev", + "league/oauth2-client": "dev-master", + "php-di/php-di": "dev-master", + "league/commonmark": "^2.7@dev", + "assets-package/ace": "1.0.0", "mcnd/dump": "dev-master", "mcnd/storage": "dev-master", + "mcnd/cli": "dev-master", "mcnd/event": "dev-master", - "mcnd/ulmus": "dev-master", - "mcnd/ulmus-api": "dev-master", - "mcnd/ulmus-user": "dev-master", + "mcnd/ulmus": "^1.0 || ^2.0", + "mcnd/ulmus-api": "^1.3.0", + "mcnd/ulmus-user": "~1.1.2", "mcnd/picea": "dev-master", "mcnd/picea-ui": "dev-master", - "mcnd/picea-asset": "dev-master", + "mcnd/picea-asset": "~1.0.2", "mcnd/cronard": "dev-master", - "mcnd/lean-console": "dev-master", + "mcnd/lean-api": "dev-master", + "mcnd/lean-console": "~1.3.2", "mcnd/kash": "dev-master", "mcnd/taxus": "dev-master", "mcnd/notes": "dev-master", @@ -35,9 +46,17 @@ "mcnd/negundo-client": "dev-master" }, "repositories": [ + { + "type": "composer", + "url": "https://git.mcnd.ca/api/packages/mcndave/composer" + }, + { + "type": "composer", + "url": "https://git.mcnd.ca/api/packages/assets-package/composer" + }, { "type": "vcs", - "url": "https://git.mcnd.ca/mcndave/cronard.git" + "url": "https://git.mcnd.ca/mcndave/cli.git" }, { "type": "vcs", @@ -51,6 +70,10 @@ "type": "vcs", "url": "https://git.mcnd.ca/mcndave/lean.git" }, + { + "type": "vcs", + "url": "https://git.mcnd.ca/mcndave/lean-api.git" + }, { "type": "vcs", "url": "https://git.mcnd.ca/mcndave/lean-console.git" @@ -118,6 +141,10 @@ { "type": "vcs", "url": "https://git.mcnd.ca/mcndave/negundo-client.git" + }, + { + "type": "vcs", + "url": "https://git.mcnd.ca/assets-package/ace.git" } ], "autoload": { @@ -136,6 +163,7 @@ "extra" : { "lean" : { "autoload": { + "order": 1000, "definitions" : [ "meta/definitions/definitions.php", "meta/definitions/storage.php", diff --git a/skeleton/meta/config.php b/skeleton/meta/config.php index d3c0275..a43390b 100644 --- a/skeleton/meta/config.php +++ b/skeleton/meta/config.php @@ -10,7 +10,7 @@ return [ ], 'keys' => (function() { - return explode(',', getenv('KEYS') ?? "") ; + return explode(',', getenvonce('KEYS') ?? "") ; })(), 'meta' => [ @@ -19,7 +19,7 @@ return [ 'ulmus' => [ 'connections' => [ - 'default' => [ + /*'default' => [ 'adapter' => getenv("DATABASE_ADAPTER"), 'host' => getenv("DATABASE_HOST"), 'port' => getenv("DATABASE_PORT"), @@ -29,18 +29,18 @@ return [ 'settings' => [ 'charset' => getenv("DATABASE_CHARSET"), ], - ], + ],*/ - 'sqlite' => [ + 'default' => [ 'adapter' => getenv("SQLITE_ADAPTER"), 'path' => getenv('PROJECT_PATH') . DIRECTORY_SEPARATOR . getenv("SQLITE_PATH"), 'pragma' => explode(',', getenv("SQLITE_PRAGMA")), 'pragma_begin' => array_merge( - explode(',', getenv("SQLITE_PRAGMA_BEGIN")), explode(',', getenv('DEBUG') ? getenv("SQLITE_PRAGMA_DEBUG_BEGIN") : "") - ), - 'pragma_close' => array_merge( - explode(',', getenv("SQLITE_PRAGMA_CLOSE")), explode(',', getenv('DEBUG') ? getenv("SQLITE_PRAGMA_DEBUG_CLOSE") : "") - ), + explode(',', getenv("SQLITE_PRAGMA_BEGIN")), explode(',', getenv('DEBUG') ? getenv("SQLITE_PRAGMA_DEBUG_BEGIN") : "") + ), + 'pragma_close' => array_merge( + explode(',', getenv("SQLITE_PRAGMA_CLOSE")), explode(',', getenv('DEBUG') ? getenv("SQLITE_PRAGMA_DEBUG_CLOSE") : "") + ), ], ] ] diff --git a/skeleton/meta/definitions/auth.php b/skeleton/meta/definitions/auth.php index 0b05eab..53b7c74 100644 --- a/skeleton/meta/definitions/auth.php +++ b/skeleton/meta/definitions/auth.php @@ -4,6 +4,7 @@ use function DI\autowire, DI\create, DI\get; use %NAMESPACE%\Entity; +use Psr\Container\ContainerInterface; use Psr\Http\Message\ServerRequestInterface; use Ulmus\Entity\Field\Datetime, @@ -21,16 +22,18 @@ use TheBugs\Email\{ EmailConfiguration, MailerInterface, SwiftMailer }; use Taxus\{ Taxus, PermissionGrantInterface }; +use Lean\Factory\HttpFactoryInterface; + return [ \Ulmus\User\Entity\UserInterface::class => autowire(Entity\User::class), - SecurityHandler::class => create(SecurityHandler::class)->constructor(function() { - return Lean\Factory\HttpFactory::createRedirectResponse(getenv("URL_BASE")."/login"); + SecurityHandler::class => create(SecurityHandler::class)->constructor(function(ContainerInterface $c) { + return $c->get(HttpFactoryInterface::class)::createRedirectResponse(getenv("URL_BASE")."/login"); }, get('authentication.unauthorize'), get(Taxus::class)), - 'authentication.unauthorize' => function($c, Picea $picea) { - return function($message) use ($picea) { - return Lean\Factory\HttpFactory::createHtmlResponse($picea->renderHtml('lean/error/401', [ + 'authentication.unauthorize' => function(ContainerInterface $c, Picea $picea) { + return function($message) use ($c, $picea) { + return $c->get(HttpFactoryInterface::class)::createHtmlResponse($picea->renderHtml('lean/error/401', [ 'title' => "", 'subtitle' => "", 'message' => $message, @@ -40,7 +43,7 @@ return [ EmailConfiguration::class => function($c) { $email = new EmailConfiguration( EmailConfiguration::AUTH_TYPE_SMTP ); - $email->smtpHost = getenv('SMTP_HOSTNAME'); + $email->smtpHost = getenv('SMTP_HOST'); $email->smtpPort = getenv('SMTP_PORT'); $email->smtpUsername = getenv('SMTP_USERNAME'); $email->smtpPassword = getenv('SMTP_PASSWORD'); diff --git a/skeleton/meta/definitions/definitions.php b/skeleton/meta/definitions/definitions.php index d6e82dc..866aabf 100644 --- a/skeleton/meta/definitions/definitions.php +++ b/skeleton/meta/definitions/definitions.php @@ -7,6 +7,10 @@ use function DI\{ autowire, add, create, get }; $dir = dirname(__DIR__, 2); return [ + \%NAMESPACE%\Factory\FormFactoryInterface::class => autowire(\%NAMESPACE%\Factory\FormFactory::class), + \Lean\Api\Factory\MessageFactoryInterface::class => autowire(\%NAMESPACE%\Lib\Message::class), + + '%APPKEY%' => [ 'picea' => [ 'context' => "%ESCAPED_NAMESPACE%\\View", diff --git a/skeleton/meta/definitions/env.php b/skeleton/meta/definitions/env.php index c906296..ba08ecf 100644 --- a/skeleton/meta/definitions/env.php +++ b/skeleton/meta/definitions/env.php @@ -1,3 +1,13 @@ 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) { + 'app.errorhandler.html' => function(\Psr\Container\ContainerInterface $c, Picea $picea) { + return function(\Throwable $exception) use ($c, $picea) { error_log($exception->getMessage()); - return HttpFactory::createHtmlResponse($picea->renderHtml('lean/error/500', [ + return $c->get(HttpFactoryInterface::class)::createHtmlResponse($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(), diff --git a/skeleton/public/index.php b/skeleton/public/index.php index 2741b00..9f0dd25 100644 --- a/skeleton/public/index.php +++ b/skeleton/public/index.php @@ -1,8 +1,14 @@ %s
%s", $t->getMessage(), $t->getFile() . ":" . $t->getLine(), $t->getTraceAsString()); -} \ No newline at end of file + echo sprintf("%s
%s", $t->getMessage(), $t->getFile(), $t->getLine(), getenv('DEBUG') ? var_export($t->getTrace(), true) : ""); + error_log($t->getMessage()); + header("HTTP/1.1 500 Internal Server Error"); +} diff --git a/skeleton/src/Factory/FormFactory.php b/skeleton/src/Factory/FormFactory.php new file mode 100644 index 0000000..6d459a9 --- /dev/null +++ b/skeleton/src/Factory/FormFactory.php @@ -0,0 +1,46 @@ +languageHandler, $this->messageFactory, $entity); + } + + public function saveContext(? ServerRequestInterface $request = null, ? string $formName = null): FormContextInterface + { + return new Form\SaveContext($request ?: $this->request, $formName); + } + + public function delete(EntityInterface $entity): FormInterface + { + return new Form\Delete($this->languageHandler, $this->messageFactory, $entity); + } + + public function deleteContext(?ServerRequestInterface $request = null, ?string $formName = null): FormContextInterface + { + return new Lib\FormContext($request, $formName); + } + */ +} \ No newline at end of file diff --git a/skeleton/src/Factory/FormFactoryInterface.php b/skeleton/src/Factory/FormFactoryInterface.php new file mode 100644 index 0000000..e47e54e --- /dev/null +++ b/skeleton/src/Factory/FormFactoryInterface.php @@ -0,0 +1,25 @@ +initializeController($picea, $session, $user, $breadcrumb, $eventManager); - } - - #[Ignore] - public function initializeController(Picea $picea, Session $session, UserInterface $user, \Notes\Breadcrumb\Breadcrumb $breadcrumb, EventManager $eventManager) { - $this->picea = $picea; - $this->session = $session; - $this->eventManager = $eventManager; - $this->breadcrumb = $breadcrumb; - $this->user = $user; - } - } \ No newline at end of file diff --git a/skeleton/src/Lib/Message.php b/skeleton/src/Lib/Message.php index cc16603..8ec3b51 100644 --- a/skeleton/src/Lib/Message.php +++ b/skeleton/src/Lib/Message.php @@ -2,9 +2,10 @@ namespace %NAMESPACE%\Lib; +use Lean\Api\Factory\MessageFactoryInterface; use Picea\Ui\Method\FormMessage; -class Message implements FormMessage { +class Message implements FormMessage, MessageFactoryInterface { const MESSAGE_TYPE = [ 'success' => [ @@ -34,7 +35,7 @@ class Message implements FormMessage { public ? string $header = null; - public function __construct(string $message, string $type = "error", ? string $header = null, ? string $class = null) + public function __construct(string $message = "", string $type = "error", ? string $header = null, ? string $class = null) { $this->message = $message; @@ -86,18 +87,33 @@ class Message implements FormMessage { return $this->type === "error"; } - public static function generateSuccess(string $message, ? string $header = null, ? string $class = null) : self + public static function generateSuccess(string $message, ? string $header = null, ? string $class = null) : MessageFactoryInterface { return new static($message, 'success', $header, $class); } - public static function generateError(string $message, ? string $header = null, ? string $class = null) : self + public static function generateError(string $message, ? string $header = null, ? string $class = null) : MessageFactoryInterface { return new static($message, 'error', $header, $class); } - public static function generateWarning(string $message, ? string $header = null, ? string $class = null) : self + public static function generateWarning(string $message, ? string $header = null, ? string $class = null) : MessageFactoryInterface { return new static($message, 'warning', $header, $class); } + + public static function generateInformation(string $message, ?string $header = null, ?string $class = null): MessageFactoryInterface + { + return new static($message, 'information', $header, $class); + } + + public static function generateDebug(string $message, ?string $header = null, ?string $class = null): MessageFactoryInterface + { + return new static($message, 'debug', $header, $class); + } + + public static function generateTrace(string $message, ?string $header = null, ?string $class = null): MessageFactoryInterface + { + return new static($message, 'trace', $header, $class); + } } diff --git a/src/ApplicationStrategy.php b/src/ApplicationStrategy.php index 7c55b3b..308647a 100644 --- a/src/ApplicationStrategy.php +++ b/src/ApplicationStrategy.php @@ -2,8 +2,10 @@ namespace Lean; +use League\Route\Http\Exception\MethodNotAllowedException; use League\Route\Strategy; use League\Route\Http\Exception\NotFoundException; +use Lean\ApplicationStrategy\MethodNotAllowedInterface; use Lean\ApplicationStrategy\NotFoundDecoratorInterface; use Lean\ApplicationStrategy\ThrowableHandlerInterface; use Lean\Factory\HttpFactoryInterface; @@ -31,4 +33,42 @@ class ApplicationStrategy extends Strategy\ApplicationStrategy { { return $this->getContainer()->get(ThrowableHandlerInterface::class); } + + public function getMethodNotAllowedDecorator(MethodNotAllowedException $exception): MiddlewareInterface + { + return $this->getContainer()->has(MethodNotAllowedInterface::class) ? $this->getContainer()->get(MethodNotAllowedInterface::class) : parent::getMethodNotAllowedDecorator($exception); + } + +/*<<<<<<< HEAD + /*======= + public function getMethodNotAllowedDecorator(MethodNotAllowedException $exception): MiddlewareInterface + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if (php_sapi_name() !== 'cli' and ! defined('STDIN')) { + return $this->throw404($request); + } + + return $handler->handle($request);; + } + + public function throw404(ServerRequestInterface $request) : ResponseInterface + { + if ( getenv('DEBUG') && $this->di->has(\Picea\Asset\Asset::class) ) { + $params = $request->getServerParams(); + + $scpName = basename(explode('?', $params['REQUEST_URI'] ?? "", 2)[0]); + list(, $ext) = array_pad(explode('.', $scpName), 2, null); + + if ($ext && in_array($ext, ApplicationStrategy::ASSET_TRIGGER_UPDATE)) { + $this->di->get(\Picea\Asset\Asset::class)->launchInstall(); + } + } + + return $this->di->get('error.404'); + } + }; +<<<<<<< HEAD + } + +>>>>>>> af8b686 (- WIP on ulmus-user mechanics updates)*/ } \ No newline at end of file diff --git a/src/ApplicationStrategy/MethodNotAllowedInterface.php b/src/ApplicationStrategy/MethodNotAllowedInterface.php new file mode 100644 index 0000000..90b6653 --- /dev/null +++ b/src/ApplicationStrategy/MethodNotAllowedInterface.php @@ -0,0 +1,10 @@ +throw404($request); } - return $handler->handle($request);; + return $handler->handle($request); } public function throw404(ServerRequestInterface $request) : ResponseInterface { - return $this->checkAssetTrigger($request) ?: $this->httpFactory->createHtmlResponse($this->picea->renderHtml("lean/error/404", [], $this), 404); + return $this->checkAssetTrigger($request) ?: $this->container->get('error.404'); } protected function checkAssetTrigger(ServerRequestInterface $request) : false|ResponseInterface diff --git a/src/Composer.php b/src/Composer.php index 3ffd7e2..a3a52ee 100644 --- a/src/Composer.php +++ b/src/Composer.php @@ -27,7 +27,8 @@ class Composer $source = static::createPath('vendor', 'mcnd', 'lean', 'skeleton'); $dest = static::createPath('skeleton'); - `cp -a $source $dest`; + shell_exec("cp -a $source $dest"); + #`cp -a $source $dest`; $iterator = new \RecursiveDirectoryIterator($dest); $iterator->setFlags(\RecursiveDirectoryIterator::SKIP_DOTS); @@ -102,7 +103,7 @@ class Composer throw new \UnexpectedValueException("Composer file 'composer.json' could not be found within your project's root folder."); } - return $content ??= file_exists($path) ? json_decode(file_get_contents($path), true) : false; + return $content ??= file_exists($path) ? json_decode(file_get_contents($path), true, 512, \JSON_THROW_ON_ERROR) : false; } protected static function getNamespaceFromAutoload(Event $event) : ? string @@ -125,7 +126,7 @@ class Composer $event->getIO()->writeError("Your composer file do not seem to contains a valid psr-4 project pointing to the src folder."); - return null; + return false; } protected static function createPath(... $path) : string diff --git a/src/ControllerTrait.php b/src/ControllerTrait.php index 265267d..ff632e0 100644 --- a/src/ControllerTrait.php +++ b/src/ControllerTrait.php @@ -8,6 +8,8 @@ use Notes\Attribute\Ignore; use Notes\Route\Attribute\Object\Route; use Notes\Security\Attribute\Security; use Picea, Picea\Ui\Method\FormContext; +use Picea\Ui\Method\FormContextInterface; +use Picea\Ui\Method\FormInterface; use TheBugs\Email\MailerInterface; use Storage\{ Cookie, Session }; use League\CommonMark\CommonMarkConverter; @@ -20,9 +22,6 @@ use function file_get_contents; #[Route(method: [ "GET", "POST" ])] trait ControllerTrait { - ##[Inject] - # public \Notes\Breadcrumb\Breadcrumb $breadcrumb; - #[Inject] public Session $session; @@ -84,6 +83,13 @@ trait ControllerTrait { return HttpFactory::createRedirectResponse($url, $code, $headers); } + #[Ignore] + protected function refresh(int $code = 204, array $headers = []) { + return static::renderNothing($code, [ + 'Refresh' => 0 + ] + $headers); + } + #[Ignore] public static function renderNothing(int $code = 204, array $headers = []) : ResponseInterface { @@ -205,6 +211,16 @@ trait ControllerTrait { return $this->picea->compiler->getExtensionFromToken('route')->buildRouteUrl($name, $parameters); } + #[Ignore] + public function form(FormInterface $form, ? FormContextInterface $formContext = null) : Picea\Ui\Method\FormHandler + { + if (! $this->picea ) { + throw new \Exception("A picea object must be provided on class initialization to use this method."); + } + + return $this->picea->compiler->getExtensionInstanceOf(Picea\Ui\Method\Form::class)->formClass($form, $formContext); + } + #[Ignore] public function json($data, int $flags = 0) : string { @@ -212,7 +228,7 @@ trait ControllerTrait { } #[Ignore] - public function pushContext(FormContext $context) : FormContext + public function pushContext(FormContextInterface $context) : FormContextInterface { $this->contextList[$context->formName ?? uniqid("context_")] = $context; @@ -220,7 +236,7 @@ trait ControllerTrait { } #[Ignore] - public function context(? string $name = null) : ? FormContext + public function context(? string $name = null) : ? FormContextInterface { return $name ? $this->contextList[$name] : array_values($this->contextList)[0] ?? null; } diff --git a/src/Factory/HttpFactory.php b/src/Factory/HttpFactory.php index 1ab5214..ed48175 100644 --- a/src/Factory/HttpFactory.php +++ b/src/Factory/HttpFactory.php @@ -5,13 +5,15 @@ namespace Lean\Factory; use Laminas\Diactoros\Response\{EmptyResponse, HtmlResponse, JsonResponse, RedirectResponse, TextResponse}; use Lean\Response\{ DownloadResponse, ImageResponse, FileDownloadResponse, PdfResponse }; use Laminas\Diactoros\Response; +use Laminas\Diactoros\StreamFactory; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; class HttpFactory implements HttpFactoryInterface { - public static function createResponse(string $url, int $code = 302, array $headers = []) : ResponseInterface + public static function createResponse(mixed $body = 'php://memory', int $code = 200, array $headers = []) : ResponseInterface { - return new Response($url, $code, $headers); + return new Response($body, $code, $headers); } public static function createRedirectResponse(string $url, int $code = 302, array $headers = []) : ResponseInterface @@ -58,4 +60,9 @@ class HttpFactory implements HttpFactoryInterface { return new FileDownloadResponse($path, $code, $headers); } + + public static function createStream(string $content): StreamInterface + { + return new StreamFactory()->createStream($content); + } } diff --git a/src/Factory/HttpFactoryInterface.php b/src/Factory/HttpFactoryInterface.php index 4ef594b..d44ae58 100644 --- a/src/Factory/HttpFactoryInterface.php +++ b/src/Factory/HttpFactoryInterface.php @@ -2,10 +2,8 @@ namespace Lean\Factory; -use Laminas\Diactoros\Response\{EmptyResponse, HtmlResponse, JsonResponse, RedirectResponse, TextResponse}; -use Lean\Response\{ DownloadResponse, ImageResponse, FileDownloadResponse, PdfResponse }; -use Laminas\Diactoros\Response; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; interface HttpFactoryInterface { @@ -19,4 +17,5 @@ interface HttpFactoryInterface public static function createDownloadableResponse(string $data, string $filename, int $code = 200, array $headers = []) : ResponseInterface; public static function createImageResponse(string $data, int $code = 200, array $headers = []) : ResponseInterface; public static function createFileDownloadResponse(string $path, int $code = 200, array $headers = []) : ResponseInterface; -} + public static function createStream(string $content) : StreamInterface; +} \ No newline at end of file diff --git a/src/File.php b/src/File.php index 1b2d4e2..a5875ab 100644 --- a/src/File.php +++ b/src/File.php @@ -14,20 +14,20 @@ class File $beginning = false; $text = array(); - while ($counter && ! $beginning) { + while ($counter && !$beginning) { $t = ""; while ($t !== PHP_EOL) { - if(fseek($handle, $pos, SEEK_END) == -1) { + if (fseek($handle, $pos, SEEK_END) == -1) { $beginning = true; break; } $t = fgetc($handle); - $pos --; + $pos--; } - $counter --; + $counter--; if ($beginning) { rewind($handle); @@ -36,8 +36,40 @@ class File $text[$lines - $counter - 1] = fgets($handle); } - fclose ($handle); + fclose($handle); return array_reverse($text); } + + public static function sanitizeFilePath($path) : string + { + // Remove null bytes + $path = str_replace("\0", "", $path); + + $path = str_replace([ "\\", "/" ], DIRECTORY_SEPARATOR, $path); + + $path = preg_replace('#' . DIRECTORY_SEPARATOR . '+#', DIRECTORY_SEPARATOR, $path); + + // Remove . and .. directory traversal attempts + $path = preg_replace('#\.{1,2}/#', '', $path); + + $path = trim($path, '/\\'); + + return $path; + } + + public static function assertFileExists(string $filepath) : void + { + if (! is_file($filepath)) { + throw new \InvalidArgumentException("File does not exist: {$filepath}"); + } + } + + public static function assertFolderExists(string $path) : void + { + if (! is_dir($path)) { + throw new \InvalidArgumentException("Folder does not exist: {$path}"); + } + } + } \ No newline at end of file diff --git a/src/Kernel.php b/src/Kernel.php index 9b18038..27754e4 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -11,33 +11,34 @@ use League\Route\Strategy\ApplicationStrategy; use Psr\Http\Message\ServerRequestInterface; -use Tell\I18n; -use Ulmus\Container\AdapterProxy; - use Laminas\Diactoros\ServerRequestFactory, Laminas\HttpHandlerRunner\Emitter\EmitterInterface; use Ulmus\Ulmus; -class Kernel { - +class Kernel +{ protected Container $container; protected string $locale; public array $paths = []; - public int $errorReporting = E_ALL & ~E_USER_DEPRECATED & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE; + public int $errorReportingDev = -1 & ~E_USER_DEPRECATED & ~E_DEPRECATED ; + + public int $errorReportingProd = E_ALL & ~E_USER_DEPRECATED & ~E_DEPRECATED /*& ~E_NOTICE*/; public array $definitionFilePaths; public string $errorLogPath; - public string $projectPath; + public function __construct( + public string $projectPath + ) { + # Still not sure about adding a folder check each call just to add a basic project setup error ... + # File::assertFolderExists($this->projectPath); - public function __construct(string $projectPath) - { - $this->projectPath = $projectPath; + define('IS_CLI', php_sapi_name() === 'cli' OR defined('STDIN')); $this->setEnvironment(); $this->initializeEngine(); @@ -46,21 +47,27 @@ class Kernel { $this->handleRequest(); } - public static function putenv($var, $value) + public static function putenv($var, $value) : void { putenv("$var=$value"); $_ENV[$var] = $value; } - protected function setEnvironment() { + protected function setEnvironment() : void + { static::putenv('PROJECT_PATH', $this->projectPath); // Environment vars (accessible from \DI\env(), getenv(), $_ENV and $_SERVER) Dotenv::create(getenv("PROJECT_PATH"))->load(); // Override using headers - if ( ( $keys = getenv('KEYS') ) && ( $auth = $_SERVER["HTTP_X_DEV_AUTH"] ?? null ) && in_array($auth, explode(',', $keys)) ) { - foreach (['APP_ENV', 'DEBUG',] as $env) { + if (($keys = getenv('KEYS')) && ($auth = $_SERVER["HTTP_X_DEV_AUTH"] ?? null) && in_array($auth, explode(',', $keys))) { + unset($_SERVER["HTTP_X_DEV_AUTH"]); + + unset($_ENV['KEYS'], $_SERVER['KEYS']); + putenv("KEYS="); + + foreach(['APP_ENV', 'DEBUG',] as $env) { if (null !== $value = $_SERVER["HTTP_X_$env"] ?? null) { static::putenv($env, $value); } @@ -69,12 +76,14 @@ class Kernel { // Paths and directories foreach($this->paths as $name => $envkey) { - if ( ! getenv($name) ) { + if (!getenv($name)) { $path = getenv("PROJECT_PATH") . DIRECTORY_SEPARATOR . getenv($envkey); if (getenv('DEBUG') && ! file_exists($path)) { - if (false === mkdir($path, 0755, true)) { - throw new \Exception(sprintf("Folder '%s' could not be created", $path)); + if (false === @mkdir($path, 0755, true)) { + if (! IS_CLI) { + throw new \Exception(sprintf("Folder '%s' could not be created", $path)); + } } } @@ -91,15 +100,20 @@ class Kernel { setlocale(LC_ALL, $this->locale = getenv("DEFAULT_LOCAL")); setlocale(LC_TIME, getenv("DEFAULT_TIME"), getenv("DEFAULT_TIME_FALLBACK")); - if ( class_exists('Locale') ) { + if (class_exists('Locale')) { \Locale::setDefault($this->locale); } - ini_set("log_errors", "1"); - ini_set("error_log", $this->errorLogPath); - ini_set('display_errors', getenv("DEBUG") ? 'on' : 'on'); + ini_set("error_log", $this->errorLogPath ?? getenv("LOGS_PATH") . DIRECTORY_SEPARATOR. date("Y-m").".log"); + ini_set("log_errors", true); + ini_set('display_errors', getenv("DEBUG") ? 'on' : 'off'); - error_reporting($this->errorReporting); + if (getenv("DEBUG")) { + error_reporting(getenv('ERROR_REPORTING_DEV') ?: $this->errorReportingDev); + } + else { + error_reporting(getenv('ERROR_REPORTING_PROD') ?: $this->errorReportingProd); + } return $this; } @@ -112,7 +126,8 @@ class Kernel { if (getenv("APP_ENV") === "prod") { if (getenv("CACHE_PATH")) { - # check if ACPU is there first, $containerBuilder->enableDefinitionCache(); + # check if ACPU is there first, + # $containerBuilder->enableDefinitionCache(); # Do not work .. ? $containerBuilder->enableCompilation(getenv("CACHE_PATH") . "/di/"); # No proxies yet... $containerBuilder->writeProxiesToFile(true, getenv("CACHE_PATH") . "/di/"); @@ -121,7 +136,7 @@ class Kernel { else { $compiled = getenv("CACHE_PATH") . "/di/CompiledContainer.php"; - if ( file_exists($compiled) ) { + if (file_exists($compiled)) { unlink($compiled); } } @@ -156,8 +171,7 @@ class Kernel { } # Must be removed from KERNEL ! - $this->container->has('ulmus.caching') and ( Ulmus::$cache = $this->container->get('ulmus.caching') ); - # $this->container->has(AdapterProxy::class) and ( $ ) + $this->container->has('ulmus.caching') and (Ulmus::$cache = $this->container->get('ulmus.caching')); return $this; } @@ -180,4 +194,4 @@ class Kernel { return $this; } -}; +} \ No newline at end of file diff --git a/src/Lean.php b/src/Lean.php index 32fc06b..bd48417 100644 --- a/src/Lean.php +++ b/src/Lean.php @@ -244,4 +244,15 @@ class Lean return call_user_func($func); } + + public static function path(...$parts) : string + { + return implode(DIRECTORY_SEPARATOR, $parts); + } + + + public static function rawPath(...$parts) : string + { + return implode(DIRECTORY_SEPARATOR, $parts); + } } diff --git a/src/Response/FileDownloadResponse.php b/src/Response/FileDownloadResponse.php index 9bcce2a..cdf5e97 100644 --- a/src/Response/FileDownloadResponse.php +++ b/src/Response/FileDownloadResponse.php @@ -43,7 +43,6 @@ class FileDownloadResponse extends Response else { $finfo = finfo_open(FILEINFO_MIME_TYPE); $mime = finfo_file($finfo, $filepath); - finfo_close($finfo); } parent::__construct( diff --git a/src/Routing.php b/src/Routing.php index c359c40..adc6a50 100644 --- a/src/Routing.php +++ b/src/Routing.php @@ -44,6 +44,11 @@ class Routing { $container->set(ServerRequestInterface::class, $request); + # Decoding arguments received from the router + foreach($arguments as &$arg) { + $arg = urldecode($arg); + } + return $this->response ?? $object->$method($request, $arguments); }); } diff --git a/view/lean/block/message.phtml b/view/lean/block/message.phtml new file mode 100644 index 0000000..a969bf8 --- /dev/null +++ b/view/lean/block/message.phtml @@ -0,0 +1,11 @@ +{% arguments array $contextList %} + +{% if $contextList %} + +{% endif %} \ No newline at end of file diff --git a/view/lean/block/since.phtml b/view/lean/block/since.phtml new file mode 100644 index 0000000..65be88c --- /dev/null +++ b/view/lean/block/since.phtml @@ -0,0 +1,23 @@ +{% arguments \DateInterval $interval %} + +{% if $interval->y %} + {% lang "lean.date.interval.years", [ 'years' => $interval->y ] %}, +{% endif %} + +{% if $interval->m %} + {% lang "lean.date.interval.months", [ 'months' => $interval->m ] %}, +{% endif %} + +{% if $interval->d %} + {% lang "lean.date.interval.days", [ 'days' => $interval->d ] %}, +{% endif %} + +{% if $interval->h %} + {% lang "lean.date.interval.hours", [ 'hours' => $interval->h ] %}, +{% endif %} + +{% if $interval->i %} + {% lang "lean.date.interval.minutes", [ 'minutes' => $interval->i ] %} +{% else %} + {% lang "lean.date.interval.seconds", [ 'seconds' => $interval->s ] %} +{% endif %} \ No newline at end of file diff --git a/view/lean/error/401.phtml b/view/lean/error/401.phtml index f421d07..8fb45de 100644 --- a/view/lean/error/401.phtml +++ b/view/lean/error/401.phtml @@ -9,7 +9,7 @@