mirror of
https://github.com/getformwork/formwork.git
synced 2025-01-17 21:49:04 +01:00
Refactor code with Rector
This commit is contained in:
parent
0f87c1b375
commit
a992d69d98
@ -6,14 +6,18 @@ use Formwork\Languages\Languages;
|
||||
use Formwork\Utils\Constraint;
|
||||
use Formwork\Utils\Date;
|
||||
use Formwork\Utils\Str;
|
||||
use InvalidArgumentException;
|
||||
|
||||
return function (Languages $languages) {
|
||||
return [
|
||||
'format' => function (Field $field, ?string $format = null, string $type = 'pattern'): string {
|
||||
$format = match (strtolower($type)) {
|
||||
'pattern' => Date::patternToFormat($format),
|
||||
'date' => $format
|
||||
};
|
||||
if ($format !== null) {
|
||||
$format = match (strtolower($type)) {
|
||||
'pattern' => Date::patternToFormat($format),
|
||||
'date' => $format,
|
||||
default => throw new InvalidArgumentException('Invalid date format type')
|
||||
};
|
||||
}
|
||||
return $field->isEmpty() ? '' : Date::formatTimestamp($field->toTimestamp(), $format);
|
||||
},
|
||||
|
||||
|
@ -7,7 +7,7 @@ use Formwork\Fields\Field;
|
||||
|
||||
return function (App $app) {
|
||||
return [
|
||||
'validate' => function (Field $field, $value): int {
|
||||
'validate' => function (Field $field, $value): int|float {
|
||||
if (!is_numeric($value)) {
|
||||
throw new ValidationException(sprintf('Invalid value for field "%s" of type "%s"', $field->name(), $field->type()));
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ return function (App $app) {
|
||||
throw new ValidationException(sprintf('The value of field "%s" of type "%s" does not match the required pattern', $field->name(), $field->value()));
|
||||
}
|
||||
|
||||
return (string) $value;
|
||||
return $value;
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -11,7 +11,7 @@ return function (Site $site) {
|
||||
return [
|
||||
'toHTML' => function (Field $field) use ($site): string {
|
||||
$currentPage = $site->currentPage();
|
||||
return Markdown::parse((string) $field->value(), ['baseRoute' => $currentPage ? $currentPage->route() : '/']);
|
||||
return Markdown::parse((string) $field->value(), ['baseRoute' => $currentPage !== null ? $currentPage->route() : '/']);
|
||||
},
|
||||
|
||||
'toString' => function (Field $field): string {
|
||||
@ -35,17 +35,15 @@ return function (Site $site) {
|
||||
throw new ValidationException(sprintf('Invalid value for field "%s" of type "%s"', $field->name(), $field->type()));
|
||||
}
|
||||
|
||||
if ($field->has('min') && strlen($value) < $field->get('min')) {
|
||||
if ($field->has('min') && strlen((string) $value) < $field->get('min')) {
|
||||
throw new ValidationException(sprintf('The minimum allowed length for field "%s" of type "%s" is %d', $field->name(), $field->value(), $field->get('min')));
|
||||
}
|
||||
|
||||
if ($field->has('max') && strlen($value) > $field->get('max')) {
|
||||
if ($field->has('max') && strlen((string) $value) > $field->get('max')) {
|
||||
throw new ValidationException(sprintf('The maximum allowed length for field "%s" of type "%s" is %d', $field->name(), $field->value(), $field->get('max')));
|
||||
}
|
||||
|
||||
$value = str_replace("\r\n", "\n", $value);
|
||||
|
||||
return $value;
|
||||
return str_replace("\r\n", "\n", (string) $value);
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -8,7 +8,7 @@ use Formwork\Utils\Constraint;
|
||||
|
||||
return function (App $app) {
|
||||
return [
|
||||
'validate' => function (Field $field, $value): ?string {
|
||||
'validate' => function (Field $field, $value): string {
|
||||
if (Constraint::isEmpty($value)) {
|
||||
return '';
|
||||
}
|
||||
@ -17,15 +17,15 @@ return function (App $app) {
|
||||
throw new ValidationException(sprintf('Invalid value for field "%s" of type "%s"', $field->name(), $field->type()));
|
||||
}
|
||||
|
||||
if ($field->has('min') && strlen($value) < $field->get('min')) {
|
||||
if ($field->has('min') && strlen((string) $value) < $field->get('min')) {
|
||||
throw new ValidationException(sprintf('The minimum allowed length for field "%s" of type "%s" is %d', $field->name(), $field->value(), $field->get('min')));
|
||||
}
|
||||
|
||||
if ($field->has('max') && strlen($value) > $field->get('max')) {
|
||||
if ($field->has('max') && strlen((string) $value) > $field->get('max')) {
|
||||
throw new ValidationException(sprintf('The maximum allowed length for field "%s" of type "%s" is %d', $field->name(), $field->value(), $field->get('max')));
|
||||
}
|
||||
|
||||
if ($field->has('pattern') && !Constraint::matchesRegex($value, $field->get('pattern'))) {
|
||||
if ($field->has('pattern') && !Constraint::matchesRegex((string) $value, $field->get('pattern'))) {
|
||||
throw new ValidationException(sprintf('The value of field "%s" of type "%s" does not match the required pattern', $field->name(), $field->value()));
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ use Formwork\Fields\Field;
|
||||
|
||||
return function (App $app) {
|
||||
return [
|
||||
'validate' => function (Field $field, $value): int {
|
||||
'validate' => function (Field $field, $value): int|float {
|
||||
if (!is_numeric($value)) {
|
||||
throw new ValidationException(sprintf('Invalid value for field "%s" of type "%s"', $field->name(), $field->type()));
|
||||
}
|
||||
|
@ -17,15 +17,15 @@ return function (App $app) {
|
||||
throw new ValidationException(sprintf('Invalid value for field "%s" of type "%s"', $field->name(), $field->type()));
|
||||
}
|
||||
|
||||
if ($field->has('min') && strlen($value) < $field->get('min')) {
|
||||
if ($field->has('min') && strlen((string) $value) < $field->get('min')) {
|
||||
throw new ValidationException(sprintf('The minimum allowed length for field "%s" of type "%s" is %d', $field->name(), $field->value(), $field->get('min')));
|
||||
}
|
||||
|
||||
if ($field->has('max') && strlen($value) > $field->get('max')) {
|
||||
if ($field->has('max') && strlen((string) $value) > $field->get('max')) {
|
||||
throw new ValidationException(sprintf('The maximum allowed length for field "%s" of type "%s" is %d', $field->name(), $field->value(), $field->get('max')));
|
||||
}
|
||||
|
||||
if ($field->has('pattern') && !Constraint::matchesRegex($value, $field->get('pattern'))) {
|
||||
if ($field->has('pattern') && !Constraint::matchesRegex((string) $value, $field->get('pattern'))) {
|
||||
throw new ValidationException(sprintf('The value of field "%s" of type "%s" does not match the required pattern', $field->name(), $field->value()));
|
||||
}
|
||||
|
||||
|
@ -16,17 +16,15 @@ return function (App $app) {
|
||||
throw new ValidationException(sprintf('Invalid value for field "%s" of type "%s"', $field->name(), $field->type()));
|
||||
}
|
||||
|
||||
if ($field->has('min') && strlen($value) < $field->get('min')) {
|
||||
if ($field->has('min') && strlen((string) $value) < $field->get('min')) {
|
||||
throw new ValidationException(sprintf('The minimum allowed length for field "%s" of type "%s" is %d', $field->name(), $field->value(), $field->get('min')));
|
||||
}
|
||||
|
||||
if ($field->has('max') && strlen($value) > $field->get('max')) {
|
||||
if ($field->has('max') && strlen((string) $value) > $field->get('max')) {
|
||||
throw new ValidationException(sprintf('The maximum allowed length for field "%s" of type "%s" is %d', $field->name(), $field->value(), $field->get('max')));
|
||||
}
|
||||
|
||||
$value = str_replace("\r\n", "\n", (string) $value);
|
||||
|
||||
return $value;
|
||||
return str_replace("\r\n", "\n", (string) $value);
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -34,7 +34,7 @@ return function (App $app) {
|
||||
$currentPage = $app->site()->currentPage();
|
||||
return Markdown::parse(
|
||||
$markdown,
|
||||
['baseRoute' => $currentPage ? $currentPage->route() : '/']
|
||||
['baseRoute' => $currentPage !== null ? $currentPage->route() : '/']
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -34,4 +34,4 @@ $_SERVER['SCRIPT_FILENAME'] = $root . DIRECTORY_SEPARATOR . 'index.php';
|
||||
$_SERVER['SCRIPT_NAME'] = '/index.php';
|
||||
$_SERVER['PHP_SELF'] = '/index.php';
|
||||
|
||||
require 'index.php';
|
||||
require __DIR__ . '/index.php';
|
||||
|
@ -64,7 +64,7 @@ final class App
|
||||
if ($this->container->has($name)) {
|
||||
return $this->container->get($name);
|
||||
}
|
||||
throw new BadMethodCallException(sprintf('Call to undefined method %s::%s()', static::class, $name));
|
||||
throw new BadMethodCallException(sprintf('Call to undefined method %s::%s()', self::class, $name));
|
||||
}
|
||||
|
||||
public function config(): Config
|
||||
@ -141,7 +141,7 @@ final class App
|
||||
{
|
||||
$container->define(Container::class, $container);
|
||||
|
||||
$container->define(static::class, $this);
|
||||
$container->define(self::class, $this);
|
||||
|
||||
$container->define(Config::class)
|
||||
->loader(ConfigServiceLoader::class)
|
||||
|
@ -51,15 +51,15 @@ class Backupper
|
||||
|
||||
$destination = FileSystem::joinPaths($path, $name);
|
||||
|
||||
$zip = new ZipArchive();
|
||||
$zipArchive = new ZipArchive();
|
||||
|
||||
if (($status = $zip->open($destination, ZipArchive::CREATE)) === true) {
|
||||
if (($status = $zipArchive->open($destination, ZipArchive::CREATE)) === true) {
|
||||
foreach (FileSystem::listRecursive($source, FileSystem::LIST_ALL) as $file) {
|
||||
if ($this->isCopiable($file)) {
|
||||
$zip->addFile($file, $file);
|
||||
$zipArchive->addFile($file, $file);
|
||||
}
|
||||
}
|
||||
$zip->close();
|
||||
$zipArchive->close();
|
||||
}
|
||||
|
||||
$this->deleteOldBackups();
|
||||
|
@ -8,22 +8,11 @@ use Formwork\Utils\FileSystem;
|
||||
class FilesCache extends AbstractCache
|
||||
{
|
||||
/**
|
||||
* Cache path
|
||||
* @param string $path Cache path
|
||||
* @param int $defaultTtl Cached data time-to-live
|
||||
*/
|
||||
protected string $path;
|
||||
|
||||
/**
|
||||
* Cached data time-to-live
|
||||
*/
|
||||
protected int $defaultTtl;
|
||||
|
||||
/**
|
||||
* Create a new FilesCache instance
|
||||
*/
|
||||
public function __construct(string $path, int $defaultTtl)
|
||||
public function __construct(protected string $path, protected int $defaultTtl)
|
||||
{
|
||||
$this->path = $path;
|
||||
$this->defaultTtl = $defaultTtl;
|
||||
if (!FileSystem::exists($this->path)) {
|
||||
FileSystem::createDirectory($this->path, recursive: true);
|
||||
}
|
||||
|
@ -12,10 +12,6 @@ use UnexpectedValueException;
|
||||
|
||||
class ServeCommand
|
||||
{
|
||||
protected string $host;
|
||||
|
||||
protected int $port;
|
||||
|
||||
/**
|
||||
* @var array<mixed>
|
||||
*/
|
||||
@ -25,10 +21,8 @@ class ServeCommand
|
||||
|
||||
protected CLImate $climate;
|
||||
|
||||
public function __construct(string $host = '127.0.0.1', int $port = 8000)
|
||||
public function __construct(protected string $host = '127.0.0.1', protected int $port = 8000)
|
||||
{
|
||||
$this->host = $host;
|
||||
$this->port = $port;
|
||||
$this->climate = new CLImate();
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ class Config implements Arrayable
|
||||
|
||||
protected const INTERPOLATION_REGEX = '/\$(?!\$)\{([%a-z._]+)\}/i';
|
||||
|
||||
protected bool $resolved;
|
||||
protected bool $resolved = false;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
@ -28,7 +28,6 @@ class Config implements Arrayable
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
$this->data = $data;
|
||||
$this->resolved = false;
|
||||
}
|
||||
|
||||
public function get(string $key, mixed $default = null): mixed
|
||||
@ -50,15 +49,8 @@ class Config implements Arrayable
|
||||
{
|
||||
if (FileSystem::isReadable($path) && FileSystem::extension($path) === 'yaml') {
|
||||
$name = FileSystem::name($path);
|
||||
|
||||
$data = (array) Yaml::parseFile($path);
|
||||
|
||||
if (isset($this->data[$name])) {
|
||||
$this->data[$name] = array_replace_recursive($this->data[$name], $data);
|
||||
} else {
|
||||
$this->data[$name] = $data;
|
||||
}
|
||||
|
||||
$this->data[$name] = isset($this->data[$name]) ? array_replace_recursive($this->data[$name], $data) : $data;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,9 +10,9 @@ use Formwork\Utils\FileSystem;
|
||||
|
||||
class AssetController
|
||||
{
|
||||
public function load(RouteParams $params, Config $config): FileResponse
|
||||
public function load(RouteParams $routeParams, Config $config): FileResponse
|
||||
{
|
||||
$path = FileSystem::joinPaths($config->get('system.images.processPath'), $params->get('id'), $params->get('name'));
|
||||
$path = FileSystem::joinPaths($config->get('system.images.processPath'), $routeParams->get('id'), $routeParams->get('name'));
|
||||
|
||||
if (FileSystem::isFile($path)) {
|
||||
return new FileResponse($path);
|
||||
|
@ -18,12 +18,12 @@ use Formwork\View\ViewFactory;
|
||||
|
||||
class PageController extends AbstractController
|
||||
{
|
||||
public function __construct(protected App $app, protected Config $config, protected Router $router, protected Site $site, protected FilesCache $cache)
|
||||
public function __construct(protected App $app, protected Config $config, protected Router $router, protected Site $site, protected FilesCache $filesCache)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function load(RouteParams $params, ViewFactory $viewFactory): Response
|
||||
public function load(RouteParams $routeParams, ViewFactory $viewFactory): Response
|
||||
{
|
||||
if ($this->site->get('maintenance.enabled') && !$this->app->panel()?->isLoggedIn()) {
|
||||
if ($this->site->get('maintenance.page') !== null) {
|
||||
@ -36,35 +36,35 @@ class PageController extends AbstractController
|
||||
}
|
||||
|
||||
if (!isset($route)) {
|
||||
$route = $params->get('page', $this->config->get('system.pages.index'));
|
||||
$route = $routeParams->get('page', $this->config->get('system.pages.index'));
|
||||
|
||||
if ($resolvedAlias = $this->site->resolveRouteAlias($route)) {
|
||||
$route = $resolvedAlias;
|
||||
}
|
||||
}
|
||||
|
||||
if ($page = $this->site->findPage($route)) {
|
||||
if (($page = $this->site->findPage($route)) !== null) {
|
||||
if ($page->canonicalRoute() !== null) {
|
||||
$canonical = $page->canonicalRoute();
|
||||
|
||||
if ($params->get('page', '/') !== $canonical) {
|
||||
if ($routeParams->get('page', '/') !== $canonical) {
|
||||
$route = $this->router->rewrite(['page' => $canonical]);
|
||||
return new RedirectResponse($this->site->uri($route), ResponseStatus::MovedPermanently);
|
||||
}
|
||||
}
|
||||
|
||||
if (($params->has('tagName') || $params->has('paginationPage')) && $page->scheme()->options()->get('type') !== 'listing') {
|
||||
if (($routeParams->has('tagName') || $routeParams->has('paginationPage')) && $page->scheme()->options()->get('type') !== 'listing') {
|
||||
return $this->getPageResponse($this->site->errorPage());
|
||||
}
|
||||
|
||||
if ($this->config->get('system.cache.enabled') && ($page->has('publishDate') || $page->has('unpublishDate'))) {
|
||||
if (($page->isPublished() && !$page->publishDate()->isEmpty() && !$this->site->modifiedSince($page->publishDate()->toTimestamp()))
|
||||
|| (!$page->isPublished() && !$page->unpublishDate()->isEmpty() && !$this->site->modifiedSince($page->unpublishDate()->toTimestamp()))) {
|
||||
// Clear cache if the site was not modified since the page has been published or unpublished
|
||||
$this->cache->clear();
|
||||
if ($this->site->path() !== null) {
|
||||
FileSystem::touch($this->site->path());
|
||||
}
|
||||
if ($this->config->get('system.cache.enabled') && ($page->has('publishDate') || $page->has('unpublishDate')) && (
|
||||
($page->isPublished() && !$page->publishDate()->isEmpty() && !$this->site->modifiedSince($page->publishDate()->toTimestamp()))
|
||||
|| (!$page->isPublished() && !$page->unpublishDate()->isEmpty() && !$this->site->modifiedSince($page->unpublishDate()->toTimestamp()))
|
||||
)) {
|
||||
// Clear cache if the site was not modified since the page has been published or unpublished
|
||||
$this->filesCache->clear();
|
||||
if ($this->site->path() !== null) {
|
||||
FileSystem::touch($this->site->path());
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ class PageController extends AbstractController
|
||||
$upperLevel = $this->config->get('system.pages.index');
|
||||
}
|
||||
|
||||
if (($parent = $this->site->findPage($upperLevel)) && $parent->files()->has($filename)) {
|
||||
if ((($parent = $this->site->findPage($upperLevel)) !== null) && $parent->files()->has($filename)) {
|
||||
return new FileResponse($parent->files()->get($filename)->path());
|
||||
}
|
||||
}
|
||||
@ -104,23 +104,23 @@ class PageController extends AbstractController
|
||||
|
||||
$cacheKey = $page->uri(includeLanguage: true);
|
||||
|
||||
if ($config->get('system.cache.enabled') && $this->cache->has($cacheKey)) {
|
||||
if ($config->get('system.cache.enabled') && $this->filesCache->has($cacheKey)) {
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
$cachedTime = $this->cache->cachedTime($cacheKey);
|
||||
$cachedTime = $this->filesCache->cachedTime($cacheKey);
|
||||
// Validate cached response
|
||||
if (!$site->modifiedSince($cachedTime)) {
|
||||
return $this->cache->fetch($cacheKey);
|
||||
return $this->filesCache->fetch($cacheKey);
|
||||
}
|
||||
|
||||
$this->cache->delete($cacheKey);
|
||||
$this->filesCache->delete($cacheKey);
|
||||
}
|
||||
|
||||
$response = new Response($page->render(), $page->responseStatus(), $page->headers());
|
||||
|
||||
if ($config->get('system.cache.enabled') && $page->cacheable()) {
|
||||
$this->cache->save($cacheKey, $response);
|
||||
$this->filesCache->save($cacheKey, $response);
|
||||
}
|
||||
|
||||
return $response;
|
||||
|
@ -44,7 +44,7 @@ final class Collection extends AbstractCollection
|
||||
*/
|
||||
public static function create(array $data = [], ?string $dataType = null, bool $associative = false, bool $mutable = false): static
|
||||
{
|
||||
$collection = new static();
|
||||
$collection = new self();
|
||||
|
||||
$collection->associative = $associative;
|
||||
$collection->dataType = $dataType;
|
||||
|
@ -4,11 +4,8 @@ namespace Formwork\Data;
|
||||
|
||||
class CollectionDataProxy
|
||||
{
|
||||
protected AbstractCollection $collection;
|
||||
|
||||
public function __construct(AbstractCollection $collection)
|
||||
public function __construct(protected AbstractCollection $collection)
|
||||
{
|
||||
$this->collection = $collection;
|
||||
}
|
||||
|
||||
public function __get(string $name): Collection
|
||||
|
@ -31,14 +31,14 @@ final class DataGetter implements Arrayable
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return empty($this->data);
|
||||
return $this->data === [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from another getter
|
||||
*/
|
||||
public static function fromGetter(DataGetter $getter): self
|
||||
public static function fromGetter(DataGetter $dataGetter): self
|
||||
{
|
||||
return new static($getter->data);
|
||||
return new self($dataGetter->data);
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ final class DataSetter implements Arrayable
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return empty($this->data);
|
||||
return $this->data === [];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -36,6 +36,6 @@ final class DataSetter implements Arrayable
|
||||
*/
|
||||
public static function fromGetter(DataGetter|DataSetter $getter): self
|
||||
{
|
||||
return new static($getter->toArray());
|
||||
return new self($getter->toArray());
|
||||
}
|
||||
}
|
||||
|
@ -9,11 +9,6 @@ class Pagination
|
||||
*/
|
||||
protected int $count = 0;
|
||||
|
||||
/**
|
||||
* Number of items in each pagination page
|
||||
*/
|
||||
protected int $length = 0;
|
||||
|
||||
/**
|
||||
* Number of pagination pages
|
||||
*/
|
||||
@ -26,13 +21,13 @@ class Pagination
|
||||
|
||||
/**
|
||||
* Create a new Pagination instance
|
||||
*
|
||||
* @param int $length Number of items in each pagination page
|
||||
*/
|
||||
public function __construct(AbstractCollection $collection, int $length)
|
||||
public function __construct(AbstractCollection $collection, protected int $length)
|
||||
{
|
||||
$this->count = $collection->count();
|
||||
|
||||
$this->length = $length;
|
||||
|
||||
$this->pages = $this->count > 0 ? (int) ceil($this->count / $this->length) : 1;
|
||||
}
|
||||
|
||||
|
@ -30,15 +30,15 @@ class ErrorHandlers
|
||||
/**
|
||||
* Display error page
|
||||
*/
|
||||
public function displayErrorPage(ResponseStatus $status = ResponseStatus::InternalServerError): void
|
||||
public function displayErrorPage(ResponseStatus $responseStatus = ResponseStatus::InternalServerError): void
|
||||
{
|
||||
Response::cleanOutputBuffers();
|
||||
|
||||
if ($this->request->isXmlHttpRequest()) {
|
||||
JsonResponse::error('Error', $status)->send();
|
||||
JsonResponse::error('Error', $responseStatus)->send();
|
||||
} else {
|
||||
$view = $this->viewFactory->make('errors.error', ['status' => $status->code(), 'message' => $status->message()]);
|
||||
$response = new Response($view->render(), $status);
|
||||
$view = $this->viewFactory->make('errors.error', ['status' => $responseStatus->code(), 'message' => $responseStatus->message()]);
|
||||
$response = new Response($view->render(), $responseStatus);
|
||||
$response->send();
|
||||
// Don't exit, otherwise the error will not be logged
|
||||
}
|
||||
@ -47,16 +47,16 @@ class ErrorHandlers
|
||||
/**
|
||||
* Display error page on exception
|
||||
*/
|
||||
public function getExceptionHandler(Throwable $exception): void
|
||||
public function getExceptionHandler(Throwable $throwable): void
|
||||
{
|
||||
static::displayErrorPage();
|
||||
error_log(sprintf(
|
||||
"Uncaught %s: %s in %s:%s\nStack trace:\n%s\n",
|
||||
$exception::class,
|
||||
$exception->getMessage(),
|
||||
$exception->getFile(),
|
||||
$exception->getLine(),
|
||||
$exception->getTraceAsString()
|
||||
$throwable::class,
|
||||
$throwable->getMessage(),
|
||||
$throwable->getFile(),
|
||||
$throwable->getLine(),
|
||||
$throwable->getTraceAsString()
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -7,23 +7,17 @@ use Formwork\App;
|
||||
|
||||
class TranslatedException extends Exception
|
||||
{
|
||||
/**
|
||||
* Language string of the translated message
|
||||
*/
|
||||
protected string $languageString;
|
||||
|
||||
/**
|
||||
* Create a new TranslatedException instance
|
||||
*
|
||||
* @param string $message Exception message
|
||||
* @param string $languageString Language string of the translated message
|
||||
* @param int $code Exception code
|
||||
* @param Exception $previous Previous Exception
|
||||
* @param string $message Exception message
|
||||
* @param string $languageString Language string of the translated message
|
||||
* @param int $code Exception code
|
||||
* @param Exception $previousException Previous Exception
|
||||
*/
|
||||
public function __construct(string $message, string $languageString, int $code = 0, ?Exception $previous = null)
|
||||
public function __construct(string $message, protected string $languageString, int $code = 0, ?Exception $previousException = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
$this->languageString = $languageString;
|
||||
parent::__construct($message, $code, $previousException);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,31 +23,18 @@ class DynamicFieldValue
|
||||
*/
|
||||
protected bool $computing = false;
|
||||
|
||||
/**
|
||||
* Dynamic value key
|
||||
*/
|
||||
protected string $key;
|
||||
|
||||
/**
|
||||
* Uncomputed value
|
||||
*/
|
||||
protected string $uncomputedValue;
|
||||
|
||||
/**
|
||||
* Field to which the value belongs
|
||||
*/
|
||||
protected Field $field;
|
||||
|
||||
/**
|
||||
* Computed value
|
||||
*/
|
||||
protected mixed $value;
|
||||
|
||||
public function __construct(string $key, string $uncomputedValue, Field $field)
|
||||
/**
|
||||
* @param string $key Dynamic value key
|
||||
* @param string $uncomputedValue Uncomputed value
|
||||
* @param Field $field Field to which the value belongs
|
||||
*/
|
||||
public function __construct(protected string $key, protected string $uncomputedValue, protected Field $field)
|
||||
{
|
||||
$this->key = $key;
|
||||
$this->uncomputedValue = $uncomputedValue;
|
||||
$this->field = $field;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,9 +15,10 @@ use Formwork\Translations\Translation;
|
||||
use Formwork\Utils\Arr;
|
||||
use Formwork\Utils\Constraint;
|
||||
use Formwork\Utils\Str;
|
||||
use Stringable;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class Field implements Arrayable
|
||||
class Field implements Arrayable, Stringable
|
||||
{
|
||||
use DataArrayable;
|
||||
use DataMultipleGetter {
|
||||
@ -31,16 +32,6 @@ class Field implements Arrayable
|
||||
|
||||
protected const UNTRANSLATABLE_KEYS = ['name', 'type', 'value', 'default', 'translate'];
|
||||
|
||||
/**
|
||||
* Field name
|
||||
*/
|
||||
protected string $name;
|
||||
|
||||
/**
|
||||
* Parent field collection
|
||||
*/
|
||||
protected ?FieldCollection $parent;
|
||||
|
||||
/**
|
||||
* Field validation status
|
||||
*/
|
||||
@ -56,14 +47,12 @@ class Field implements Arrayable
|
||||
/**
|
||||
* Create a new Field instance
|
||||
*
|
||||
* @param string $name Field name
|
||||
* @param array<string, mixed> $data
|
||||
* @param ?FieldCollection $parentFieldCollection Parent field collection
|
||||
*/
|
||||
public function __construct(string $name, array $data = [], ?FieldCollection $parent = null)
|
||||
public function __construct(protected string $name, array $data = [], protected ?FieldCollection $parentFieldCollection = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
$this->parent = $parent;
|
||||
|
||||
$this->setMultiple($data);
|
||||
|
||||
if ($this->has('fields')) {
|
||||
@ -74,7 +63,7 @@ class Field implements Arrayable
|
||||
public function __toString(): string
|
||||
{
|
||||
if ($this->hasMethod('toString')) {
|
||||
return $this->callMethod('toString');
|
||||
return (string) $this->callMethod('toString');
|
||||
}
|
||||
|
||||
return (string) $this->value();
|
||||
@ -93,7 +82,7 @@ class Field implements Arrayable
|
||||
*/
|
||||
public function parent(): ?FieldCollection
|
||||
{
|
||||
return $this->parent;
|
||||
return $this->parentFieldCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -266,7 +255,7 @@ class Field implements Arrayable
|
||||
}
|
||||
|
||||
if ($this->isTranslatable($key)) {
|
||||
$value = $this->translate($value);
|
||||
return $this->translate($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
@ -17,9 +17,9 @@ class FieldFactory
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function make(string $name, array $data = [], ?FieldCollection $parent = null): Field
|
||||
public function make(string $name, array $data = [], ?FieldCollection $parentFieldCollection = null): Field
|
||||
{
|
||||
$field = new Field($name, $data, $parent);
|
||||
$field = new Field($name, $data, $parentFieldCollection);
|
||||
|
||||
$field->setTranslation($this->translations->getCurrent());
|
||||
|
||||
|
@ -8,14 +8,10 @@ use Formwork\Utils\FileSystem;
|
||||
use Formwork\Utils\MimeType;
|
||||
use Formwork\Utils\Str;
|
||||
use RuntimeException;
|
||||
use Stringable;
|
||||
|
||||
class File implements Arrayable
|
||||
class File implements Arrayable, Stringable
|
||||
{
|
||||
/**
|
||||
* File path
|
||||
*/
|
||||
protected string $path;
|
||||
|
||||
/**
|
||||
* File name
|
||||
*/
|
||||
@ -34,7 +30,7 @@ class File implements Arrayable
|
||||
/**
|
||||
* File type in a human-readable format
|
||||
*/
|
||||
protected ?string $type;
|
||||
protected ?string $type = null;
|
||||
|
||||
/**
|
||||
* File size in a human-readable format
|
||||
@ -55,10 +51,11 @@ class File implements Arrayable
|
||||
|
||||
/**
|
||||
* Create a new File instance
|
||||
*
|
||||
* @param string $path File path
|
||||
*/
|
||||
public function __construct(string $path)
|
||||
public function __construct(protected string $path)
|
||||
{
|
||||
$this->path = $path;
|
||||
$this->name = basename($path);
|
||||
$this->extension = FileSystem::extension($path);
|
||||
}
|
||||
@ -105,7 +102,7 @@ class File implements Arrayable
|
||||
*/
|
||||
public function type(): ?string
|
||||
{
|
||||
if (isset($this->type)) {
|
||||
if ($this->type !== null) {
|
||||
return $this->type;
|
||||
}
|
||||
if (Str::startsWith($this->mimeType(), 'image')) {
|
||||
@ -151,10 +148,7 @@ class File implements Arrayable
|
||||
*/
|
||||
public function lastModifiedTime(): int
|
||||
{
|
||||
if (isset($this->lastModifiedTime)) {
|
||||
return $this->lastModifiedTime;
|
||||
}
|
||||
return FileSystem::lastModifiedTime($this->path);
|
||||
return $this->lastModifiedTime ?? FileSystem::lastModifiedTime($this->path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,17 +25,17 @@ class FileUploader
|
||||
return Arr::map($this->config->get('system.files.allowedExtensions'), fn (string $ext) => MimeType::fromExtension($ext));
|
||||
}
|
||||
|
||||
public function upload(UploadedFile $file, string $destinationPath, ?string $name = null): File
|
||||
public function upload(UploadedFile $uploadedFile, string $destinationPath, ?string $name = null): File
|
||||
{
|
||||
$mimeType = MimeType::fromFile($file->tempPath());
|
||||
$mimeType = MimeType::fromFile($uploadedFile->tempPath());
|
||||
|
||||
if (!in_array($mimeType, $this->allowedMimeTypes(), true)) {
|
||||
throw new RuntimeException(sprintf('Invalid mime type %s for file uploads', $mimeType));
|
||||
}
|
||||
|
||||
$filename = Str::slug($name ?? pathinfo($file->clientName(), PATHINFO_FILENAME)) . '.' . MimeType::toExtension($mimeType);
|
||||
$filename = Str::slug($name ?? pathinfo($uploadedFile->clientName(), PATHINFO_FILENAME)) . '.' . MimeType::toExtension($mimeType);
|
||||
|
||||
$file->move($destinationPath, $filename);
|
||||
$uploadedFile->move($destinationPath, $filename);
|
||||
|
||||
return new File(FileSystem::joinPaths($destinationPath, $filename));
|
||||
}
|
||||
|
@ -10,14 +10,14 @@ class FileResponse extends Response
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __construct(string $path, ResponseStatus $status = ResponseStatus::OK, array $headers = [], bool $download = false)
|
||||
public function __construct(string $path, ResponseStatus $responseStatus = ResponseStatus::OK, array $headers = [], bool $download = false)
|
||||
{
|
||||
$headers += [
|
||||
'Content-Type' => FileSystem::mimeType($path),
|
||||
'Content-Disposition' => !$download ? 'inline' : Header::make(['attachment', 'filename' => basename($path)]),
|
||||
'Content-Disposition' => $download ? Header::make(['attachment', 'filename' => basename($path)]) : 'inline',
|
||||
'Content-Length' => (string) FileSystem::fileSize($path),
|
||||
];
|
||||
parent::__construct(FileSystem::read($path), $status, $headers);
|
||||
parent::__construct(FileSystem::read($path), $responseStatus, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,8 +34,6 @@ class UploadedFile
|
||||
UPLOAD_ERR_EXTENSION => 'panel.uploader.error.phpExtension',
|
||||
];
|
||||
|
||||
protected string $fieldName;
|
||||
|
||||
protected string $clientName;
|
||||
|
||||
protected string $clientFullPath;
|
||||
@ -51,9 +49,8 @@ class UploadedFile
|
||||
/**
|
||||
* @param array{name: string, full_path: string, type: string, tmp_name: string, error: string, size: string} $data
|
||||
*/
|
||||
public function __construct(string $fieldName, array $data)
|
||||
public function __construct(protected string $fieldName, array $data)
|
||||
{
|
||||
$this->fieldName = $fieldName;
|
||||
$this->clientName = $data['name'];
|
||||
$this->clientFullPath = $data['full_path'];
|
||||
$this->clientMimeType = $data['type'];
|
||||
@ -127,7 +124,7 @@ class UploadedFile
|
||||
// throw new TranslatedException(sprintf('File "%s" already exists', $filename), 'panel.uploader.error.alreadyExists');
|
||||
// }
|
||||
|
||||
if (move_uploaded_file($this->tempPath, $destinationPath) !== false) {
|
||||
if (move_uploaded_file($this->tempPath, $destinationPath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -10,12 +10,12 @@ class JsonResponse extends Response
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __construct(string $data, ResponseStatus $status = ResponseStatus::OK, array $headers = [])
|
||||
public function __construct(string $data, ResponseStatus $responseStatus = ResponseStatus::OK, array $headers = [])
|
||||
{
|
||||
$headers += [
|
||||
'Content-Type' => Header::make(['application/json', 'charset' => 'utf-8']),
|
||||
];
|
||||
parent::__construct($data, $status, $headers);
|
||||
parent::__construct($data, $responseStatus, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -23,14 +23,14 @@ class JsonResponse extends Response
|
||||
*
|
||||
* @param array<mixed> $data
|
||||
*/
|
||||
public static function success(string $message, ResponseStatus $status = ResponseStatus::OK, array $data = []): self
|
||||
public static function success(string $message, ResponseStatus $responseStatus = ResponseStatus::OK, array $data = []): self
|
||||
{
|
||||
return new static(Json::encode([
|
||||
'status' => 'success',
|
||||
'message' => $message,
|
||||
'code' => $status,
|
||||
'code' => $responseStatus,
|
||||
'data' => $data,
|
||||
]), $status);
|
||||
]), $responseStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,13 +38,13 @@ class JsonResponse extends Response
|
||||
*
|
||||
* @param array<mixed> $data
|
||||
*/
|
||||
public static function error(string $message, ResponseStatus $status = ResponseStatus::BadRequest, array $data = []): self
|
||||
public static function error(string $message, ResponseStatus $responseStatus = ResponseStatus::BadRequest, array $data = []): self
|
||||
{
|
||||
return new static(Json::encode([
|
||||
'status' => 'error',
|
||||
'message' => $message,
|
||||
'code' => $status,
|
||||
'code' => $responseStatus,
|
||||
'data' => $data,
|
||||
]), $status);
|
||||
]), $responseStatus);
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,11 @@ class RedirectResponse extends Response
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __construct(string $uri, ResponseStatus $status = ResponseStatus::Found, array $headers = [])
|
||||
public function __construct(string $uri, ResponseStatus $responseStatus = ResponseStatus::Found, array $headers = [])
|
||||
{
|
||||
$headers += [
|
||||
'Location' => $uri,
|
||||
];
|
||||
parent::__construct('', $status, $headers);
|
||||
parent::__construct('', $responseStatus, $headers);
|
||||
}
|
||||
}
|
||||
|
@ -393,7 +393,7 @@ class Request
|
||||
|
||||
foreach ($files as $fieldName => $data) {
|
||||
if (is_array($data['name'])) {
|
||||
foreach ($data['name'] as $i => $name) {
|
||||
foreach (array_keys($data['name']) as $i) {
|
||||
/**
|
||||
* @var array<string, list<UploadedFile>> $result
|
||||
*/
|
||||
|
@ -6,16 +6,6 @@ use Formwork\Http\Utils\Header;
|
||||
|
||||
class Response implements ResponseInterface
|
||||
{
|
||||
/**
|
||||
* Response content
|
||||
*/
|
||||
protected string $content;
|
||||
|
||||
/**
|
||||
* Response HTTP status
|
||||
*/
|
||||
protected ResponseStatus $status;
|
||||
|
||||
/**
|
||||
* Response HTTP headers
|
||||
*
|
||||
@ -25,15 +15,15 @@ class Response implements ResponseInterface
|
||||
|
||||
/**
|
||||
* Create a new Response instance
|
||||
*
|
||||
* @param string $content Response content
|
||||
* @param ResponseStatus $responseStatus Response HTTP status
|
||||
*/
|
||||
public function __construct(string $content, ResponseStatus $status = ResponseStatus::OK, array $headers = [])
|
||||
public function __construct(protected string $content, protected ResponseStatus $responseStatus = ResponseStatus::OK, array $headers = [])
|
||||
{
|
||||
$headers += [
|
||||
'Content-Type' => Header::make(['text/html', 'charset' => 'utf-8']),
|
||||
];
|
||||
|
||||
$this->content = $content;
|
||||
$this->status = $status;
|
||||
$this->headers = $headers;
|
||||
}
|
||||
|
||||
@ -55,7 +45,7 @@ class Response implements ResponseInterface
|
||||
*/
|
||||
public function status(): ResponseStatus
|
||||
{
|
||||
return $this->status;
|
||||
return $this->responseStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -71,7 +61,7 @@ class Response implements ResponseInterface
|
||||
*/
|
||||
public function sendStatus(): void
|
||||
{
|
||||
Header::status($this->status);
|
||||
Header::status($this->responseStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -99,7 +89,7 @@ class Response implements ResponseInterface
|
||||
{
|
||||
return [
|
||||
'content' => $this->content,
|
||||
'status' => $this->status,
|
||||
'status' => $this->responseStatus,
|
||||
'headers' => $this->headers,
|
||||
];
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ interface ResponseInterface extends ArraySerializable
|
||||
*
|
||||
* @param array<string, string> $headers
|
||||
*/
|
||||
public function __construct(string $content, ResponseStatus $status = ResponseStatus::OK, array $headers = []);
|
||||
public function __construct(string $content, ResponseStatus $responseStatus = ResponseStatus::OK, array $headers = []);
|
||||
|
||||
/**
|
||||
* @param array{content: string, status: ResponseStatus, headers: array<string, string>} $properties
|
||||
|
@ -22,18 +22,18 @@ class Messages implements Arrayable
|
||||
$this->data = &$data;
|
||||
}
|
||||
|
||||
public function has(MessageType $type): bool
|
||||
public function has(MessageType $messageType): bool
|
||||
{
|
||||
return !empty($this->data[$type->value]);
|
||||
return !empty($this->data[$messageType->value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function get(MessageType $type): array
|
||||
public function get(MessageType $messageType): array
|
||||
{
|
||||
$messages = $this->data[$type->value] ?? [];
|
||||
$this->remove($type);
|
||||
$messages = $this->data[$messageType->value] ?? [];
|
||||
$this->remove($messageType);
|
||||
return $messages;
|
||||
}
|
||||
|
||||
@ -50,22 +50,22 @@ class Messages implements Arrayable
|
||||
/**
|
||||
* @param list<string>|string $messages
|
||||
*/
|
||||
public function set(MessageType $type, string|array $messages): void
|
||||
public function set(MessageType $messageType, string|array $messages): void
|
||||
{
|
||||
$this->data[$type->value] = (array) $messages;
|
||||
$this->data[$messageType->value] = (array) $messages;
|
||||
}
|
||||
|
||||
public function add(MessageType $type, string $message): void
|
||||
public function add(MessageType $messageType, string $message): void
|
||||
{
|
||||
if (empty($this->data[$type->value])) {
|
||||
$this->set($type, []);
|
||||
if (empty($this->data[$messageType->value])) {
|
||||
$this->set($messageType, []);
|
||||
}
|
||||
$this->data[$type->value][] = $message;
|
||||
$this->data[$messageType->value][] = $message;
|
||||
}
|
||||
|
||||
public function remove(MessageType $type): void
|
||||
public function remove(MessageType $messageType): void
|
||||
{
|
||||
unset($this->data[$type->value]);
|
||||
unset($this->data[$messageType->value]);
|
||||
}
|
||||
|
||||
public function removeAll(): void
|
||||
|
@ -30,8 +30,6 @@ class Session implements Arrayable
|
||||
|
||||
protected const SESSION_ID_REGEX = '/^[a-z0-9,-]{22,256}$/i';
|
||||
|
||||
protected Request $request;
|
||||
|
||||
protected Messages $messages;
|
||||
|
||||
protected string $name = self::SESSION_NAME;
|
||||
@ -40,7 +38,7 @@ class Session implements Arrayable
|
||||
|
||||
protected int $duration = 0;
|
||||
|
||||
public function __construct(Request $request)
|
||||
public function __construct(protected Request $request)
|
||||
{
|
||||
if (!extension_loaded('session')) {
|
||||
throw new RuntimeException('Sessions extension not available');
|
||||
@ -49,8 +47,6 @@ class Session implements Arrayable
|
||||
if (session_status() === PHP_SESSION_DISABLED) {
|
||||
throw new RuntimeException('Sessions disabled by PHP configuration');
|
||||
}
|
||||
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function exists(string $id): bool
|
||||
|
@ -20,14 +20,14 @@ class Header
|
||||
*
|
||||
* @return string|void
|
||||
*/
|
||||
public static function status(ResponseStatus $status, bool $send = true, bool $exit = false)
|
||||
public static function status(ResponseStatus $responseStatus, bool $send = true, bool $exit = false)
|
||||
{
|
||||
$protocol = $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0';
|
||||
$status = implode(' ', [$protocol, $status->value]);
|
||||
$responseStatus = implode(' ', [$protocol, $responseStatus->value]);
|
||||
if (!$send) {
|
||||
return $status;
|
||||
return $responseStatus;
|
||||
}
|
||||
header($status);
|
||||
header($responseStatus);
|
||||
if ($exit) {
|
||||
exit;
|
||||
}
|
||||
@ -65,14 +65,14 @@ class Header
|
||||
/**
|
||||
* Redirect to a given URI and exit from the script
|
||||
*
|
||||
* @param ResponseStatus $status Redirect HTTP response status code
|
||||
* @param ResponseStatus $responseStatus Redirect HTTP response status code
|
||||
*/
|
||||
public static function redirect(string $uri, ResponseStatus $status = ResponseStatus::Found): void
|
||||
public static function redirect(string $uri, ResponseStatus $responseStatus = ResponseStatus::Found): void
|
||||
{
|
||||
if ($status->type() !== ResponseStatusType::Redirection) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid response status "%s" for redirection, only 3XX statuses are allowed', $status->value));
|
||||
if ($responseStatus->type() !== ResponseStatusType::Redirection) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid response status "%s" for redirection, only 3XX statuses are allowed', $responseStatus->value));
|
||||
}
|
||||
static::status($status);
|
||||
static::status($responseStatus);
|
||||
static::send('Location', $uri);
|
||||
exit;
|
||||
}
|
||||
|
@ -24,14 +24,11 @@ class IpAnonymizer
|
||||
*/
|
||||
public static function anonymize(string $ip): string
|
||||
{
|
||||
switch (strlen(self::packIPAddress($ip))) {
|
||||
case 4:
|
||||
return static::anonymizeIPv4($ip);
|
||||
case 16:
|
||||
return static::anonymizeIPv6($ip);
|
||||
default:
|
||||
throw new InvalidArgumentException(sprintf('Invalid IP address %s', $ip));
|
||||
}
|
||||
return match (strlen(self::packIPAddress($ip))) {
|
||||
4 => static::anonymizeIPv4($ip),
|
||||
16 => static::anonymizeIPv6($ip),
|
||||
default => throw new InvalidArgumentException(sprintf('Invalid IP address %s', $ip)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -12,17 +12,13 @@ class ColorProfile
|
||||
|
||||
protected const ICC_PROFILE_SIGNATURE_OFFSET = 36;
|
||||
|
||||
protected string $data;
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected array $tags;
|
||||
|
||||
public function __construct(string $data)
|
||||
public function __construct(protected string $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
|
||||
if (strpos($this->data, self::ICC_PROFILE_SIGNATURE) !== self::ICC_PROFILE_SIGNATURE_OFFSET) {
|
||||
throw new InvalidArgumentException('Invalid ICC profile data');
|
||||
}
|
||||
|
@ -9,17 +9,14 @@ class ExifData implements Arrayable
|
||||
{
|
||||
protected ExifReader $reader;
|
||||
|
||||
protected string $data;
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected array $tags;
|
||||
|
||||
public function __construct(string $data)
|
||||
public function __construct(protected string $data)
|
||||
{
|
||||
$this->reader = new ExifReader();
|
||||
$this->data = $data;
|
||||
$this->tags = $this->reader->read($this->data);
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,9 @@
|
||||
namespace Formwork\Images\Exif;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use Stringable;
|
||||
|
||||
class ExifDateTime extends DateTimeImmutable
|
||||
class ExifDateTime extends DateTimeImmutable implements Stringable
|
||||
{
|
||||
public const EXIF = 'Y:m:d H:i:s';
|
||||
|
||||
|
@ -14,8 +14,6 @@ use UnexpectedValueException;
|
||||
|
||||
abstract class AbstractHandler implements HandlerInterface
|
||||
{
|
||||
protected string $data;
|
||||
|
||||
protected DecoderInterface $decoder;
|
||||
|
||||
/**
|
||||
@ -26,9 +24,8 @@ abstract class AbstractHandler implements HandlerInterface
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function __construct(string $data, array $options = [])
|
||||
public function __construct(protected string $data, array $options = [])
|
||||
{
|
||||
$this->data = $data;
|
||||
$this->decoder = $this->getDecoder();
|
||||
$this->options = [...$this->defaults(), ...$options];
|
||||
}
|
||||
@ -38,11 +35,11 @@ abstract class AbstractHandler implements HandlerInterface
|
||||
return new static(FileSystem::read($path));
|
||||
}
|
||||
|
||||
public static function fromGdImage(GdImage $image, array $options = []): static
|
||||
public static function fromGdImage(GdImage $gdImage, array $options = []): static
|
||||
{
|
||||
$handler = new static('', $options);
|
||||
$handler->setDataFromGdImage($image);
|
||||
return $handler;
|
||||
$static = new static('', $options);
|
||||
$static->setDataFromGdImage($gdImage);
|
||||
return $static;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,7 +66,7 @@ abstract class AbstractHandler implements HandlerInterface
|
||||
*
|
||||
* @throws RuntimeException if the image has no color profile
|
||||
*/
|
||||
abstract public function setColorProfile(ColorProfile $profile): void;
|
||||
abstract public function setColorProfile(ColorProfile $colorProfile): void;
|
||||
|
||||
/**
|
||||
* Remove color profile
|
||||
@ -97,7 +94,7 @@ abstract class AbstractHandler implements HandlerInterface
|
||||
*
|
||||
* @throws RuntimeException if the image does not support EXIF data
|
||||
*/
|
||||
abstract public function setExifData(ExifData $data): void;
|
||||
abstract public function setExifData(ExifData $exifData): void;
|
||||
|
||||
/**
|
||||
* Remove EXIF data
|
||||
@ -137,19 +134,19 @@ abstract class AbstractHandler implements HandlerInterface
|
||||
];
|
||||
}
|
||||
|
||||
public function process(?TransformCollection $transforms = null, ?string $handler = null): AbstractHandler
|
||||
public function process(?TransformCollection $transformCollection = null, ?string $handler = null): AbstractHandler
|
||||
{
|
||||
$handler ??= $this::class;
|
||||
$handler ??= static::class;
|
||||
|
||||
if (!is_subclass_of($handler, self::class)) {
|
||||
throw new UnexpectedValueException(sprintf('Invalid handler of type %s, only instances of %s are allowed', get_debug_type($handler), self::class));
|
||||
}
|
||||
|
||||
if ($handler === $this::class && $transforms === null) {
|
||||
if ($handler === static::class && $transformCollection === null) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$info = $this->getInfo();
|
||||
$imageInfo = $this->getInfo();
|
||||
|
||||
if ($this->options['preserveColorProfile'] && $this->hasColorProfile() && $handler::supportsColorProfile()) {
|
||||
$colorProfile = $this->getColorProfile();
|
||||
@ -161,13 +158,13 @@ abstract class AbstractHandler implements HandlerInterface
|
||||
|
||||
$image = $this->toGdImage();
|
||||
|
||||
if ($transforms !== null) {
|
||||
foreach ($transforms as $transform) {
|
||||
$image = $transform->apply($image, $info);
|
||||
if ($transformCollection !== null) {
|
||||
foreach ($transformCollection as $transform) {
|
||||
$image = $transform->apply($image, $imageInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if ($handler === $this::class) {
|
||||
if ($handler === static::class) {
|
||||
$this->setDataFromGdImage($image);
|
||||
$instance = $this;
|
||||
} else {
|
||||
@ -193,7 +190,7 @@ abstract class AbstractHandler implements HandlerInterface
|
||||
*/
|
||||
abstract protected function getDecoder(): DecoderInterface;
|
||||
|
||||
abstract protected function setDataFromGdImage(GdImage $image): void;
|
||||
abstract protected function setDataFromGdImage(GdImage $gdImage): void;
|
||||
|
||||
protected function toGdImage(): GdImage
|
||||
{
|
||||
|
@ -50,10 +50,13 @@ class GifHandler extends AbstractHandler
|
||||
$info['animationRepeatCount']++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($block['type'] === 'IMG' && $info['isAnimation']) {
|
||||
$info['animationFrames']++;
|
||||
if ($block['type'] !== 'IMG') {
|
||||
continue;
|
||||
}
|
||||
if (!$info['isAnimation']) {
|
||||
continue;
|
||||
}
|
||||
$info['animationFrames']++;
|
||||
}
|
||||
|
||||
return new ImageInfo($info);
|
||||
@ -74,7 +77,7 @@ class GifHandler extends AbstractHandler
|
||||
throw new UnsupportedFeatureException('GIF does not support color profiles');
|
||||
}
|
||||
|
||||
public function setColorProfile(ColorProfile $profile): void
|
||||
public function setColorProfile(ColorProfile $colorProfile): void
|
||||
{
|
||||
throw new UnsupportedFeatureException('GIF does not support color profiles');
|
||||
}
|
||||
@ -99,7 +102,7 @@ class GifHandler extends AbstractHandler
|
||||
throw new UnsupportedFeatureException('GIF does not support EXIF data');
|
||||
}
|
||||
|
||||
public function setExifData(ExifData $data): void
|
||||
public function setExifData(ExifData $exifData): void
|
||||
{
|
||||
throw new UnsupportedFeatureException('GIF does not support EXIF data');
|
||||
}
|
||||
@ -114,13 +117,13 @@ class GifHandler extends AbstractHandler
|
||||
return new GifDecoder();
|
||||
}
|
||||
|
||||
protected function setDataFromGdImage(GdImage $image): void
|
||||
protected function setDataFromGdImage(GdImage $gdImage): void
|
||||
{
|
||||
imagetruecolortopalette($image, true, $this->options['gifColors']);
|
||||
imagetruecolortopalette($gdImage, true, $this->options['gifColors']);
|
||||
|
||||
ob_start();
|
||||
|
||||
if (imagegif($image, null) === false) {
|
||||
if (imagegif($gdImage, null) === false) {
|
||||
throw new RuntimeException('Cannot set data from GdImage');
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ interface HandlerInterface
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public static function fromGdImage(GdImage $image, array $options = []): HandlerInterface;
|
||||
public static function fromGdImage(GdImage $gdImage, array $options = []): HandlerInterface;
|
||||
|
||||
/**
|
||||
* Get image info as an array
|
||||
@ -47,7 +47,7 @@ interface HandlerInterface
|
||||
*
|
||||
* @throws RuntimeException if the image has no color profile
|
||||
*/
|
||||
public function setColorProfile(ColorProfile $profile): void;
|
||||
public function setColorProfile(ColorProfile $colorProfile): void;
|
||||
|
||||
/**
|
||||
* Remove color profile
|
||||
@ -75,7 +75,7 @@ interface HandlerInterface
|
||||
*
|
||||
* @throws RuntimeException if the image does not support EXIF data
|
||||
*/
|
||||
public function setExifData(ExifData $data): void;
|
||||
public function setExifData(ExifData $exifData): void;
|
||||
|
||||
/**
|
||||
* Remove EXIF data
|
||||
@ -98,5 +98,5 @@ interface HandlerInterface
|
||||
*/
|
||||
public function defaults(): array;
|
||||
|
||||
public function process(?TransformCollection $transforms = null, ?string $handler = null): self;
|
||||
public function process(?TransformCollection $transformCollection = null, ?string $handler = null): self;
|
||||
}
|
||||
|
@ -56,9 +56,13 @@ class JpegHandler extends AbstractHandler
|
||||
public function hasColorProfile(): bool
|
||||
{
|
||||
foreach ($this->decoder->decode($this->data) as $segment) {
|
||||
if ($segment['type'] === 0xe2 && str_starts_with($segment['value'], self::ICC_PROFILE_HEADER)) {
|
||||
return true;
|
||||
if ($segment['type'] !== 0xe2) {
|
||||
continue;
|
||||
}
|
||||
if (!str_starts_with($segment['value'], self::ICC_PROFILE_HEADER)) {
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -89,11 +93,11 @@ class JpegHandler extends AbstractHandler
|
||||
return new ColorProfile(implode('', $profileChunks));
|
||||
}
|
||||
|
||||
public function setColorProfile(ColorProfile $profile): void
|
||||
public function setColorProfile(ColorProfile $colorProfile): void
|
||||
{
|
||||
foreach ($this->decoder->decode($this->data) as $segment) {
|
||||
if ($segment['type'] === 0xd8) {
|
||||
$this->data = substr_replace($this->data, $this->encodeColorProfile($profile->getData()), $segment['position'], 0);
|
||||
$this->data = substr_replace($this->data, $this->encodeColorProfile($colorProfile->getData()), $segment['position'], 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -117,9 +121,13 @@ class JpegHandler extends AbstractHandler
|
||||
public function hasExifData(): bool
|
||||
{
|
||||
foreach ($this->decoder->decode($this->data) as $segment) {
|
||||
if ($segment['type'] === 0xe1 && str_starts_with($segment['value'], self::EXIF_HEADER)) {
|
||||
return true;
|
||||
if ($segment['type'] !== 0xe1) {
|
||||
continue;
|
||||
}
|
||||
if (!str_starts_with($segment['value'], self::EXIF_HEADER)) {
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -127,18 +135,22 @@ class JpegHandler extends AbstractHandler
|
||||
public function getExifData(): ?ExifData
|
||||
{
|
||||
foreach ($this->decoder->decode($this->data) as $segment) {
|
||||
if ($segment['type'] === 0xe1 && str_starts_with($segment['value'], self::EXIF_HEADER)) {
|
||||
return new ExifData(substr($segment['value'], strlen(self::EXIF_HEADER)));
|
||||
if ($segment['type'] !== 0xe1) {
|
||||
continue;
|
||||
}
|
||||
if (!str_starts_with($segment['value'], self::EXIF_HEADER)) {
|
||||
continue;
|
||||
}
|
||||
return new ExifData(substr($segment['value'], strlen(self::EXIF_HEADER)));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setExifData(ExifData $data): void
|
||||
public function setExifData(ExifData $exifData): void
|
||||
{
|
||||
foreach ($this->decoder->decode($this->data) as $segment) {
|
||||
if ($segment['type'] === 0xd8) {
|
||||
$this->data = substr_replace($this->data, $this->encodeExifData($data->getData()), $segment['position'], 0);
|
||||
$this->data = substr_replace($this->data, $this->encodeExifData($exifData->getData()), $segment['position'], 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -189,13 +201,13 @@ class JpegHandler extends AbstractHandler
|
||||
return new JpegDecoder();
|
||||
}
|
||||
|
||||
protected function setDataFromGdImage(GdImage $image): void
|
||||
protected function setDataFromGdImage(GdImage $gdImage): void
|
||||
{
|
||||
imageinterlace($image, $this->options['jpegProgressive']);
|
||||
imageinterlace($gdImage, $this->options['jpegProgressive']);
|
||||
|
||||
ob_start();
|
||||
|
||||
if (imagejpeg($image, null, $this->options['jpegQuality']) === false) {
|
||||
if (imagejpeg($gdImage, null, $this->options['jpegQuality']) === false) {
|
||||
throw new RuntimeException('Cannot set data from GdImage');
|
||||
}
|
||||
|
||||
|
@ -81,11 +81,11 @@ class PngHandler extends AbstractHandler
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setColorProfile(ColorProfile $profile): void
|
||||
public function setColorProfile(ColorProfile $colorProfile): void
|
||||
{
|
||||
foreach ($this->decoder->decode($this->data) as $chunk) {
|
||||
if ($chunk['type'] === 'IHDR') {
|
||||
$iCCPChunk = $this->encodeChunk('iCCP', $this->encodeProfile($profile->name(), $profile->getData()));
|
||||
$iCCPChunk = $this->encodeChunk('iCCP', $this->encodeProfile($colorProfile->name(), $colorProfile->getData()));
|
||||
$this->data = substr_replace($this->data, $iCCPChunk, $chunk['position'], 0);
|
||||
break;
|
||||
}
|
||||
@ -130,11 +130,11 @@ class PngHandler extends AbstractHandler
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setExifData(ExifData $data): void
|
||||
public function setExifData(ExifData $exifData): void
|
||||
{
|
||||
foreach ($this->decoder->decode($this->data) as $chunk) {
|
||||
if ($chunk['type'] === 'IHDR') {
|
||||
$iCCPChunk = $this->encodeChunk('eXIf', $data->getData());
|
||||
$iCCPChunk = $this->encodeChunk('eXIf', $exifData->getData());
|
||||
$this->data = substr_replace($this->data, $iCCPChunk, $chunk['position'], 0);
|
||||
break;
|
||||
}
|
||||
@ -192,11 +192,11 @@ class PngHandler extends AbstractHandler
|
||||
return new PngDecoder();
|
||||
}
|
||||
|
||||
protected function setDataFromGdImage(GdImage $image): void
|
||||
protected function setDataFromGdImage(GdImage $gdImage): void
|
||||
{
|
||||
ob_start();
|
||||
|
||||
if (imagepng($image, null, $this->options['pngCompression']) === false) {
|
||||
if (imagepng($gdImage, null, $this->options['pngCompression']) === false) {
|
||||
throw new RuntimeException('Cannot set data from GdImage');
|
||||
}
|
||||
|
||||
|
@ -68,10 +68,13 @@ class WebpHandler extends AbstractHandler
|
||||
$info['isAnimation'] = true;
|
||||
$info['animationRepeatCount'] = unpack('v', $chunk['value'], 4)[1];
|
||||
}
|
||||
|
||||
if ($info['isAnimation'] && $chunk['type'] === 'ANMF') {
|
||||
$info['animationFrames']++;
|
||||
if (!$info['isAnimation']) {
|
||||
continue;
|
||||
}
|
||||
if ($chunk['type'] !== 'ANMF') {
|
||||
continue;
|
||||
}
|
||||
$info['animationFrames']++;
|
||||
}
|
||||
|
||||
return new ImageInfo($info);
|
||||
@ -104,13 +107,13 @@ class WebpHandler extends AbstractHandler
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setColorProfile(ColorProfile $profile): void
|
||||
public function setColorProfile(ColorProfile $colorProfile): void
|
||||
{
|
||||
foreach ($this->decoder->decode($this->data) as $chunk) {
|
||||
if ($chunk['type'] === 'VP8X') {
|
||||
$VP8XFlags = ord($chunk['value'][0]) | self::ICC_FLAG;
|
||||
$this->data = substr_replace($this->data, chr($VP8XFlags), $chunk['offset'] + 8, 1);
|
||||
$ICCPChunk = $this->encodeChunk('ICCP', $profile->getData());
|
||||
$ICCPChunk = $this->encodeChunk('ICCP', $colorProfile->getData());
|
||||
$this->data = substr_replace($this->data, $ICCPChunk, $chunk['position'], 0);
|
||||
$this->updateRIFFHeader();
|
||||
break;
|
||||
@ -161,7 +164,7 @@ class WebpHandler extends AbstractHandler
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setExifData(ExifData $data): void
|
||||
public function setExifData(ExifData $exifData): void
|
||||
{
|
||||
foreach ($this->decoder->decode($this->data) as $chunk) {
|
||||
if ($chunk['type'] === 'VP8X') {
|
||||
@ -170,7 +173,7 @@ class WebpHandler extends AbstractHandler
|
||||
}
|
||||
|
||||
if (in_array($chunk['type'], ['VP8 ', 'VP8L'], true)) {
|
||||
$ExifChunk = $this->encodeChunk('EXIF', $data->getData());
|
||||
$ExifChunk = $this->encodeChunk('EXIF', $exifData->getData());
|
||||
$this->data = substr_replace($this->data, $ExifChunk, $chunk['position'], 0);
|
||||
$this->updateRIFFHeader();
|
||||
}
|
||||
@ -213,11 +216,11 @@ class WebpHandler extends AbstractHandler
|
||||
return new WebpDecoder();
|
||||
}
|
||||
|
||||
protected function setDataFromGdImage(GdImage $image): void
|
||||
protected function setDataFromGdImage(GdImage $gdImage): void
|
||||
{
|
||||
ob_start();
|
||||
|
||||
if (imagewebp($image, null, $this->options['webpQuality']) === false) {
|
||||
if (imagewebp($gdImage, null, $this->options['webpQuality']) === false) {
|
||||
throw new RuntimeException('Cannot set data from GdImage');
|
||||
}
|
||||
|
||||
@ -228,7 +231,7 @@ class WebpHandler extends AbstractHandler
|
||||
|
||||
protected function setVP8XChunk(): void
|
||||
{
|
||||
if (strpos($this->data, 'VP8X', 12) === false) {
|
||||
if (!str_contains(substr($this->data, 12), 'VP8X')) {
|
||||
$info = $this->getInfo();
|
||||
$data = chr(self::ALPHA_FLAG) . "\x0\x0\x0" . substr(pack('V', $info->width() - 1), 0, 3) . substr(pack('V', $info->height() - 1), 0, 3);
|
||||
$chunk = $this->encodeChunk('VP8X', $data);
|
||||
|
@ -39,11 +39,6 @@ class Image extends File
|
||||
{
|
||||
protected string $path;
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected array $options = [];
|
||||
|
||||
protected AbstractHandler $handler;
|
||||
|
||||
protected ImageInfo $info;
|
||||
@ -57,10 +52,9 @@ class Image extends File
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function __construct(string $path, array $options)
|
||||
public function __construct(string $path, protected array $options)
|
||||
{
|
||||
parent::__construct($path);
|
||||
$this->options = $options;
|
||||
$this->transforms = new TransformCollection();
|
||||
}
|
||||
|
||||
@ -140,9 +134,9 @@ class Image extends File
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function blur(int $amount, BlurMode $mode = BlurMode::Mean): self
|
||||
public function blur(int $amount, BlurMode $blurMode = BlurMode::Mean): self
|
||||
{
|
||||
$this->transforms->add(new Blur($amount, $mode));
|
||||
$this->transforms->add(new Blur($amount, $blurMode));
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -229,9 +223,9 @@ class Image extends File
|
||||
*
|
||||
* @throws RuntimeException if the image has no color profile
|
||||
*/
|
||||
public function setColorProfile(ColorProfile $profile): void
|
||||
public function setColorProfile(ColorProfile $colorProfile): void
|
||||
{
|
||||
$this->handler()->setColorProfile($profile);
|
||||
$this->handler()->setColorProfile($colorProfile);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -267,9 +261,9 @@ class Image extends File
|
||||
*
|
||||
* @throws RuntimeException if the image does not support EXIF data
|
||||
*/
|
||||
public function setExifData(ExifData $data): void
|
||||
public function setExifData(ExifData $exifData): void
|
||||
{
|
||||
$this->handler()->setExifData($data);
|
||||
$this->handler()->setExifData($exifData);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -357,7 +351,13 @@ class Image extends File
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [...parent::toArray(), 'imageInfo' => $this->info()->toArray(), 'exif' => $this->getExifData()?->toArray(), 'colorProfile' => $this->getColorProfile()?->name(), 'uri' => $this->uri()];
|
||||
return [
|
||||
...parent::toArray(),
|
||||
'imageInfo' => $this->info()->toArray(),
|
||||
'exif' => $this->getExifData()?->toArray(),
|
||||
'colorProfile' => $this->getColorProfile()?->name(),
|
||||
'uri' => $this->uri(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHash(?string $mimeType = null): string
|
||||
|
@ -14,19 +14,19 @@ class ImageInfo implements Arrayable
|
||||
|
||||
protected int $height;
|
||||
|
||||
protected ?ColorSpace $colorSpace;
|
||||
protected ?ColorSpace $colorSpace = null;
|
||||
|
||||
protected ?int $colorDepth;
|
||||
protected ?int $colorDepth = null;
|
||||
|
||||
protected ?int $colorNumber;
|
||||
protected ?int $colorNumber = null;
|
||||
|
||||
protected bool $hasAlphaChannel;
|
||||
|
||||
protected bool $isAnimation;
|
||||
|
||||
protected ?int $animationFrames;
|
||||
protected ?int $animationFrames = null;
|
||||
|
||||
protected ?int $animationRepeatCount;
|
||||
protected ?int $animationRepeatCount = null;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $info
|
||||
|
@ -36,18 +36,18 @@ class Blur extends AbstractTransform
|
||||
|
||||
protected BlurMode $mode;
|
||||
|
||||
final public function __construct(int $amount, BlurMode $mode)
|
||||
final public function __construct(int $amount, BlurMode $blurMode)
|
||||
{
|
||||
if (!Constraint::isInIntegerRange($amount, 0, 100)) {
|
||||
throw new InvalidArgumentException(sprintf('$amount value must be in range 0-100, %d given', $amount));
|
||||
}
|
||||
|
||||
if (!isset(self::CONVOLUTION_KERNELS[$mode->name])) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid blur mode, "%s" given', $mode->name));
|
||||
if (!isset(self::CONVOLUTION_KERNELS[$blurMode->name])) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid blur mode, "%s" given', $blurMode->name));
|
||||
}
|
||||
|
||||
$this->amount = $amount;
|
||||
$this->mode = $mode;
|
||||
$this->mode = $blurMode;
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
@ -55,12 +55,12 @@ class Blur extends AbstractTransform
|
||||
return new static($data['amount'], $data['mode']);
|
||||
}
|
||||
|
||||
public function apply(GdImage $image, ImageInfo $info): GdImage
|
||||
public function apply(GdImage $gdImage, ImageInfo $imageInfo): GdImage
|
||||
{
|
||||
for ($i = 0; $i < $this->amount; $i++) {
|
||||
imageconvolution($image, self::CONVOLUTION_KERNELS[$this->mode->name], 1, 0.55);
|
||||
imageconvolution($gdImage, self::CONVOLUTION_KERNELS[$this->mode->name], 1, 0.55);
|
||||
}
|
||||
|
||||
return $image;
|
||||
return $gdImage;
|
||||
}
|
||||
}
|
||||
|
@ -27,9 +27,9 @@ class Brightness extends AbstractTransform
|
||||
return new static($data['amount']);
|
||||
}
|
||||
|
||||
public function apply(GdImage $image, ImageInfo $info): GdImage
|
||||
public function apply(GdImage $gdImage, ImageInfo $imageInfo): GdImage
|
||||
{
|
||||
imagefilter($image, IMG_FILTER_BRIGHTNESS, $this->amount);
|
||||
return $image;
|
||||
imagefilter($gdImage, IMG_FILTER_BRIGHTNESS, $this->amount);
|
||||
return $gdImage;
|
||||
}
|
||||
}
|
||||
|
@ -46,9 +46,9 @@ class Colorize extends AbstractTransform
|
||||
return new static($data['red'], $data['green'], $data['blue'], $data['alpha']);
|
||||
}
|
||||
|
||||
public function apply(GdImage $image, ImageInfo $info): GdImage
|
||||
public function apply(GdImage $gdImage, ImageInfo $imageInfo): GdImage
|
||||
{
|
||||
imagefilter($image, IMG_FILTER_COLORIZE, $this->red, $this->green, $this->blue, $this->alpha);
|
||||
return $image;
|
||||
imagefilter($gdImage, IMG_FILTER_COLORIZE, $this->red, $this->green, $this->blue, $this->alpha);
|
||||
return $gdImage;
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,10 @@ class Contrast extends AbstractTransform
|
||||
return new static($data['amount']);
|
||||
}
|
||||
|
||||
public function apply(GdImage $image, ImageInfo $info): GdImage
|
||||
public function apply(GdImage $gdImage, ImageInfo $imageInfo): GdImage
|
||||
{
|
||||
// For GD -100 = max contrast, 100 = min contrast; we change $amount sign for a more predictable behavior
|
||||
imagefilter($image, IMG_FILTER_CONTRAST, -$this->amount);
|
||||
return $image;
|
||||
imagefilter($gdImage, IMG_FILTER_CONTRAST, -$this->amount);
|
||||
return $gdImage;
|
||||
}
|
||||
}
|
||||
|
@ -7,20 +7,8 @@ use GdImage;
|
||||
|
||||
class Crop extends AbstractTransform
|
||||
{
|
||||
protected int $originX;
|
||||
|
||||
protected int $originY;
|
||||
|
||||
protected int $width;
|
||||
|
||||
protected int $height;
|
||||
|
||||
final public function __construct(int $originX, int $originY, int $width, int $height)
|
||||
final public function __construct(protected int $originX, protected int $originY, protected int $width, protected int $height)
|
||||
{
|
||||
$this->originX = $originX;
|
||||
$this->originY = $originY;
|
||||
$this->width = $width;
|
||||
$this->height = $height;
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
@ -28,7 +16,7 @@ class Crop extends AbstractTransform
|
||||
return new static($data['originX'], $data['originY'], $data['width'], $data['height']);
|
||||
}
|
||||
|
||||
public function apply(GdImage $image, ImageInfo $info): GdImage
|
||||
public function apply(GdImage $gdImage, ImageInfo $imageInfo): GdImage
|
||||
{
|
||||
$destinationImage = imagecreatetruecolor($this->width, $this->height);
|
||||
|
||||
@ -36,7 +24,7 @@ class Crop extends AbstractTransform
|
||||
|
||||
imagecopy(
|
||||
$destinationImage,
|
||||
$image,
|
||||
$gdImage,
|
||||
0,
|
||||
0,
|
||||
$this->originX,
|
||||
@ -48,12 +36,12 @@ class Crop extends AbstractTransform
|
||||
return $destinationImage;
|
||||
}
|
||||
|
||||
protected function enableTransparency(GdImage $image): void
|
||||
protected function enableTransparency(GdImage $gdImage): void
|
||||
{
|
||||
$transparent = imagecolorallocatealpha($image, 0, 0, 0, 127);
|
||||
imagealphablending($image, true);
|
||||
imagesavealpha($image, true);
|
||||
imagecolortransparent($image, $transparent);
|
||||
imagefill($image, 0, 0, $transparent);
|
||||
$transparent = imagecolorallocatealpha($gdImage, 0, 0, 0, 127);
|
||||
imagealphablending($gdImage, true);
|
||||
imagesavealpha($gdImage, true);
|
||||
imagecolortransparent($gdImage, $transparent);
|
||||
imagefill($gdImage, 0, 0, $transparent);
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,9 @@ class Desaturate extends AbstractTransform
|
||||
return new static();
|
||||
}
|
||||
|
||||
public function apply(GdImage $image, ImageInfo $info): GdImage
|
||||
public function apply(GdImage $gdImage, ImageInfo $imageInfo): GdImage
|
||||
{
|
||||
imagefilter($image, IMG_FILTER_GRAYSCALE);
|
||||
return $image;
|
||||
imagefilter($gdImage, IMG_FILTER_GRAYSCALE);
|
||||
return $gdImage;
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,9 @@ class EdgeDetect extends AbstractTransform
|
||||
return new static();
|
||||
}
|
||||
|
||||
public function apply(GdImage $image, ImageInfo $info): GdImage
|
||||
public function apply(GdImage $gdImage, ImageInfo $imageInfo): GdImage
|
||||
{
|
||||
imagefilter($image, IMG_FILTER_EDGEDETECT);
|
||||
return $image;
|
||||
imagefilter($gdImage, IMG_FILTER_EDGEDETECT);
|
||||
return $gdImage;
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,9 @@ class Emboss extends AbstractTransform
|
||||
return new static();
|
||||
}
|
||||
|
||||
public function apply(GdImage $image, ImageInfo $info): GdImage
|
||||
public function apply(GdImage $gdImage, ImageInfo $imageInfo): GdImage
|
||||
{
|
||||
imagefilter($image, IMG_FILTER_EMBOSS);
|
||||
return $image;
|
||||
imagefilter($gdImage, IMG_FILTER_EMBOSS);
|
||||
return $gdImage;
|
||||
}
|
||||
}
|
||||
|
@ -16,13 +16,13 @@ class Flip extends AbstractTransform
|
||||
|
||||
protected FlipDirection $direction;
|
||||
|
||||
final public function __construct(FlipDirection $direction)
|
||||
final public function __construct(FlipDirection $flipDirection)
|
||||
{
|
||||
if (!isset(self::DIRECTIONS[$direction->name])) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid flip direction, "%s" given', $direction->name));
|
||||
if (!isset(self::DIRECTIONS[$flipDirection->name])) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid flip direction, "%s" given', $flipDirection->name));
|
||||
}
|
||||
|
||||
$this->direction = $direction;
|
||||
$this->direction = $flipDirection;
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
@ -30,9 +30,9 @@ class Flip extends AbstractTransform
|
||||
return new static($data['direction']);
|
||||
}
|
||||
|
||||
public function apply(GdImage $image, ImageInfo $info): GdImage
|
||||
public function apply(GdImage $gdImage, ImageInfo $imageInfo): GdImage
|
||||
{
|
||||
imageflip($image, self::DIRECTIONS[$this->direction->name]);
|
||||
return $image;
|
||||
imageflip($gdImage, self::DIRECTIONS[$this->direction->name]);
|
||||
return $gdImage;
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,9 @@ class Invert extends AbstractTransform
|
||||
return new static();
|
||||
}
|
||||
|
||||
public function apply(GdImage $image, ImageInfo $info): GdImage
|
||||
public function apply(GdImage $gdImage, ImageInfo $imageInfo): GdImage
|
||||
{
|
||||
imagefilter($image, IMG_FILTER_NEGATE);
|
||||
return $image;
|
||||
imagefilter($gdImage, IMG_FILTER_NEGATE);
|
||||
return $gdImage;
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,8 @@ use GdImage;
|
||||
|
||||
class Pixelate extends AbstractTransform
|
||||
{
|
||||
protected int $amount;
|
||||
|
||||
final public function __construct(int $amount)
|
||||
final public function __construct(protected int $amount)
|
||||
{
|
||||
$this->amount = $amount;
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
@ -19,9 +16,9 @@ class Pixelate extends AbstractTransform
|
||||
return new static($data['amount']);
|
||||
}
|
||||
|
||||
public function apply(GdImage $image, ImageInfo $info): GdImage
|
||||
public function apply(GdImage $gdImage, ImageInfo $imageInfo): GdImage
|
||||
{
|
||||
imagefilter($image, IMG_FILTER_PIXELATE, $this->amount);
|
||||
return $image;
|
||||
imagefilter($gdImage, IMG_FILTER_PIXELATE, $this->amount);
|
||||
return $gdImage;
|
||||
}
|
||||
}
|
||||
|
@ -7,17 +7,8 @@ use GdImage;
|
||||
|
||||
class Resize extends AbstractTransform
|
||||
{
|
||||
protected int $width;
|
||||
|
||||
protected int $height;
|
||||
|
||||
protected ResizeMode $mode;
|
||||
|
||||
final public function __construct(int $width, int $height, ResizeMode $mode = ResizeMode::Cover)
|
||||
final public function __construct(protected int $width, protected int $height, protected ResizeMode $resizeMode = ResizeMode::Cover)
|
||||
{
|
||||
$this->width = $width;
|
||||
$this->height = $height;
|
||||
$this->mode = $mode;
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
@ -25,10 +16,10 @@ class Resize extends AbstractTransform
|
||||
return new static($data['width'], $data['height'], $data['mode']);
|
||||
}
|
||||
|
||||
public function apply(GdImage $image, ImageInfo $info): GdImage
|
||||
public function apply(GdImage $gdImage, ImageInfo $imageInfo): GdImage
|
||||
{
|
||||
$sourceWidth = imagesx($image);
|
||||
$sourceHeight = imagesy($image);
|
||||
$sourceWidth = imagesx($gdImage);
|
||||
$sourceHeight = imagesy($gdImage);
|
||||
|
||||
$cropAreaWidth = $sourceWidth;
|
||||
$cropAreaHeight = $sourceHeight;
|
||||
@ -48,7 +39,7 @@ class Resize extends AbstractTransform
|
||||
$width = $this->width;
|
||||
$height = $this->height;
|
||||
|
||||
switch ($this->mode) {
|
||||
switch ($this->resizeMode) {
|
||||
case ResizeMode::Fill:
|
||||
$cropAreaWidth = $sourceWidth;
|
||||
$cropAreaHeight = $sourceHeight;
|
||||
@ -87,13 +78,13 @@ class Resize extends AbstractTransform
|
||||
|
||||
$destinationImage = imagecreatetruecolor((int) $width, (int) $height);
|
||||
|
||||
if ($info->hasAlphaChannel()) {
|
||||
if ($imageInfo->hasAlphaChannel()) {
|
||||
$this->enableTransparency($destinationImage);
|
||||
}
|
||||
|
||||
imagecopyresampled(
|
||||
$destinationImage,
|
||||
$image,
|
||||
$gdImage,
|
||||
(int) $destinationX,
|
||||
(int) $destinationY,
|
||||
(int) $cropOriginX,
|
||||
@ -107,12 +98,12 @@ class Resize extends AbstractTransform
|
||||
return $destinationImage;
|
||||
}
|
||||
|
||||
protected function enableTransparency(GdImage $image): void
|
||||
protected function enableTransparency(GdImage $gdImage): void
|
||||
{
|
||||
$transparent = imagecolorallocatealpha($image, 0, 0, 0, 127);
|
||||
imagealphablending($image, true);
|
||||
imagesavealpha($image, true);
|
||||
imagecolortransparent($image, $transparent);
|
||||
imagefill($image, 0, 0, $transparent);
|
||||
$transparent = imagecolorallocatealpha($gdImage, 0, 0, 0, 127);
|
||||
imagealphablending($gdImage, true);
|
||||
imagesavealpha($gdImage, true);
|
||||
imagecolortransparent($gdImage, $transparent);
|
||||
imagefill($gdImage, 0, 0, $transparent);
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,8 @@ use GdImage;
|
||||
|
||||
class Rotate extends AbstractTransform
|
||||
{
|
||||
protected float $angle;
|
||||
|
||||
final public function __construct(float $angle)
|
||||
final public function __construct(protected float $angle)
|
||||
{
|
||||
$this->angle = $angle;
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
@ -19,10 +16,9 @@ class Rotate extends AbstractTransform
|
||||
return new static($data['angle']);
|
||||
}
|
||||
|
||||
public function apply(GdImage $image, ImageInfo $info): GdImage
|
||||
public function apply(GdImage $gdImage, ImageInfo $imageInfo): GdImage
|
||||
{
|
||||
$backgroundColor = imagecolorallocatealpha($image, 0, 0, 0, 127);
|
||||
$image = imagerotate($image, $this->angle, $backgroundColor);
|
||||
return $image;
|
||||
$backgroundColor = imagecolorallocatealpha($gdImage, 0, 0, 0, 127);
|
||||
return imagerotate($gdImage, $this->angle, $backgroundColor);
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,8 @@ use GdImage;
|
||||
|
||||
class Scale extends AbstractTransform
|
||||
{
|
||||
protected float $factor;
|
||||
|
||||
final public function __construct(float $factor)
|
||||
final public function __construct(protected float $factor)
|
||||
{
|
||||
$this->factor = $factor;
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
@ -19,9 +16,9 @@ class Scale extends AbstractTransform
|
||||
return new static($data['factor']);
|
||||
}
|
||||
|
||||
public function apply(GdImage $image, ImageInfo $info): GdImage
|
||||
public function apply(GdImage $gdImage, ImageInfo $imageInfo): GdImage
|
||||
{
|
||||
$resize = new Resize((int) floor(imagesx($image) * $this->factor), (int) floor(imagesy($image) * $this->factor));
|
||||
return $resize->apply($image, $info);
|
||||
$resize = new Resize((int) floor(imagesx($gdImage) * $this->factor), (int) floor(imagesy($gdImage) * $this->factor));
|
||||
return $resize->apply($gdImage, $imageInfo);
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,9 @@ class Sharpen extends AbstractTransform
|
||||
return new static();
|
||||
}
|
||||
|
||||
public function apply(GdImage $image, ImageInfo $info): GdImage
|
||||
public function apply(GdImage $gdImage, ImageInfo $imageInfo): GdImage
|
||||
{
|
||||
imagefilter($image, IMG_FILTER_MEAN_REMOVAL);
|
||||
return $image;
|
||||
imagefilter($gdImage, IMG_FILTER_MEAN_REMOVAL);
|
||||
return $gdImage;
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,9 @@ class Smoothen extends AbstractTransform
|
||||
return new static();
|
||||
}
|
||||
|
||||
public function apply(GdImage $image, ImageInfo $info): GdImage
|
||||
public function apply(GdImage $gdImage, ImageInfo $imageInfo): GdImage
|
||||
{
|
||||
imagefilter($image, IMG_FILTER_SMOOTH);
|
||||
return $image;
|
||||
imagefilter($gdImage, IMG_FILTER_SMOOTH);
|
||||
return $gdImage;
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use GdImage;
|
||||
|
||||
interface TransformInterface extends ArraySerializable
|
||||
{
|
||||
public function apply(GdImage $image, ImageInfo $info): GdImage;
|
||||
public function apply(GdImage $gdImage, ImageInfo $imageInfo): GdImage;
|
||||
|
||||
public function getSpecifier(): string;
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ class Interpolator
|
||||
*/
|
||||
public static function interpolate(string $string, array $vars): mixed
|
||||
{
|
||||
$interpolator = new NodeInterpolator(Parser::parseTokenStream(Tokenizer::tokenizeString($string)), $vars);
|
||||
return $interpolator->interpolate();
|
||||
$nodeInterpolator = new NodeInterpolator(Parser::parseTokenStream(Tokenizer::tokenizeString($string)), $vars);
|
||||
return $nodeInterpolator->interpolate();
|
||||
}
|
||||
}
|
||||
|
@ -15,20 +15,11 @@ use InvalidArgumentException;
|
||||
|
||||
class NodeInterpolator
|
||||
{
|
||||
protected AbstractNode $node;
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected array $vars;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $vars
|
||||
*/
|
||||
public function __construct(AbstractNode $node, array $vars)
|
||||
public function __construct(protected AbstractNode $node, protected array $vars)
|
||||
{
|
||||
$this->node = $node;
|
||||
$this->vars = $vars;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,16 +54,16 @@ class NodeInterpolator
|
||||
*
|
||||
* @param array<mixed>|object|null $parent
|
||||
*/
|
||||
protected function interpolateIdentifierNode(IdentifierNode $node, array|object|null $parent = null): mixed
|
||||
protected function interpolateIdentifierNode(IdentifierNode $identifierNode, array|object|null $parent = null): mixed
|
||||
{
|
||||
$name = $node->value();
|
||||
$name = $identifierNode->value();
|
||||
|
||||
$arguments = [];
|
||||
|
||||
$traverse = $node->traverse();
|
||||
$traverse = $identifierNode->traverse();
|
||||
|
||||
if ($node->arguments() !== null) {
|
||||
foreach ($node->arguments()->value() as $argument) {
|
||||
if ($identifierNode->arguments() !== null) {
|
||||
foreach ($identifierNode->arguments()->value() as $argument) {
|
||||
$arguments[] = $this->interpolateNode($argument);
|
||||
}
|
||||
}
|
||||
@ -82,40 +73,38 @@ class NodeInterpolator
|
||||
throw new InterpolationException(sprintf('Undefined variable "%s"', $name));
|
||||
}
|
||||
$value = $this->vars[$name];
|
||||
} else {
|
||||
if (is_array($parent)) {
|
||||
if (!array_key_exists($name, $parent)) {
|
||||
throw new InterpolationException(sprintf('Undefined array key "%s"', $name));
|
||||
}
|
||||
$value = $parent[$name];
|
||||
} elseif (is_object($parent)) {
|
||||
switch (true) {
|
||||
case method_exists($parent, $name):
|
||||
$value = $parent->{$name}(...$arguments);
|
||||
break;
|
||||
|
||||
case is_callable([$parent, '__call']):
|
||||
$value = $parent->__call($name, $arguments);
|
||||
break;
|
||||
|
||||
case property_exists($parent, $name) && $node->arguments() === null:
|
||||
$value = $parent->{$name};
|
||||
break;
|
||||
|
||||
case is_callable([$parent, '__get']) && $node->arguments() === null:
|
||||
$value = $parent->__get($name);
|
||||
break;
|
||||
|
||||
case defined($parent::class . '::' . $name) && $node->arguments() === null:
|
||||
$value = constant($parent::class . '::' . $name);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InterpolationException(sprintf('Undefined class method, property or constant %s::%s', $parent::class, $name));
|
||||
}
|
||||
} else {
|
||||
throw new InvalidArgumentException(sprintf('%s() accepts only arrays and objects as $parent argument', __METHOD__));
|
||||
} elseif (is_array($parent)) {
|
||||
if (!array_key_exists($name, $parent)) {
|
||||
throw new InterpolationException(sprintf('Undefined array key "%s"', $name));
|
||||
}
|
||||
$value = $parent[$name];
|
||||
} elseif (is_object($parent)) {
|
||||
switch (true) {
|
||||
case method_exists($parent, $name):
|
||||
$value = $parent->{$name}(...$arguments);
|
||||
break;
|
||||
|
||||
case is_callable([$parent, '__call']):
|
||||
$value = $parent->__call($name, $arguments);
|
||||
break;
|
||||
|
||||
case property_exists($parent, $name) && $identifierNode->arguments() === null:
|
||||
$value = $parent->{$name};
|
||||
break;
|
||||
|
||||
case is_callable([$parent, '__get']) && $identifierNode->arguments() === null:
|
||||
$value = $parent->__get($name);
|
||||
break;
|
||||
|
||||
case defined($parent::class . '::' . $name) && $identifierNode->arguments() === null:
|
||||
$value = constant($parent::class . '::' . $name);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InterpolationException(sprintf('Undefined class method, property or constant %s::%s', $parent::class, $name));
|
||||
}
|
||||
} else {
|
||||
throw new InvalidArgumentException(sprintf('%s() accepts only arrays and objects as $parent argument', __METHOD__));
|
||||
}
|
||||
|
||||
if ($traverse !== null) {
|
||||
@ -146,7 +135,7 @@ class NodeInterpolator
|
||||
}
|
||||
|
||||
// Call closures if arguments (zero or more) are given
|
||||
if ($value instanceof Closure && $node->arguments() !== null) {
|
||||
if ($value instanceof Closure && $identifierNode->arguments() !== null) {
|
||||
return $value(...$arguments);
|
||||
}
|
||||
|
||||
@ -158,12 +147,12 @@ class NodeInterpolator
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
protected function interpolateArrayNode(ArrayNode $node): array
|
||||
protected function interpolateArrayNode(ArrayNode $arrayNode): array
|
||||
{
|
||||
$result = [];
|
||||
$keys = $this->interpolateArrayKeysNode($node->keys());
|
||||
$keys = $this->interpolateArrayKeysNode($arrayNode->keys());
|
||||
|
||||
foreach ($node->value() as $i => $value) {
|
||||
foreach ($arrayNode->value() as $i => $value) {
|
||||
$key = $keys[$i];
|
||||
$result[$key] = $this->interpolateNode($value);
|
||||
}
|
||||
@ -176,13 +165,13 @@ class NodeInterpolator
|
||||
*
|
||||
* @return list<array-key>
|
||||
*/
|
||||
protected function interpolateArrayKeysNode(ArrayKeysNode $node): array
|
||||
protected function interpolateArrayKeysNode(ArrayKeysNode $arrayKeysNode): array
|
||||
{
|
||||
$offset = -1;
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($node->value() as $key) {
|
||||
foreach ($arrayKeysNode->value() as $key) {
|
||||
switch ($key->type()) {
|
||||
case ImplicitArrayKeyNode::TYPE:
|
||||
$offset++;
|
||||
@ -222,14 +211,12 @@ class NodeInterpolator
|
||||
protected function validateArrayKey(mixed $key): int|string
|
||||
{
|
||||
switch (true) {
|
||||
case is_int($key):
|
||||
return $key;
|
||||
|
||||
case is_bool($key):
|
||||
case is_float($key):
|
||||
case is_string($key) && ctype_digit($key) && $key[0] !== '0':
|
||||
return (int) $key;
|
||||
|
||||
case is_int($key):
|
||||
case is_string($key):
|
||||
return $key;
|
||||
|
||||
|
@ -2,7 +2,9 @@
|
||||
|
||||
namespace Formwork\Interpolator\Nodes;
|
||||
|
||||
abstract class AbstractNode
|
||||
use Stringable;
|
||||
|
||||
abstract class AbstractNode implements Stringable
|
||||
{
|
||||
/**
|
||||
* Node type
|
||||
@ -14,7 +16,7 @@ abstract class AbstractNode
|
||||
*/
|
||||
protected mixed $value;
|
||||
|
||||
public function __toString()
|
||||
public function __toString(): string
|
||||
{
|
||||
return 'node of type ' . static::TYPE;
|
||||
}
|
||||
|
@ -9,15 +9,12 @@ class ArrayNode extends AbstractNode
|
||||
*/
|
||||
public const TYPE = 'array';
|
||||
|
||||
protected ArrayKeysNode $keys;
|
||||
|
||||
/**
|
||||
* @param list<mixed> $value
|
||||
*/
|
||||
public function __construct(array $value, ArrayKeysNode $keys)
|
||||
public function __construct(array $value, protected ArrayKeysNode $arrayKeysNode)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->keys = $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,6 +22,6 @@ class ArrayNode extends AbstractNode
|
||||
*/
|
||||
public function keys(): ArrayKeysNode
|
||||
{
|
||||
return $this->keys;
|
||||
return $this->arrayKeysNode;
|
||||
}
|
||||
}
|
||||
|
@ -9,21 +9,9 @@ class IdentifierNode extends AbstractNode
|
||||
*/
|
||||
public const TYPE = 'identifier';
|
||||
|
||||
/**
|
||||
* Node arguments
|
||||
*/
|
||||
protected ?ArgumentsNode $arguments;
|
||||
|
||||
/**
|
||||
* Node used to traverse
|
||||
*/
|
||||
protected ?AbstractNode $traverse;
|
||||
|
||||
public function __construct(string $value, ?ArgumentsNode $arguments, ?AbstractNode $traverse)
|
||||
public function __construct(string $value, protected ?ArgumentsNode $argumentsNode, protected ?AbstractNode $node)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->arguments = $arguments;
|
||||
$this->traverse = $traverse;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -31,7 +19,7 @@ class IdentifierNode extends AbstractNode
|
||||
*/
|
||||
public function arguments(): ?ArgumentsNode
|
||||
{
|
||||
return $this->arguments;
|
||||
return $this->argumentsNode;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,6 +27,6 @@ class IdentifierNode extends AbstractNode
|
||||
*/
|
||||
public function traverse(): ?AbstractNode
|
||||
{
|
||||
return $this->traverse;
|
||||
return $this->node;
|
||||
}
|
||||
}
|
||||
|
@ -14,11 +14,8 @@ use Formwork\Interpolator\Nodes\StringNode;
|
||||
|
||||
class Parser implements ParserInterface
|
||||
{
|
||||
protected TokenStream $stream;
|
||||
|
||||
public function __construct(TokenStream $stream)
|
||||
public function __construct(protected TokenStream $tokenStream)
|
||||
{
|
||||
$this->stream = $stream;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -26,18 +23,18 @@ class Parser implements ParserInterface
|
||||
*/
|
||||
public function parse(): AbstractNode
|
||||
{
|
||||
$node = $this->parseIdentifierToken();
|
||||
$this->stream->expectEnd();
|
||||
return $node;
|
||||
$identifierNode = $this->parseIdentifierToken();
|
||||
$this->tokenStream->expectEnd();
|
||||
return $identifierNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a given TokenStream object
|
||||
*/
|
||||
public static function parseTokenStream(TokenStream $stream): AbstractNode
|
||||
public static function parseTokenStream(TokenStream $tokenStream): AbstractNode
|
||||
{
|
||||
$parser = new static($stream);
|
||||
return $parser->parse();
|
||||
$static = new static($tokenStream);
|
||||
return $static->parse();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -45,21 +42,21 @@ class Parser implements ParserInterface
|
||||
*/
|
||||
protected function parseIdentifierToken(): IdentifierNode
|
||||
{
|
||||
$token = $this->stream->expect(Token::TYPE_IDENTIFIER);
|
||||
$token = $this->tokenStream->expect(Token::TYPE_IDENTIFIER);
|
||||
|
||||
$traverse = null;
|
||||
|
||||
$arguments = null;
|
||||
|
||||
if ($this->stream->current()->test(Token::TYPE_PUNCTUATION, '(')) {
|
||||
if ($this->tokenStream->current()->test(Token::TYPE_PUNCTUATION, '(')) {
|
||||
$arguments = $this->parseArguments();
|
||||
}
|
||||
|
||||
if ($this->stream->current()->test(Token::TYPE_PUNCTUATION, '.')) {
|
||||
if ($this->tokenStream->current()->test(Token::TYPE_PUNCTUATION, '.')) {
|
||||
$traverse = $this->parseDotNotation();
|
||||
}
|
||||
|
||||
if ($this->stream->current()->test(Token::TYPE_PUNCTUATION, '[')) {
|
||||
if ($this->tokenStream->current()->test(Token::TYPE_PUNCTUATION, '[')) {
|
||||
$traverse = $this->parseBracketsNotation();
|
||||
}
|
||||
|
||||
@ -72,7 +69,7 @@ class Parser implements ParserInterface
|
||||
*/
|
||||
protected function parseNumberToken(): NumberNode
|
||||
{
|
||||
$token = $this->stream->expect(Token::TYPE_NUMBER);
|
||||
$token = $this->tokenStream->expect(Token::TYPE_NUMBER);
|
||||
// @phpstan-ignore-next-line
|
||||
return new NumberNode($token->value() + 0);
|
||||
}
|
||||
@ -82,7 +79,7 @@ class Parser implements ParserInterface
|
||||
*/
|
||||
protected function parseStringToken(): StringNode
|
||||
{
|
||||
$token = $this->stream->expect(Token::TYPE_STRING);
|
||||
$token = $this->tokenStream->expect(Token::TYPE_STRING);
|
||||
// @phpstan-ignore-next-line
|
||||
return new StringNode(stripcslashes(trim($token->value(), '\'"')));
|
||||
}
|
||||
@ -92,7 +89,7 @@ class Parser implements ParserInterface
|
||||
*/
|
||||
protected function parseDotNotation(): IdentifierNode
|
||||
{
|
||||
$this->stream->expect(Token::TYPE_PUNCTUATION, '.');
|
||||
$this->tokenStream->expect(Token::TYPE_PUNCTUATION, '.');
|
||||
return $this->parseIdentifierToken();
|
||||
}
|
||||
|
||||
@ -101,24 +98,17 @@ class Parser implements ParserInterface
|
||||
*/
|
||||
protected function parseBracketsNotation(): AbstractNode
|
||||
{
|
||||
$this->stream->expect(Token::TYPE_PUNCTUATION, '[');
|
||||
$this->tokenStream->expect(Token::TYPE_PUNCTUATION, '[');
|
||||
|
||||
$token = $this->stream->current();
|
||||
$token = $this->tokenStream->current();
|
||||
|
||||
switch ($token->type()) {
|
||||
case Token::TYPE_NUMBER:
|
||||
$key = $this->parseNumberToken();
|
||||
break;
|
||||
$key = match ($token->type()) {
|
||||
Token::TYPE_NUMBER => $this->parseNumberToken(),
|
||||
Token::TYPE_STRING => $this->parseStringToken(),
|
||||
default => throw new SyntaxError(sprintf('Unexpected %s at position %d', $token, $token->position())),
|
||||
};
|
||||
|
||||
case Token::TYPE_STRING:
|
||||
$key = $this->parseStringToken();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new SyntaxError(sprintf('Unexpected %s at position %d', $token, $token->position()));
|
||||
}
|
||||
|
||||
$this->stream->expect(Token::TYPE_PUNCTUATION, ']');
|
||||
$this->tokenStream->expect(Token::TYPE_PUNCTUATION, ']');
|
||||
|
||||
return $key;
|
||||
}
|
||||
@ -128,18 +118,18 @@ class Parser implements ParserInterface
|
||||
*/
|
||||
protected function parseArguments(): ArgumentsNode
|
||||
{
|
||||
$this->stream->expect(Token::TYPE_PUNCTUATION, '(');
|
||||
$this->tokenStream->expect(Token::TYPE_PUNCTUATION, '(');
|
||||
|
||||
$arguments = [];
|
||||
|
||||
while (!$this->stream->current()->test(Token::TYPE_PUNCTUATION, ')')) {
|
||||
while (!$this->tokenStream->current()->test(Token::TYPE_PUNCTUATION, ')')) {
|
||||
if ($arguments !== []) {
|
||||
$this->stream->expect(Token::TYPE_PUNCTUATION, ',');
|
||||
$this->tokenStream->expect(Token::TYPE_PUNCTUATION, ',');
|
||||
}
|
||||
$arguments[] = $this->parseExpression();
|
||||
}
|
||||
|
||||
$this->stream->expect(Token::TYPE_PUNCTUATION, ')');
|
||||
$this->tokenStream->expect(Token::TYPE_PUNCTUATION, ')');
|
||||
|
||||
return new ArgumentsNode($arguments);
|
||||
}
|
||||
@ -149,7 +139,7 @@ class Parser implements ParserInterface
|
||||
*/
|
||||
protected function parseExpression(): AbstractNode
|
||||
{
|
||||
$token = $this->stream->current();
|
||||
$token = $this->tokenStream->current();
|
||||
|
||||
switch ($token->type()) {
|
||||
case Token::TYPE_IDENTIFIER:
|
||||
@ -177,21 +167,21 @@ class Parser implements ParserInterface
|
||||
*/
|
||||
protected function parseArrayExpression(): ArrayNode
|
||||
{
|
||||
$this->stream->expect(Token::TYPE_PUNCTUATION, '[');
|
||||
$this->tokenStream->expect(Token::TYPE_PUNCTUATION, '[');
|
||||
|
||||
$elements = [];
|
||||
|
||||
$keys = [];
|
||||
|
||||
while (!$this->stream->current()->test(Token::TYPE_PUNCTUATION, ']')) {
|
||||
while (!$this->tokenStream->current()->test(Token::TYPE_PUNCTUATION, ']')) {
|
||||
if ($elements !== []) {
|
||||
$this->stream->expect(Token::TYPE_PUNCTUATION, ',');
|
||||
$this->tokenStream->expect(Token::TYPE_PUNCTUATION, ',');
|
||||
}
|
||||
|
||||
$value = $this->parseExpression();
|
||||
|
||||
if ($this->stream->current()->test(Token::TYPE_ARROW)) {
|
||||
$arrow = $this->stream->consume();
|
||||
if ($this->tokenStream->current()->test(Token::TYPE_ARROW)) {
|
||||
$arrow = $this->tokenStream->consume();
|
||||
|
||||
if ($value->type() === ArrayNode::TYPE) {
|
||||
throw new SyntaxError(sprintf('Unexpected %s at position %d', $arrow, $arrow->position()));
|
||||
@ -207,7 +197,7 @@ class Parser implements ParserInterface
|
||||
$keys[] = $key;
|
||||
}
|
||||
|
||||
$this->stream->expect(Token::TYPE_PUNCTUATION, ']');
|
||||
$this->tokenStream->expect(Token::TYPE_PUNCTUATION, ']');
|
||||
|
||||
return new ArrayNode($elements, new ArrayKeysNode($keys));
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use Formwork\Interpolator\Nodes\AbstractNode;
|
||||
|
||||
interface ParserInterface
|
||||
{
|
||||
public function __construct(TokenStream $stream);
|
||||
public function __construct(TokenStream $tokenStream);
|
||||
|
||||
/**
|
||||
* Parse the tokens
|
||||
@ -16,5 +16,5 @@ interface ParserInterface
|
||||
/**
|
||||
* Parse a given TokenStream object
|
||||
*/
|
||||
public static function parseTokenStream(TokenStream $stream): AbstractNode;
|
||||
public static function parseTokenStream(TokenStream $tokenStream): AbstractNode;
|
||||
}
|
||||
|
@ -2,7 +2,9 @@
|
||||
|
||||
namespace Formwork\Interpolator;
|
||||
|
||||
class Token
|
||||
use Stringable;
|
||||
|
||||
class Token implements Stringable
|
||||
{
|
||||
/**
|
||||
* Identifier token type
|
||||
@ -34,29 +36,11 @@ class Token
|
||||
*/
|
||||
public const TYPE_END = 'end';
|
||||
|
||||
/**
|
||||
* Token type
|
||||
*/
|
||||
protected string $type;
|
||||
|
||||
/**
|
||||
* Token value
|
||||
*/
|
||||
protected ?string $value;
|
||||
|
||||
/**
|
||||
* Token position
|
||||
*/
|
||||
protected int $position;
|
||||
|
||||
public function __construct(string $type, ?string $value, int $position)
|
||||
public function __construct(protected string $type, protected ?string $value, protected int $position)
|
||||
{
|
||||
$this->type = $type;
|
||||
$this->value = $value;
|
||||
$this->position = $position;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
public function __toString(): string
|
||||
{
|
||||
return sprintf(
|
||||
'token%s of type %s',
|
||||
|
@ -6,13 +6,6 @@ use Formwork\Interpolator\Errors\SyntaxError;
|
||||
|
||||
class TokenStream
|
||||
{
|
||||
/**
|
||||
* Array containing tokens
|
||||
*
|
||||
* @var list<Token>
|
||||
*/
|
||||
protected array $tokens;
|
||||
|
||||
/**
|
||||
* Pointer to the current token
|
||||
*/
|
||||
@ -26,9 +19,8 @@ class TokenStream
|
||||
/**
|
||||
* @param list<Token> $tokens
|
||||
*/
|
||||
public function __construct(array $tokens)
|
||||
public function __construct(protected array $tokens)
|
||||
{
|
||||
$this->tokens = $tokens;
|
||||
$this->count = count($tokens);
|
||||
}
|
||||
|
||||
|
@ -36,11 +36,6 @@ class Tokenizer implements TokenizerInterface
|
||||
*/
|
||||
protected const ARROW_SEQUENCE = '=>';
|
||||
|
||||
/**
|
||||
* Tokenizer input string
|
||||
*/
|
||||
protected string $input;
|
||||
|
||||
/**
|
||||
* Tokenizer input length
|
||||
*/
|
||||
@ -51,9 +46,8 @@ class Tokenizer implements TokenizerInterface
|
||||
*/
|
||||
protected int $position = 0;
|
||||
|
||||
public function __construct(string $input)
|
||||
public function __construct(protected string $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->length = strlen($input);
|
||||
}
|
||||
|
||||
@ -114,7 +108,7 @@ class Tokenizer implements TokenizerInterface
|
||||
*/
|
||||
public static function tokenizeString(string $string): TokenStream
|
||||
{
|
||||
$tokenizer = new static($string);
|
||||
return $tokenizer->tokenize();
|
||||
$static = new static($string);
|
||||
return $static->tokenize();
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,10 @@
|
||||
|
||||
namespace Formwork\Languages;
|
||||
|
||||
class Language
|
||||
{
|
||||
/**
|
||||
* Language code
|
||||
*/
|
||||
protected string $code;
|
||||
use Stringable;
|
||||
|
||||
class Language implements Stringable
|
||||
{
|
||||
/**
|
||||
* Language name (in English)
|
||||
*/
|
||||
@ -19,10 +16,8 @@ class Language
|
||||
*/
|
||||
protected ?string $nativeName = null;
|
||||
|
||||
public function __construct(string $code)
|
||||
public function __construct(protected string $code)
|
||||
{
|
||||
$this->code = $code;
|
||||
|
||||
if (LanguageCodes::hasCode($code)) {
|
||||
$this->name = LanguageCodes::codeToName($code);
|
||||
$this->nativeName = LanguageCodes::codeToNativeName($code);
|
||||
|
@ -100,15 +100,12 @@ class Languages
|
||||
*/
|
||||
protected function resolveLanguage(Language|string|null $language): ?Language
|
||||
{
|
||||
switch (true) {
|
||||
case $language instanceof Language:
|
||||
return $language;
|
||||
|
||||
case is_string($language):
|
||||
return $this->available->get($language, null);
|
||||
|
||||
default:
|
||||
return null;
|
||||
if ($language instanceof Language) {
|
||||
return $language;
|
||||
}
|
||||
if (is_string($language)) {
|
||||
return $this->available->get($language, null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -4,18 +4,12 @@ namespace Formwork\Log;
|
||||
|
||||
class Log extends Registry
|
||||
{
|
||||
/**
|
||||
* Limit of registry entries
|
||||
*/
|
||||
protected int $limit;
|
||||
|
||||
/**
|
||||
* Create a new Log instance
|
||||
*/
|
||||
public function __construct(string $filename, int $limit = 128)
|
||||
public function __construct(string $filename, protected int $limit = 128)
|
||||
{
|
||||
parent::__construct($filename);
|
||||
$this->limit = $limit;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,11 +15,6 @@ class Registry
|
||||
*/
|
||||
protected array $storage = [];
|
||||
|
||||
/**
|
||||
* Registry filename
|
||||
*/
|
||||
protected string $filename;
|
||||
|
||||
/**
|
||||
* Whether the registry is saved
|
||||
*/
|
||||
@ -28,9 +23,8 @@ class Registry
|
||||
/**
|
||||
* Create a new Registry instance
|
||||
*/
|
||||
public function __construct(string $filename)
|
||||
public function __construct(protected string $filename)
|
||||
{
|
||||
$this->filename = $filename;
|
||||
if (FileSystem::exists($this->filename)) {
|
||||
$this->storage = Json::parseFile($filename);
|
||||
$this->saved = true;
|
||||
|
@ -3,8 +3,9 @@
|
||||
namespace Formwork\Metadata;
|
||||
|
||||
use Formwork\Utils\Str;
|
||||
use Stringable;
|
||||
|
||||
class Metadata
|
||||
class Metadata implements Stringable
|
||||
{
|
||||
protected const HTTP_EQUIV_NAMES = ['content-type', 'default-style', 'refresh'];
|
||||
|
||||
@ -13,11 +14,6 @@ class Metadata
|
||||
*/
|
||||
protected string $name;
|
||||
|
||||
/**
|
||||
* Metadata content
|
||||
*/
|
||||
protected string $content;
|
||||
|
||||
/**
|
||||
* Metadata prefix
|
||||
*/
|
||||
@ -26,14 +22,14 @@ class Metadata
|
||||
/**
|
||||
* Create a new Metadata instance
|
||||
*/
|
||||
public function __construct(string $name, string $content)
|
||||
public function __construct(string $name, protected string $content)
|
||||
{
|
||||
$this->name = strtolower($name);
|
||||
$this->content = $content;
|
||||
|
||||
if ($prefix = Str::before($name, ':')) {
|
||||
$this->prefix = $prefix;
|
||||
if (($prefix = Str::before($name, ':')) === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
|
@ -26,9 +26,10 @@ use Formwork\Utils\Uri;
|
||||
use InvalidArgumentException;
|
||||
use ReflectionClass;
|
||||
use RuntimeException;
|
||||
use Stringable;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class Page implements Arrayable
|
||||
class Page implements Arrayable, Stringable
|
||||
{
|
||||
use PageData;
|
||||
use PageStatus;
|
||||
@ -147,7 +148,7 @@ class Page implements Arrayable
|
||||
|
||||
$this->loadFiles();
|
||||
|
||||
if ($this->contentFile && !$this->contentFile->isEmpty()) {
|
||||
if ($this->contentFile instanceof ContentFile && !$this->contentFile->isEmpty()) {
|
||||
$this->data = [
|
||||
...$this->data,
|
||||
...$this->contentFile->frontmatter(),
|
||||
@ -162,7 +163,7 @@ class Page implements Arrayable
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->title() ?? $this->slug();
|
||||
return (string) ($this->title() ?? $this->slug());
|
||||
}
|
||||
|
||||
public function site(): Site
|
||||
@ -250,13 +251,9 @@ class Page implements Arrayable
|
||||
*/
|
||||
public function canonicalRoute(): ?string
|
||||
{
|
||||
if (isset($this->canonicalRoute)) {
|
||||
return $this->canonicalRoute;
|
||||
}
|
||||
|
||||
return $this->canonicalRoute = !empty($this->data['canonicalRoute'])
|
||||
? Path::normalize($this->data['canonicalRoute'])
|
||||
: null;
|
||||
return $this->canonicalRoute ?? ($this->canonicalRoute = empty($this->data['canonicalRoute'])
|
||||
? null
|
||||
: Path::normalize($this->data['canonicalRoute']));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -272,7 +269,7 @@ class Page implements Arrayable
|
||||
*/
|
||||
public function num(): ?int
|
||||
{
|
||||
if (isset($this->num)) {
|
||||
if ($this->num !== null) {
|
||||
return $this->num;
|
||||
}
|
||||
|
||||
@ -356,7 +353,7 @@ class Page implements Arrayable
|
||||
|
||||
// Get a default 404 Not Found status for the error page
|
||||
if ($this->isErrorPage() && $this->responseStatus() === ResponseStatus::OK
|
||||
&& !isset($this->contentFile, $this->contentFile->frontmatter()['responseStatus'])) {
|
||||
&& $this->contentFile === null) {
|
||||
$this->responseStatus = ResponseStatus::NotFound;
|
||||
}
|
||||
|
||||
@ -522,7 +519,7 @@ class Page implements Arrayable
|
||||
|
||||
$site = $this->site;
|
||||
|
||||
if (isset($this->path) && FileSystem::isDirectory($this->path, assertExists: false)) {
|
||||
if ($this->path !== null && FileSystem::isDirectory($this->path, assertExists: false)) {
|
||||
foreach (FileSystem::listFiles($this->path) as $file) {
|
||||
$name = FileSystem::name($file);
|
||||
|
||||
@ -567,7 +564,7 @@ class Page implements Arrayable
|
||||
: array_keys($contentFiles)[0];
|
||||
|
||||
// Set actual language
|
||||
$this->language ??= $key ? new Language($key) : null;
|
||||
$this->language ??= $key !== '' ? new Language($key) : null;
|
||||
|
||||
$this->contentFile ??= new ContentFile($contentFiles[$key]['path']);
|
||||
|
||||
@ -627,11 +624,11 @@ class Page implements Arrayable
|
||||
{
|
||||
$reflectionClass = new ReflectionClass($this);
|
||||
|
||||
foreach ($reflectionClass->getProperties() as $property) {
|
||||
unset($this->{$property->getName()});
|
||||
foreach ($reflectionClass->getProperties() as $reflectionProperty) {
|
||||
unset($this->{$reflectionProperty->getName()});
|
||||
|
||||
if ($property->hasDefaultValue()) {
|
||||
$this->{$property->getName()} = $property->getDefaultValue();
|
||||
if ($reflectionProperty->hasDefaultValue()) {
|
||||
$this->{$reflectionProperty->getName()} = $reflectionProperty->getDefaultValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ class PageCollection extends AbstractCollection implements Paginable
|
||||
$value = Str::removeHTML((string) $page->get($key));
|
||||
|
||||
$queryMatches = preg_match_all($queryRegex, $value);
|
||||
$keywordsMatches = empty($keywords) ? 0 : preg_match_all($keywordsRegex, $value);
|
||||
$keywordsMatches = $keywords === [] ? 0 : preg_match_all($keywordsRegex, $value);
|
||||
|
||||
$score += ($queryMatches * 2 + min($keywordsMatches, 3)) * $scores[$key];
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ class Pagination extends BasePagination
|
||||
{
|
||||
use PaginationUri;
|
||||
|
||||
public function __construct(PageCollection $collection, int $length)
|
||||
public function __construct(PageCollection $pageCollection, int $length)
|
||||
{
|
||||
parent::__construct($collection, $length);
|
||||
parent::__construct($pageCollection, $length);
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,9 @@ use Formwork\Schemes\Scheme;
|
||||
use Formwork\Schemes\Schemes;
|
||||
use Formwork\Utils\Arr;
|
||||
use Formwork\Utils\FileSystem;
|
||||
use Stringable;
|
||||
|
||||
class Site implements Arrayable
|
||||
class Site implements Arrayable, Stringable
|
||||
{
|
||||
use PageData;
|
||||
use PageTraversal;
|
||||
@ -116,9 +117,9 @@ class Site implements Arrayable
|
||||
$this->setMultiple($data);
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->title();
|
||||
return (string) $this->title();
|
||||
}
|
||||
|
||||
public function site(): Site
|
||||
@ -308,10 +309,7 @@ class Site implements Arrayable
|
||||
*/
|
||||
public function retrievePage(string $path): Page
|
||||
{
|
||||
if (isset($this->storage[$path])) {
|
||||
return $this->storage[$path];
|
||||
}
|
||||
return $this->storage[$path] = new Page(['site' => $this, 'path' => $path]);
|
||||
return $this->storage[$path] ?? ($this->storage[$path] = new Page(['site' => $this, 'path' => $path]));
|
||||
}
|
||||
|
||||
public function retrievePages(string $path, bool $recursive = false): PageCollection
|
||||
@ -372,9 +370,7 @@ class Site implements Arrayable
|
||||
}
|
||||
}
|
||||
|
||||
$page = $this->retrievePage($path);
|
||||
|
||||
return $page;
|
||||
return $this->retrievePage($path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,8 +11,9 @@ use Formwork\Utils\FileSystem;
|
||||
use Formwork\View\Exceptions\RenderingException;
|
||||
use Formwork\View\Renderer;
|
||||
use Formwork\View\View;
|
||||
use Stringable;
|
||||
|
||||
class Template extends View
|
||||
class Template extends View implements Stringable
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
@ -47,13 +48,10 @@ class Template extends View
|
||||
*/
|
||||
public function assets(): Assets
|
||||
{
|
||||
if (isset($this->assets)) {
|
||||
return $this->assets;
|
||||
}
|
||||
return $this->assets = new Assets(
|
||||
return $this->assets ?? ($this->assets = new Assets(
|
||||
$this->path() . 'assets/',
|
||||
$this->site->uri('/site/templates/assets/', includeLanguage: false)
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,9 +30,13 @@ trait PageData
|
||||
*/
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return (property_exists($this, $key) && !(new ReflectionProperty($this, $key))->isPromoted())
|
||||
|| $this->fields->has($key)
|
||||
|| Arr::has($this->data, $key);
|
||||
if (property_exists($this, $key) && !(new ReflectionProperty($this, $key))->isPromoted()) {
|
||||
return true;
|
||||
}
|
||||
if ($this->fields->has($key)) {
|
||||
return true;
|
||||
}
|
||||
return Arr::has($this->data, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -201,11 +201,7 @@ trait PageTraversal
|
||||
*/
|
||||
public function siblings(): PageCollection
|
||||
{
|
||||
if (isset($this->siblings)) {
|
||||
return $this->siblings;
|
||||
}
|
||||
|
||||
return $this->siblings = $this->inclusiveSiblings()->without($this);
|
||||
return $this->siblings ?? ($this->siblings = $this->inclusiveSiblings()->without($this));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,8 +133,8 @@ trait PaginationUri
|
||||
|
||||
$router = App::instance()->router();
|
||||
|
||||
if ($router->current() === null) {
|
||||
throw new RuntimeException(sprintf('Cannot generate pagination routes, current route is not defined'));
|
||||
if (!$router->current() instanceof Route) {
|
||||
throw new RuntimeException('Cannot generate pagination routes, current route is not defined');
|
||||
}
|
||||
|
||||
$routeName = Str::removeEnd($router->current()->getName(), static::$routeSuffix);
|
||||
|
@ -69,9 +69,9 @@ abstract class AbstractController extends BaseAbstractController
|
||||
return $this->router->generate($name, $params);
|
||||
}
|
||||
|
||||
protected function redirect(string $route, ResponseStatus $status = ResponseStatus::Found): RedirectResponse
|
||||
protected function redirect(string $route, ResponseStatus $responseStatus = ResponseStatus::Found): RedirectResponse
|
||||
{
|
||||
return new RedirectResponse($this->site->uri($route, includeLanguage: false), $status);
|
||||
return new RedirectResponse($this->site->uri($route, includeLanguage: false), $responseStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,12 +79,12 @@ abstract class AbstractController extends BaseAbstractController
|
||||
*
|
||||
* @param string $default Default route if HTTP referer is not available
|
||||
*/
|
||||
protected function redirectToReferer(ResponseStatus $status = ResponseStatus::Found, string $default = '/'): RedirectResponse
|
||||
protected function redirectToReferer(ResponseStatus $responseStatus = ResponseStatus::Found, string $default = '/'): RedirectResponse
|
||||
{
|
||||
if (!in_array($this->request->referer(), [null, Uri::current()], true) && $this->request->validateReferer($this->panel()->uri('/'))) {
|
||||
return new RedirectResponse($this->request->referer(), $status);
|
||||
return new RedirectResponse($this->request->referer(), $responseStatus);
|
||||
}
|
||||
return new RedirectResponse($this->panel()->uri($default), $status);
|
||||
return new RedirectResponse($this->panel()->uri($default), $responseStatus);
|
||||
}
|
||||
|
||||
protected function translate(string $key, int|float|string|Stringable ...$arguments): string
|
||||
|
@ -18,9 +18,9 @@ class AuthenticationController extends AbstractController
|
||||
/**
|
||||
* Authentication@login action
|
||||
*/
|
||||
public function login(Request $request, CsrfToken $csrfToken, AccessLimiter $limiter): Response
|
||||
public function login(Request $request, CsrfToken $csrfToken, AccessLimiter $accessLimiter): Response
|
||||
{
|
||||
if ($limiter->hasReachedLimit()) {
|
||||
if ($accessLimiter->hasReachedLimit()) {
|
||||
$minutes = round($this->config->get('system.panel.loginResetTime') / 60);
|
||||
$csrfToken->generate();
|
||||
return $this->error($this->translate('panel.login.attempt.tooMany', $minutes));
|
||||
@ -51,7 +51,7 @@ class AuthenticationController extends AbstractController
|
||||
$this->error($this->translate('panel.login.attempt.failed'));
|
||||
}
|
||||
|
||||
$limiter->registerAttempt();
|
||||
$accessLimiter->registerAttempt();
|
||||
|
||||
$user = $this->panel()->users()->get($data->get('username'));
|
||||
|
||||
@ -69,7 +69,7 @@ class AuthenticationController extends AbstractController
|
||||
$time = $accessLog->log($data->get('username'));
|
||||
$lastAccessRegistry->set($data->get('username'), $time);
|
||||
|
||||
$limiter->resetAttempts();
|
||||
$accessLimiter->resetAttempts();
|
||||
|
||||
if (($destination = $request->session()->get('FORMWORK_REDIRECT_TO')) !== null) {
|
||||
$request->session()->remove('FORMWORK_REDIRECT_TO');
|
||||
|
@ -36,10 +36,10 @@ class BackupController extends AbstractController
|
||||
/**
|
||||
* Backup@download action
|
||||
*/
|
||||
public function download(RouteParams $params): Response
|
||||
public function download(RouteParams $routeParams): Response
|
||||
{
|
||||
$this->ensurePermission('backup.download');
|
||||
$file = FileSystem::joinPaths($this->config->get('system.backup.path'), basename(base64_decode($params->get('backup'))));
|
||||
$file = FileSystem::joinPaths($this->config->get('system.backup.path'), basename(base64_decode($routeParams->get('backup'))));
|
||||
try {
|
||||
if (FileSystem::isFile($file, assertExists: false)) {
|
||||
return new FileResponse($file, download: true);
|
||||
|
@ -23,10 +23,10 @@ class ErrorsController extends AbstractController
|
||||
/**
|
||||
* Errors@internalServerError action
|
||||
*/
|
||||
public function internalServerError(Throwable $exception): Response
|
||||
public function internalServerError(Throwable $throwable): Response
|
||||
{
|
||||
return $this->makeErrorResponse(ResponseStatus::InternalServerError, 'internalServerError', [
|
||||
'href' => $this->makeGitHubIssueUri($exception),
|
||||
'href' => $this->makeGitHubIssueUri($throwable),
|
||||
'label' => $this->translate('panel.errors.action.reportToGithub'),
|
||||
]);
|
||||
}
|
||||
@ -47,32 +47,32 @@ class ErrorsController extends AbstractController
|
||||
*
|
||||
* @param array<mixed> $action
|
||||
*/
|
||||
protected function makeErrorResponse(ResponseStatus $status, string $name, array $action): Response
|
||||
protected function makeErrorResponse(ResponseStatus $responseStatus, string $name, array $action): Response
|
||||
{
|
||||
Response::cleanOutputBuffers();
|
||||
|
||||
if ($this->request->isXmlHttpRequest()) {
|
||||
return JsonResponse::error('Error', $status);
|
||||
return JsonResponse::error('Error', $responseStatus);
|
||||
}
|
||||
|
||||
return new Response($this->view('errors.error', [
|
||||
'title' => $this->translate('panel.errors.error.' . $name . '.status'),
|
||||
'code' => $status->code(),
|
||||
'code' => $responseStatus->code(),
|
||||
'status' => $this->translate('panel.errors.error.' . $name . '.status'),
|
||||
'heading' => $this->translate('panel.errors.error.' . $name . '.heading'),
|
||||
'description' => $this->translate('panel.errors.error.' . $name . '.description'),
|
||||
'action' => $action,
|
||||
]), $status);
|
||||
]), $responseStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a URI to a new GitHub issue with pre-filled data from an (uncaught) exception
|
||||
*/
|
||||
protected function makeGitHubIssueUri(Throwable $exception): string
|
||||
protected function makeGitHubIssueUri(Throwable $throwable): string
|
||||
{
|
||||
$query = http_build_query([
|
||||
'labels' => 'bug',
|
||||
'title' => $exception->getMessage(),
|
||||
'title' => $throwable->getMessage(),
|
||||
'body' => sprintf(
|
||||
"### Description\n\n[Please enter a description and the steps to reproduce the problem...]\n\n" .
|
||||
"**Formwork**: %s\n**Php**: %s\n**OS**: %s\n**SAPI**: %s\n\n" .
|
||||
@ -81,11 +81,11 @@ class ErrorsController extends AbstractController
|
||||
PHP_VERSION,
|
||||
PHP_OS_FAMILY,
|
||||
PHP_SAPI,
|
||||
$exception::class,
|
||||
$exception->getMessage(),
|
||||
$exception->getFile(),
|
||||
$exception->getLine(),
|
||||
$exception->getTraceAsString()
|
||||
$throwable::class,
|
||||
$throwable->getMessage(),
|
||||
$throwable->getFile(),
|
||||
$throwable->getLine(),
|
||||
$throwable->getTraceAsString()
|
||||
),
|
||||
]);
|
||||
|
||||
|
@ -265,13 +265,13 @@ class OptionsController extends AbstractController
|
||||
* @param array<string, mixed> $options
|
||||
* @param array<string, mixed> $defaults
|
||||
*/
|
||||
protected function updateOptions(string $type, FieldCollection $fields, array $options, array $defaults): bool
|
||||
protected function updateOptions(string $type, FieldCollection $fieldCollection, array $options, array $defaults): bool
|
||||
{
|
||||
$old = $options;
|
||||
$options = [];
|
||||
|
||||
// Update options with new values
|
||||
foreach ($fields as $field) {
|
||||
foreach ($fieldCollection as $field) {
|
||||
if ($field->isRequired() && $field->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ use Formwork\Http\RequestData;
|
||||
use Formwork\Http\RequestMethod;
|
||||
use Formwork\Http\Response;
|
||||
use Formwork\Images\Image;
|
||||
use Formwork\Languages\Language;
|
||||
use Formwork\Pages\Page;
|
||||
use Formwork\Pages\Site;
|
||||
use Formwork\Parsers\Yaml;
|
||||
@ -81,11 +82,11 @@ class PagesController extends AbstractController
|
||||
{
|
||||
$this->ensurePermission('pages.create');
|
||||
|
||||
$data = $this->request->input();
|
||||
$requestData = $this->request->input();
|
||||
|
||||
// Let's create the page
|
||||
try {
|
||||
$page = $this->createPage($data);
|
||||
$page = $this->createPage($requestData);
|
||||
$this->panel()->notify($this->translate('panel.pages.page.created'), 'success');
|
||||
} catch (TranslatedException $e) {
|
||||
$this->panel()->notify($e->getTranslatedMessage(), 'error');
|
||||
@ -102,18 +103,18 @@ class PagesController extends AbstractController
|
||||
/**
|
||||
* Pages@edit action
|
||||
*/
|
||||
public function edit(RouteParams $params): Response
|
||||
public function edit(RouteParams $routeParams): Response
|
||||
{
|
||||
$this->ensurePermission('pages.edit');
|
||||
|
||||
$page = $this->site()->findPage($params->get('page'));
|
||||
$page = $this->site()->findPage($routeParams->get('page'));
|
||||
|
||||
if ($page === null) {
|
||||
$this->panel()->notify($this->translate('panel.pages.page.cannotEdit.pageNotFound'), 'error');
|
||||
return $this->redirectToReferer(default: '/pages/');
|
||||
}
|
||||
|
||||
if ($params->has('language')) {
|
||||
if ($routeParams->has('language')) {
|
||||
if (empty($this->config->get('system.languages.available'))) {
|
||||
if ($page->route() === null) {
|
||||
throw new UnexpectedValueException('Unexpected missing page route');
|
||||
@ -121,7 +122,7 @@ class PagesController extends AbstractController
|
||||
return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => trim($page->route(), '/')]));
|
||||
}
|
||||
|
||||
$language = $params->get('language');
|
||||
$language = $routeParams->get('language');
|
||||
|
||||
if (!in_array($language, $this->config->get('system.languages.available'), true)) {
|
||||
$this->panel()->notify($this->translate('panel.pages.page.cannotEdit.invalidLanguage', $language), 'error');
|
||||
@ -186,7 +187,7 @@ class PagesController extends AbstractController
|
||||
}
|
||||
|
||||
// Redirect if page route has changed
|
||||
if ($params->get('page') !== ($route = trim($page->route(), '/'))) {
|
||||
if ($routeParams->get('page') !== ($route = trim($page->route(), '/'))) {
|
||||
return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $route]));
|
||||
}
|
||||
|
||||
@ -213,7 +214,7 @@ class PagesController extends AbstractController
|
||||
'fields' => $fields,
|
||||
'templates' => $this->site()->templates()->keys(),
|
||||
'parents' => $this->site()->descendants()->sortBy('relativePath'),
|
||||
'currentLanguage' => $params->get('language', $page->language()?->code()),
|
||||
'currentLanguage' => $routeParams->get('language', $page->language()?->code()),
|
||||
]));
|
||||
}
|
||||
|
||||
@ -224,30 +225,30 @@ class PagesController extends AbstractController
|
||||
{
|
||||
$this->ensurePermission('pages.reorder');
|
||||
|
||||
$data = $this->request->input();
|
||||
$requestData = $this->request->input();
|
||||
|
||||
if (!$data->hasMultiple(['page', 'before', 'parent'])) {
|
||||
if (!$requestData->hasMultiple(['page', 'before', 'parent'])) {
|
||||
return JsonResponse::error($this->translate('panel.pages.page.cannotMove'));
|
||||
}
|
||||
|
||||
$parent = $this->resolveParent($data->get('parent'));
|
||||
$parent = $this->resolveParent($requestData->get('parent'));
|
||||
if (!$parent->hasChildren()) {
|
||||
return JsonResponse::error($this->translate('panel.pages.page.cannotMove'));
|
||||
}
|
||||
|
||||
$pages = $parent->children();
|
||||
$keys = $pages->keys();
|
||||
$pageCollection = $parent->children();
|
||||
$keys = $pageCollection->keys();
|
||||
|
||||
$from = Arr::indexOf($keys, $data->get('page'));
|
||||
$to = Arr::indexOf($keys, $data->get('before'));
|
||||
$from = Arr::indexOf($keys, $requestData->get('page'));
|
||||
$to = Arr::indexOf($keys, $requestData->get('before'));
|
||||
|
||||
if ($from === null || $to === null) {
|
||||
return JsonResponse::error($this->translate('panel.pages.page.cannotMove'));
|
||||
}
|
||||
|
||||
$pages->moveItem($from, $to);
|
||||
$pageCollection->moveItem($from, $to);
|
||||
|
||||
foreach ($pages->values() as $i => $page) {
|
||||
foreach ($pageCollection->values() as $i => $page) {
|
||||
$name = basename($page->relativePath());
|
||||
$newName = preg_replace(Page::NUM_REGEX, $i + 1 . '-', $name)
|
||||
?? throw new RuntimeException(sprintf('Replacement failed with error: %s', preg_last_error_msg()));
|
||||
@ -263,19 +264,19 @@ class PagesController extends AbstractController
|
||||
/**
|
||||
* Pages@delete action
|
||||
*/
|
||||
public function delete(RouteParams $params): RedirectResponse
|
||||
public function delete(RouteParams $routeParams): RedirectResponse
|
||||
{
|
||||
$this->ensurePermission('pages.delete');
|
||||
|
||||
$page = $this->site()->findPage($params->get('page'));
|
||||
$page = $this->site()->findPage($routeParams->get('page'));
|
||||
|
||||
if ($page === null) {
|
||||
$this->panel()->notify($this->translate('panel.pages.page.cannotDelete.pageNotFound'), 'error');
|
||||
return $this->redirectToReferer(default: '/pages/');
|
||||
}
|
||||
|
||||
if ($params->has('language')) {
|
||||
$language = $params->get('language');
|
||||
if ($routeParams->has('language')) {
|
||||
$language = $routeParams->get('language');
|
||||
if ($page->languages()->available()->has($language)) {
|
||||
$page->setLanguage($language);
|
||||
} else {
|
||||
@ -291,7 +292,7 @@ class PagesController extends AbstractController
|
||||
|
||||
if ($page->path() !== null) {
|
||||
// Delete just the content file only if there are more than one language
|
||||
if ($page->contentFile() && $params->has('language') && count($page->languages()->available()) > 1) {
|
||||
if ($page->contentFile() !== null && $routeParams->has('language') && count($page->languages()->available()) > 1) {
|
||||
FileSystem::delete($page->contentFile()->path());
|
||||
} else {
|
||||
FileSystem::delete($page->path(), recursive: true);
|
||||
@ -301,7 +302,7 @@ class PagesController extends AbstractController
|
||||
$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/' . $params->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/')]))) {
|
||||
return $this->redirectToReferer(default: '/pages/');
|
||||
}
|
||||
return $this->redirect($this->generateRoute('panel.pages'));
|
||||
@ -310,11 +311,11 @@ class PagesController extends AbstractController
|
||||
/**
|
||||
* Pages@uploadFile action
|
||||
*/
|
||||
public function uploadFile(RouteParams $params): RedirectResponse
|
||||
public function uploadFile(RouteParams $routeParams): RedirectResponse
|
||||
{
|
||||
$this->ensurePermission('pages.uploadFiles');
|
||||
|
||||
$page = $this->site()->findPage($params->get('page'));
|
||||
$page = $this->site()->findPage($routeParams->get('page'));
|
||||
|
||||
if ($page === null) {
|
||||
$this->panel()->notify($this->translate('panel.pages.page.cannotUploadFile.pageNotFound'), 'error');
|
||||
@ -326,146 +327,146 @@ class PagesController extends AbstractController
|
||||
$this->processPageUploads($this->request->files()->getAll(), $page);
|
||||
} catch (TranslatedException $e) {
|
||||
$this->panel()->notify($this->translate('panel.uploader.error', $e->getTranslatedMessage()), 'error');
|
||||
return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $params->get('page')]));
|
||||
return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $routeParams->get('page')]));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$this->panel()->notify($this->translate('panel.uploader.uploaded'), 'success');
|
||||
return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $params->get('page')]));
|
||||
return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $routeParams->get('page')]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pages@deleteFile action
|
||||
*/
|
||||
public function deleteFile(RouteParams $params): RedirectResponse
|
||||
public function deleteFile(RouteParams $routeParams): RedirectResponse
|
||||
{
|
||||
$this->ensurePermission('pages.deleteFiles');
|
||||
|
||||
$page = $this->site()->findPage($params->get('page'));
|
||||
$page = $this->site()->findPage($routeParams->get('page'));
|
||||
|
||||
if ($page === null) {
|
||||
$this->panel()->notify($this->translate('panel.pages.page.cannotDeleteFile.pageNotFound'), 'error');
|
||||
return $this->redirectToReferer(default: '/pages/');
|
||||
}
|
||||
|
||||
if (!$page->files()->has($params->get('filename'))) {
|
||||
if (!$page->files()->has($routeParams->get('filename'))) {
|
||||
$this->panel()->notify($this->translate('panel.pages.page.cannotDeleteFile.fileNotFound'), 'error');
|
||||
return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $params->get('page')]));
|
||||
return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $routeParams->get('page')]));
|
||||
}
|
||||
|
||||
FileSystem::delete($page->path() . $params->get('filename'));
|
||||
FileSystem::delete($page->path() . $routeParams->get('filename'));
|
||||
|
||||
$this->panel()->notify($this->translate('panel.pages.page.fileDeleted'), 'success');
|
||||
return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $params->get('page')]));
|
||||
return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $routeParams->get('page')]));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Pages@renameFile action
|
||||
*/
|
||||
public function renameFile(RouteParams $params, Request $request): RedirectResponse
|
||||
public function renameFile(RouteParams $routeParams, Request $request): RedirectResponse
|
||||
{
|
||||
$this->ensurePermission('pages.renameFiles');
|
||||
|
||||
$page = $this->site()->findPage($params->get('page'));
|
||||
$page = $this->site()->findPage($routeParams->get('page'));
|
||||
|
||||
if ($page === null) {
|
||||
$this->panel()->notify($this->translate('panel.pages.page.cannotRenameFile.pageNotFound'), 'error');
|
||||
return $this->redirectToReferer(default: '/pages/');
|
||||
}
|
||||
|
||||
if (!$page->files()->has($params->get('filename'))) {
|
||||
if (!$page->files()->has($routeParams->get('filename'))) {
|
||||
$this->panel()->notify($this->translate('panel.pages.page.cannotRenameFile.fileNotFound'), 'error');
|
||||
return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $params->get('page')]));
|
||||
return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $routeParams->get('page')]));
|
||||
}
|
||||
|
||||
$name = Str::slug(FileSystem::name($request->input()->get('filename')));
|
||||
$extension = FileSystem::extension($params->get('filename'));
|
||||
$extension = FileSystem::extension($routeParams->get('filename'));
|
||||
|
||||
$newName = $name . '.' . $extension;
|
||||
|
||||
$previousName = $params->get('filename');
|
||||
$previousName = $routeParams->get('filename');
|
||||
|
||||
if ($newName !== $previousName) {
|
||||
if ($page->files()->has($newName)) {
|
||||
$this->panel()->notify($this->translate('panel.pages.page.cannotRenameFile.fileAlreadyExists'), 'error');
|
||||
return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $params->get('page')]));
|
||||
return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $routeParams->get('page')]));
|
||||
}
|
||||
|
||||
FileSystem::move($page->path() . $previousName, $page->path() . $newName);
|
||||
$this->panel()->notify($this->translate('panel.pages.page.fileRenamed'), 'success');
|
||||
}
|
||||
|
||||
return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $params->get('page')]));
|
||||
return $this->redirect($this->generateRoute('panel.pages.edit', ['page' => $routeParams->get('page')]));
|
||||
}
|
||||
|
||||
public function getFileInfo(RouteParams $params): JsonResponse
|
||||
public function getFileInfo(RouteParams $routeParams): JsonResponse
|
||||
{
|
||||
$this->ensurePermission('pages.getFileInfo');
|
||||
|
||||
$page = $this->site()->findPage($params->get('page'));
|
||||
$page = $this->site()->findPage($routeParams->get('page'));
|
||||
|
||||
if ($page === null) {
|
||||
return JsonResponse::error($this->translate('panel.pages.page.cannotRenameFile.pageNotFound'));
|
||||
}
|
||||
|
||||
if (!$page->files()->has($params->get('filename'))) {
|
||||
if (!$page->files()->has($routeParams->get('filename'))) {
|
||||
return JsonResponse::error($this->translate('panel.pages.page.cannotRenameFile.fileNotFound'));
|
||||
}
|
||||
|
||||
return JsonResponse::success('Yes!', data: $page->files()->get($params->get('filename'))->toArray());
|
||||
return JsonResponse::success('Yes!', data: $page->files()->get($routeParams->get('filename'))->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new page
|
||||
*/
|
||||
protected function createPage(RequestData $data): Page
|
||||
protected function createPage(RequestData $requestData): Page
|
||||
{
|
||||
// Ensure no required data is missing
|
||||
if (!$data->hasMultiple(['title', 'slug', 'template', 'parent'])) {
|
||||
if (!$requestData->hasMultiple(['title', 'slug', 'template', 'parent'])) {
|
||||
throw new TranslatedException('Missing required POST data', 'panel.pages.page.cannotCreate.varMissing');
|
||||
}
|
||||
|
||||
try {
|
||||
$parent = $this->resolveParent($data->get('parent'));
|
||||
$parent = $this->resolveParent($requestData->get('parent'));
|
||||
} catch (RuntimeException) {
|
||||
throw new TranslatedException('Parent page not found', 'panel.pages.page.cannotCreate.invalidParent');
|
||||
}
|
||||
|
||||
// Validate page slug
|
||||
if (!$this->validateSlug($data->get('slug'))) {
|
||||
if (!$this->validateSlug($requestData->get('slug'))) {
|
||||
throw new TranslatedException('Invalid page slug', 'panel.pages.page.cannotCreate.invalidSlug');
|
||||
}
|
||||
|
||||
$route = $parent->route() . $data->get('slug') . '/';
|
||||
$route = $parent->route() . $requestData->get('slug') . '/';
|
||||
|
||||
// Ensure there isn't a page with the same route
|
||||
if ($this->site()->findPage($route)) {
|
||||
if ($this->site()->findPage($route) !== null) {
|
||||
throw new TranslatedException('A page with the same route already exists', 'panel.pages.page.cannotCreate.alreadyExists');
|
||||
}
|
||||
|
||||
// Validate page template
|
||||
if (!$this->site()->templates()->has($data->get('template'))) {
|
||||
if (!$this->site()->templates()->has($requestData->get('template'))) {
|
||||
throw new TranslatedException('Invalid page template', 'panel.pages.page.cannotCreate.invalidTemplate');
|
||||
}
|
||||
|
||||
$scheme = $this->app->schemes()->get('pages.' . $data->get('template'));
|
||||
$scheme = $this->app->schemes()->get('pages.' . $requestData->get('template'));
|
||||
|
||||
$path = $parent->path() . $this->makePageNum($parent, $scheme->options()->get('num')) . '-' . $data->get('slug') . '/';
|
||||
$path = $parent->path() . $this->makePageNum($parent, $scheme->options()->get('num')) . '-' . $requestData->get('slug') . '/';
|
||||
|
||||
FileSystem::createDirectory($path, recursive: true);
|
||||
|
||||
$language = $this->site()->languages()->default();
|
||||
|
||||
$filename = $data->get('template');
|
||||
$filename .= empty($language) ? '' : '.' . $language;
|
||||
$filename = $requestData->get('template');
|
||||
$filename .= $language !== null ? '.' . $language : '';
|
||||
$filename .= $this->config->get('system.content.extension');
|
||||
|
||||
FileSystem::createFile($path . $filename);
|
||||
|
||||
$contentData = [
|
||||
'title' => $data->get('title'),
|
||||
'title' => $requestData->get('title'),
|
||||
'published' => false,
|
||||
];
|
||||
|
||||
@ -479,14 +480,14 @@ class PagesController extends AbstractController
|
||||
/**
|
||||
* Update a page
|
||||
*/
|
||||
protected function updatePage(Page $page, RequestData $data, FieldCollection $fields): Page
|
||||
protected function updatePage(Page $page, RequestData $requestData, FieldCollection $fieldCollection): Page
|
||||
{
|
||||
// Ensure no required data is missing
|
||||
if (!$data->hasMultiple(['title', 'content'])) {
|
||||
if (!$requestData->hasMultiple(['title', 'content'])) {
|
||||
throw new TranslatedException('Missing required POST data', 'panel.pages.page.cannotEdit.varMissing');
|
||||
}
|
||||
|
||||
if (!$page->contentFile()) {
|
||||
if ($page->contentFile() === null) {
|
||||
throw new RuntimeException('Unexpected missing content file');
|
||||
}
|
||||
|
||||
@ -494,15 +495,15 @@ class PagesController extends AbstractController
|
||||
$frontmatter = $page->contentFile()->frontmatter();
|
||||
|
||||
// Preserve the title if not given
|
||||
if (!empty($data->get('title'))) {
|
||||
$frontmatter['title'] = $data->get('title');
|
||||
if (!empty($requestData->get('title'))) {
|
||||
$frontmatter['title'] = $requestData->get('title');
|
||||
}
|
||||
|
||||
// Get page defaults
|
||||
$defaults = $page->defaults();
|
||||
|
||||
// Handle data from fields
|
||||
foreach ($fields as $field) {
|
||||
foreach ($fieldCollection as $field) {
|
||||
$default = array_key_exists($field->name(), $defaults) && $field->value() === $defaults[$field->name()];
|
||||
|
||||
// Remove empty and default values
|
||||
@ -515,9 +516,9 @@ class PagesController extends AbstractController
|
||||
$frontmatter[$field->name()] = $field->value();
|
||||
}
|
||||
|
||||
$content = str_replace("\r\n", "\n", $data->get('content'));
|
||||
$content = str_replace("\r\n", "\n", $requestData->get('content'));
|
||||
|
||||
$language = $data->get('language');
|
||||
$language = $requestData->get('language');
|
||||
|
||||
// Validate language
|
||||
if (!empty($language) && !in_array($language, $this->config->get('system.languages.available'), true)) {
|
||||
@ -527,7 +528,7 @@ class PagesController extends AbstractController
|
||||
$differ = $frontmatter !== $page->contentFile()->frontmatter() || $content !== $page->data()['content'] || $language !== $page->language();
|
||||
|
||||
if ($differ) {
|
||||
$filename = $data->get('template');
|
||||
$filename = $requestData->get('template');
|
||||
$filename .= empty($language) ? '' : '.' . $language;
|
||||
$filename .= $this->config->get('system.content.extension');
|
||||
|
||||
@ -552,7 +553,7 @@ class PagesController extends AbstractController
|
||||
$page->setLanguage($language);
|
||||
}
|
||||
|
||||
if (!$page->contentFile()) {
|
||||
if ($page->contentFile() === null) {
|
||||
throw new RuntimeException('Unexpected missing content file');
|
||||
}
|
||||
|
||||
@ -572,7 +573,7 @@ class PagesController extends AbstractController
|
||||
|
||||
try {
|
||||
$page = $this->changePageName($page, $name);
|
||||
} catch (RuntimeException $e) {
|
||||
} catch (RuntimeException) {
|
||||
throw new TranslatedException('Cannot change page num', 'panel.pages.page.cannotChangeNum');
|
||||
}
|
||||
}
|
||||
@ -580,7 +581,7 @@ class PagesController extends AbstractController
|
||||
|
||||
// Check if parent page has to change
|
||||
try {
|
||||
if ($page->parent() !== ($parent = $this->resolveParent($data->get('parent')))) {
|
||||
if ($page->parent() !== ($parent = $this->resolveParent($requestData->get('parent')))) {
|
||||
$page = $this->changePageParent($page, $parent);
|
||||
}
|
||||
} catch (RuntimeException) {
|
||||
@ -588,7 +589,7 @@ class PagesController extends AbstractController
|
||||
}
|
||||
|
||||
// Check if page template has to change
|
||||
if ($page->template()->name() !== ($template = $data->get('template'))) {
|
||||
if ($page->template()->name() !== ($template = $requestData->get('template'))) {
|
||||
if (!$this->site()->templates()->has($template)) {
|
||||
throw new TranslatedException('Invalid page template', 'panel.pages.page.cannotEdit.invalidTemplate');
|
||||
}
|
||||
@ -596,7 +597,7 @@ class PagesController extends AbstractController
|
||||
}
|
||||
|
||||
// Check if page slug has to change
|
||||
if ($page->slug() !== ($slug = $data->get('slug'))) {
|
||||
if ($page->slug() !== ($slug = $requestData->get('slug'))) {
|
||||
if (!$this->validateSlug($slug)) {
|
||||
throw new TranslatedException('Invalid page slug', 'panel.pages.page.cannotEdit.invalidSlug');
|
||||
}
|
||||
@ -604,7 +605,7 @@ class PagesController extends AbstractController
|
||||
if ($page->isIndexPage() || $page->isErrorPage()) {
|
||||
throw new TranslatedException('Cannot change slug of index or error pages', 'panel.pages.page.cannotEdit.indexOrErrorPageSlug');
|
||||
}
|
||||
if ($this->site()->findPage($page->parent()?->route() . $slug . '/')) {
|
||||
if ($this->site()->findPage($page->parent()?->route() . $slug . '/') !== null) {
|
||||
throw new TranslatedException('A page with the same route already exists', 'panel.pages.page.cannotEdit.alreadyExists');
|
||||
}
|
||||
$page = $this->changePageName($page, ltrim($page->num() . '-', '-') . $slug);
|
||||
@ -620,7 +621,7 @@ class PagesController extends AbstractController
|
||||
*/
|
||||
protected function processPageUploads(array $files, Page $page): void
|
||||
{
|
||||
$uploader = new FileUploader($this->config);
|
||||
$fileUploader = new FileUploader($this->config);
|
||||
|
||||
foreach ($files as $file) {
|
||||
if (!$file->isUploaded()) {
|
||||
@ -629,7 +630,7 @@ class PagesController extends AbstractController
|
||||
if ($page->path() === null) {
|
||||
throw new UnexpectedValueException('Unexpected missing page path');
|
||||
}
|
||||
$uploadedFile = $uploader->upload($file, $page->path());
|
||||
$uploadedFile = $fileUploader->upload($file, $page->path());
|
||||
// Process JPEG and PNG images according to system options (e.g. quality)
|
||||
if ($this->config->get('system.uploads.processImages') && in_array($uploadedFile->mimeType(), ['image/jpeg', 'image/png'], true)) {
|
||||
$image = new Image($uploadedFile->path(), $this->config->get('system.images'));
|
||||
@ -697,7 +698,7 @@ class PagesController extends AbstractController
|
||||
throw new UnexpectedValueException('Unexpected missing page path');
|
||||
}
|
||||
|
||||
if (!$page->contentFile()) {
|
||||
if ($page->contentFile() === null) {
|
||||
throw new UnexpectedValueException('Unexpected missing content file');
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ class UpdatesController extends AbstractController
|
||||
$this->ensurePermission('updates.check');
|
||||
try {
|
||||
$upToDate = $updater->checkUpdates();
|
||||
} catch (RuntimeException $e) {
|
||||
} catch (RuntimeException) {
|
||||
return JsonResponse::error($this->translate('panel.updates.status.cannotCheck'), ResponseStatus::InternalServerError, [
|
||||
'status' => $this->translate('panel.updates.status.cannotCheck'),
|
||||
]);
|
||||
@ -46,7 +46,7 @@ class UpdatesController extends AbstractController
|
||||
$backupper = new Backupper($this->config);
|
||||
try {
|
||||
$backupper->backup();
|
||||
} catch (TranslatedException $e) {
|
||||
} catch (TranslatedException) {
|
||||
return JsonResponse::error($this->translate('panel.updates.status.cannotMakeBackup'), ResponseStatus::InternalServerError, [
|
||||
'status' => $this->translate('panel.updates.status.cannotMakeBackup'),
|
||||
]);
|
||||
@ -54,7 +54,7 @@ class UpdatesController extends AbstractController
|
||||
}
|
||||
try {
|
||||
$updater->update();
|
||||
} catch (RuntimeException $e) {
|
||||
} catch (RuntimeException) {
|
||||
return JsonResponse::error($this->translate('panel.updates.status.cannotInstall'), ResponseStatus::InternalServerError, [
|
||||
'status' => $this->translate('panel.updates.status.cannotInstall'),
|
||||
]);
|
||||
|
@ -43,29 +43,29 @@ class UsersController extends AbstractController
|
||||
{
|
||||
$this->ensurePermission('users.create');
|
||||
|
||||
$data = $this->request->input();
|
||||
$requestData = $this->request->input();
|
||||
|
||||
// Ensure no required data is missing
|
||||
if (!$data->hasMultiple(['username', 'fullname', 'password', 'email', 'language'])) {
|
||||
if (!$requestData->hasMultiple(['username', 'fullname', 'password', 'email', 'language'])) {
|
||||
$this->panel()->notify($this->translate('panel.users.user.cannotCreate.varMissing'), 'error');
|
||||
return $this->redirect($this->generateRoute('panel.users'));
|
||||
}
|
||||
|
||||
// Ensure there isn't a user with the same username
|
||||
if ($this->panel()->users()->has($data->get('username'))) {
|
||||
if ($this->panel()->users()->has($requestData->get('username'))) {
|
||||
$this->panel()->notify($this->translate('panel.users.user.cannotCreate.alreadyExists'), 'error');
|
||||
return $this->redirect($this->generateRoute('panel.users'));
|
||||
}
|
||||
|
||||
$userData = [
|
||||
'username' => $data->get('username'),
|
||||
'fullname' => $data->get('fullname'),
|
||||
'hash' => Password::hash($data->get('password')),
|
||||
'email' => $data->get('email'),
|
||||
'language' => $data->get('language'),
|
||||
'username' => $requestData->get('username'),
|
||||
'fullname' => $requestData->get('fullname'),
|
||||
'hash' => Password::hash($requestData->get('password')),
|
||||
'email' => $requestData->get('email'),
|
||||
'language' => $requestData->get('language'),
|
||||
];
|
||||
|
||||
Yaml::encodeToFile($userData, FileSystem::joinPaths($this->config->get('system.panel.paths.accounts'), $data->get('username') . '.yaml'));
|
||||
Yaml::encodeToFile($userData, FileSystem::joinPaths($this->config->get('system.panel.paths.accounts'), $requestData->get('username') . '.yaml'));
|
||||
|
||||
$this->panel()->notify($this->translate('panel.users.user.created'), 'success');
|
||||
return $this->redirect($this->generateRoute('panel.users'));
|
||||
@ -74,15 +74,15 @@ class UsersController extends AbstractController
|
||||
/**
|
||||
* Users@delete action
|
||||
*/
|
||||
public function delete(RouteParams $params): RedirectResponse
|
||||
public function delete(RouteParams $routeParams): RedirectResponse
|
||||
{
|
||||
$this->ensurePermission('users.delete');
|
||||
|
||||
$user = $this->panel()->users()->get($params->get('user'));
|
||||
$user = $this->panel()->users()->get($routeParams->get('user'));
|
||||
|
||||
try {
|
||||
if (!$user) {
|
||||
throw new TranslatedException(sprintf('User "%s" not found', $params->get('user')), 'panel.users.user.notFound');
|
||||
throw new TranslatedException(sprintf('User "%s" not found', $routeParams->get('user')), 'panel.users.user.notFound');
|
||||
}
|
||||
if (!$this->user()->canDeleteUser($user)) {
|
||||
throw new TranslatedException(
|
||||
@ -109,13 +109,13 @@ class UsersController extends AbstractController
|
||||
/**
|
||||
* Users@profile action
|
||||
*/
|
||||
public function profile(RouteParams $params): Response
|
||||
public function profile(RouteParams $routeParams): Response
|
||||
{
|
||||
$scheme = $this->app->schemes()->get('users.user');
|
||||
|
||||
$fields = $scheme->fields();
|
||||
|
||||
$user = $this->panel()->users()->get($params->get('user'));
|
||||
$user = $this->panel()->users()->get($routeParams->get('user'));
|
||||
|
||||
if ($user === null) {
|
||||
$this->panel()->notify($this->translate('panel.users.user.notFound'), 'error');
|
||||
@ -206,9 +206,9 @@ class UsersController extends AbstractController
|
||||
{
|
||||
$imagesPath = FileSystem::joinPaths($this->config->get('system.panel.paths.assets'), '/images/users/');
|
||||
|
||||
$uploader = new FileUploader($this->config);
|
||||
$fileUploader = new FileUploader($this->config);
|
||||
|
||||
$uploadedFile = $uploader->upload($file, $imagesPath, FileSystem::randomName());
|
||||
$uploadedFile = $fileUploader->upload($file, $imagesPath, FileSystem::randomName());
|
||||
|
||||
if ($uploadedFile->type() === 'image') {
|
||||
$userImageSize = $this->config->get('system.panel.userImageSize');
|
||||
|
@ -181,10 +181,7 @@ final class Panel
|
||||
*/
|
||||
public function assets(): Assets
|
||||
{
|
||||
if (isset($this->assets)) {
|
||||
return $this->assets;
|
||||
}
|
||||
return $this->assets = new Assets($this->config->get('system.panel.paths.assets'), $this->realUri('/assets/'));
|
||||
return $this->assets ?? ($this->assets = new Assets($this->config->get('system.panel.paths.assets'), $this->realUri('/assets/')));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -195,7 +192,7 @@ final class Panel
|
||||
public function availableTranslations(): array
|
||||
{
|
||||
/**
|
||||
* @var array<string, string>
|
||||
* @var array<string, string> $translations
|
||||
*/
|
||||
static $translations = [];
|
||||
|
||||
@ -267,10 +264,10 @@ final class Panel
|
||||
protected function loadErrorHandler(): void
|
||||
{
|
||||
if ($this->config->get('system.errors.setHandlers')) {
|
||||
$this->errors = $this->container->build(Controllers\ErrorsController::class);
|
||||
set_exception_handler(function (Throwable $exception): void {
|
||||
$this->errors->internalServerError($exception)->send();
|
||||
throw $exception;
|
||||
$this->errors = $this->container->build(ErrorsController::class);
|
||||
set_exception_handler(function (Throwable $throwable): void {
|
||||
$this->errors->internalServerError($throwable)->send();
|
||||
throw $throwable;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user