From aba9a65a3c7bd52baa3cb11e6aa1455f29b823e5 Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Tue, 29 Nov 2022 19:53:41 +0000 Subject: [PATCH] - Work done on the kinda-micro-framework to allow smoother deployment --- meta/definitions/language.php | 13 +-- meta/definitions/software.php | 2 +- skeleton/meta/definitions/definitions.php | 1 + .../meta}/definitions/security.php | 0 skeleton/src/Form/Form.php | 2 +- src/Application.php | 10 +++ src/ControllerTrait.php | 2 +- src/Lean.php | 15 +++- src/Response/DownloadResponse.php | 80 +++++++++++++++++++ src/Response/FileDownloadResponse.php | 77 ++++++++++++++++++ src/Response/ImageResponse.php | 74 +++++++++++++++++ src/Response/MarkdownResponse.php | 20 +++++ src/Response/PdfResponse.php | 70 ++++++++++++++++ src/Routing.php | 3 +- 14 files changed, 357 insertions(+), 12 deletions(-) rename {meta => skeleton/meta}/definitions/security.php (100%) create mode 100644 src/Response/DownloadResponse.php create mode 100644 src/Response/FileDownloadResponse.php create mode 100644 src/Response/ImageResponse.php create mode 100644 src/Response/MarkdownResponse.php create mode 100644 src/Response/PdfResponse.php diff --git a/meta/definitions/language.php b/meta/definitions/language.php index 3eee449..f72b077 100644 --- a/meta/definitions/language.php +++ b/meta/definitions/language.php @@ -8,23 +8,24 @@ return [ Tell\I18n::class => create( Tell\I18n::class ) ->constructor( get(Tell\Reader\JsonReader::class), get(Tell\Reader\PhpReader::class), - getenv("DEBUG") ? create(Tell\PrintMissingKey::class) : get('tell.fallback') + get('tell.fallback') /* getenv("DEBUG") ? create(Tell\PrintMissingKey::class) : get('tell.fallback') */ ), 'tell.fallback' => function($c) { - $tell = new Tell\I18n( $c->get(Tell\Reader\PhpReader::class) ); - $tell->locale = "en_US"; + $i18n = new Tell\I18n( $c->get(Tell\Reader\JsonReader::class), $c->get(Tell\Reader\PhpReader::class), new Tell\PrintMissingKey() ); + $i18n->locale(getenv('DEFAULT_LOCAL_FALLBACK') ?: "en_US"); + $i18n->initialize(true); - return $tell; + return $i18n; }, # TODO -- accept folders from Lean Apps Tell\Reader\PhpReader::class => function($c) { - return new Tell\Reader\PhpReader($c->get(Lean\Lean::class)->getI18n('php'), true); + return new Tell\Reader\PhpReader($c->get(Lean\Lean::class)->getI18n('php'), false); }, Tell\Reader\JsonReader::class => function($c) { - return new Tell\Reader\JsonReader($c->get(Lean\Lean::class)->getI18n('json'), true); + return new Tell\Reader\JsonReader($c->get(Lean\Lean::class)->getI18n('json'), true, \JSON_PRETTY_PRINT); }, LanguageHandler::class => autowire(LanguageHandler::class), diff --git a/meta/definitions/software.php b/meta/definitions/software.php index 1f7b05b..32da575 100644 --- a/meta/definitions/software.php +++ b/meta/definitions/software.php @@ -28,7 +28,7 @@ return [ 'order' => 10, ], [ - 'path' => getenv("PROJECT_PATH") . "/vendor/mcnd/lean/view/", + 'path' => getenv("PROJECT_PATH") . implode(DIRECTORY_SEPARATOR, [ "", "vendor", "mcnd" , "lean" , "view" ]), 'order' => 99, ], ], diff --git a/skeleton/meta/definitions/definitions.php b/skeleton/meta/definitions/definitions.php index 601e853..4eaf0e4 100644 --- a/skeleton/meta/definitions/definitions.php +++ b/skeleton/meta/definitions/definitions.php @@ -31,6 +31,7 @@ return array_merge( require("$dir/auth.php"), require("$dir/storage.php"), + require("$dir/security.php"), require("$dir/env/" . getenv('APP_ENV') . ".php"), [ 'config' => function () { return require(getenv("META_PATH")."/config.php"); } ] ); diff --git a/meta/definitions/security.php b/skeleton/meta/definitions/security.php similarity index 100% rename from meta/definitions/security.php rename to skeleton/meta/definitions/security.php diff --git a/skeleton/src/Form/Form.php b/skeleton/src/Form/Form.php index 4463be2..8591eed 100644 --- a/skeleton/src/Form/Form.php +++ b/skeleton/src/Form/Form.php @@ -23,7 +23,7 @@ class Form implements \Picea\Ui\Method\FormInterface { public function execute(FormContextInterface $context) : void { - $context->pushMessage(Message::generateSuccess( + $context->pushMessage(Lib\Message::generateSuccess( lang("form.success") )); } diff --git a/src/Application.php b/src/Application.php index e72d83a..4df16b7 100644 --- a/src/Application.php +++ b/src/Application.php @@ -6,6 +6,8 @@ namespace Lean; class Application { + public string $name; + public string $piceaContext; public array $piceaExtensions; @@ -22,8 +24,16 @@ class Application public array $tellPhp; + public array $data = []; + + public function __construct(string $name) { + $this->name = $name; + } + public function fromArray(array $data) : self { + $this->data = array_replace($this->data, $data); + if (is_array($picea = $data['picea'] ?? false)) { if ($picea['context'] ?? false ) { $this->piceaContext = $picea['context']; diff --git a/src/ControllerTrait.php b/src/ControllerTrait.php index 3f3c7f4..ac47def 100644 --- a/src/ControllerTrait.php +++ b/src/ControllerTrait.php @@ -2,7 +2,7 @@ namespace Lean; -use CSLSJ\Common\RequestResponse\{ PdfResponse, ImageResponse, DownloadResponse }; +use Lean\Response\{ PdfResponse, ImageResponse, DownloadResponse }; use Picea, Picea\Ui\Method\FormContext; diff --git a/src/Lean.php b/src/Lean.php index 2bc43a0..26bde6b 100644 --- a/src/Lean.php +++ b/src/Lean.php @@ -30,7 +30,7 @@ class Lean foreach(array_filter($list) as $application) { if ( $this->container->has($application) ) { - $this->applications[] = ( new Application() )->fromArray($this->container->get($application)); + $this->applications[] = ( new Application($application) )->fromArray($this->container->get($application)); } else { throw new \RuntimeException("Trying to load an application '$application' which have not been configured yet"); @@ -38,6 +38,17 @@ class Lean } } + public function getApplication(string $name) : ? Application + { + foreach($this->applications as $app) { + if ($app->name === $name) { + return $app; + } + } + + return null; + } + public function getPiceaContext() : string { foreach(array_reverse($this->applications) as $apps) { @@ -116,7 +127,7 @@ class Lean require($path . "http.php"), require($path . "language.php"), require($path . "routes.php"), - require($path . "security.php"), + # require($path . "security.php"), require($path . "software.php"), require($path . "template.php"), ); diff --git a/src/Response/DownloadResponse.php b/src/Response/DownloadResponse.php new file mode 100644 index 0000000..c8525eb --- /dev/null +++ b/src/Response/DownloadResponse.php @@ -0,0 +1,80 @@ +createBody($content), + $status, + $this->injectFilename($filename, $this->injectContentType("application/octet-stream", $headers)) + ); + } + + /** + * Inject the provided Content-Type, if none is already present. + * + * @return array Headers with injected Content-Type + */ + private function injectFilename(string $filename, array $headers) : array + { + $hasContentType = array_reduce(array_keys($headers), function ($carry, $item) { + return $carry ?: (strtolower($item) === 'content-disposition'); + }, false); + + if (! $hasContentType) { + $headers['content-disposition'] = ['Content-Disposition:attachment; filename="' . $filename . '"']; + } + + return $headers; + } + + /** + * Create the message body. + * + * @param string|StreamInterface $content + * @throws Exception\InvalidArgumentException if $content is neither a string or stream. + */ + private function createBody(string $content) : StreamInterface + { + if ($content instanceof StreamInterface) { + return $content; + } + + $body = new Stream('php://temp', 'wb+'); + $body->write($content); + $body->rewind(); + return $body; + } +} diff --git a/src/Response/FileDownloadResponse.php b/src/Response/FileDownloadResponse.php new file mode 100644 index 0000000..2c9f5bf --- /dev/null +++ b/src/Response/FileDownloadResponse.php @@ -0,0 +1,77 @@ +createBody($filepath); + + parent::__construct( + $body, + $status, + $this->injectContentType( (new \Mimey\MimeTypes())->getMimeType( pathinfo($filepath, PATHINFO_EXTENSION) ), $headers) + ); + } + + /** + * Create the message body. + * + * @param string|StreamInterface $filepath + * @throws Exception\InvalidArgumentException if $filepath is neither a string or stream. + */ + private function createBody($filepath) : StreamInterface + { + if ($filepath instanceof StreamInterface) { + return $filepath; + } + + if (! is_string($filepath)) { + throw new Exception\InvalidArgumentException(sprintf( + 'Invalid content (%s) provided to %s', + (is_object($filepath) ? get_class($filepath) : gettype($filepath)), + __CLASS__ + )); + } + + if ( ! file_exists($filepath) ) { + throw new Exception\InvalidArgumentException("Given file (%s) do not look like a valid file path.", $filepath); + } + + if ( ! is_readable($filepath) ) { + throw new Exception\InvalidArgumentException("Given file (%s) do not seem to be readable. This could indicate a permission problem", $filepath); + } + + return new Stream(fopen($filepath, "r"), 'r'); + } +} diff --git a/src/Response/ImageResponse.php b/src/Response/ImageResponse.php new file mode 100644 index 0000000..e978cee --- /dev/null +++ b/src/Response/ImageResponse.php @@ -0,0 +1,74 @@ +createBody($content), + $status, + $this->injectContentType('image', $headers) + ); + } + + /** + * Create the message body. + * + * @param string|StreamInterface $content + * @throws Exception\InvalidArgumentException if $content is neither a string or stream. + */ + private function createBody($content) : StreamInterface + { + if ($content instanceof StreamInterface) { + return $content; + } + + if (! is_string($content)) { + throw new Exception\InvalidArgumentException(sprintf( + 'Invalid content (%s) provided to %s', + (is_object($content) ? get_class($content) : gettype($content)), + __CLASS__ + )); + } + + $body = new Stream('php://temp', 'wb+'); + $body->write($content); + $body->rewind(); + return $body; + } +} diff --git a/src/Response/MarkdownResponse.php b/src/Response/MarkdownResponse.php new file mode 100644 index 0000000..025fda9 --- /dev/null +++ b/src/Response/MarkdownResponse.php @@ -0,0 +1,20 @@ +convertToHtml($markdown), $status, $headers); + } +} diff --git a/src/Response/PdfResponse.php b/src/Response/PdfResponse.php new file mode 100644 index 0000000..a5d6751 --- /dev/null +++ b/src/Response/PdfResponse.php @@ -0,0 +1,70 @@ +createBody($content), + $status, + $this->injectContentType('application/pdf; charset=utf-8', $headers) + ); + } + + /** + * Create the message body. + * + * @param string|StreamInterface $content + * @throws Exception\InvalidArgumentException if $content is neither a string or stream. + */ + private function createBody($content) : StreamInterface + { + if ($content instanceof StreamInterface) { + return $content; + } + + if (! is_string($content)) { + throw new Exception\InvalidArgumentException(sprintf( + 'Invalid content (%s) provided to %s', + (is_object($content) ? get_class($content) : gettype($content)), + __CLASS__ + )); + } + + $body = new Stream('php://temp', 'wb+'); + $body->write($content); + $body->rewind(); + return $body; + } +} diff --git a/src/Routing.php b/src/Routing.php index 22eecc2..88851ca 100644 --- a/src/Routing.php +++ b/src/Routing.php @@ -64,6 +64,7 @@ class Routing { public function registerRoute(ContainerInterface $container, string $urlBase) { $this->router->group(rtrim($urlBase, "/"), function (RouteGroup $route) use ($container) { + foreach($this->fetcher->compile() as $annotation) { # Register routes to the UrlExtension from picea (handling url, route and asset extensions) if ( null !== ( $name = $annotation->name ?? null ) ) { @@ -97,10 +98,10 @@ class Routing { } if ( $forbidden = $this->security->taxus($class, $method, $object->user ?? null) ) { - return $forbidden; } + # @TODO of course, this as to go ; moving to a simple callback method soon which can then be fed by Picea if ( $container->has(Picea::class) ) { $container->get(Picea::class)->globalVariables['route'] = $annotation; }