diff --git a/formwork/fields/date.php b/formwork/fields/date.php index 95f34c865..ac2431c68 100644 --- a/formwork/fields/date.php +++ b/formwork/fields/date.php @@ -1,15 +1,18 @@ function (Field $field, ?string $format = null, string $type = 'pattern'): string { + 'format' => function (Field $field, ?string $format = null, string $type = 'pattern') use ($app): string { + $format ??= $app->config()->get('system.date.dateFormat'); + $translation = $app->translations()->getCurrent(); + if ($format !== null) { $format = match (strtolower($type)) { 'pattern' => Date::patternToFormat($format), @@ -17,15 +20,19 @@ default => throw new InvalidArgumentException('Invalid date format type') }; } - return $field->isEmpty() ? '' : Date::formatTimestamp($field->toTimestamp(), $format); + return $field->isEmpty() ? '' : Date::formatTimestamp($field->toTimestamp(), $format, $translation); }, - 'toTimestamp' => function (Field $field): ?int { - return $field->isEmpty() ? null : Date::toTimestamp($field->value()); + 'toTimestamp' => function (Field $field) use ($app): ?int { + $formats = [ + $app->config()->get('system.date.dateFormat'), + $app->config()->get('system.date.datetimeFormat'), + ]; + return $field->isEmpty() ? null : Date::toTimestamp($field->value(), $formats); }, - 'toDuration' => function (Field $field) use ($languages): string { - return $field->isEmpty() ? '' : Date::formatTimestampAsDistance($field->toTimestamp(), $languages->current()); + 'toDuration' => function (Field $field) use ($app): string { + return $field->isEmpty() ? '' : Date::formatTimestampAsDistance($field->toTimestamp(), $app->translations()->getCurrent()); }, 'toString' => function (Field $field): string { @@ -36,13 +43,18 @@ return $field; }, - 'validate' => function (Field $field, $value): ?string { + 'validate' => function (Field $field, $value) use ($app): ?string { if (Constraint::isEmpty($value)) { return null; } + $formats = [ + $app->config()->get('system.date.dateFormat'), + $app->config()->get('system.date.datetimeFormat'), + ]; + try { - return date('Y-m-d H:i:s', Date::toTimestamp($value)); + return date('Y-m-d H:i:s', Date::toTimestamp($value, $formats)); } catch (InvalidArgumentException $e) { throw new ValidationException(sprintf('Invalid value for field "%s" of type "%s":%s', $field->name(), $field->type(), Str::after($e->getMessage(), ':'))); } diff --git a/formwork/fields/markdown.php b/formwork/fields/markdown.php index 7d843331d..ad8d2808b 100644 --- a/formwork/fields/markdown.php +++ b/formwork/fields/markdown.php @@ -1,5 +1,6 @@ function (Field $field) use ($site): string { + 'toHTML' => function (Field $field) use ($app, $site): string { $currentPage = $site->currentPage(); - return Markdown::parse((string) $field->value(), ['baseRoute' => $currentPage !== null ? $currentPage->route() : '/']); + return Markdown::parse( + (string) $field->value(), + [ + 'site' => $site, + 'safeMode' => $app->config()->get('system.pages.content.safeMode'), + 'baseRoute' => $currentPage !== null ? $currentPage->route() : '/', + ] + ); }, 'toString' => function (Field $field): string { diff --git a/formwork/helpers.php b/formwork/helpers.php index ff80e13fd..5d9815135 100644 --- a/formwork/helpers.php +++ b/formwork/helpers.php @@ -40,23 +40,28 @@ $currentPage = $app->site()->currentPage(); return Markdown::parse( $markdown, - ['baseRoute' => $currentPage !== null ? $currentPage->route() : '/'] + [ + 'site' => $app->site(), + 'safeMode' => $app->config()->get('system.pages.content.safeMode'), + 'baseRoute' => $currentPage !== null ? $currentPage->route() : '/', + ] ); }, 'date' => static function (int $timestamp, ?string $format = null) use ($app): string { return Date::formatTimestamp( $timestamp, - $format ?? $app->config()->get('system.date.dateFormat') + $format ?? $app->config()->get('system.date.dateFormat'), + $app->translations()->getCurrent() ); }, 'datetime' => static function (int $timestamp) use ($app): string { - return Date::formatTimestamp($timestamp, $app->config()->get('system.date.datetimeFormat')); + return Date::formatTimestamp($timestamp, $app->config()->get('system.date.datetimeFormat'), $app->translations()->getCurrent()); }, - 'timedistance' => static function (int $timestamp): string { - return Date::formatTimestampAsDistance($timestamp); + 'timedistance' => static function (int $timestamp) use ($app): string { + return Date::formatTimestampAsDistance($timestamp, $app->translations()->getCurrent()); }, 'translate' => fn (string $key, ...$arguments) => $app->translations()->getCurrent()->translate($key, ...$arguments), diff --git a/formwork/src/App.php b/formwork/src/App.php index d341ee5e6..1bd77fe61 100644 --- a/formwork/src/App.php +++ b/formwork/src/App.php @@ -16,6 +16,9 @@ use Formwork\Http\Response; use Formwork\Images\ImageFactory; use Formwork\Languages\Languages; +use Formwork\Pages\PageCollectionFactory; +use Formwork\Pages\PageFactory; +use Formwork\Pages\PaginationFactory; use Formwork\Panel\Panel; use Formwork\Router\Router; use Formwork\Schemes\Schemes; @@ -189,6 +192,12 @@ protected function loadServices(Container $container): void ->loader(SchemesServiceLoader::class) ->alias('schemes'); + $container->define(PageFactory::class); + + $container->define(PaginationFactory::class); + + $container->define(PageCollectionFactory::class); + $container->define(Site::class) ->loader(SiteServiceLoader::class) ->alias('site'); @@ -201,6 +210,7 @@ protected function loadServices(Container $container): void $container->define(Statistics::class) ->parameter('path', fn (Config $config) => $config->get('system.statistics.path')) + ->parameter('translation', fn (Translations $translations) => $translations->getCurrent()) ->alias('statistics'); $container->define(FilesCache::class) diff --git a/formwork/src/Backupper.php b/formwork/src/Backupper.php index 763565c1d..0b684033b 100644 --- a/formwork/src/Backupper.php +++ b/formwork/src/Backupper.php @@ -2,10 +2,8 @@ namespace Formwork; -use Formwork\Config\Config; use Formwork\Exceptions\TranslatedException; use Formwork\Utils\FileSystem; -use Formwork\Utils\Uri; use Formwork\Utils\ZipErrors; use ZipArchive; @@ -16,19 +14,14 @@ class Backupper */ protected const string DATE_FORMAT = 'YmdHis'; - /** - * Backupper options - * - * @var array - */ - protected array $options = []; - /** * Return a new Backupper instance + * + * @param array $options */ - public function __construct(Config $config) - { - $this->options = $config->get('system.backup'); + public function __construct( + protected array $options + ) { } /** @@ -47,7 +40,7 @@ public function backup(): string FileSystem::createDirectory($this->options['path'], recursive: true); } - $name = sprintf('%s-%s-%s.zip', str_replace([' ', '.'], '-', Uri::host() ?? ''), $this->options['name'], date(self::DATE_FORMAT)); + $name = sprintf('%s-%s-%s.zip', str_replace([' ', '.'], '-', $this->options['hostname'] ?? 'unknown-host'), $this->options['name'], date(self::DATE_FORMAT)); $destination = FileSystem::joinPaths($path, $name); diff --git a/formwork/src/Controllers/AbstractController.php b/formwork/src/Controllers/AbstractController.php index c2e03b305..d8e5ca06c 100644 --- a/formwork/src/Controllers/AbstractController.php +++ b/formwork/src/Controllers/AbstractController.php @@ -63,7 +63,7 @@ protected function redirectToReferer( string $base = '/' ): RedirectResponse { if ( - !in_array($this->request->referer(), [null, Uri::current()], true) + !in_array($this->request->referer(), [null, $this->request->absoluteUri()], true) && $this->request->validateReferer(Path::join([$this->app->request()->root(), $base])) ) { return new RedirectResponse($this->request->referer(), $responseStatus, $headers); diff --git a/formwork/src/Exceptions/TranslatedException.php b/formwork/src/Exceptions/TranslatedException.php index e272df3c4..a0f7e2d09 100644 --- a/formwork/src/Exceptions/TranslatedException.php +++ b/formwork/src/Exceptions/TranslatedException.php @@ -3,7 +3,6 @@ namespace Formwork\Exceptions; use Exception; -use Formwork\App; class TranslatedException extends Exception { @@ -27,12 +26,4 @@ public function getLanguageString(): string { return $this->languageString; } - - /** - * Get localized message - */ - public function getTranslatedMessage(): string - { - return App::instance()->translations()->getCurrent()->translate($this->languageString); - } } diff --git a/formwork/src/Fields/Layout/Layout.php b/formwork/src/Fields/Layout/Layout.php index 0232dca21..7cd868ef2 100644 --- a/formwork/src/Fields/Layout/Layout.php +++ b/formwork/src/Fields/Layout/Layout.php @@ -2,6 +2,8 @@ namespace Formwork\Fields\Layout; +use Formwork\Translations\Translation; + class Layout { /** @@ -17,10 +19,10 @@ class Layout /** * @param array $data */ - public function __construct(array $data) + public function __construct(array $data, Translation $translation) { $this->type = $data['type']; - $this->sections = new SectionCollection($data['sections'] ?? []); + $this->sections = new SectionCollection($data['sections'] ?? [], $translation); } /** Get layout type diff --git a/formwork/src/Fields/Layout/Section.php b/formwork/src/Fields/Layout/Section.php index 379fc1e2b..b23b2656b 100644 --- a/formwork/src/Fields/Layout/Section.php +++ b/formwork/src/Fields/Layout/Section.php @@ -2,8 +2,8 @@ namespace Formwork\Fields\Layout; -use Formwork\App; use Formwork\Data\Traits\DataGetter; +use Formwork\Translations\Translation; use Formwork\Utils\Str; class Section @@ -13,7 +13,7 @@ class Section /** * @param array $data */ - public function __construct(array $data) + public function __construct(array $data, protected Translation $translation) { $this->data = $data; } @@ -31,7 +31,6 @@ public function is(string $key, bool $default = false): bool */ public function label(): string { - $translation = App::instance()->translations()->getCurrent(); - return Str::interpolate($this->get('label', ''), fn ($key) => $translation->translate($key)); + return Str::interpolate($this->get('label', ''), fn ($key) => $this->translation->translate($key)); } } diff --git a/formwork/src/Fields/Layout/SectionCollection.php b/formwork/src/Fields/Layout/SectionCollection.php index 725733ace..7c98b4c3a 100644 --- a/formwork/src/Fields/Layout/SectionCollection.php +++ b/formwork/src/Fields/Layout/SectionCollection.php @@ -3,6 +3,7 @@ namespace Formwork\Fields\Layout; use Formwork\Data\AbstractCollection; +use Formwork\Translations\Translation; use Formwork\Utils\Arr; class SectionCollection extends AbstractCollection @@ -14,8 +15,8 @@ class SectionCollection extends AbstractCollection /** * @param array> $sections */ - public function __construct(array $sections) + public function __construct(array $sections, Translation $translation) { - parent::__construct(Arr::map($sections, fn ($section) => new Section($section))); + parent::__construct(Arr::map($sections, fn ($section) => new Section($section, $translation))); } } diff --git a/formwork/src/Files/File.php b/formwork/src/Files/File.php index df90853e2..95b549c10 100644 --- a/formwork/src/Files/File.php +++ b/formwork/src/Files/File.php @@ -2,12 +2,11 @@ namespace Formwork\Files; -use Formwork\App; use Formwork\Data\Contracts\Arrayable; use Formwork\Files\Exceptions\FileUriGenerationException; use Formwork\Model\Attributes\ReadonlyModelProperty; use Formwork\Model\Model; -use Formwork\Parsers\Yaml; +use Formwork\Schemes\Scheme; use Formwork\Utils\FileSystem; use Formwork\Utils\MimeType; use Formwork\Utils\Str; @@ -16,9 +15,9 @@ class File extends Model implements Arrayable, Stringable { - protected const string MODEL_IDENTIFIER = 'file'; + public const string SCHEME_IDENTIFIER = 'files.file'; - protected const string SCHEME_IDENTIFIER = 'files.file'; + protected const string MODEL_IDENTIFIER = 'file'; /** * File name @@ -79,7 +78,6 @@ public function __construct(protected string $path) { $this->name = basename($path); $this->extension = FileSystem::extension($path); - $this->loadData(); } public function __toString(): string @@ -208,6 +206,14 @@ public function uri(): string return $this->uriGenerator->generate($this); } + public function absoluteUri(): string + { + if (!isset($this->uriGenerator)) { + throw new FileUriGenerationException('Cannot generate file absolute uri: generator not set'); + } + return $this->uriGenerator->generateAbsolute($this); + } + public function toArray(): array { return [ @@ -220,18 +226,10 @@ public function toArray(): array ]; } - private function loadData(): void + public function setScheme(Scheme $scheme): void { - $app = App::instance(); - - $this->scheme = $app->schemes()->get(static::SCHEME_IDENTIFIER); - $this->fields = $this->scheme->fields(); - - $metadataFile = $this->path . $app->config()->get('system.files.metadataExtension'); - - $this->data = FileSystem::exists($metadataFile) ? Yaml::parseFile($metadataFile) : []; - - $this->fields->setValues($this->data); + $this->scheme = $scheme; + $this->fields = $scheme->fields(); } /** diff --git a/formwork/src/Files/FileFactory.php b/formwork/src/Files/FileFactory.php index a0e93fbc2..917ad6a5d 100644 --- a/formwork/src/Files/FileFactory.php +++ b/formwork/src/Files/FileFactory.php @@ -3,6 +3,9 @@ namespace Formwork\Files; use Closure; +use Formwork\Config\Config; +use Formwork\Parsers\Yaml; +use Formwork\Schemes\Schemes; use Formwork\Services\Container; use Formwork\Utils\FileSystem; use RuntimeException; @@ -12,7 +15,7 @@ class FileFactory /** * @param array $associations */ - public function __construct(protected Container $container, protected array $associations = []) + public function __construct(protected Container $container, protected Config $config, protected Schemes $schemes, protected array $associations = []) { } @@ -36,6 +39,15 @@ public function make(string $path): File throw new RuntimeException(sprintf('Invalid object of type %s, only instances of %s are allowed', get_debug_type($instance), File::class)); } + $instance->setScheme($this->schemes->get($instance::SCHEME_IDENTIFIER)); + + $metadataFile = $path . $this->config->get('system.files.metadataExtension'); + + $metadata = FileSystem::exists($metadataFile) ? Yaml::parseFile($metadataFile) : []; + + $instance->setMultiple($metadata); + $instance->fields()->setValues($metadata); + $instance->setUriGenerator($this->container->get(FileUriGenerator::class)); return $instance; diff --git a/formwork/src/Files/FileUriGenerator.php b/formwork/src/Files/FileUriGenerator.php index 0360cd9d8..b31552b2a 100644 --- a/formwork/src/Files/FileUriGenerator.php +++ b/formwork/src/Files/FileUriGenerator.php @@ -4,15 +4,17 @@ use Formwork\Config\Config; use Formwork\Files\Exceptions\FileUriGenerationException; +use Formwork\Http\Request; use Formwork\Router\Router; use Formwork\Site; use Formwork\Utils\FileSystem; use Formwork\Utils\Str; +use Formwork\Utils\Uri; use RuntimeException; class FileUriGenerator { - public function __construct(protected Config $config, protected Router $router, protected Site $site) + public function __construct(protected Config $config, protected Router $router, protected Request $request, protected Site $site) { } @@ -46,4 +48,9 @@ public function generate(File $file): string throw new FileUriGenerationException(sprintf('Cannot generate uri for "%s": missing file generator', $file->name())); } + + public function generateAbsolute(File $file): string + { + return Uri::resolveRelative($this->generate($file), $this->request->absoluteUri()); + } } diff --git a/formwork/src/Http/Request.php b/formwork/src/Http/Request.php index 9c1571624..b4ee771c8 100644 --- a/formwork/src/Http/Request.php +++ b/formwork/src/Http/Request.php @@ -108,14 +108,9 @@ public function baseUri(): string $defaultPort = self::DEFAULT_PORTS[$scheme]; - return Path::join( - [ - $port !== $defaultPort - ? sprintf('%s://%s:%d/', $scheme, $host, $port) - : sprintf('%s://%s', $scheme, $host), - $this->root(), - ] - ); + return $port !== $defaultPort + ? sprintf('%s://%s:%d/%s/', $scheme, $host, $port, trim($this->root(), '/')) + : sprintf('%s://%s/%s/', $scheme, $host, trim($this->root(), '/')); } public function uri(): string @@ -130,12 +125,7 @@ public function uri(): string public function absoluteUri(): string { - return Path::join( - [ - $this->baseUri(), - $this->uri(), - ] - ); + return $this->baseUri() . ltrim($this->uri(), '/'); } public function ip(): ?string @@ -185,7 +175,7 @@ public function referer(): ?string public function validateReferer(?string $path = null): bool { - $base = Uri::normalize(Uri::base() . '/' . $path); + $base = Uri::normalize(Uri::base($this->uri()) . '/' . $path); return Str::startsWith((string) $this->referer(), $base); } diff --git a/formwork/src/Images/Image.php b/formwork/src/Images/Image.php index f6dbb1a64..458ca3117 100644 --- a/formwork/src/Images/Image.php +++ b/formwork/src/Images/Image.php @@ -34,14 +34,13 @@ use Formwork\Model\Attributes\ReadonlyModelProperty; use Formwork\Utils\FileSystem; use Formwork\Utils\MimeType; -use Formwork\Utils\Uri; use RuntimeException; class Image extends File { - protected const string MODEL_IDENTIFIER = 'image'; + public const string SCHEME_IDENTIFIER = 'files.image'; - protected const string SCHEME_IDENTIFIER = 'files.image'; + protected const string MODEL_IDENTIFIER = 'image'; #[ReadonlyModelProperty] protected AbstractHandler $handler; @@ -69,11 +68,6 @@ public function path(): string return $this->process()->path; } - public function absoluteUri(): string - { - return Uri::resolveRelative($this->uri()); - } - public function mimeType(): string { if (!isset($this->mimeType)) { diff --git a/formwork/src/Pages/Page.php b/formwork/src/Pages/Page.php index 9b1957e0e..2f32a0a38 100644 --- a/formwork/src/Pages/Page.php +++ b/formwork/src/Pages/Page.php @@ -175,8 +175,11 @@ class Page extends Model implements Stringable /** * @param array $data */ - public function __construct(array $data = []) - { + public function __construct( + array $data, + protected App $app, + protected PageCollectionFactory $pageCollectionFactory, + ) { $this->setMultiple($data); $this->loadFiles(); @@ -494,8 +497,13 @@ public function setNum(?int $num = null): void $num = $this->num(); if ($mode === 'date' && $num !== null) { + $formats = [ + $this->app->config()->get('system.date.dateFormat'), + $this->app->config()->get('system.date.datetimeFormat'), + ]; + $timestamp = isset($this->data['publishDate']) - ? Date::toTimestamp($this->data['publishDate']) + ? Date::toTimestamp($this->data['publishDate'], $formats) : $this->contentFile()?->lastModifiedTime(); if ($num === (int) date(self::DATE_NUM_FORMAT, $timestamp)) { @@ -649,6 +657,9 @@ public function reload(array $data = []): void throw new RuntimeException('Unable to reload, the page has not been loaded yet'); } + $app = $this->app; + $pageCollectionFactory = $this->pageCollectionFactory; + $path = $this->path; $site = $this->site; @@ -656,7 +667,7 @@ public function reload(array $data = []): void $this->resetProperties(); - $this->__construct($data); + $this->__construct($data, $app, $pageCollectionFactory); } /** @@ -698,7 +709,7 @@ public function save(?string $language = null): void throw new UnexpectedValueException('Unexpected missing parent content path'); } - $config = App::instance()->config(); + $config = $this->app->config(); $language ??= $this->language(); @@ -804,7 +815,7 @@ protected function loadFiles(): void */ $languages = []; - $config = App::instance()->config(); + $config = $this->app->config(); $site = $this->site; @@ -837,7 +848,7 @@ protected function loadFiles(): void continue; } if (in_array($extension, $config->get('system.files.allowedExtensions'), true)) { - $files[] = App::instance()->getService(FileFactory::class)->make(FileSystem::joinPaths($this->path, $file)); + $files[] = $this->app->getService(FileFactory::class)->make(FileSystem::joinPaths($this->path, $file)); } } } diff --git a/formwork/src/Pages/PageCollection.php b/formwork/src/Pages/PageCollection.php index 01802b693..e898e5749 100644 --- a/formwork/src/Pages/PageCollection.php +++ b/formwork/src/Pages/PageCollection.php @@ -19,6 +19,14 @@ class PageCollection extends AbstractCollection implements Paginable */ protected Pagination $pagination; + /** + * @param array $data + */ + public function __construct(array $data, protected PaginationFactory $paginationFactory) + { + parent::__construct($data); + } + /** * Return the Pagination object related to the collection */ @@ -34,7 +42,7 @@ public function pagination(): Pagination */ public function paginate(int $length, int $currentPage): self { - $pagination = new Pagination($this, $length); + $pagination = $this->paginationFactory->make($this, $length); $pagination->setCurrentPage($currentPage); $pageCollection = $this->slice($pagination->offset(), $pagination->length()); diff --git a/formwork/src/Pages/PageCollectionFactory.php b/formwork/src/Pages/PageCollectionFactory.php new file mode 100644 index 000000000..082a8f368 --- /dev/null +++ b/formwork/src/Pages/PageCollectionFactory.php @@ -0,0 +1,18 @@ + $data + */ + public function make(array $data): PageCollection + { + return new PageCollection($data, $this->paginationFactory); + } +} diff --git a/formwork/src/Pages/PageFactory.php b/formwork/src/Pages/PageFactory.php new file mode 100644 index 000000000..70e1a7f62 --- /dev/null +++ b/formwork/src/Pages/PageFactory.php @@ -0,0 +1,20 @@ + $data + */ + public function make(array $data = []): Page + { + return $this->container->build(Page::class, ['data' => $data]); + } +} diff --git a/formwork/src/Pages/Pagination.php b/formwork/src/Pages/Pagination.php index d89098de2..afacaaed9 100644 --- a/formwork/src/Pages/Pagination.php +++ b/formwork/src/Pages/Pagination.php @@ -4,12 +4,14 @@ use Formwork\Data\Pagination as BasePagination; use Formwork\Pages\Traits\PaginationUri; +use Formwork\Router\Router; +use Formwork\Site; class Pagination extends BasePagination { use PaginationUri; - public function __construct(PageCollection $pageCollection, int $length) + public function __construct(PageCollection $pageCollection, int $length, protected Site $site, protected Router $router) { parent::__construct($pageCollection, $length); } diff --git a/formwork/src/Pages/PaginationFactory.php b/formwork/src/Pages/PaginationFactory.php new file mode 100644 index 000000000..a10130793 --- /dev/null +++ b/formwork/src/Pages/PaginationFactory.php @@ -0,0 +1,18 @@ +app->site(), $this->router); + } +} diff --git a/formwork/src/Pages/Traits/PageStatus.php b/formwork/src/Pages/Traits/PageStatus.php index f842624f3..ccf530e7f 100644 --- a/formwork/src/Pages/Traits/PageStatus.php +++ b/formwork/src/Pages/Traits/PageStatus.php @@ -2,6 +2,7 @@ namespace Formwork\Pages\Traits; +use Formwork\App; use Formwork\Pages\Page; use Formwork\Utils\Date; use UnexpectedValueException; @@ -15,6 +16,8 @@ trait PageStatus */ protected array $data = []; + protected App $app; + /** * Page status */ @@ -36,12 +39,17 @@ public function status(): string $now = time(); + $formats = [ + $this->app->config()->get('system.date.dateFormat'), + $this->app->config()->get('system.date.datetimeFormat'), + ]; + if ($publishDate = ($this->data['publishDate'] ?? null)) { if (!is_string($publishDate)) { throw new UnexpectedValueException('Invalid publish date'); } - $published = $published && Date::toTimestamp($publishDate) < $now; + $published = $published && Date::toTimestamp($publishDate, $formats) < $now; } if ($unpublishDate = ($this->data['unpublishDate'] ?? null)) { @@ -49,7 +57,7 @@ public function status(): string throw new UnexpectedValueException('Invalid unpublish date'); } - $published = $published && Date::toTimestamp($unpublishDate) > $now; + $published = $published && Date::toTimestamp($unpublishDate, $formats) > $now; } $this->status = match (true) { diff --git a/formwork/src/Pages/Traits/PageTraversal.php b/formwork/src/Pages/Traits/PageTraversal.php index 1d43bc1c8..2662f54ea 100644 --- a/formwork/src/Pages/Traits/PageTraversal.php +++ b/formwork/src/Pages/Traits/PageTraversal.php @@ -4,6 +4,7 @@ use Formwork\Pages\Page; use Formwork\Pages\PageCollection; +use Formwork\Pages\PageCollectionFactory; use Formwork\Site; use Formwork\Utils\FileSystem; use RuntimeException; @@ -15,6 +16,8 @@ trait PageTraversal */ protected Page|Site|null $parent; + protected PageCollectionFactory $pageCollectionFactory; + /** * Collection of page children */ @@ -105,7 +108,7 @@ public function children(): PageCollection } if ($this->contentPath() === null) { - return $this->children = new PageCollection(); + return $this->children = $this->pageCollectionFactory->make([]); } return $this->children = $this->site()->retrievePages($this->contentPath()); @@ -137,7 +140,7 @@ public function descendants(): PageCollection } if ($this->contentPath() === null) { - return $this->descendants = new PageCollection(); + return $this->descendants = $this->pageCollectionFactory->make([]); } return $this->descendants = $this->site()->retrievePages($this->contentPath(), recursive: true); @@ -177,7 +180,7 @@ public function ancestors(): PageCollection $page = $parent; } - return $this->ancestors = new PageCollection($ancestors); + return $this->ancestors = $this->pageCollectionFactory->make($ancestors); } /** @@ -214,7 +217,7 @@ public function inclusiveSiblings(): PageCollection } if ($this->contentPath() === null || $this->parent() === null) { - return $this->inclusiveSiblings = new PageCollection([$this->route() => $this]); + return $this->inclusiveSiblings = $this->pageCollectionFactory->make([$this->route() => $this]); } return $this->inclusiveSiblings = $this->parent()->children(); diff --git a/formwork/src/Pages/Traits/PageUri.php b/formwork/src/Pages/Traits/PageUri.php index 05b71904d..5d38b05ae 100644 --- a/formwork/src/Pages/Traits/PageUri.php +++ b/formwork/src/Pages/Traits/PageUri.php @@ -9,6 +9,8 @@ trait PageUri { + protected App $app; + /** * Get page or site route */ @@ -26,7 +28,7 @@ abstract public function site(): Site; */ public function uri(string $path = '', bool|string $includeLanguage = true): string { - $base = App::instance()->request()->root(); + $base = $this->app->request()->root(); $route = $this->canonicalRoute() ?? $this->route(); @@ -49,6 +51,6 @@ public function uri(string $path = '', bool|string $includeLanguage = true): str */ public function absoluteUri(string $path = '', bool|string $includeLanguage = true): string { - return Uri::resolveRelative($this->uri($path, $includeLanguage)); + return Uri::resolveRelative($this->uri($path, $includeLanguage), $this->app->request()->absoluteUri()); } } diff --git a/formwork/src/Pages/Traits/PaginationUri.php b/formwork/src/Pages/Traits/PaginationUri.php index e79578e21..1976d2c32 100644 --- a/formwork/src/Pages/Traits/PaginationUri.php +++ b/formwork/src/Pages/Traits/PaginationUri.php @@ -2,8 +2,9 @@ namespace Formwork\Pages\Traits; -use Formwork\App; use Formwork\Router\Route; +use Formwork\Router\Router; +use Formwork\Site; use Formwork\Utils\Str; use RuntimeException; use UnexpectedValueException; @@ -20,6 +21,10 @@ trait PaginationUri */ protected static string $routeSuffix = '.pagination'; + protected Site $site; + + protected Router $router; + /** * Base route (without the pagination) */ @@ -39,13 +44,11 @@ public function route(int $pageNumber): string throw new UnexpectedValueException(sprintf('Cannot get the route for page %d, the pagination has only %d pages', $pageNumber, $this->length)); } - $router = App::instance()->router(); - if ($pageNumber === 1) { - return $router->generateWith($this->baseRoute()->getName(), []); + return $this->router->generateWith($this->baseRoute()->getName(), []); } - return $router->generateWith($this->paginationRoute()->getName(), [ + return $this->router->generateWith($this->paginationRoute()->getName(), [ static::$routeParam => $pageNumber, ]); } @@ -55,7 +58,7 @@ public function route(int $pageNumber): string */ public function uri(int $pageNumber): string { - return App::instance()->site()->uri($this->route($pageNumber)); + return $this->site->uri($this->route($pageNumber)); } /** @@ -131,19 +134,17 @@ protected function baseRoute(): Route return $this->baseRoute; } - $router = App::instance()->router(); - - if (!$router->current() instanceof Route) { + if (!$this->router->current() instanceof Route) { throw new RuntimeException('Cannot generate pagination routes, current route is not defined'); } - $routeName = Str::removeEnd($router->current()->getName(), static::$routeSuffix); + $routeName = Str::removeEnd($this->router->current()->getName(), static::$routeSuffix); - if (!$router->routes()->has($routeName)) { + if (!$this->router->routes()->has($routeName)) { throw new RuntimeException(sprintf('Cannot generate pagination routes, base route "%s" is not defined', $routeName)); } - return $this->baseRoute = $router->routes()->get($routeName); + return $this->baseRoute = $this->router->routes()->get($routeName); } /** @@ -155,13 +156,12 @@ protected function paginationRoute(): Route return $this->paginationRoute; } - $router = App::instance()->router(); $routeName = $this->baseRoute()->getName() . static::$routeSuffix; - if (!$router->routes()->has($routeName)) { + if (!$this->router->routes()->has($routeName)) { throw new RuntimeException(sprintf('Cannot generate pagination for route "%s", route "%s" is not defined', $this->baseRoute()->getName(), $routeName)); } - return $this->paginationRoute = $router->routes()->get($routeName); + return $this->paginationRoute = $this->router->routes()->get($routeName); } } diff --git a/formwork/src/Panel/Controllers/BackupController.php b/formwork/src/Panel/Controllers/BackupController.php index edcf1e280..fd6375257 100644 --- a/formwork/src/Panel/Controllers/BackupController.php +++ b/formwork/src/Panel/Controllers/BackupController.php @@ -24,18 +24,18 @@ public function make(): JsonResponse|Response return $this->forward(ErrorsController::class, 'forbidden'); } - $backupper = new Backupper($this->config); + $backupper = $backupper = new Backupper([...$this->config->get('system.backup'), 'hostname' => $this->request->host()]); try { $file = $backupper->backup(); } catch (TranslatedException $e) { - return JsonResponse::error($this->translate('panel.backup.error.cannotMake', $e->getTranslatedMessage()), ResponseStatus::InternalServerError); + return JsonResponse::error($this->translate('panel.backup.error.cannotMake', $this->translate($e->getLanguageString())), ResponseStatus::InternalServerError); } $filename = basename($file); $uriName = urlencode(base64_encode($filename)); return JsonResponse::success($this->translate('panel.backup.ready'), data: [ 'filename' => $filename, 'uri' => $this->panel->uri('/backup/download/' . $uriName . '/'), - 'date' => Date::formatTimestamp(FileSystem::lastModifiedTime($file), $this->config->get('system.date.datetimeFormat')), + 'date' => Date::formatTimestamp(FileSystem::lastModifiedTime($file), $this->config->get('system.date.datetimeFormat'), $this->translations->getCurrent()), 'size' => FileSystem::formatSize(FileSystem::size($file)), 'deleteUri' => $this->panel->uri('/backup/delete/' . $uriName . '/'), 'maxFiles' => $this->config->get('system.backup.maxFiles'), @@ -58,7 +58,7 @@ public function download(RouteParams $routeParams): Response } throw new RuntimeException($this->translate('panel.backup.error.cannotDownload.invalidFilename')); } catch (TranslatedException $e) { - $this->panel->notify($this->translate('panel.backup.error.cannotDownload', $e->getTranslatedMessage()), 'error'); + $this->panel->notify($this->translate('panel.backup.error.cannotDownload', $this->translate($e->getLanguageString())), 'error'); return $this->redirectToReferer(default: $this->generateRoute('panel.tools.backups'), base: $this->panel->panelRoot()); } } @@ -81,7 +81,7 @@ public function delete(RouteParams $routeParams): Response } throw new RuntimeException($this->translate('panel.backup.error.cannotDelete.invalidFilename')); } catch (TranslatedException $e) { - $this->panel->notify($this->translate('panel.backup.error.cannotDelete', $e->getTranslatedMessage()), 'error'); + $this->panel->notify($this->translate('panel.backup.error.cannotDelete', $this->translate($e->getLanguageString())), 'error'); return $this->redirectToReferer(default: $this->generateRoute('panel.tools.backups'), base: $this->panel->panelRoot()); } } diff --git a/formwork/src/Panel/Controllers/PagesController.php b/formwork/src/Panel/Controllers/PagesController.php index df8643ee9..a0a68dd60 100644 --- a/formwork/src/Panel/Controllers/PagesController.php +++ b/formwork/src/Panel/Controllers/PagesController.php @@ -15,6 +15,7 @@ use Formwork\Http\Response; use Formwork\Http\ResponseStatus; use Formwork\Pages\Page; +use Formwork\Pages\PageFactory; use Formwork\Panel\ContentHistory\ContentHistory; use Formwork\Panel\ContentHistory\ContentHistoryEvent; use Formwork\Parsers\Yaml; @@ -66,7 +67,7 @@ public function index(): Response /** * Pages@create action */ - public function create(): Response + public function create(PageFactory $pageFactory): Response { if (!$this->hasPermission('pages.create')) { return $this->forward(ErrorsController::class, 'forbidden'); @@ -80,10 +81,10 @@ public function create(): Response $fields->setValues($requestData)->validate(); // Let's create the page - $page = $this->createPage($fields); + $page = $this->createPage($fields, $pageFactory); $this->panel->notify($this->translate('panel.pages.page.created'), 'success'); } catch (TranslatedException $e) { - $this->panel->notify($e->getTranslatedMessage(), 'error'); + $this->panel->notify($this->translate($e->getLanguageString()), 'error'); return $this->redirectToReferer(default: $this->generateRoute('panel.pages'), base: $this->panel->panelRoot()); } catch (InvalidValueException $e) { $identifier = $e->getIdentifier() ?? 'varMissing'; @@ -176,7 +177,7 @@ public function edit(RouteParams $routeParams): Response $this->panel->notify($this->translate('panel.pages.page.edited'), 'success'); } catch (TranslatedException $e) { - $this->panel->notify($e->getTranslatedMessage(), 'error'); + $this->panel->notify($this->translate($e->getLanguageString()), 'error'); } catch (InvalidValueException $e) { $identifier = $e->getIdentifier() ?? 'varMissing'; $this->panel->notify($this->translate('panel.pages.page.cannotEdit.' . $identifier), 'error'); @@ -337,7 +338,7 @@ public function delete(RouteParams $routeParams): Response $this->panel->notify($this->translate('panel.pages.page.deleted'), 'success'); // Try to redirect to referer unless it's to Pages@edit - if ($this->request->referer() !== null && !Str::startsWith(Uri::normalize($this->request->referer()), Uri::make(['path' => $this->panel->uri('/pages/' . $routeParams->get('page') . '/edit/')]))) { + if ($this->request->referer() !== null && !Str::startsWith(Uri::normalize($this->request->referer()), Uri::make(['path' => $this->panel->uri('/pages/' . $routeParams->get('page') . '/edit/')], $this->request->baseUri()))) { return $this->redirectToReferer(default: $this->generateRoute('panel.pages'), base: $this->panel->panelRoot()); } return $this->redirect($this->generateRoute('panel.pages')); @@ -363,7 +364,7 @@ public function uploadFile(RouteParams $routeParams): Response try { $this->processPageUploads($this->request->files()->getAll(), $page); } catch (TranslatedException $e) { - $this->panel->notify($this->translate('upload.error', $e->getTranslatedMessage()), 'error'); + $this->panel->notify($this->translate('upload.error', $this->translate($e->getLanguageString())), 'error'); return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $routeParams->get('page')])); } } @@ -444,7 +445,7 @@ public function renameFile(RouteParams $routeParams): Response $previousFileRoute = $this->generateRoute('panel.pages.file', ['page' => $routeParams->get('page'), 'filename' => $previousName]); - if (Str::removeEnd((string) Uri::path($this->request->referer()), '/') === $this->site->uri($previousFileRoute)) { + if (Str::removeEnd((string) Uri::path((string) $this->request->referer()), '/') === $this->site->uri($previousFileRoute)) { return $this->redirect($this->generateRoute('panel.pages.file', ['page' => $routeParams->get('page'), 'filename' => $newName])); } @@ -485,7 +486,7 @@ public function replaceFile(RouteParams $routeParams): Response try { $this->processPageUploads($this->request->files()->getAll(), $page, [$page->files()->get($filename)->mimeType()], FileSystem::name($filename), true); } catch (TranslatedException $e) { - $this->panel->notify($this->translate('upload.error', $e->getTranslatedMessage()), 'error'); + $this->panel->notify($this->translate('upload.error', $this->translate($e->getLanguageString())), 'error'); return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $routeParams->get('page')])); } } @@ -556,9 +557,9 @@ public function file(RouteParams $routeParams): Response /** * Create a new page */ - protected function createPage(FieldCollection $fieldCollection): Page + protected function createPage(FieldCollection $fieldCollection, PageFactory $pageFactory): Page { - $page = new Page(['site' => $this->site, 'published' => false]); + $page = $pageFactory->make(['site' => $this->site, 'published' => false]); $data = $fieldCollection->everyItem()->value()->toArray(); diff --git a/formwork/src/Panel/Controllers/ToolsController.php b/formwork/src/Panel/Controllers/ToolsController.php index 1ce498d08..f8f4daeb6 100644 --- a/formwork/src/Panel/Controllers/ToolsController.php +++ b/formwork/src/Panel/Controllers/ToolsController.php @@ -40,7 +40,7 @@ public function backups(): Response return $this->forward(ErrorsController::class, 'forbidden'); } - $backupper = new Backupper($this->config); + $backupper = new Backupper([...$this->config->get('system.backup'), 'hostname' => $this->request->host()]); $backups = Arr::map($backupper->getBackups(), fn (string $path, int $timestamp): array => [ 'name' => basename($path), diff --git a/formwork/src/Panel/Controllers/UpdatesController.php b/formwork/src/Panel/Controllers/UpdatesController.php index 45b9fd84b..8e9c73841 100644 --- a/formwork/src/Panel/Controllers/UpdatesController.php +++ b/formwork/src/Panel/Controllers/UpdatesController.php @@ -50,7 +50,7 @@ public function update(Updater $updater, AbstractCache $cache): JsonResponse|Res } if ($this->config->get('system.updates.backupBefore')) { - $backupper = new Backupper($this->config); + $backupper = new Backupper([...$this->config->get('system.backup'), 'hostname' => $this->request->host()]); try { $backupper->backup(); } catch (TranslatedException) { diff --git a/formwork/src/Panel/Controllers/UsersController.php b/formwork/src/Panel/Controllers/UsersController.php index ce368b4a2..485dc77ab 100644 --- a/formwork/src/Panel/Controllers/UsersController.php +++ b/formwork/src/Panel/Controllers/UsersController.php @@ -108,7 +108,7 @@ public function delete(RouteParams $routeParams): Response $this->deleteUserImage($user); } } catch (TranslatedException $e) { - $this->panel->notify($e->getTranslatedMessage(), 'error'); + $this->panel->notify($this->translate($e->getLanguageString()), 'error'); return $this->redirectToReferer(default: $this->generateRoute('panel.users'), base: $this->panel->panelRoot()); } @@ -147,7 +147,7 @@ public function deleteImage(RouteParams $routeParams): Response $this->panel->notify($this->translate('panel.user.image.deleted'), 'success'); } catch (TranslatedException $e) { - $this->panel->notify($e->getTranslatedMessage(), 'error'); + $this->panel->notify($this->translate($e->getLanguageString()), 'error'); } } else { $this->panel->notify($this->translate('panel.users.user.cannotEdit', $user->username()), 'error'); diff --git a/formwork/src/Panel/Security/AccessLimiter.php b/formwork/src/Panel/Security/AccessLimiter.php index 954ec2fb7..14f8c67e5 100644 --- a/formwork/src/Panel/Security/AccessLimiter.php +++ b/formwork/src/Panel/Security/AccessLimiter.php @@ -4,7 +4,6 @@ use Formwork\Http\Request; use Formwork\Log\Registry; -use Formwork\Utils\Uri; class AccessLimiter { @@ -33,7 +32,7 @@ public function __construct( protected Request $request ) { // Hash visitor IP address followed by current host - $this->attemptHash = hash('sha256', $request->ip() . '@' . Uri::host()); + $this->attemptHash = hash('sha256', $request->ip() . '@' . $request->host()); if ($registry->has($this->attemptHash)) { [$this->attempts, $this->lastAttemptTime] = $registry->get($this->attemptHash); diff --git a/formwork/src/Parsers/Extensions/CommonMark/FormworkExtension.php b/formwork/src/Parsers/Extensions/CommonMark/FormworkExtension.php index 3af72a03f..7139a22b6 100644 --- a/formwork/src/Parsers/Extensions/CommonMark/FormworkExtension.php +++ b/formwork/src/Parsers/Extensions/CommonMark/FormworkExtension.php @@ -2,6 +2,7 @@ namespace Formwork\Parsers\Extensions\CommonMark; +use Formwork\Site; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\ConfigurableExtensionInterface; @@ -13,6 +14,8 @@ class FormworkExtension implements ConfigurableExtensionInterface public function configureSchema(ConfigurationBuilderInterface $configurationBuilder): void { $configurationBuilder->addSchema('formwork', Expect::structure([ + 'site' => Expect::type(Site::class), + 'safeMode' => Expect::bool(true), 'imageAltProperty' => Expect::string('alt'), 'baseRoute' => Expect::string('/'), ])); diff --git a/formwork/src/Parsers/Extensions/CommonMark/ImageAltProcessor.php b/formwork/src/Parsers/Extensions/CommonMark/ImageAltProcessor.php index b50f35f3f..6fcf3029d 100644 --- a/formwork/src/Parsers/Extensions/CommonMark/ImageAltProcessor.php +++ b/formwork/src/Parsers/Extensions/CommonMark/ImageAltProcessor.php @@ -2,7 +2,6 @@ namespace Formwork\Parsers\Extensions\CommonMark; -use Formwork\App; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\CommonMark\Node\Inline\Image; use League\Config\ConfigurationInterface; @@ -22,7 +21,7 @@ public function __invoke(DocumentParsedEvent $documentParsedEvent): void $baseRoute = $this->configuration->get('formwork/baseRoute'); - $site = App::instance()->site(); + $site = $this->configuration->get('formwork/site'); $uri = $node->getUrl(); diff --git a/formwork/src/Parsers/Extensions/CommonMark/LinkBaseProcessor.php b/formwork/src/Parsers/Extensions/CommonMark/LinkBaseProcessor.php index 7506043c6..e1daef41b 100644 --- a/formwork/src/Parsers/Extensions/CommonMark/LinkBaseProcessor.php +++ b/formwork/src/Parsers/Extensions/CommonMark/LinkBaseProcessor.php @@ -2,7 +2,6 @@ namespace Formwork\Parsers\Extensions\CommonMark; -use Formwork\App; use Formwork\Utils\Uri; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\CommonMark\Node\Inline\Image; @@ -24,7 +23,7 @@ public function __invoke(DocumentParsedEvent $documentParsedEvent): void $baseRoute = $this->configuration->get('formwork/baseRoute'); - $site = App::instance()->site(); + $site = $this->configuration->get('formwork/site'); $uri = $node->getUrl(); diff --git a/formwork/src/Parsers/Markdown.php b/formwork/src/Parsers/Markdown.php index 7a6ebb635..13d2f94de 100644 --- a/formwork/src/Parsers/Markdown.php +++ b/formwork/src/Parsers/Markdown.php @@ -2,7 +2,6 @@ namespace Formwork\Parsers; -use Formwork\App; use Formwork\Parsers\Extensions\CommonMark\FormworkExtension; use Formwork\Parsers\Extensions\CommonMark\ImageRenderer; use Formwork\Sanitizer\HtmlSanitizer; @@ -21,7 +20,7 @@ class Markdown extends AbstractParser */ public static function parse(string $input, array $options = []): string { - $safeMode = App::instance()->config()->get('system.pages.content.safeMode', true); + $safeMode = $options['safeMode'] ?? true; $environment = new Environment([ 'html_input' => $safeMode ? 'escape' : 'allow', diff --git a/formwork/src/Schemes/Scheme.php b/formwork/src/Schemes/Scheme.php index 152de4ea0..9c944edd4 100644 --- a/formwork/src/Schemes/Scheme.php +++ b/formwork/src/Schemes/Scheme.php @@ -86,7 +86,7 @@ public function fields(): FieldCollection $fieldCollection->setMultiple(Arr::map($this->data['fields'] ?? [], fn ($data, $name) => $this->fieldFactory->make($name, $data, $fieldCollection))); - $layout = new Layout($this->data['layout'] ?? ['type' => 'default', 'sections' => []]); + $layout = new Layout($this->data['layout'] ?? ['type' => 'default', 'sections' => []], $this->translations->getCurrent()); $fieldCollection->setLayout($layout); diff --git a/formwork/src/Schemes/SchemeFactory.php b/formwork/src/Schemes/SchemeFactory.php new file mode 100644 index 000000000..d10c3b760 --- /dev/null +++ b/formwork/src/Schemes/SchemeFactory.php @@ -0,0 +1,20 @@ + $data + */ + public function make(string $id, array $data = []): Scheme + { + return $this->container->build(Scheme::class, compact('id', 'data')); + } +} diff --git a/formwork/src/Schemes/Schemes.php b/formwork/src/Schemes/Schemes.php index c67a8c0c7..a2499c235 100644 --- a/formwork/src/Schemes/Schemes.php +++ b/formwork/src/Schemes/Schemes.php @@ -3,7 +3,6 @@ namespace Formwork\Schemes; use Formwork\Parsers\Yaml; -use Formwork\Services\Container; use Formwork\Utils\FileSystem; use Formwork\Utils\Str; use InvalidArgumentException; @@ -22,7 +21,7 @@ class Schemes */ protected array $data = []; - public function __construct(protected Container $container) + public function __construct(protected SchemeFactory $schemeFactory) { } @@ -71,6 +70,6 @@ public function get(string $id): Scheme $data = Yaml::parseFile($this->data[$id]); - return $this->storage[$id] = $this->container->build(Scheme::class, compact('id', 'data')); + return $this->storage[$id] = $this->schemeFactory->make($id, $data); } } diff --git a/formwork/src/Services/Loaders/SchemesServiceLoader.php b/formwork/src/Services/Loaders/SchemesServiceLoader.php index dbde15a88..933ccef44 100644 --- a/formwork/src/Services/Loaders/SchemesServiceLoader.php +++ b/formwork/src/Services/Loaders/SchemesServiceLoader.php @@ -6,6 +6,7 @@ use Formwork\Fields\Dynamic\DynamicFieldValue; use Formwork\Fields\FieldFactory; use Formwork\Languages\Languages; +use Formwork\Schemes\SchemeFactory; use Formwork\Schemes\Schemes; use Formwork\Services\Container; use Formwork\Services\ResolutionAwareServiceLoaderInterface; @@ -18,6 +19,8 @@ public function __construct(protected Config $config, protected Languages $langu public function load(Container $container): object { + $container->define(SchemeFactory::class); + $container->define(FieldFactory::class); DynamicFieldValue::$varsLoader = fn () => $container->call(require $this->config->get('system.fields.dynamic.vars.file')); diff --git a/formwork/src/Site.php b/formwork/src/Site.php index fffd1e93d..6e3676095 100644 --- a/formwork/src/Site.php +++ b/formwork/src/Site.php @@ -10,6 +10,8 @@ use Formwork\Pages\Exceptions\PageNotFoundException; use Formwork\Pages\Page; use Formwork\Pages\PageCollection; +use Formwork\Pages\PageCollectionFactory; +use Formwork\Pages\PageFactory; use Formwork\Pages\Traits\PageTraversal; use Formwork\Pages\Traits\PageUid; use Formwork\Pages\Traits\PageUri; @@ -103,6 +105,8 @@ public function __construct( array $data, protected App $app, protected Config $config, + protected PageFactory $pageFactory, + protected PageCollectionFactory $pageCollectionFactory, ) { $this->setMultiple($data); } @@ -136,12 +140,12 @@ public function parent(): Page|Site|null public function siblings(): PageCollection { - return $this->siblings ??= new PageCollection([]); + return $this->siblings ??= $this->pageCollectionFactory->make([]); } public function inclusiveSiblings(): PageCollection { - return $this->inclusiveSiblings ??= new PageCollection([$this->route() => $this]); + return $this->inclusiveSiblings ??= $this->pageCollectionFactory->make([$this->route() => $this]); } /** @@ -290,7 +294,7 @@ public function hasPages(): bool */ public function retrievePage(string $path): Page { - return $this->storage[$path] ?? ($this->storage[$path] = new Page(['site' => $this, 'path' => $path])); + return $this->storage[$path] ?? ($this->storage[$path] = $this->pageFactory->make(['site' => $this, 'path' => $path])); } public function retrievePages(string $path, bool $recursive = false): PageCollection @@ -316,7 +320,7 @@ public function retrievePages(string $path, bool $recursive = false): PageCollec } } - $pageCollection = new PageCollection($pages); + $pageCollection = $this->pageCollectionFactory->make($pages); return $pageCollection->sortBy('relativePath'); } diff --git a/formwork/src/Statistics/Statistics.php b/formwork/src/Statistics/Statistics.php index 0c2702f6e..1196e4b9e 100644 --- a/formwork/src/Statistics/Statistics.php +++ b/formwork/src/Statistics/Statistics.php @@ -2,11 +2,11 @@ namespace Formwork\Statistics; -use Formwork\App; use Formwork\Http\Request; use Formwork\Http\Utils\IpAnonymizer; use Formwork\Http\Utils\Visitor; use Formwork\Log\Registry; +use Formwork\Translations\Translation; use Formwork\Utils\Arr; use Formwork\Utils\Date; use Formwork\Utils\FileSystem; @@ -67,7 +67,7 @@ class Statistics /** * Create a new Statistics instance */ - public function __construct(string $path, protected App $app, protected Request $request) + public function __construct(string $path, protected Request $request, protected Translation $translation) { if (!FileSystem::exists($path)) { FileSystem::createDirectory($path); @@ -123,7 +123,7 @@ public function getChartData(int $limit = self::CHART_LIMIT): array $labels = Arr::map( iterator_to_array($this->generateDays($limit)), - fn (string $day): string => Date::formatTimestamp(Date::toTimestamp($day, self::DATE_FORMAT), "D\nj M") + fn (string $day): string => Date::formatTimestamp(Date::toTimestamp($day, self::DATE_FORMAT), "D\nj M", $this->translation) ); return [ diff --git a/formwork/src/Templates/Template.php b/formwork/src/Templates/Template.php index 347546af6..46197c313 100644 --- a/formwork/src/Templates/Template.php +++ b/formwork/src/Templates/Template.php @@ -3,8 +3,6 @@ namespace Formwork\Templates; use Closure; -use Formwork\App; -use Formwork\Assets; use Formwork\Schemes\Scheme; use Formwork\Site; use Formwork\Utils\Constraint; @@ -16,19 +14,21 @@ class Template implements Stringable { - /** - * Template assets instance - */ - protected Assets $assets; - - protected string $path; - /** * Create a new Template instance + * + * @param array $vars + * @param array $methods */ - public function __construct(protected string $name, protected Scheme $scheme, protected App $app, protected Site $site, protected ViewFactory $viewFactory) - { - $this->path = $this->app->config()->get('system.templates.path'); + public function __construct( + protected string $name, + protected array $vars, + protected string $path, + protected array $methods, + protected Scheme $scheme, + protected Site $site, + protected ViewFactory $viewFactory + ) { } public function __toString(): string @@ -56,17 +56,6 @@ public function path(): string return $this->path; } - /** - * Get Assets instance - */ - public function assets(): Assets - { - return $this->assets ?? ($this->assets = new Assets( - FileSystem::joinPaths($this->path, 'assets'), - $this->site->uri('/site/templates/assets/', includeLanguage: false) - )); - } - /** * Render template * @@ -94,38 +83,14 @@ public function render(array $vars = []): string $view = $this->viewFactory->make( $this->name, - [...$this->defaultVars(), ...$vars, ...$controllerVars], + [...$this->vars, ...$vars, ...$controllerVars], $this->path, - [...$this->defaultMethods()] + [...$this->methods] ); return $view->render(); } - /** - * @return array - */ - protected function defaultVars(): array - { - return [ - 'router' => $this->app->router(), - 'site' => $this->site, - 'csrfToken' => $this->app->getService('csrfToken'), - ]; - } - - /** - * Default template methods - * - * @return array - */ - protected function defaultMethods(): array - { - return [ - 'assets' => fn () => $this->assets(), - ]; - } - /** * Load template controller if exists * @@ -138,7 +103,7 @@ protected function loadController(array $vars = []): ?array $controllerFile = FileSystem::joinPaths($this->path, 'controllers', $this->name . '.php'); if (FileSystem::exists($controllerFile)) { - return (array) Renderer::load($controllerFile, [...$this->defaultVars(), ...$vars], $this); + return (array) Renderer::load($controllerFile, [...$this->vars, ...$vars], $this); } return null; diff --git a/formwork/src/Templates/TemplateFactory.php b/formwork/src/Templates/TemplateFactory.php index 677c5fb00..214e7e5de 100644 --- a/formwork/src/Templates/TemplateFactory.php +++ b/formwork/src/Templates/TemplateFactory.php @@ -2,19 +2,40 @@ namespace Formwork\Templates; +use Formwork\App; +use Formwork\Assets; +use Formwork\Config\Config; use Formwork\Schemes\Schemes; +use Formwork\Security\CsrfToken; use Formwork\Services\Container; +use Formwork\Utils\FileSystem; class TemplateFactory { - public function __construct(protected Container $container, protected Schemes $schemes) + public function __construct(protected Container $container, protected App $app, protected Config $config, protected Schemes $schemes) { } public function make(string $name): Template { + $path = $this->config->get('system.templates.path'); + + $assets = new Assets( + FileSystem::joinPaths($path, 'assets'), + $this->app->site()->uri('/site/templates/assets/', includeLanguage: false) + ); + return $this->container->build(Template::class, [ - 'name' => $name, + 'name' => $name, + 'path' => $path, + 'methods' => [ + 'assets' => fn () => $assets, + ], + 'vars' => [ + 'router' => $this->app->router(), + 'site' => $this->app->site(), + 'csrfToken' => $this->app->getService(CsrfToken::class), + ], 'scheme' => $this->schemes->get('pages.' . $name), ]); } diff --git a/formwork/src/Users/User.php b/formwork/src/Users/User.php index 0a2ec1714..c8c68e6ae 100644 --- a/formwork/src/Users/User.php +++ b/formwork/src/Users/User.php @@ -2,7 +2,6 @@ namespace Formwork\Users; -use Formwork\App; use Formwork\Config\Config; use Formwork\Files\FileFactory; use Formwork\Http\Request; @@ -10,6 +9,7 @@ use Formwork\Log\Registry; use Formwork\Model\Model; use Formwork\Panel\Security\Password; +use Formwork\Schemes\Schemes; use Formwork\Users\Exceptions\AuthenticationFailedException; use Formwork\Users\Exceptions\UserImageNotFoundException; use Formwork\Users\Exceptions\UserNotLoggedException; @@ -53,9 +53,9 @@ class User extends Model * * @param array $data */ - public function __construct(array $data, protected Role $role, protected App $app, protected Config $config, protected Request $request, protected FileFactory $fileFactory) + public function __construct(array $data, protected Role $role, protected Schemes $schemes, protected Config $config, protected Request $request, protected FileFactory $fileFactory) { - $this->scheme = $app->schemes()->get('users.user'); + $this->scheme = $this->schemes->get('users.user'); $this->fields = $this->scheme->fields(); $this->fields->setModel($this); diff --git a/formwork/src/Utils/Date.php b/formwork/src/Utils/Date.php index c3e458ccc..237cc1c53 100644 --- a/formwork/src/Utils/Date.php +++ b/formwork/src/Utils/Date.php @@ -4,8 +4,8 @@ use DateTime; use Exception; -use Formwork\App; use Formwork\Traits\StaticClass; +use Formwork\Translations\Translation; use InvalidArgumentException; use RuntimeException; @@ -80,15 +80,14 @@ class Date /** * Parse a date according to a given format (or the default format if not given) and return the timestamp + * + * @param array|string $format */ - public static function toTimestamp(string $date, ?string $format = null): int + public static function toTimestamp(string $date, string|array $format): int { try { - $dateTime = static::createDateTime($date, (array) ($format ?? static::getDefaultFormats())); + $dateTime = static::createDateTime($date, (array) $format); } catch (InvalidArgumentException $e) { - if ($format !== null) { - throw $e; - } // Try to parse the date anyway if the format is not given try { $dateTime = new DateTime($date); @@ -139,14 +138,8 @@ public static function patternToFormat(string $pattern): string /** * Formats a DateTime object using the current translation for weekdays and months */ - public static function formatDateTime(DateTime $dateTime, ?string $format = null, ?string $language = null): string + public static function formatDateTime(DateTime $dateTime, string $format, Translation $translation): string { - $format ??= App::instance()->config()->get('system.date.dateFormat'); - - $language ??= App::instance()->translations()->getCurrent()->code(); - - $translation = App::instance()->translations()->get($language, fallbackIfInvalid: true); - return preg_replace_callback( self::DATE_FORMAT_REGEX, fn (array $matches): string => match ($matches[0]) { @@ -154,30 +147,26 @@ public static function formatDateTime(DateTime $dateTime, ?string $format = null 'F' => $translation->getStrings('date.months.long')[$dateTime->format('n') - 1], 'D' => $translation->getStrings('date.weekdays.short')[(int) $dateTime->format('w')], 'l' => $translation->getStrings('date.weekdays.long')[(int) $dateTime->format('w')], - 'r' => static::formatDateTime($dateTime, DateTime::RFC2822), + 'r' => static::formatDateTime($dateTime, DateTime::RFC2822, $translation), default => $dateTime->format($matches[1] ?? $matches[0]) }, $format - ); + ) ?? throw new RuntimeException(sprintf('Date formatting failed with error: %s', preg_last_error_msg())); } /** * The same as self::formatDateTime() but takes a timestamp instead of a DateTime object */ - public static function formatTimestamp(int $timestamp, ?string $format = null, ?string $language = null): string + public static function formatTimestamp(int $timestamp, string $format, Translation $translation): string { - return static::formatDateTime((new DateTime())->setTimestamp($timestamp), $format, $language); + return static::formatDateTime((new DateTime())->setTimestamp($timestamp), $format, $translation); } /** * Formats a DateTime object as a time distance from now */ - public static function formatDateTimeAsDistance(DateTime $dateTime, ?string $language = null): string + public static function formatDateTimeAsDistance(DateTime $dateTime, Translation $translation): string { - $language ??= App::instance()->translations()->getCurrent()->code(); - - $translation = App::instance()->translations()->get($language, fallbackIfInvalid: true); - $time = $dateTime->getTimestamp(); $now = time(); @@ -213,22 +202,9 @@ public static function formatDateTimeAsDistance(DateTime $dateTime, ?string $lan /** * The same as self::formatDateTimeAsDistance() but takes a timestamp instead of a DateTime object */ - public static function formatTimestampAsDistance(int $timestamp, ?string $language = null): string - { - return static::formatDateTimeAsDistance((new DateTime())->setTimestamp($timestamp), $language); - } - - /** - * Get default date formats from config - * - * @return array - */ - protected static function getDefaultFormats(): array + public static function formatTimestampAsDistance(int $timestamp, Translation $translation): string { - return [ - App::instance()->config()->get('system.date.dateFormat'), - App::instance()->config()->get('system.date.datetimeFormat'), - ]; + return static::formatDateTimeAsDistance((new DateTime())->setTimestamp($timestamp), $translation); } /** diff --git a/formwork/src/Utils/Uri.php b/formwork/src/Utils/Uri.php index 2a87ea739..5943059c0 100644 --- a/formwork/src/Utils/Uri.php +++ b/formwork/src/Utils/Uri.php @@ -2,7 +2,6 @@ namespace Formwork\Utils; -use Formwork\App; use Formwork\Traits\StaticClass; use InvalidArgumentException; @@ -22,25 +21,11 @@ class Uri */ protected static ?string $current = null; - /** - * Get current URI - */ - public static function current(): string - { - if (!isset(static::$current)) { - static::$current = static::base() . rtrim(App::instance()->request()->root(), '/') . App::instance()->request()->uri(); - } - return static::$current; - } - /** * Get the scheme of current or a given URI */ - public static function scheme(?string $uri = null): ?string + public static function scheme(string $uri): ?string { - if ($uri === null) { - return App::instance()->request()->isSecure() ? 'https' : 'http'; - } $scheme = static::parseComponent($uri, PHP_URL_SCHEME); return $scheme !== null ? strtolower((string) $scheme) : null; } @@ -48,11 +33,8 @@ public static function scheme(?string $uri = null): ?string /** * Get the host of current or a given URI */ - public static function host(?string $uri = null): ?string + public static function host(string $uri): ?string { - if ($uri === null) { - return strtolower((string) $_SERVER['SERVER_NAME']); - } $host = static::parseComponent($uri, PHP_URL_HOST); return $host !== null ? strtolower((string) $host) : null; } @@ -60,76 +42,65 @@ public static function host(?string $uri = null): ?string /** * Get the port of current or a given URI */ - public static function port(?string $uri = null): ?int + public static function port(string $uri): ?int { - if ($uri === null) { - return (int) $_SERVER['SERVER_PORT']; - } - return static::parseComponent($uri, PHP_URL_PORT) ?? static::getDefaultPort(static::scheme($uri)); + return static::parseComponent($uri, PHP_URL_PORT); } /** * Return the default port of current URI or a given scheme */ - public static function getDefaultPort(?string $scheme = null): ?int + public static function getDefaultPort(string $scheme): int { - $scheme ??= static::scheme(); - return self::DEFAULT_PORTS[$scheme] ?? null; + return self::DEFAULT_PORTS[$scheme] ?? throw new InvalidArgumentException(sprintf('Unknown scheme "%s"', $scheme)); } /** * Return whether current or a given port is default */ - public static function isDefaultPort(?int $port = null, ?string $scheme = null): bool + public static function isDefaultPort(int $port, string $scheme): bool { - $port ??= static::port(); - $scheme ??= static::scheme(); - return $port !== null && $scheme !== null && $port === static::getDefaultPort($scheme); + return $port === static::getDefaultPort($scheme); } /** * Get the path of current or a given URI */ - public static function path(?string $uri = null): ?string + public static function path(string $uri): ?string { - $uri ??= static::current(); return static::parseComponent($uri, PHP_URL_PATH); } /** * Get the absolute path of current or a given URI */ - public static function absolutePath(?string $uri = null): string + public static function absolutePath(string $uri): string { - $uri ??= static::current(); return static::base($uri) . static::path($uri); } /** * Get the query of current or a given URI */ - public static function query(?string $uri = null): ?string + public static function query(string $uri): ?string { - $uri ??= static::current(); return static::parseComponent($uri, PHP_URL_QUERY); } /** * Get the fragment of current or a given URI */ - public static function fragment(?string $uri = null): ?string + public static function fragment(string $uri): ?string { - $uri ??= static::current(); return static::parseComponent($uri, PHP_URL_FRAGMENT); } /** * Get the base URI (scheme://host:port) of current or a given URI */ - public static function base(?string $uri = null): string + public static function base(string $uri): string { - $port = static::port($uri); - return static::scheme($uri) . '://' . static::host($uri) . (static::isDefaultPort($port, static::scheme($uri)) ? '' : ':' . $port); + return sprintf('%s://%s%s', static::scheme($uri), static::host($uri), static::port($uri) !== null ? ':' . static::port($uri) : ''); } /** @@ -137,9 +108,8 @@ public static function base(?string $uri = null): string * * @return array|string> */ - public static function queryToArray(?string $uri = null): array + public static function queryToArray(string $uri): array { - $uri ??= static::current(); parse_str(static::query($uri) ?? '', $array); return $array; } @@ -150,9 +120,8 @@ public static function queryToArray(?string $uri = null): array * * @return array{scheme: ?string, host: ?string, port: ?int, path: ?string, query: ?string, fragment: ?string} */ - public static function parse(?string $uri = null): array + public static function parse(string $uri): array { - $uri ??= static::current(); return [ 'scheme' => static::scheme($uri), 'host' => static::host($uri), @@ -170,7 +139,7 @@ public static function parse(?string $uri = null): array * * @see Uri::parse() */ - public static function make(array $parts, ?string $uri = null, bool $forcePort = false): string + public static function make(array $parts, string $uri, bool $forcePort = false): string { $givenParts = array_keys($parts); $parts = [...static::parse($uri), ...$parts]; @@ -212,27 +181,24 @@ public static function normalize(string $uri): string /** * Remove query from current or a given URI */ - public static function removeQuery(?string $uri = null): string + public static function removeQuery(string $uri): string { - $uri ??= static::current(); return static::make(['query' => ''], $uri); } /** * Remove fragment from current or a given URI */ - public static function removeFragment(?string $uri = null): string + public static function removeFragment(string $uri): string { - $uri ??= static::current(); return static::make(['fragment' => ''], $uri); } /** * Resolve a relative URI against current or a given base URI */ - public static function resolveRelative(string $uri, ?string $base = null): string + public static function resolveRelative(string $uri, string $base): string { - $base ??= static::current(); if (Str::startsWith($uri, '#')) { return static::make(['fragment' => $uri], $base); } diff --git a/panel/routes.php b/panel/routes.php index dabe14059..7e2825121 100644 --- a/panel/routes.php +++ b/panel/routes.php @@ -1,6 +1,5 @@ [ - 'action' => static function (Request $request, App $app, Site $site, Panel $panel) { + 'action' => static function (Request $request, Site $site, Panel $panel) { // Register panel if no user exists if ($site->users()->isEmpty()) { if (!$request->isLocalhost()) {