Upgrade to 4.0.0
This commit is contained in:
@@ -14,7 +14,6 @@ use Kirby\Http\Router;
|
||||
use Kirby\Toolkit\Collection as BaseCollection;
|
||||
use Kirby\Toolkit\I18n;
|
||||
use Kirby\Toolkit\Pagination;
|
||||
use Kirby\Toolkit\Properties;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Throwable;
|
||||
|
||||
@@ -32,8 +31,6 @@ use Throwable;
|
||||
*/
|
||||
class Api
|
||||
{
|
||||
use Properties;
|
||||
|
||||
/**
|
||||
* Authentication callback
|
||||
*/
|
||||
@@ -86,6 +83,28 @@ class Api
|
||||
*/
|
||||
protected string|null $requestMethod = null;
|
||||
|
||||
/**
|
||||
* Creates a new API instance
|
||||
*/
|
||||
public function __construct(array $props)
|
||||
{
|
||||
$this->authentication = $props['authentication'] ?? null;
|
||||
$this->data = $props['data'] ?? [];
|
||||
$this->routes = $props['routes'] ?? [];
|
||||
$this->debug = $props['debug'] ?? false;
|
||||
|
||||
if ($collections = $props['collections'] ?? null) {
|
||||
$this->collections = array_change_key_case($collections);
|
||||
}
|
||||
|
||||
if ($models = $props['models'] ?? null) {
|
||||
$this->models = array_change_key_case($models);
|
||||
}
|
||||
|
||||
$this->setRequestData($props['requestData'] ?? null);
|
||||
$this->setRequestMethod($props['requestMethod'] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic accessor for any given data
|
||||
*
|
||||
@@ -96,14 +115,6 @@ class Api
|
||||
return $this->data($method, ...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new API instance
|
||||
*/
|
||||
public function __construct(array $props)
|
||||
{
|
||||
$this->setProperties($props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the authentication method
|
||||
* if set
|
||||
@@ -115,8 +126,6 @@ class Api
|
||||
|
||||
/**
|
||||
* Returns the authentication callback
|
||||
*
|
||||
* @return \Closure|null
|
||||
*/
|
||||
public function authentication(): Closure|null
|
||||
{
|
||||
@@ -130,8 +139,11 @@ class Api
|
||||
* @throws \Kirby\Exception\NotFoundException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function call(string|null $path = null, string $method = 'GET', array $requestData = [])
|
||||
{
|
||||
public function call(
|
||||
string|null $path = null,
|
||||
string $method = 'GET',
|
||||
array $requestData = []
|
||||
): mixed {
|
||||
$path = rtrim($path ?? '', '/');
|
||||
|
||||
$this->setRequestMethod($method);
|
||||
@@ -194,14 +206,34 @@ class Api
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance while
|
||||
* merging initial and new properties
|
||||
*/
|
||||
public function clone(array $props = []): static
|
||||
{
|
||||
return new static(array_merge([
|
||||
'autentication' => $this->authentication,
|
||||
'data' => $this->data,
|
||||
'routes' => $this->routes,
|
||||
'debug' => $this->debug,
|
||||
'collections' => $this->collections,
|
||||
'models' => $this->models,
|
||||
'requestData' => $this->requestData,
|
||||
'requestMethod' => $this->requestMethod
|
||||
], $props));
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter and getter for an API collection
|
||||
*
|
||||
* @throws \Kirby\Exception\NotFoundException If no collection for `$name` exists
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function collection(string $name, array|BaseCollection|null $collection = null): Collection
|
||||
{
|
||||
public function collection(
|
||||
string $name,
|
||||
array|BaseCollection|null $collection = null
|
||||
): Collection {
|
||||
if (isset($this->collections[$name]) === false) {
|
||||
throw new NotFoundException(sprintf('The collection "%s" does not exist', $name));
|
||||
}
|
||||
@@ -223,7 +255,7 @@ class Api
|
||||
*
|
||||
* @throws \Kirby\Exception\NotFoundException If no data for `$key` exists
|
||||
*/
|
||||
public function data(string|null $key = null, ...$args)
|
||||
public function data(string|null $key = null, ...$args): mixed
|
||||
{
|
||||
if ($key === null) {
|
||||
return $this->data;
|
||||
@@ -264,8 +296,10 @@ class Api
|
||||
* @param array models or collections
|
||||
* @return string|null key of match
|
||||
*/
|
||||
protected function match(array $array, $object = null): string|null
|
||||
{
|
||||
protected function match(
|
||||
array $array,
|
||||
$object = null
|
||||
): string|null {
|
||||
foreach ($array as $definition => $model) {
|
||||
if ($object instanceof $model['type']) {
|
||||
return $definition;
|
||||
@@ -280,8 +314,10 @@ class Api
|
||||
*
|
||||
* @throws \Kirby\Exception\NotFoundException If no model for `$name` exists
|
||||
*/
|
||||
public function model(string|null $name = null, $object = null): Model
|
||||
{
|
||||
public function model(
|
||||
string|null $name = null,
|
||||
$object = null
|
||||
): Model {
|
||||
// Try to auto-match object with API models
|
||||
$name ??= $this->match($this->models, $object);
|
||||
|
||||
@@ -304,17 +340,12 @@ class Api
|
||||
* Getter for request data
|
||||
* Can either get all the data
|
||||
* or certain parts of it.
|
||||
*
|
||||
* @param string|null $type
|
||||
* @param string|null $key
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function requestData(
|
||||
string|null $type = null,
|
||||
string|null $key = null,
|
||||
$default = null
|
||||
) {
|
||||
mixed $default = null
|
||||
): mixed {
|
||||
if ($type === null) {
|
||||
return $this->requestData;
|
||||
}
|
||||
@@ -332,24 +363,30 @@ class Api
|
||||
/**
|
||||
* Returns the request body if available
|
||||
*/
|
||||
public function requestBody(string|null $key = null, $default = null)
|
||||
{
|
||||
public function requestBody(
|
||||
string|null $key = null,
|
||||
mixed $default = null
|
||||
): mixed {
|
||||
return $this->requestData('body', $key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the files from the request if available
|
||||
*/
|
||||
public function requestFiles(string|null $key = null, $default = null)
|
||||
{
|
||||
public function requestFiles(
|
||||
string|null $key = null,
|
||||
mixed $default = null
|
||||
): mixed {
|
||||
return $this->requestData('files', $key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all headers from the request if available
|
||||
*/
|
||||
public function requestHeaders(string|null $key = null, $default = null)
|
||||
{
|
||||
public function requestHeaders(
|
||||
string|null $key = null,
|
||||
mixed $default = null
|
||||
): mixed {
|
||||
return $this->requestData('headers', $key, $default);
|
||||
}
|
||||
|
||||
@@ -364,8 +401,10 @@ class Api
|
||||
/**
|
||||
* Returns the request query if available
|
||||
*/
|
||||
public function requestQuery(string|null $key = null, $default = null)
|
||||
{
|
||||
public function requestQuery(
|
||||
string|null $key = null,
|
||||
mixed $default = null
|
||||
): mixed {
|
||||
return $this->requestData('query', $key, $default);
|
||||
}
|
||||
|
||||
@@ -403,102 +442,14 @@ class Api
|
||||
return $this->routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the authentication callback
|
||||
* @return $this
|
||||
*/
|
||||
protected function setAuthentication(Closure|null $authentication = null): static
|
||||
{
|
||||
$this->authentication = $authentication;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the collections definition
|
||||
* @return $this
|
||||
*/
|
||||
protected function setCollections(array|null $collections = null): static
|
||||
{
|
||||
if ($collections !== null) {
|
||||
$this->collections = array_change_key_case($collections);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the injected data
|
||||
* @return $this
|
||||
*/
|
||||
protected function setData(array|null $data = null): static
|
||||
{
|
||||
$this->data = $data ?? [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the debug flag
|
||||
* @return $this
|
||||
*/
|
||||
protected function setDebug(bool $debug = false): static
|
||||
{
|
||||
$this->debug = $debug;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the model definitions
|
||||
* @return $this
|
||||
*/
|
||||
protected function setModels(array|null $models = null): static
|
||||
{
|
||||
if ($models !== null) {
|
||||
$this->models = array_change_key_case($models);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the request data
|
||||
* @return $this
|
||||
*/
|
||||
protected function setRequestData(array|null $requestData = null): static
|
||||
{
|
||||
$defaults = [
|
||||
'query' => [],
|
||||
'body' => [],
|
||||
'files' => []
|
||||
];
|
||||
|
||||
$this->requestData = array_merge($defaults, (array)$requestData);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the request method
|
||||
* @return $this
|
||||
*/
|
||||
protected function setRequestMethod(string|null $requestMethod = null): static
|
||||
{
|
||||
$this->requestMethod = $requestMethod ?? 'GET';
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the route definitions
|
||||
* @return $this
|
||||
*/
|
||||
protected function setRoutes(array|null $routes = null): static
|
||||
{
|
||||
$this->routes = $routes ?? [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the API call
|
||||
*/
|
||||
public function render(string $path, string $method = 'GET', array $requestData = [])
|
||||
{
|
||||
public function render(
|
||||
string $path,
|
||||
string $method = 'GET',
|
||||
array $requestData = []
|
||||
): mixed {
|
||||
try {
|
||||
$result = $this->call($path, $method, $requestData);
|
||||
} catch (Throwable $e) {
|
||||
@@ -619,6 +570,34 @@ class Api
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the request data
|
||||
* @return $this
|
||||
*/
|
||||
protected function setRequestData(
|
||||
array|null $requestData = []
|
||||
): static {
|
||||
$defaults = [
|
||||
'query' => [],
|
||||
'body' => [],
|
||||
'files' => []
|
||||
];
|
||||
|
||||
$this->requestData = array_merge($defaults, (array)$requestData);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the request method
|
||||
* @return $this
|
||||
*/
|
||||
protected function setRequestMethod(
|
||||
string $requestMethod = null
|
||||
): static {
|
||||
$this->requestMethod = $requestMethod ?? 'GET';
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload helper method
|
||||
*
|
||||
@@ -627,8 +606,11 @@ class Api
|
||||
*
|
||||
* @throws \Exception If request has no files or there was an error with the upload
|
||||
*/
|
||||
public function upload(Closure $callback, bool $single = false, bool $debug = false): array
|
||||
{
|
||||
public function upload(
|
||||
Closure $callback,
|
||||
bool $single = false,
|
||||
bool $debug = false
|
||||
): array {
|
||||
$trials = 0;
|
||||
$uploads = [];
|
||||
$errors = [];
|
||||
@@ -668,8 +650,9 @@ class Api
|
||||
|
||||
try {
|
||||
if ($upload['error'] !== 0) {
|
||||
$errorMessage = $errorMessages[$upload['error']] ?? I18n::translate('upload.error.default');
|
||||
throw new Exception($errorMessage);
|
||||
throw new Exception(
|
||||
$errorMessages[$upload['error']] ?? I18n::translate('upload.error.default')
|
||||
);
|
||||
}
|
||||
|
||||
// get the extension of the uploaded file
|
||||
@@ -696,7 +679,9 @@ class Api
|
||||
$debug === false &&
|
||||
move_uploaded_file($upload['tmp_name'], $source) === false
|
||||
) {
|
||||
throw new Exception(I18n::translate('upload.error.cantMove'));
|
||||
throw new Exception(
|
||||
I18n::translate('upload.error.cantMove')
|
||||
);
|
||||
}
|
||||
|
||||
$data = $callback($source, $filename);
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Kirby\Api;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Kirby\Toolkit\Collection as BaseCollection;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
@@ -20,23 +21,22 @@ use Kirby\Toolkit\Str;
|
||||
*/
|
||||
class Collection
|
||||
{
|
||||
protected Api $api;
|
||||
protected $data;
|
||||
protected $model;
|
||||
protected $select = null;
|
||||
protected $view;
|
||||
protected string|null $model;
|
||||
protected array|null $select = null;
|
||||
protected string|null $view;
|
||||
|
||||
/**
|
||||
* Collection constructor
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(Api $api, $data, array $schema)
|
||||
{
|
||||
$this->api = $api;
|
||||
$this->data = $data;
|
||||
$this->model = $schema['model'] ?? null;
|
||||
$this->view = $schema['view'] ?? null;
|
||||
public function __construct(
|
||||
protected Api $api,
|
||||
protected BaseCollection|array|null $data,
|
||||
array $schema
|
||||
) {
|
||||
$this->model = $schema['model'] ?? null;
|
||||
$this->view = $schema['view'] ?? null;
|
||||
|
||||
if ($data === null) {
|
||||
if (($schema['default'] ?? null) instanceof Closure === false) {
|
||||
|
||||
@@ -22,21 +22,20 @@ use Kirby\Toolkit\Str;
|
||||
*/
|
||||
class Model
|
||||
{
|
||||
protected Api $api;
|
||||
protected $data;
|
||||
protected $fields;
|
||||
protected $select;
|
||||
protected $views;
|
||||
protected array $fields;
|
||||
protected array|null $select;
|
||||
protected array $views;
|
||||
|
||||
/**
|
||||
* Model constructor
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(Api $api, $data, array $schema)
|
||||
{
|
||||
$this->api = $api;
|
||||
$this->data = $data;
|
||||
public function __construct(
|
||||
protected Api $api,
|
||||
protected object|array|string|null $data,
|
||||
array $schema
|
||||
) {
|
||||
$this->fields = $schema['fields'] ?? [];
|
||||
$this->select = $schema['select'] ?? null;
|
||||
$this->views = $schema['views'] ?? [];
|
||||
|
||||
@@ -16,7 +16,7 @@ use TypeError;
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage 3.10
|
||||
* // TODO: include in test coverage once blueprint refactoring is done
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Collection extends BaseCollection
|
||||
@@ -37,6 +37,8 @@ class Collection extends BaseCollection
|
||||
* The Kirby Collection class only shows the key to
|
||||
* avoid huge tress with dump, but for the blueprint
|
||||
* collections this is really not useful
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ use Kirby\Filesystem\F;
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* // TODO: include in test coverage once blueprint refactoring is done
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Config
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Kirby\Blueprint;
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* // TODO: include in test coverage once blueprint refactoring is done
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Extension
|
||||
|
||||
@@ -16,7 +16,7 @@ use ReflectionUnionType;
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* // TODO: include in test coverage once blueprint refactoring is done
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Factory
|
||||
|
||||
@@ -13,7 +13,7 @@ use Kirby\Cms\ModelWithContent;
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* // TODO: include in test coverage once blueprint refactoring is done
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Node
|
||||
|
||||
@@ -14,7 +14,7 @@ use Kirby\Toolkit\I18n;
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* // TODO: include in test coverage once blueprint refactoring is done
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class NodeI18n extends NodeProperty
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Kirby\Blueprint;
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* // TODO: include in test coverage once blueprint refactoring is done
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class NodeIcon extends NodeString
|
||||
|
||||
@@ -13,7 +13,7 @@ use Kirby\Cms\ModelWithContent;
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* // TODO: include in test coverage once blueprint refactoring is done
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
abstract class NodeProperty
|
||||
|
||||
@@ -13,7 +13,7 @@ use Kirby\Cms\ModelWithContent;
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* // TODO: include in test coverage once blueprint refactoring is done
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class NodeString extends NodeProperty
|
||||
|
||||
@@ -14,12 +14,12 @@ use Kirby\Cms\ModelWithContent;
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* // TODO: include in test coverage in 3.10
|
||||
* // TODO: include in test coverage once blueprint refactoring is done
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class NodeText extends NodeI18n
|
||||
{
|
||||
public function render(ModelWithContent $model): ?string
|
||||
public function render(ModelWithContent $model): string|null
|
||||
{
|
||||
if ($text = parent::render($model)) {
|
||||
return $model->toSafeString($text);
|
||||
|
||||
@@ -18,10 +18,13 @@ use Kirby\Session\Session;
|
||||
*/
|
||||
class Api extends BaseApi
|
||||
{
|
||||
/**
|
||||
* @var App
|
||||
*/
|
||||
protected $kirby;
|
||||
protected App $kirby;
|
||||
|
||||
public function __construct(array $props)
|
||||
{
|
||||
$this->kirby = $props['kirby'];
|
||||
parent::__construct($props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an API call for the given path,
|
||||
@@ -31,7 +34,7 @@ class Api extends BaseApi
|
||||
string|null $path = null,
|
||||
string $method = 'GET',
|
||||
array $requestData = []
|
||||
) {
|
||||
): mixed {
|
||||
$this->setRequestMethod($method);
|
||||
$this->setRequestData($requestData);
|
||||
|
||||
@@ -46,21 +49,37 @@ class Api extends BaseApi
|
||||
return parent::call($path, $method, $requestData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance while
|
||||
* merging initial and new properties
|
||||
*/
|
||||
public function clone(array $props = []): static
|
||||
{
|
||||
return parent::clone(array_merge([
|
||||
'kirby' => $this->kirby
|
||||
], $props));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Kirby\Exception\NotFoundException if the field type cannot be found or the field cannot be loaded
|
||||
*/
|
||||
public function fieldApi($model, string $name, string|null $path = null)
|
||||
{
|
||||
public function fieldApi(
|
||||
ModelWithContent $model,
|
||||
string $name,
|
||||
string|null $path = null
|
||||
): mixed {
|
||||
$field = Form::for($model)->field($name);
|
||||
|
||||
$fieldApi = new static(
|
||||
array_merge($this->propertyData, [
|
||||
'data' => array_merge($this->data(), ['field' => $field]),
|
||||
'routes' => $field->api(),
|
||||
]),
|
||||
);
|
||||
$fieldApi = $this->clone([
|
||||
'data' => array_merge($this->data(), ['field' => $field]),
|
||||
'routes' => $field->api(),
|
||||
]);
|
||||
|
||||
return $fieldApi->call($path, $this->requestMethod(), $this->requestData());
|
||||
return $fieldApi->call(
|
||||
$path,
|
||||
$this->requestMethod(),
|
||||
$this->requestData()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,11 +89,24 @@ class Api extends BaseApi
|
||||
* @param string $path Path to file's parent model
|
||||
* @throws \Kirby\Exception\NotFoundException if the file cannot be found
|
||||
*/
|
||||
public function file(string $path, string $filename): File|null
|
||||
{
|
||||
public function file(
|
||||
string $path,
|
||||
string $filename
|
||||
): File|null {
|
||||
return Find::file($path, $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the all readable files for the parent
|
||||
*
|
||||
* @param string $path Path to file's parent model
|
||||
* @throws \Kirby\Exception\NotFoundException if the file cannot be found
|
||||
*/
|
||||
public function files(string $path): Files
|
||||
{
|
||||
return $this->parent($path)->files()->filter('isAccessible', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the model's object for the given path
|
||||
*
|
||||
@@ -82,7 +114,7 @@ class Api extends BaseApi
|
||||
* @throws \Kirby\Exception\InvalidArgumentException if the model type is invalid
|
||||
* @throws \Kirby\Exception\NotFoundException if the model cannot be found
|
||||
*/
|
||||
public function parent(string $path): Model|null
|
||||
public function parent(string $path): ModelWithContent|null
|
||||
{
|
||||
return Find::parent($path);
|
||||
}
|
||||
@@ -100,7 +132,9 @@ class Api extends BaseApi
|
||||
*/
|
||||
public function language(): string|null
|
||||
{
|
||||
return $this->requestQuery('language') ?? $this->requestHeaders('x-language');
|
||||
return
|
||||
$this->requestQuery('language') ??
|
||||
$this->requestHeaders('x-language');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,11 +153,12 @@ class Api extends BaseApi
|
||||
* parent. The subpages can be filtered
|
||||
* by status (draft, listed, unlisted, published, all)
|
||||
*/
|
||||
public function pages(string|null $parentId = null, string|null $status = null): Pages
|
||||
{
|
||||
public function pages(
|
||||
string|null $parentId = null,
|
||||
string|null $status = null
|
||||
): Pages {
|
||||
$parent = $parentId === null ? $this->site() : $this->page($parentId);
|
||||
|
||||
return match ($status) {
|
||||
$pages = match ($status) {
|
||||
'all' => $parent->childrenAndDrafts(),
|
||||
'draft', 'drafts' => $parent->drafts(),
|
||||
'listed' => $parent->children()->listed(),
|
||||
@@ -131,6 +166,8 @@ class Api extends BaseApi
|
||||
'published' => $parent->children(),
|
||||
default => $parent->children()
|
||||
};
|
||||
|
||||
return $pages->filter('isAccessible', true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,17 +197,6 @@ class Api extends BaseApi
|
||||
], $options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the parent Kirby instance
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function setKirby(App $kirby): static
|
||||
{
|
||||
$this->kirby = $kirby;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the site object
|
||||
*/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,15 +17,12 @@ use Kirby\Exception\InvalidArgumentException;
|
||||
*/
|
||||
trait AppCaches
|
||||
{
|
||||
protected $caches = [];
|
||||
protected array $caches = [];
|
||||
|
||||
/**
|
||||
* Returns a cache instance by key
|
||||
*
|
||||
* @param string $key
|
||||
* @return \Kirby\Cache\Cache
|
||||
*/
|
||||
public function cache(string $key)
|
||||
public function cache(string $key): Cache
|
||||
{
|
||||
if (isset($this->caches[$key]) === true) {
|
||||
return $this->caches[$key];
|
||||
@@ -67,9 +64,6 @@ trait AppCaches
|
||||
|
||||
/**
|
||||
* Returns the cache options by key
|
||||
*
|
||||
* @param string $key
|
||||
* @return array
|
||||
*/
|
||||
protected function cacheOptions(string $key): array
|
||||
{
|
||||
@@ -106,9 +100,6 @@ trait AppCaches
|
||||
* Takes care of converting prefixed plugin cache setups
|
||||
* to the right cache key, while leaving regular cache
|
||||
* setups untouched.
|
||||
*
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
protected function cacheOptionsKey(string $key): string
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ use Kirby\Toolkit\I18n;
|
||||
use Throwable;
|
||||
use Whoops\Handler\CallbackHandler;
|
||||
use Whoops\Handler\Handler;
|
||||
use Whoops\Handler\HandlerInterface;
|
||||
use Whoops\Handler\PlainTextHandler;
|
||||
use Whoops\Handler\PrettyPageHandler;
|
||||
use Whoops\Run as Whoops;
|
||||
@@ -26,16 +27,21 @@ use Whoops\Run as Whoops;
|
||||
trait AppErrors
|
||||
{
|
||||
/**
|
||||
* Whoops instance cache
|
||||
* Allows to disable Whoops globally in CI;
|
||||
* can be overridden by explicitly setting
|
||||
* the `whoops` option to `true` or `false`
|
||||
*
|
||||
* @var \Whoops\Run
|
||||
* @internal
|
||||
*/
|
||||
protected $whoops;
|
||||
public static bool $enableWhoops = true;
|
||||
|
||||
/**
|
||||
* Whoops instance cache
|
||||
*/
|
||||
protected Whoops $whoops;
|
||||
|
||||
/**
|
||||
* Registers the PHP error handler for CLI usage
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function handleCliErrors(): void
|
||||
{
|
||||
@@ -45,11 +51,20 @@ trait AppErrors
|
||||
/**
|
||||
* Registers the PHP error handler
|
||||
* based on the environment
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function handleErrors(): void
|
||||
{
|
||||
// no matter the environment, exit early if
|
||||
// Whoops was disabled globally
|
||||
// (but continue if the option was explicitly
|
||||
// set to `true` in the config)
|
||||
if (
|
||||
static::$enableWhoops === false &&
|
||||
$this->option('whoops') !== true
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->environment()->cli() === true) {
|
||||
$this->handleCliErrors();
|
||||
return;
|
||||
@@ -65,8 +80,6 @@ trait AppErrors
|
||||
|
||||
/**
|
||||
* Registers the PHP error handler for HTML output
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function handleHtmlErrors(): void
|
||||
{
|
||||
@@ -76,7 +89,7 @@ trait AppErrors
|
||||
if ($this->option('whoops', true) !== false) {
|
||||
$handler = new PrettyPageHandler();
|
||||
$handler->setPageTitle('Kirby CMS Debugger');
|
||||
$handler->setResourcesPath(dirname(__DIR__, 2) . '/assets');
|
||||
$handler->addResourcePath(dirname(__DIR__, 2) . '/assets');
|
||||
$handler->addCustomCss('whoops.css');
|
||||
|
||||
if ($editor = $this->option('editor')) {
|
||||
@@ -114,8 +127,6 @@ trait AppErrors
|
||||
|
||||
/**
|
||||
* Registers the PHP error handler for JSON output
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function handleJsonErrors(): void
|
||||
{
|
||||
@@ -162,11 +173,8 @@ trait AppErrors
|
||||
|
||||
/**
|
||||
* Enables Whoops with the specified handler
|
||||
*
|
||||
* @param Callable|\Whoops\Handler\HandlerInterface $handler
|
||||
* @return void
|
||||
*/
|
||||
protected function setWhoopsHandler($handler): void
|
||||
protected function setWhoopsHandler(callable|HandlerInterface $handler): void
|
||||
{
|
||||
$whoops = $this->whoops();
|
||||
$whoops->clearHandlers();
|
||||
@@ -190,8 +198,6 @@ trait AppErrors
|
||||
|
||||
/**
|
||||
* Clears the Whoops handlers and disables Whoops
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function unsetWhoopsHandler(): void
|
||||
{
|
||||
@@ -202,15 +208,9 @@ trait AppErrors
|
||||
|
||||
/**
|
||||
* Returns the Whoops error handler instance
|
||||
*
|
||||
* @return \Whoops\Run
|
||||
*/
|
||||
protected function whoops()
|
||||
protected function whoops(): Whoops
|
||||
{
|
||||
if ($this->whoops !== null) {
|
||||
return $this->whoops;
|
||||
}
|
||||
|
||||
return $this->whoops = new Whoops();
|
||||
return $this->whoops ??= new Whoops();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Closure;
|
||||
use Kirby\Content\Field;
|
||||
use Kirby\Exception\DuplicateException;
|
||||
use Kirby\Filesystem\Asset;
|
||||
use Kirby\Filesystem\Dir;
|
||||
@@ -10,7 +11,6 @@ use Kirby\Filesystem\F;
|
||||
use Kirby\Filesystem\Mime;
|
||||
use Kirby\Form\Field as FormField;
|
||||
use Kirby\Image\Image;
|
||||
use Kirby\Panel\Panel;
|
||||
use Kirby\Text\KirbyTag;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Collection as ToolkitCollection;
|
||||
@@ -29,17 +29,13 @@ trait AppPlugins
|
||||
{
|
||||
/**
|
||||
* A list of all registered plugins
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $plugins = [];
|
||||
protected static array $plugins = [];
|
||||
|
||||
/**
|
||||
* The extension registry
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $extensions = [
|
||||
protected array $extensions = [
|
||||
// load options first to make them available for the rest
|
||||
'options' => [],
|
||||
|
||||
@@ -77,6 +73,8 @@ trait AppPlugins
|
||||
'sections' => [],
|
||||
'siteMethods' => [],
|
||||
'snippets' => [],
|
||||
'structureMethods' => [],
|
||||
'structureObjectMethods' => [],
|
||||
'tags' => [],
|
||||
'templates' => [],
|
||||
'thirdParty' => [],
|
||||
@@ -90,21 +88,19 @@ trait AppPlugins
|
||||
/**
|
||||
* Flag when plugins have been loaded
|
||||
* to not load them again
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $pluginsAreLoaded = false;
|
||||
protected bool $pluginsAreLoaded = false;
|
||||
|
||||
/**
|
||||
* Register all given extensions
|
||||
*
|
||||
* @internal
|
||||
* @param array $extensions
|
||||
* @param \Kirby\Cms\Plugin $plugin|null The plugin which defined those extensions
|
||||
* @return array
|
||||
*/
|
||||
public function extend(array $extensions, Plugin $plugin = null): array
|
||||
{
|
||||
public function extend(
|
||||
array $extensions,
|
||||
Plugin $plugin = null
|
||||
): array {
|
||||
foreach ($this->extensions as $type => $registered) {
|
||||
if (isset($extensions[$type]) === true) {
|
||||
$this->{'extend' . $type}($extensions[$type], $plugin);
|
||||
@@ -116,11 +112,8 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers API extensions
|
||||
*
|
||||
* @param array|bool $api
|
||||
* @return array
|
||||
*/
|
||||
protected function extendApi($api): array
|
||||
protected function extendApi(array|bool $api): array
|
||||
{
|
||||
if (is_array($api) === true) {
|
||||
if (($api['routes'] ?? []) instanceof Closure) {
|
||||
@@ -135,9 +128,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional custom Panel areas
|
||||
*
|
||||
* @param array $areas
|
||||
* @return array
|
||||
*/
|
||||
protected function extendAreas(array $areas): array
|
||||
{
|
||||
@@ -151,9 +141,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional asset methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendAssetMethods(array $methods): array
|
||||
{
|
||||
@@ -162,9 +149,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional authentication challenges
|
||||
*
|
||||
* @param array $challenges
|
||||
* @return array
|
||||
*/
|
||||
protected function extendAuthChallenges(array $challenges): array
|
||||
{
|
||||
@@ -173,9 +157,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional block methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendBlockMethods(array $methods): array
|
||||
{
|
||||
@@ -184,9 +165,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional block models
|
||||
*
|
||||
* @param array $models
|
||||
* @return array
|
||||
*/
|
||||
protected function extendBlockModels(array $models): array
|
||||
{
|
||||
@@ -195,9 +173,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional blocks methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendBlocksMethods(array $methods): array
|
||||
{
|
||||
@@ -206,9 +181,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional blueprints
|
||||
*
|
||||
* @param array $blueprints
|
||||
* @return array
|
||||
*/
|
||||
protected function extendBlueprints(array $blueprints): array
|
||||
{
|
||||
@@ -217,9 +189,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional cache types
|
||||
*
|
||||
* @param array $cacheTypes
|
||||
* @return array
|
||||
*/
|
||||
protected function extendCacheTypes(array $cacheTypes): array
|
||||
{
|
||||
@@ -228,9 +197,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional CLI commands
|
||||
*
|
||||
* @param array $commands
|
||||
* @return array
|
||||
*/
|
||||
protected function extendCommands(array $commands): array
|
||||
{
|
||||
@@ -239,9 +205,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional collection filters
|
||||
*
|
||||
* @param array $filters
|
||||
* @return array
|
||||
*/
|
||||
protected function extendCollectionFilters(array $filters): array
|
||||
{
|
||||
@@ -250,9 +213,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional collection methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendCollectionMethods(array $methods): array
|
||||
{
|
||||
@@ -261,9 +221,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional collections
|
||||
*
|
||||
* @param array $collections
|
||||
* @return array
|
||||
*/
|
||||
protected function extendCollections(array $collections): array
|
||||
{
|
||||
@@ -272,9 +229,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers core components
|
||||
*
|
||||
* @param array $components
|
||||
* @return array
|
||||
*/
|
||||
protected function extendComponents(array $components): array
|
||||
{
|
||||
@@ -283,9 +237,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional controllers
|
||||
*
|
||||
* @param array $controllers
|
||||
* @return array
|
||||
*/
|
||||
protected function extendControllers(array $controllers): array
|
||||
{
|
||||
@@ -294,9 +245,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional file methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendFileMethods(array $methods): array
|
||||
{
|
||||
@@ -305,9 +253,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional custom file types and mimes
|
||||
*
|
||||
* @param array $fileTypes
|
||||
* @return array
|
||||
*/
|
||||
protected function extendFileTypes(array $fileTypes): array
|
||||
{
|
||||
@@ -358,9 +303,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional files methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendFilesMethods(array $methods): array
|
||||
{
|
||||
@@ -369,9 +311,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional field methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendFieldMethods(array $methods): array
|
||||
{
|
||||
@@ -380,9 +319,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers Panel fields
|
||||
*
|
||||
* @param array $fields
|
||||
* @return array
|
||||
*/
|
||||
protected function extendFields(array $fields): array
|
||||
{
|
||||
@@ -391,9 +327,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers hooks
|
||||
*
|
||||
* @param array $hooks
|
||||
* @return array
|
||||
*/
|
||||
protected function extendHooks(array $hooks): array
|
||||
{
|
||||
@@ -414,20 +347,14 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers markdown component
|
||||
*
|
||||
* @param Closure $markdown
|
||||
* @return Closure
|
||||
*/
|
||||
protected function extendMarkdown(Closure $markdown)
|
||||
protected function extendMarkdown(Closure $markdown): Closure
|
||||
{
|
||||
return $this->extensions['markdown'] = $markdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional layout methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendLayoutMethods(array $methods): array
|
||||
{
|
||||
@@ -436,9 +363,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional layout column methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendLayoutColumnMethods(array $methods): array
|
||||
{
|
||||
@@ -447,9 +371,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional layouts methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendLayoutsMethods(array $methods): array
|
||||
{
|
||||
@@ -458,13 +379,11 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional options
|
||||
*
|
||||
* @param array $options
|
||||
* @param \Kirby\Cms\Plugin|null $plugin
|
||||
* @return array
|
||||
*/
|
||||
protected function extendOptions(array $options, Plugin $plugin = null): array
|
||||
{
|
||||
protected function extendOptions(
|
||||
array $options,
|
||||
Plugin $plugin = null
|
||||
): array {
|
||||
if ($plugin !== null) {
|
||||
$options = [$plugin->prefix() => $options];
|
||||
}
|
||||
@@ -474,9 +393,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional page methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendPageMethods(array $methods): array
|
||||
{
|
||||
@@ -485,9 +401,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional pages methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendPagesMethods(array $methods): array
|
||||
{
|
||||
@@ -496,9 +409,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional page models
|
||||
*
|
||||
* @param array $models
|
||||
* @return array
|
||||
*/
|
||||
protected function extendPageModels(array $models): array
|
||||
{
|
||||
@@ -507,9 +417,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers pages
|
||||
*
|
||||
* @param array $pages
|
||||
* @return array
|
||||
*/
|
||||
protected function extendPages(array $pages): array
|
||||
{
|
||||
@@ -518,13 +425,11 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional permissions
|
||||
*
|
||||
* @param array $permissions
|
||||
* @param \Kirby\Cms\Plugin|null $plugin
|
||||
* @return array
|
||||
*/
|
||||
protected function extendPermissions(array $permissions, Plugin $plugin = null): array
|
||||
{
|
||||
protected function extendPermissions(
|
||||
array $permissions,
|
||||
Plugin $plugin = null
|
||||
): array {
|
||||
if ($plugin !== null) {
|
||||
$permissions = [$plugin->prefix() => $permissions];
|
||||
}
|
||||
@@ -534,11 +439,8 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional routes
|
||||
*
|
||||
* @param array|\Closure $routes
|
||||
* @return array
|
||||
*/
|
||||
protected function extendRoutes($routes): array
|
||||
protected function extendRoutes(array|Closure $routes): array
|
||||
{
|
||||
if ($routes instanceof Closure) {
|
||||
$routes = $routes($this);
|
||||
@@ -549,9 +451,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers Panel sections
|
||||
*
|
||||
* @param array $sections
|
||||
* @return array
|
||||
*/
|
||||
protected function extendSections(array $sections): array
|
||||
{
|
||||
@@ -560,9 +459,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional site methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendSiteMethods(array $methods): array
|
||||
{
|
||||
@@ -571,31 +467,38 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers SmartyPants component
|
||||
*
|
||||
* @param \Closure $smartypants
|
||||
* @return \Closure
|
||||
*/
|
||||
protected function extendSmartypants(Closure $smartypants)
|
||||
protected function extendSmartypants(Closure $smartypants): Closure
|
||||
{
|
||||
return $this->extensions['smartypants'] = $smartypants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional snippets
|
||||
*
|
||||
* @param array $snippets
|
||||
* @return array
|
||||
*/
|
||||
protected function extendSnippets(array $snippets): array
|
||||
{
|
||||
return $this->extensions['snippets'] = array_merge($this->extensions['snippets'], $snippets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional structure methods
|
||||
*/
|
||||
protected function extendStructureMethods(array $methods): array
|
||||
{
|
||||
return $this->extensions['structureMethods'] = Structure::$methods = array_merge(Structure::$methods, $methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional structure object methods
|
||||
*/
|
||||
protected function extendStructureObjectMethods(array $methods): array
|
||||
{
|
||||
return $this->extensions['structureObjectMethods'] = StructureObject::$methods = array_merge(StructureObject::$methods, $methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional KirbyTags
|
||||
*
|
||||
* @param array $tags
|
||||
* @return array
|
||||
*/
|
||||
protected function extendTags(array $tags): array
|
||||
{
|
||||
@@ -604,9 +507,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional templates
|
||||
*
|
||||
* @param array $templates
|
||||
* @return array
|
||||
*/
|
||||
protected function extendTemplates(array $templates): array
|
||||
{
|
||||
@@ -615,9 +515,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers translations
|
||||
*
|
||||
* @param array $translations
|
||||
* @return array
|
||||
*/
|
||||
protected function extendTranslations(array $translations): array
|
||||
{
|
||||
@@ -628,9 +525,6 @@ trait AppPlugins
|
||||
* Add third party extensions to the registry
|
||||
* so they can be used as plugins for plugins
|
||||
* for example.
|
||||
*
|
||||
* @param array $extensions
|
||||
* @return array
|
||||
*/
|
||||
protected function extendThirdParty(array $extensions): array
|
||||
{
|
||||
@@ -639,9 +533,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional user methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendUserMethods(array $methods): array
|
||||
{
|
||||
@@ -650,9 +541,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional user models
|
||||
*
|
||||
* @param array $models
|
||||
* @return array
|
||||
*/
|
||||
protected function extendUserModels(array $models): array
|
||||
{
|
||||
@@ -661,9 +549,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional users methods
|
||||
*
|
||||
* @param array $methods
|
||||
* @return array
|
||||
*/
|
||||
protected function extendUsersMethods(array $methods): array
|
||||
{
|
||||
@@ -672,9 +557,6 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Registers additional custom validators
|
||||
*
|
||||
* @param array $validators
|
||||
* @return array
|
||||
*/
|
||||
protected function extendValidators(array $validators): array
|
||||
{
|
||||
@@ -687,11 +569,12 @@ trait AppPlugins
|
||||
* @internal
|
||||
* @param string $type i.e. `'hooks'`
|
||||
* @param string $name i.e. `'page.delete:before'`
|
||||
* @param mixed $fallback
|
||||
* @return mixed
|
||||
*/
|
||||
public function extension(string $type, string $name, $fallback = null)
|
||||
{
|
||||
public function extension(
|
||||
string $type,
|
||||
string $name,
|
||||
mixed $fallback = null
|
||||
): mixed {
|
||||
return $this->extensions($type)[$name] ?? $fallback;
|
||||
}
|
||||
|
||||
@@ -699,10 +582,8 @@ trait AppPlugins
|
||||
* Returns the extensions registry
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $type
|
||||
* @return array
|
||||
*/
|
||||
public function extensions(string $type = null)
|
||||
public function extensions(string $type = null): array
|
||||
{
|
||||
if ($type === null) {
|
||||
return $this->extensions;
|
||||
@@ -716,7 +597,7 @@ trait AppPlugins
|
||||
* This is only used for models for now, but
|
||||
* could be extended later
|
||||
*/
|
||||
protected function extensionsFromFolders()
|
||||
protected function extensionsFromFolders(): void
|
||||
{
|
||||
$models = [];
|
||||
|
||||
@@ -739,10 +620,8 @@ trait AppPlugins
|
||||
* Register extensions that could be located in
|
||||
* the options array. I.e. hooks and routes can be
|
||||
* setup from the config.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function extensionsFromOptions()
|
||||
protected function extensionsFromOptions(): void
|
||||
{
|
||||
// register routes and hooks from options
|
||||
$this->extend([
|
||||
@@ -754,10 +633,8 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Apply all plugin extensions
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function extensionsFromPlugins()
|
||||
protected function extensionsFromPlugins(): void
|
||||
{
|
||||
// register all their extensions
|
||||
foreach ($this->plugins() as $plugin) {
|
||||
@@ -771,21 +648,16 @@ trait AppPlugins
|
||||
|
||||
/**
|
||||
* Apply all passed extensions
|
||||
*
|
||||
* @param array $props
|
||||
* @return void
|
||||
*/
|
||||
protected function extensionsFromProps(array $props)
|
||||
protected function extensionsFromProps(array $props): void
|
||||
{
|
||||
$this->extend($props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply all default extensions
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function extensionsFromSystem()
|
||||
protected function extensionsFromSystem(): void
|
||||
{
|
||||
// mixins
|
||||
FormField::$mixins = $this->core->fieldMixins();
|
||||
@@ -813,9 +685,6 @@ trait AppPlugins
|
||||
/**
|
||||
* Checks if a native component was extended
|
||||
* @since 3.7.0
|
||||
*
|
||||
* @param string $component
|
||||
* @return bool
|
||||
*/
|
||||
public function isNativeComponent(string $component): bool
|
||||
{
|
||||
@@ -825,11 +694,8 @@ trait AppPlugins
|
||||
/**
|
||||
* Returns the native implementation
|
||||
* of a core component
|
||||
*
|
||||
* @param string $component
|
||||
* @return \Closure|false
|
||||
*/
|
||||
public function nativeComponent(string $component)
|
||||
public function nativeComponent(string $component): Closure|false
|
||||
{
|
||||
return $this->core->components()[$component] ?? false;
|
||||
}
|
||||
@@ -837,13 +703,13 @@ trait AppPlugins
|
||||
/**
|
||||
* Kirby plugin factory and getter
|
||||
*
|
||||
* @param string $name
|
||||
* @param array|null $extends If null is passed it will be used as getter. Otherwise as factory.
|
||||
* @return \Kirby\Cms\Plugin|null
|
||||
* @throws \Kirby\Exception\DuplicateException
|
||||
*/
|
||||
public static function plugin(string $name, array $extends = null)
|
||||
{
|
||||
public static function plugin(
|
||||
string $name,
|
||||
array $extends = null
|
||||
): PLugin|null {
|
||||
if ($extends === null) {
|
||||
return static::$plugins[$name] ?? null;
|
||||
}
|
||||
@@ -867,7 +733,6 @@ trait AppPlugins
|
||||
*
|
||||
* @internal
|
||||
* @param array|null $plugins Can be used to overwrite the plugins registry
|
||||
* @return array
|
||||
*/
|
||||
public function plugins(array $plugins = null): array
|
||||
{
|
||||
|
||||
@@ -17,12 +17,10 @@ use Kirby\Toolkit\Str;
|
||||
*/
|
||||
trait AppTranslations
|
||||
{
|
||||
protected $translations;
|
||||
protected Translations|null $translations = null;
|
||||
|
||||
/**
|
||||
* Setup internationalization
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function i18n(): void
|
||||
{
|
||||
@@ -88,8 +86,6 @@ trait AppTranslations
|
||||
* Returns the language code that will be used
|
||||
* for the Panel if no user is logged in or if
|
||||
* no language is configured for the user
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function panelLanguage(): string
|
||||
{
|
||||
@@ -113,11 +109,10 @@ trait AppTranslations
|
||||
* Otherwise fall back to the default language
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $languageCode
|
||||
* @return \Kirby\Cms\Language|null
|
||||
*/
|
||||
public function setCurrentLanguage(string $languageCode = null)
|
||||
{
|
||||
public function setCurrentLanguage(
|
||||
string $languageCode = null
|
||||
): Language|null {
|
||||
if ($this->multilang() === false) {
|
||||
Locale::set($this->option('locale', 'en_US.utf-8'));
|
||||
return $this->language = null;
|
||||
@@ -140,8 +135,6 @@ trait AppTranslations
|
||||
* Set the current translation
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $translationCode
|
||||
* @return void
|
||||
*/
|
||||
public function setCurrentTranslation(string $translationCode = null): void
|
||||
{
|
||||
@@ -152,9 +145,8 @@ trait AppTranslations
|
||||
* Load a specific translation by locale
|
||||
*
|
||||
* @param string|null $locale Locale name or `null` for the current locale
|
||||
* @return \Kirby\Cms\Translation
|
||||
*/
|
||||
public function translation(string|null $locale = null)
|
||||
public function translation(string|null $locale = null): Translation
|
||||
{
|
||||
$locale = $locale ?? I18n::locale();
|
||||
$locale = basename($locale);
|
||||
@@ -180,10 +172,8 @@ trait AppTranslations
|
||||
|
||||
/**
|
||||
* Returns all available translations
|
||||
*
|
||||
* @return \Kirby\Cms\Translations
|
||||
*/
|
||||
public function translations()
|
||||
public function translations(): Translations
|
||||
{
|
||||
if ($this->translations instanceof Translations) {
|
||||
return $this->translations;
|
||||
@@ -207,8 +197,6 @@ trait AppTranslations
|
||||
}
|
||||
}
|
||||
|
||||
$this->translations = Translations::load($this->root('i18n:translations'), $translations);
|
||||
|
||||
return $this->translations;
|
||||
return $this->translations = Translations::load($this->root('i18n:translations'), $translations);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,20 +16,15 @@ use Throwable;
|
||||
*/
|
||||
trait AppUsers
|
||||
{
|
||||
/**
|
||||
* Cache for the auth auth layer
|
||||
*
|
||||
* @var Auth
|
||||
*/
|
||||
protected $auth;
|
||||
protected Auth|null $auth = null;
|
||||
protected User|string|null $user = null;
|
||||
protected Users|null $users = null;
|
||||
|
||||
/**
|
||||
* Returns the Authentication layer class
|
||||
*
|
||||
* @internal
|
||||
* @return \Kirby\Cms\Auth
|
||||
*/
|
||||
public function auth()
|
||||
public function auth(): Auth
|
||||
{
|
||||
return $this->auth ??= new Auth($this);
|
||||
}
|
||||
@@ -48,8 +43,10 @@ trait AppUsers
|
||||
* if called with callback: Return value from the callback
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function impersonate(string|null $who = null, Closure|null $callback = null)
|
||||
{
|
||||
public function impersonate(
|
||||
string|null $who = null,
|
||||
Closure|null $callback = null
|
||||
): mixed {
|
||||
$auth = $this->auth();
|
||||
|
||||
$userBefore = $auth->currentUserFromImpersonation();
|
||||
@@ -73,10 +70,9 @@ trait AppUsers
|
||||
/**
|
||||
* Set the currently active user id
|
||||
*
|
||||
* @param \Kirby\Cms\User|string $user
|
||||
* @return \Kirby\Cms\App
|
||||
* @return $this
|
||||
*/
|
||||
protected function setUser($user = null)
|
||||
protected function setUser(User|string $user = null): static
|
||||
{
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
@@ -85,10 +81,9 @@ trait AppUsers
|
||||
/**
|
||||
* Create your own set of app users
|
||||
*
|
||||
* @param array|null $users
|
||||
* @return \Kirby\Cms\App
|
||||
* @return $this
|
||||
*/
|
||||
protected function setUsers(array $users = null)
|
||||
protected function setUsers(array $users = null): static
|
||||
{
|
||||
if ($users !== null) {
|
||||
$this->users = Users::factory($users, [
|
||||
@@ -103,14 +98,14 @@ trait AppUsers
|
||||
* Returns a specific user by id
|
||||
* or the current user if no id is given
|
||||
*
|
||||
* @param string|null $id
|
||||
* @param bool $allowImpersonation If set to false, only the actually
|
||||
* logged in user will be returned
|
||||
* (when `$id` is passed as `null`)
|
||||
* @return \Kirby\Cms\User|null
|
||||
*/
|
||||
public function user(string|null $id = null, bool $allowImpersonation = true)
|
||||
{
|
||||
public function user(
|
||||
string|null $id = null,
|
||||
bool $allowImpersonation = true
|
||||
): User|null {
|
||||
if ($id !== null) {
|
||||
return $this->users()->find($id);
|
||||
}
|
||||
@@ -128,15 +123,12 @@ trait AppUsers
|
||||
|
||||
/**
|
||||
* Returns all users
|
||||
*
|
||||
* @return \Kirby\Cms\Users
|
||||
*/
|
||||
public function users()
|
||||
public function users(): Users
|
||||
{
|
||||
if ($this->users instanceof Users) {
|
||||
return $this->users;
|
||||
}
|
||||
|
||||
return $this->users = Users::load($this->root('accounts'), ['kirby' => $this]);
|
||||
return $this->users ??= Users::load(
|
||||
$this->root('accounts'),
|
||||
['kirby' => $this]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,55 +32,37 @@ class Auth
|
||||
/**
|
||||
* Available auth challenge classes
|
||||
* from the core and plugins
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $challenges = [];
|
||||
public static array $challenges = [];
|
||||
|
||||
/**
|
||||
* Currently impersonated user
|
||||
*
|
||||
* @var \Kirby\Cms\User|null
|
||||
*/
|
||||
protected $impersonate;
|
||||
|
||||
/**
|
||||
* Kirby instance
|
||||
*
|
||||
* @var \Kirby\Cms\App
|
||||
*/
|
||||
protected $kirby;
|
||||
protected User|null $impersonate = null;
|
||||
|
||||
/**
|
||||
* Cache of the auth status object
|
||||
*
|
||||
* @var \Kirby\Cms\Auth\Status
|
||||
*/
|
||||
protected $status;
|
||||
protected Status|null $status = null;
|
||||
|
||||
/**
|
||||
* Instance of the currently logged in user or
|
||||
* `false` if the user was not yet determined
|
||||
*
|
||||
* @var \Kirby\Cms\User|null|false
|
||||
*/
|
||||
protected $user = false;
|
||||
protected User|false|null $user = false;
|
||||
|
||||
/**
|
||||
* Exception that was thrown while
|
||||
* determining the current user
|
||||
*
|
||||
* @var \Throwable
|
||||
*/
|
||||
protected $userException;
|
||||
protected Throwable|null $userException = null;
|
||||
|
||||
/**
|
||||
* @param \Kirby\Cms\App $kirby
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __construct(App $kirby)
|
||||
{
|
||||
$this->kirby = $kirby;
|
||||
public function __construct(
|
||||
protected App $kirby
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,17 +70,18 @@ class Auth
|
||||
* (one-time auth code)
|
||||
* @since 3.5.0
|
||||
*
|
||||
* @param string $email
|
||||
* @param bool $long If `true`, a long session will be created
|
||||
* @param string $mode Either 'login' or 'password-reset'
|
||||
* @return \Kirby\Cms\Auth\Status
|
||||
* @param 'login'|'password-reset'|'2fa' $mode Purpose of the code
|
||||
*
|
||||
* @throws \Kirby\Exception\LogicException If there is no suitable authentication challenge (only in debug mode)
|
||||
* @throws \Kirby\Exception\NotFoundException If the user does not exist (only in debug mode)
|
||||
* @throws \Kirby\Exception\PermissionException If the rate limit is exceeded
|
||||
*/
|
||||
public function createChallenge(string $email, bool $long = false, string $mode = 'login')
|
||||
{
|
||||
public function createChallenge(
|
||||
string $email,
|
||||
bool $long = false,
|
||||
string $mode = 'login'
|
||||
): Status {
|
||||
$email = Idn::decodeEmail($email);
|
||||
|
||||
$session = $this->kirby->session([
|
||||
@@ -145,7 +128,10 @@ class Auth
|
||||
$session->set('kirby.challenge.type', $challenge);
|
||||
|
||||
if ($code !== null) {
|
||||
$session->set('kirby.challenge.code', password_hash($code, PASSWORD_DEFAULT));
|
||||
$session->set(
|
||||
'kirby.challenge.code',
|
||||
password_hash($code, PASSWORD_DEFAULT)
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -179,10 +165,8 @@ class Auth
|
||||
|
||||
/**
|
||||
* Returns the csrf token if it exists and if it is valid
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
public function csrf()
|
||||
public function csrf(): string|false
|
||||
{
|
||||
// get the csrf from the header
|
||||
$fromHeader = $this->kirby->request()->csrf();
|
||||
@@ -201,8 +185,6 @@ class Auth
|
||||
/**
|
||||
* Returns either predefined csrf or the one from session
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function csrfFromSession(): string
|
||||
{
|
||||
@@ -217,11 +199,10 @@ class Auth
|
||||
* valid credentials
|
||||
*
|
||||
* @param \Kirby\Http\Request\Auth\BasicAuth|null $auth
|
||||
* @return \Kirby\Cms\User|null
|
||||
* @throws \Kirby\Exception\InvalidArgumentException if the authorization header is invalid
|
||||
* @throws \Kirby\Exception\PermissionException if basic authentication is not allowed
|
||||
*/
|
||||
public function currentUserFromBasicAuth(BasicAuth $auth = null)
|
||||
public function currentUserFromBasicAuth(BasicAuth $auth = null): User|null
|
||||
{
|
||||
if ($this->kirby->option('api.basicAuth', false) !== true) {
|
||||
throw new PermissionException('Basic authentication is not activated');
|
||||
@@ -240,8 +221,8 @@ class Auth
|
||||
}
|
||||
}
|
||||
|
||||
$request = $this->kirby->request();
|
||||
$auth = $auth ?? $request->auth();
|
||||
$request = $this->kirby->request();
|
||||
$auth ??= $request->auth();
|
||||
|
||||
if (!$auth || $auth->type() !== 'basic') {
|
||||
throw new InvalidArgumentException('Invalid authorization header');
|
||||
@@ -257,10 +238,8 @@ class Auth
|
||||
|
||||
/**
|
||||
* Returns the currently impersonated user
|
||||
*
|
||||
* @return \Kirby\Cms\User|null
|
||||
*/
|
||||
public function currentUserFromImpersonation()
|
||||
public function currentUserFromImpersonation(): User|null
|
||||
{
|
||||
return $this->impersonate;
|
||||
}
|
||||
@@ -269,12 +248,10 @@ class Auth
|
||||
* Returns the logged in user by checking
|
||||
* the current session and finding a valid
|
||||
* valid user id in there
|
||||
*
|
||||
* @param \Kirby\Session\Session|array|null $session
|
||||
* @return \Kirby\Cms\User|null
|
||||
*/
|
||||
public function currentUserFromSession($session = null)
|
||||
{
|
||||
public function currentUserFromSession(
|
||||
Session|array $session = null
|
||||
): User|null {
|
||||
$session = $this->session($session);
|
||||
|
||||
$id = $session->data()->get('kirby.userId');
|
||||
@@ -318,12 +295,12 @@ class Auth
|
||||
* Returns the list of enabled challenges in the
|
||||
* configured order
|
||||
* @since 3.5.1
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function enabledChallenges(): array
|
||||
{
|
||||
return A::wrap($this->kirby->option('auth.challenges', ['email']));
|
||||
return A::wrap(
|
||||
$this->kirby->option('auth.challenges', ['totp', 'email'])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -333,10 +310,9 @@ class Auth
|
||||
* `null` to use the actual user again,
|
||||
* `'kirby'` for a virtual admin user or
|
||||
* `'nobody'` to disable the actual user
|
||||
* @return \Kirby\Cms\User|null
|
||||
* @throws \Kirby\Exception\NotFoundException if the given user cannot be found
|
||||
*/
|
||||
public function impersonate(string|null $who = null)
|
||||
public function impersonate(string|null $who = null): User|null
|
||||
{
|
||||
// clear the status cache
|
||||
$this->status = null;
|
||||
@@ -360,8 +336,6 @@ class Auth
|
||||
/**
|
||||
* Returns the hashed ip of the visitor
|
||||
* which is used to track invalid logins
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function ipHash(): string
|
||||
{
|
||||
@@ -373,9 +347,6 @@ class Auth
|
||||
|
||||
/**
|
||||
* Check if logins are blocked for the current ip or email
|
||||
*
|
||||
* @param string $email
|
||||
* @return bool
|
||||
*/
|
||||
public function isBlocked(string $email): bool
|
||||
{
|
||||
@@ -450,16 +421,12 @@ class Auth
|
||||
/**
|
||||
* Sets a user object as the current user in the cache
|
||||
* @internal
|
||||
*
|
||||
* @param \Kirby\Cms\User $user
|
||||
* @return void
|
||||
*/
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
// stop impersonating
|
||||
$this->impersonate = null;
|
||||
|
||||
$this->user = $user;
|
||||
$this->user = $user;
|
||||
|
||||
// clear the status cache
|
||||
$this->status = null;
|
||||
@@ -469,13 +436,13 @@ class Auth
|
||||
* Returns the authentication status object
|
||||
* @since 3.5.1
|
||||
*
|
||||
* @param \Kirby\Session\Session|array|null $session
|
||||
* @param bool $allowImpersonation If set to false, only the actually
|
||||
* logged in user will be returned
|
||||
* @return \Kirby\Cms\Auth\Status
|
||||
*/
|
||||
public function status($session = null, bool $allowImpersonation = true)
|
||||
{
|
||||
public function status(
|
||||
Session|array $session = null,
|
||||
bool $allowImpersonation = true
|
||||
): Status {
|
||||
// try to return from cache
|
||||
if ($this->status && $session === null && $allowImpersonation === true) {
|
||||
return $this->status;
|
||||
@@ -588,8 +555,6 @@ class Auth
|
||||
|
||||
/**
|
||||
* Returns the absolute path to the logins log
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function logfile(): string
|
||||
{
|
||||
@@ -598,8 +563,6 @@ class Auth
|
||||
|
||||
/**
|
||||
* Read all tracked logins
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function log(): array
|
||||
{
|
||||
@@ -642,8 +605,6 @@ class Auth
|
||||
|
||||
/**
|
||||
* Logout the current user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function logout(): void
|
||||
{
|
||||
@@ -668,8 +629,6 @@ class Auth
|
||||
/**
|
||||
* Clears the cached user data after logout
|
||||
* @internal
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function flush(): void
|
||||
{
|
||||
@@ -681,12 +640,12 @@ class Auth
|
||||
/**
|
||||
* Tracks a login
|
||||
*
|
||||
* @param string|null $email
|
||||
* @param bool $triggerHook If `false`, no user.login:failed hook is triggered
|
||||
* @return bool
|
||||
*/
|
||||
public function track(string|null $email, bool $triggerHook = true): bool
|
||||
{
|
||||
public function track(
|
||||
string|null $email,
|
||||
bool $triggerHook = true
|
||||
): bool {
|
||||
if ($triggerHook === true) {
|
||||
$this->kirby->trigger('user.login:failed', compact('email'));
|
||||
}
|
||||
@@ -730,7 +689,6 @@ class Auth
|
||||
* @param bool $allowImpersonation If set to false, 'impersonate' won't
|
||||
* be returned as authentication type
|
||||
* even if an impersonation is active
|
||||
* @return string
|
||||
*/
|
||||
public function type(bool $allowImpersonation = true): string
|
||||
{
|
||||
@@ -759,15 +717,15 @@ class Auth
|
||||
/**
|
||||
* Validates the currently logged in user
|
||||
*
|
||||
* @param \Kirby\Session\Session|array|null $session
|
||||
* @param bool $allowImpersonation If set to false, only the actually
|
||||
* logged in user will be returned
|
||||
* @return \Kirby\Cms\User|null
|
||||
*
|
||||
* @throws \Throwable If an authentication error occurred
|
||||
*/
|
||||
public function user($session = null, bool $allowImpersonation = true)
|
||||
{
|
||||
public function user(
|
||||
Session|array $session = null,
|
||||
bool $allowImpersonation = true
|
||||
): User|null {
|
||||
if ($allowImpersonation === true && $this->impersonate !== null) {
|
||||
return $this->impersonate;
|
||||
}
|
||||
@@ -820,7 +778,7 @@ class Auth
|
||||
public function verifyChallenge(
|
||||
#[SensitiveParameter]
|
||||
string $code
|
||||
) {
|
||||
): User {
|
||||
try {
|
||||
$session = $this->kirby->session();
|
||||
|
||||
@@ -887,9 +845,11 @@ class Auth
|
||||
throw new PermissionException(['key' => 'access.code']);
|
||||
}
|
||||
|
||||
throw new LogicException('Invalid authentication challenge: ' . $challenge);
|
||||
throw new LogicException(
|
||||
'Invalid authentication challenge: ' . $challenge
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
$details = $e instanceof \Kirby\Exception\Exception ? $e->getDetails() : [];
|
||||
$details = $e instanceof Exception ? $e->getDetails() : [];
|
||||
|
||||
if (
|
||||
empty($email) === false &&
|
||||
@@ -925,8 +885,10 @@ class Auth
|
||||
* @throws \Throwable Either the passed `$exception` or the `$fallback`
|
||||
* (no exception if debugging is disabled and no fallback was passed)
|
||||
*/
|
||||
protected function fail(Throwable $exception, Throwable $fallback = null): void
|
||||
{
|
||||
protected function fail(
|
||||
Throwable $exception,
|
||||
Throwable $fallback = null
|
||||
): void {
|
||||
$debug = $this->kirby->option('auth.debug', 'log');
|
||||
|
||||
// throw the original exception only in debug mode
|
||||
@@ -948,11 +910,8 @@ class Auth
|
||||
|
||||
/**
|
||||
* Creates a session object from the passed options
|
||||
*
|
||||
* @param \Kirby\Session\Session|array|null $session
|
||||
* @return \Kirby\Session\Session
|
||||
*/
|
||||
protected function session($session = null)
|
||||
protected function session(Session|array $session = null): Session
|
||||
{
|
||||
// use passed session options or session object if set
|
||||
if (is_array($session) === true) {
|
||||
|
||||
@@ -22,8 +22,7 @@ abstract class Challenge
|
||||
* for the passed user and purpose
|
||||
*
|
||||
* @param \Kirby\Cms\User $user User the code will be generated for
|
||||
* @param string $mode Purpose of the code ('login', 'reset' or '2fa')
|
||||
* @return bool
|
||||
* @param 'login'|'password-reset'|'2fa' $mode Purpose of the code
|
||||
*/
|
||||
abstract public static function isAvailable(User $user, string $mode): bool;
|
||||
|
||||
@@ -33,7 +32,7 @@ abstract class Challenge
|
||||
*
|
||||
* @param \Kirby\Cms\User $user User to generate the code for
|
||||
* @param array $options Details of the challenge request:
|
||||
* - 'mode': Purpose of the code ('login', 'reset' or '2fa')
|
||||
* - 'mode': Purpose of the code ('login', 'password-reset' or '2fa')
|
||||
* - 'timeout': Number of seconds the code will be valid for
|
||||
* @return string|null The generated and sent code or `null` in case
|
||||
* there was no code to generate by this algorithm
|
||||
@@ -47,7 +46,6 @@ abstract class Challenge
|
||||
*
|
||||
* @param \Kirby\Cms\User $user User to check the code for
|
||||
* @param string $code Code to verify
|
||||
* @return bool
|
||||
*/
|
||||
public static function verify(
|
||||
User $user,
|
||||
|
||||
@@ -23,8 +23,7 @@ class EmailChallenge extends Challenge
|
||||
* for the passed user and purpose
|
||||
*
|
||||
* @param \Kirby\Cms\User $user User the code will be generated for
|
||||
* @param string $mode Purpose of the code ('login', 'reset' or '2fa')
|
||||
* @return bool
|
||||
* @param 'login'|'password-reset'|'2fa' $mode Purpose of the code
|
||||
*/
|
||||
public static function isAvailable(User $user, string $mode): bool
|
||||
{
|
||||
@@ -37,7 +36,7 @@ class EmailChallenge extends Challenge
|
||||
*
|
||||
* @param \Kirby\Cms\User $user User to generate the code for
|
||||
* @param array $options Details of the challenge request:
|
||||
* - 'mode': Purpose of the code ('login', 'reset' or '2fa')
|
||||
* - 'mode': Purpose of the code ('login', 'password-reset' or '2fa')
|
||||
* - 'timeout': Number of seconds the code will be valid for
|
||||
* @return string The generated and sent code
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Kirby\Cms\Auth;
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\User;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Toolkit\Properties;
|
||||
|
||||
@@ -19,44 +20,32 @@ use Kirby\Toolkit\Properties;
|
||||
*/
|
||||
class Status
|
||||
{
|
||||
use Properties;
|
||||
|
||||
/**
|
||||
* Type of the active challenge
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $challenge = null;
|
||||
protected string|null $challenge = null;
|
||||
|
||||
/**
|
||||
* Challenge type to use as a fallback
|
||||
* when $challenge is `null`
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $challengeFallback = null;
|
||||
protected string|null $challengeFallback = null;
|
||||
|
||||
/**
|
||||
* Email address of the current/pending user
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $email = null;
|
||||
protected string|null $email;
|
||||
|
||||
/**
|
||||
* Kirby instance for user lookup
|
||||
*
|
||||
* @var \Kirby\Cms\App
|
||||
*/
|
||||
protected $kirby;
|
||||
protected App $kirby;
|
||||
|
||||
/**
|
||||
* Authentication status:
|
||||
* `active|impersonated|pending|inactive`
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $status;
|
||||
protected string $status;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
@@ -65,13 +54,24 @@ class Status
|
||||
*/
|
||||
public function __construct(array $props)
|
||||
{
|
||||
$this->setProperties($props);
|
||||
if (in_array($props['status'], ['active', 'impersonated', 'pending', 'inactive']) !== true) {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => [
|
||||
'argument' => '$props[\'status\']',
|
||||
'method' => 'Status::__construct'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
$this->kirby = $props['kirby'];
|
||||
$this->challenge = $props['challenge'] ?? null;
|
||||
$this->challengeFallback = $props['challengeFallback'] ?? null;
|
||||
$this->email = $props['email'] ?? null;
|
||||
$this->status = $props['status'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the authentication status
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
@@ -84,7 +84,6 @@ class Status
|
||||
* @param bool $automaticFallback If set to `false`, no faked challenge is returned;
|
||||
* WARNING: never send the resulting `null` value to the
|
||||
* user to avoid leaking whether the pending user exists
|
||||
* @return string|null
|
||||
*/
|
||||
public function challenge(bool $automaticFallback = true): string|null
|
||||
{
|
||||
@@ -100,10 +99,23 @@ class Status
|
||||
return $this->challenge ?? $this->challengeFallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance while
|
||||
* merging initial and new properties
|
||||
*/
|
||||
public function clone(array $props = []): static
|
||||
{
|
||||
return new static(array_replace_recursive([
|
||||
'kirby' => $this->kirby,
|
||||
'challenge' => $this->challenge,
|
||||
'challengeFallback' => $this->challengeFallback,
|
||||
'email' => $this->email,
|
||||
'status' => $this->status,
|
||||
], $props));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the email address of the current/pending user
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function email(): string|null
|
||||
{
|
||||
@@ -122,8 +134,6 @@ class Status
|
||||
|
||||
/**
|
||||
* Returns an array with all public status data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
@@ -136,10 +146,8 @@ class Status
|
||||
|
||||
/**
|
||||
* Returns the currently logged in user
|
||||
*
|
||||
* @return \Kirby\Cms\User
|
||||
*/
|
||||
public function user()
|
||||
public function user(): User|null
|
||||
{
|
||||
// for security, only return the user if they are
|
||||
// already logged in
|
||||
@@ -149,71 +157,4 @@ class Status
|
||||
|
||||
return $this->kirby->user($this->email());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of the active challenge
|
||||
*
|
||||
* @param string|null $challenge
|
||||
* @return $this
|
||||
*/
|
||||
protected function setChallenge(string|null $challenge = null)
|
||||
{
|
||||
$this->challenge = $challenge;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the challenge type to use as
|
||||
* a fallback when $challenge is `null`
|
||||
*
|
||||
* @param string|null $challengeFallback
|
||||
* @return $this
|
||||
*/
|
||||
protected function setChallengeFallback(string|null $challengeFallback = null)
|
||||
{
|
||||
$this->challengeFallback = $challengeFallback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the email address of the current/pending user
|
||||
*
|
||||
* @param string|null $email
|
||||
* @return $this
|
||||
*/
|
||||
protected function setEmail(string|null $email = null)
|
||||
{
|
||||
$this->email = $email;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Kirby instance for user lookup
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby
|
||||
* @return $this
|
||||
*/
|
||||
protected function setKirby(App $kirby)
|
||||
{
|
||||
$this->kirby = $kirby;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the authentication status
|
||||
*
|
||||
* @param string $status `active|impersonated|pending|inactive`
|
||||
* @return $this
|
||||
*/
|
||||
protected function setStatus(string $status)
|
||||
{
|
||||
if (in_array($status, ['active', 'impersonated', 'pending', 'inactive']) !== true) {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['argument' => '$props[\'status\']', 'method' => 'Status::__construct']
|
||||
]);
|
||||
}
|
||||
|
||||
$this->status = $status;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
65
kirby/src/Cms/Auth/TotpChallenge.php
Normal file
65
kirby/src/Cms/Auth/TotpChallenge.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms\Auth;
|
||||
|
||||
use Kirby\Cms\User;
|
||||
use Kirby\Toolkit\Totp;
|
||||
|
||||
/**
|
||||
* Verifies one-time time-based auth codes
|
||||
* that are generated with an authenticator app.
|
||||
* Users first have to set up time-based codes
|
||||
* (storing the TOTP secret in their user account).
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Nico Hoffmann <nico@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class TotpChallenge extends Challenge
|
||||
{
|
||||
/**
|
||||
* Checks whether the challenge is available
|
||||
* for the passed user and purpose
|
||||
*
|
||||
* @param \Kirby\Cms\User $user User the code will be generated for
|
||||
* @param 'login'|'password-reset'|'2fa' $mode Purpose of the code
|
||||
*/
|
||||
public static function isAvailable(User $user, string $mode): bool
|
||||
{
|
||||
// user needs to have a TOTP secret set up
|
||||
return $user->secret('totp') !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random one-time auth code and returns that code
|
||||
* for later verification
|
||||
*
|
||||
* @param \Kirby\Cms\User $user User to generate the code for
|
||||
* @param array $options Details of the challenge request:
|
||||
* - 'mode': Purpose of the code ('login', 'password-reset' or '2fa')
|
||||
* - 'timeout': Number of seconds the code will be valid for
|
||||
* @todo set return type to `null` once support for PHP 8.1 is dropped
|
||||
*/
|
||||
public static function create(User $user, array $options): string|null
|
||||
{
|
||||
// the user's app will generate the code, we only verify it
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the provided code against the created one
|
||||
*
|
||||
* @param \Kirby\Cms\User $user User to check the code for
|
||||
* @param string $code Code to verify
|
||||
*/
|
||||
public static function verify(User $user, string $code): bool
|
||||
{
|
||||
// verify if code is current, previous or next TOTP code
|
||||
$secret = $user->secret('totp');
|
||||
$totp = new Totp($secret);
|
||||
return $totp->verify($code);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Content\Content;
|
||||
use Kirby\Content\Field;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Throwable;
|
||||
@@ -24,36 +26,19 @@ class Block extends Item
|
||||
|
||||
public const ITEMS_CLASS = Blocks::class;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Cms\Content
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isHidden;
|
||||
|
||||
/**
|
||||
* Registry with all block models
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $models = [];
|
||||
public static array $models = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
protected Content $content;
|
||||
protected bool $isHidden;
|
||||
protected string $type;
|
||||
|
||||
/**
|
||||
* Proxy for content fields
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $args
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
public function __call(string $method, array $args = [])
|
||||
public function __call(string $method, array $args = []): mixed
|
||||
{
|
||||
// block methods
|
||||
if ($this->hasMethod($method)) {
|
||||
@@ -66,7 +51,6 @@ class Block extends Item
|
||||
/**
|
||||
* Creates a new block object
|
||||
*
|
||||
* @param array $params
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $params)
|
||||
@@ -90,18 +74,15 @@ class Block extends Item
|
||||
$params['content'] = [];
|
||||
}
|
||||
|
||||
$this->content = $params['content'];
|
||||
$this->isHidden = $params['isHidden'] ?? false;
|
||||
$this->type = $params['type'];
|
||||
|
||||
// create the content object
|
||||
$this->content = new Content($this->content, $this->parent);
|
||||
$this->content = new Content($params['content'], $this->parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the object to a string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
@@ -110,18 +91,14 @@ class Block extends Item
|
||||
|
||||
/**
|
||||
* Returns the content object
|
||||
*
|
||||
* @return \Kirby\Cms\Content
|
||||
*/
|
||||
public function content()
|
||||
public function content(): Content
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller for the block snippet
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function controller(): array
|
||||
{
|
||||
@@ -140,28 +117,26 @@ class Block extends Item
|
||||
* Converts the block to HTML and then
|
||||
* uses the Str::excerpt method to create
|
||||
* a non-formatted, shortened excerpt from it
|
||||
*
|
||||
* @param mixed ...$args
|
||||
* @return string
|
||||
*/
|
||||
public function excerpt(...$args)
|
||||
public function excerpt(mixed ...$args): string
|
||||
{
|
||||
return Str::excerpt($this->toHtml(), ...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a block object with registering blocks models
|
||||
*
|
||||
* @param array $params
|
||||
* @return static
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
* @internal
|
||||
*
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function factory(array $params)
|
||||
public static function factory(array $params): static
|
||||
{
|
||||
$type = $params['type'] ?? null;
|
||||
|
||||
if (empty($type) === false && $class = (static::$models[$type] ?? null)) {
|
||||
if (
|
||||
empty($type) === false &&
|
||||
$class = (static::$models[$type] ?? null)
|
||||
) {
|
||||
$object = new $class($params);
|
||||
|
||||
if ($object instanceof self) {
|
||||
@@ -170,7 +145,7 @@ class Block extends Item
|
||||
}
|
||||
|
||||
// default model for blocks
|
||||
if ($class = (static::$models['Kirby\Cms\Block'] ?? null)) {
|
||||
if ($class = (static::$models['default'] ?? null)) {
|
||||
$object = new $class($params);
|
||||
|
||||
if ($object instanceof self) {
|
||||
@@ -183,8 +158,6 @@ class Block extends Item
|
||||
|
||||
/**
|
||||
* Checks if the block is empty
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
@@ -194,8 +167,6 @@ class Block extends Item
|
||||
/**
|
||||
* Checks if the block is hidden
|
||||
* from being rendered in the frontend
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isHidden(): bool
|
||||
{
|
||||
@@ -204,8 +175,6 @@ class Block extends Item
|
||||
|
||||
/**
|
||||
* Checks if the block is not empty
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isNotEmpty(): bool
|
||||
{
|
||||
@@ -214,18 +183,14 @@ class Block extends Item
|
||||
|
||||
/**
|
||||
* Returns the sibling collection that filtered by block status
|
||||
*
|
||||
* @return \Kirby\Cms\Collection
|
||||
*/
|
||||
protected function siblingsCollection()
|
||||
protected function siblingsCollection(): Blocks
|
||||
{
|
||||
return $this->siblings->filter('isHidden', $this->isHidden());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the block type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function type(): string
|
||||
{
|
||||
@@ -235,8 +200,6 @@ class Block extends Item
|
||||
/**
|
||||
* The result is being sent to the editor
|
||||
* via the API in the panel
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
@@ -253,24 +216,24 @@ class Block extends Item
|
||||
* and then places that inside a field
|
||||
* object. This can be used further
|
||||
* with all available field methods
|
||||
*
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
public function toField()
|
||||
public function toField(): Field
|
||||
{
|
||||
return new Field($this->parent(), $this->id(), $this->toHtml());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the block to HTML
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHtml(): string
|
||||
{
|
||||
try {
|
||||
$kirby = $this->parent()->kirby();
|
||||
return (string)$kirby->snippet('blocks/' . $this->type(), $this->controller(), true);
|
||||
return (string)$kirby->snippet(
|
||||
'blocks/' . $this->type(),
|
||||
$this->controller(),
|
||||
true
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
if ($kirby->option('debug') === true) {
|
||||
return '<p>Block error: "' . $e->getMessage() . '" in block type: "' . $this->type() . '"</p>';
|
||||
|
||||
@@ -64,8 +64,8 @@ class BlockConverter
|
||||
|
||||
foreach ($blocks as $index => $block) {
|
||||
if (in_array($block['type'], ['ul', 'ol']) === true) {
|
||||
$prev = $blocks[$index-1] ?? null;
|
||||
$next = $blocks[$index+1] ?? null;
|
||||
$prev = $blocks[$index - 1] ?? null;
|
||||
$next = $blocks[$index + 1] ?? null;
|
||||
|
||||
// new list starts here
|
||||
if (!$prev || $prev['type'] !== $block['type']) {
|
||||
|
||||
@@ -27,16 +27,12 @@ class Blocks extends Items
|
||||
|
||||
/**
|
||||
* All registered blocks methods
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $methods = [];
|
||||
public static array $methods = [];
|
||||
|
||||
/**
|
||||
* Return HTML when the collection is
|
||||
* converted to a string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
@@ -47,11 +43,8 @@ class Blocks extends Items
|
||||
* Converts the blocks to HTML and then
|
||||
* uses the Str::excerpt method to create
|
||||
* a non-formatted, shortened excerpt from it
|
||||
*
|
||||
* @param mixed ...$args
|
||||
* @return string
|
||||
*/
|
||||
public function excerpt(...$args)
|
||||
public function excerpt(mixed ...$args): string
|
||||
{
|
||||
return Str::excerpt($this->toHtml(), ...$args);
|
||||
}
|
||||
@@ -59,13 +52,11 @@ class Blocks extends Items
|
||||
/**
|
||||
* Wrapper around the factory to
|
||||
* catch blocks from layouts
|
||||
*
|
||||
* @param array $items
|
||||
* @param array $params
|
||||
* @return \Kirby\Cms\Blocks
|
||||
*/
|
||||
public static function factory(array $items = null, array $params = [])
|
||||
{
|
||||
public static function factory(
|
||||
array $items = null,
|
||||
array $params = []
|
||||
): static {
|
||||
$items = static::extractFromLayouts($items);
|
||||
|
||||
// @deprecated old editor format
|
||||
@@ -79,9 +70,6 @@ class Blocks extends Items
|
||||
|
||||
/**
|
||||
* Pull out blocks from layouts
|
||||
*
|
||||
* @param array $input
|
||||
* @return array
|
||||
*/
|
||||
protected static function extractFromLayouts(array $input): array
|
||||
{
|
||||
@@ -115,9 +103,6 @@ class Blocks extends Items
|
||||
/**
|
||||
* Checks if a given block type exists in the collection
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @param string $type
|
||||
* @return bool
|
||||
*/
|
||||
public function hasType(string $type): bool
|
||||
{
|
||||
@@ -126,11 +111,8 @@ class Blocks extends Items
|
||||
|
||||
/**
|
||||
* Parse and sanitize various block formats
|
||||
*
|
||||
* @param array|string $input
|
||||
* @return array
|
||||
*/
|
||||
public static function parse($input): array
|
||||
public static function parse(array|string|null $input): array
|
||||
{
|
||||
if (empty($input) === false && is_array($input) === false) {
|
||||
try {
|
||||
@@ -175,16 +157,10 @@ class Blocks extends Items
|
||||
|
||||
/**
|
||||
* Convert all blocks to HTML
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHtml(): string
|
||||
{
|
||||
$html = [];
|
||||
|
||||
foreach ($this->data as $block) {
|
||||
$html[] = $block->toHtml();
|
||||
}
|
||||
$html = A::map($this->data, fn ($block) => $block->toHtml());
|
||||
|
||||
return implode($html);
|
||||
}
|
||||
|
||||
@@ -36,12 +36,8 @@ class Blueprint
|
||||
|
||||
/**
|
||||
* Magic getter/caller for any blueprint prop
|
||||
*
|
||||
* @param string $key
|
||||
* @param array|null $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call(string $key, array $arguments = null)
|
||||
public function __call(string $key, array $arguments = null): mixed
|
||||
{
|
||||
return $this->props[$key] ?? null;
|
||||
}
|
||||
@@ -49,7 +45,6 @@ class Blueprint
|
||||
/**
|
||||
* Creates a new blueprint object with the given props
|
||||
*
|
||||
* @param array $props
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the blueprint model is missing
|
||||
*/
|
||||
public function __construct(array $props)
|
||||
@@ -77,7 +72,8 @@ class Blueprint
|
||||
$props['name'] ??= 'default';
|
||||
|
||||
// normalize and translate the title
|
||||
$props['title'] = $this->i18n($props['title'] ?? ucfirst($props['name']));
|
||||
$props['title'] ??= ucfirst($props['name']);
|
||||
$props['title'] = $this->i18n($props['title']);
|
||||
|
||||
// convert all shortcuts
|
||||
$props = $this->convertFieldsToSections('main', $props);
|
||||
@@ -93,7 +89,7 @@ class Blueprint
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
*
|
||||
* @return array
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
@@ -103,13 +99,11 @@ class Blueprint
|
||||
/**
|
||||
* Converts all column definitions, that
|
||||
* are not wrapped in a tab, into a generic tab
|
||||
*
|
||||
* @param string $tabName
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
protected function convertColumnsToTabs(string $tabName, array $props): array
|
||||
{
|
||||
protected function convertColumnsToTabs(
|
||||
string $tabName,
|
||||
array $props
|
||||
): array {
|
||||
if (isset($props['columns']) === false) {
|
||||
return $props;
|
||||
}
|
||||
@@ -130,13 +124,11 @@ class Blueprint
|
||||
* Converts all field definitions, that are not
|
||||
* wrapped in a fields section into a generic
|
||||
* fields section.
|
||||
*
|
||||
* @param string $tabName
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
protected function convertFieldsToSections(string $tabName, array $props): array
|
||||
{
|
||||
protected function convertFieldsToSections(
|
||||
string $tabName,
|
||||
array $props
|
||||
): array {
|
||||
if (isset($props['fields']) === false) {
|
||||
return $props;
|
||||
}
|
||||
@@ -157,13 +149,11 @@ class Blueprint
|
||||
/**
|
||||
* Converts all sections that are not wrapped in
|
||||
* columns, into a single generic column.
|
||||
*
|
||||
* @param string $tabName
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
protected function convertSectionsToColumns(string $tabName, array $props): array
|
||||
{
|
||||
protected function convertSectionsToColumns(
|
||||
string $tabName,
|
||||
array $props
|
||||
): array {
|
||||
if (isset($props['sections']) === false) {
|
||||
return $props;
|
||||
}
|
||||
@@ -187,7 +177,6 @@ class Blueprint
|
||||
* props is just a string
|
||||
*
|
||||
* @param array|string $props
|
||||
* @return array
|
||||
*/
|
||||
public static function extend($props): array
|
||||
{
|
||||
@@ -221,14 +210,12 @@ class Blueprint
|
||||
|
||||
/**
|
||||
* Create a new blueprint for a model
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|null $fallback
|
||||
* @param \Kirby\Cms\Model $model
|
||||
* @return static|null
|
||||
*/
|
||||
public static function factory(string $name, string $fallback = null, Model $model)
|
||||
{
|
||||
public static function factory(
|
||||
string $name,
|
||||
string $fallback = null,
|
||||
ModelWithContent $model
|
||||
): static|null {
|
||||
try {
|
||||
$props = static::load($name);
|
||||
} catch (Exception) {
|
||||
@@ -247,9 +234,6 @@ class Blueprint
|
||||
|
||||
/**
|
||||
* Returns a single field definition by name
|
||||
*
|
||||
* @param string $name
|
||||
* @return array|null
|
||||
*/
|
||||
public function field(string $name): array|null
|
||||
{
|
||||
@@ -258,8 +242,6 @@ class Blueprint
|
||||
|
||||
/**
|
||||
* Returns all field definitions
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fields(): array
|
||||
{
|
||||
@@ -269,8 +251,6 @@ class Blueprint
|
||||
/**
|
||||
* Find a blueprint by name
|
||||
*
|
||||
* @param string $name
|
||||
* @return array
|
||||
* @throws \Kirby\Exception\NotFoundException If the blueprint cannot be found
|
||||
*/
|
||||
public static function find(string $name): array
|
||||
@@ -283,8 +263,10 @@ class Blueprint
|
||||
$root = $kirby->root('blueprints');
|
||||
$file = $root . '/' . $name . '.yml';
|
||||
|
||||
// first try to find a site blueprint,
|
||||
// then check in the plugin extensions
|
||||
// first try to find the blueprint in the `site/blueprints` root,
|
||||
// then check in the plugin extensions which includes some default
|
||||
// core blueprints (e.g. page, file, site and block defaults)
|
||||
// as well as blueprints provided by plugins
|
||||
if (F::exists($file, $root) !== true) {
|
||||
$file = $kirby->extension('blueprints', $name);
|
||||
}
|
||||
@@ -311,20 +293,14 @@ class Blueprint
|
||||
|
||||
/**
|
||||
* Used to translate any label, heading, etc.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $fallback
|
||||
* @return mixed
|
||||
*/
|
||||
protected function i18n($value, $fallback = null)
|
||||
protected function i18n(mixed $value, mixed $fallback = null): mixed
|
||||
{
|
||||
return I18n::translate($value, $fallback ?? $value);
|
||||
return I18n::translate($value, $fallback) ?? $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this is the default blueprint
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDefault(): bool
|
||||
{
|
||||
@@ -333,44 +309,33 @@ class Blueprint
|
||||
|
||||
/**
|
||||
* Loads a blueprint from file or array
|
||||
*
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
public static function load(string $name): array
|
||||
{
|
||||
$props = static::find($name);
|
||||
|
||||
$normalize = function ($props) use ($name) {
|
||||
// inject the filename as name if no name is set
|
||||
$props['name'] ??= $name;
|
||||
// inject the filename as name if no name is set
|
||||
$props['name'] ??= $name;
|
||||
|
||||
// normalize the title
|
||||
$title = $props['title'] ?? ucfirst($props['name']);
|
||||
// normalize the title
|
||||
$title = $props['title'] ?? ucfirst($props['name']);
|
||||
|
||||
// translate the title
|
||||
$props['title'] = I18n::translate($title, $title);
|
||||
// translate the title
|
||||
$props['title'] = I18n::translate($title) ?? $title;
|
||||
|
||||
return $props;
|
||||
};
|
||||
|
||||
return $normalize($props);
|
||||
return $props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent model
|
||||
*
|
||||
* @return \Kirby\Cms\Model
|
||||
*/
|
||||
public function model()
|
||||
public function model(): ModelWithContent
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the blueprint name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
@@ -379,10 +344,6 @@ class Blueprint
|
||||
|
||||
/**
|
||||
* Normalizes all required props in a column setup
|
||||
*
|
||||
* @param string $tabName
|
||||
* @param array $columns
|
||||
* @return array
|
||||
*/
|
||||
protected function normalizeColumns(string $tabName, array $columns): array
|
||||
{
|
||||
@@ -415,10 +376,6 @@ class Blueprint
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $items
|
||||
* @return string
|
||||
*/
|
||||
public static function helpList(array $items): string
|
||||
{
|
||||
$md = [];
|
||||
@@ -434,7 +391,6 @@ class Blueprint
|
||||
* Normalize field props for a single field
|
||||
*
|
||||
* @param array|string $props
|
||||
* @return array
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the filed name is missing or the field type is invalid
|
||||
*/
|
||||
public static function fieldProps($props): array
|
||||
@@ -486,10 +442,6 @@ class Blueprint
|
||||
|
||||
/**
|
||||
* Creates an error field with the given error message
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $message
|
||||
* @return array
|
||||
*/
|
||||
public static function fieldError(string $name, string $message): array
|
||||
{
|
||||
@@ -505,9 +457,6 @@ class Blueprint
|
||||
/**
|
||||
* Normalizes all fields and adds automatic labels,
|
||||
* types and widths.
|
||||
*
|
||||
* @param array $fields
|
||||
* @return array
|
||||
*/
|
||||
public static function fieldsProps($fields): array
|
||||
{
|
||||
@@ -568,12 +517,12 @@ class Blueprint
|
||||
* constructor of an extended class, if you want to make use of it.
|
||||
*
|
||||
* @param array|true|false|null|string $options
|
||||
* @param array $defaults
|
||||
* @param array $aliases
|
||||
* @return array
|
||||
*/
|
||||
protected function normalizeOptions($options, array $defaults, array $aliases = []): array
|
||||
{
|
||||
protected function normalizeOptions(
|
||||
$options,
|
||||
array $defaults,
|
||||
array $aliases = []
|
||||
): array {
|
||||
// return defaults when options are not defined or set to true
|
||||
if ($options === true) {
|
||||
return $defaults;
|
||||
@@ -601,13 +550,11 @@ class Blueprint
|
||||
|
||||
/**
|
||||
* Normalizes all required keys in sections
|
||||
*
|
||||
* @param string $tabName
|
||||
* @param array $sections
|
||||
* @return array
|
||||
*/
|
||||
protected function normalizeSections(string $tabName, array $sections): array
|
||||
{
|
||||
protected function normalizeSections(
|
||||
string $tabName,
|
||||
array $sections
|
||||
): array {
|
||||
foreach ($sections as $sectionName => $sectionProps) {
|
||||
// unset / remove section if its property is false
|
||||
if ($sectionProps === false) {
|
||||
@@ -683,9 +630,6 @@ class Blueprint
|
||||
|
||||
/**
|
||||
* Normalizes all required keys in tabs
|
||||
*
|
||||
* @param array $tabs
|
||||
* @return array
|
||||
*/
|
||||
protected function normalizeTabs($tabs): array
|
||||
{
|
||||
@@ -723,9 +667,6 @@ class Blueprint
|
||||
|
||||
/**
|
||||
* Injects a blueprint preset
|
||||
*
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
protected function preset(array $props): array
|
||||
{
|
||||
@@ -748,11 +689,8 @@ class Blueprint
|
||||
|
||||
/**
|
||||
* Returns a single section by name
|
||||
*
|
||||
* @param string $name
|
||||
* @return \Kirby\Cms\Section|null
|
||||
*/
|
||||
public function section(string $name)
|
||||
public function section(string $name): Section|null
|
||||
{
|
||||
if (empty($this->sections[$name]) === true) {
|
||||
return null;
|
||||
@@ -770,8 +708,6 @@ class Blueprint
|
||||
|
||||
/**
|
||||
* Returns all sections
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function sections(): array
|
||||
{
|
||||
@@ -783,9 +719,6 @@ class Blueprint
|
||||
|
||||
/**
|
||||
* Returns a single tab by name
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return array|null
|
||||
*/
|
||||
public function tab(string|null $name = null): array|null
|
||||
{
|
||||
@@ -798,8 +731,6 @@ class Blueprint
|
||||
|
||||
/**
|
||||
* Returns all tabs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function tabs(): array
|
||||
{
|
||||
@@ -808,8 +739,6 @@ class Blueprint
|
||||
|
||||
/**
|
||||
* Returns the blueprint title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function title(): string
|
||||
{
|
||||
@@ -818,8 +747,6 @@ class Blueprint
|
||||
|
||||
/**
|
||||
* Converts the blueprint object to a plain array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
|
||||
@@ -72,9 +72,7 @@ class Collection extends BaseCollection
|
||||
* child classes can override it again to add validation
|
||||
* and custom behavior depending on the object type
|
||||
*
|
||||
* @param string $id
|
||||
* @param object $object
|
||||
* @return void
|
||||
*/
|
||||
public function __set(string $id, $object): void
|
||||
{
|
||||
@@ -142,7 +140,6 @@ class Collection extends BaseCollection
|
||||
/**
|
||||
* Find a single element by an attribute and its value
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @return mixed|null
|
||||
*/
|
||||
@@ -162,11 +159,11 @@ class Collection extends BaseCollection
|
||||
* with an item for each group and a collection for each group.
|
||||
*
|
||||
* @param string|Closure $field
|
||||
* @param bool $i Ignore upper/lowercase for group names
|
||||
* @param bool $caseInsensitive Ignore upper/lowercase for group names
|
||||
* @return \Kirby\Cms\Collection
|
||||
* @throws \Kirby\Exception\Exception
|
||||
*/
|
||||
public function group($field, bool $i = true)
|
||||
public function group($field, bool $caseInsensitive = true)
|
||||
{
|
||||
if (is_string($field) === true) {
|
||||
$groups = new Collection([], $this->parent());
|
||||
@@ -179,8 +176,12 @@ class Collection extends BaseCollection
|
||||
throw new InvalidArgumentException('Invalid grouping value for key: ' . $key);
|
||||
}
|
||||
|
||||
$value = (string)$value;
|
||||
|
||||
// ignore upper/lowercase for group names
|
||||
$value = $i === true ? Str::lower($value) : (string)$value;
|
||||
if ($caseInsensitive === true) {
|
||||
$value = Str::lower($value);
|
||||
}
|
||||
|
||||
if (isset($groups->data[$value]) === false) {
|
||||
// create a new entry for the group if it does not exist yet
|
||||
@@ -194,7 +195,7 @@ class Collection extends BaseCollection
|
||||
return $groups;
|
||||
}
|
||||
|
||||
return parent::group($field, $i);
|
||||
return parent::group($field, $caseInsensitive);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,7 +203,6 @@ class Collection extends BaseCollection
|
||||
* is in the collection
|
||||
*
|
||||
* @param string|object $key
|
||||
* @return bool
|
||||
*/
|
||||
public function has($key): bool
|
||||
{
|
||||
@@ -219,7 +219,6 @@ class Collection extends BaseCollection
|
||||
* or ids and then search accordingly.
|
||||
*
|
||||
* @param string|object $needle
|
||||
* @return int|false
|
||||
*/
|
||||
public function indexOf($needle): int|false
|
||||
{
|
||||
@@ -261,20 +260,31 @@ class Collection extends BaseCollection
|
||||
* Add pagination and return a sliced set of data.
|
||||
*
|
||||
* @param mixed ...$arguments
|
||||
* @return \Kirby\Cms\Collection
|
||||
* @return $this|static
|
||||
*/
|
||||
public function paginate(...$arguments)
|
||||
{
|
||||
$this->pagination = Pagination::for($this, ...$arguments);
|
||||
|
||||
// slice and clone the collection according to the pagination
|
||||
return $this->slice($this->pagination->offset(), $this->pagination->limit());
|
||||
return $this->slice(
|
||||
$this->pagination->offset(),
|
||||
$this->pagination->limit()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pagination object
|
||||
*
|
||||
* @return \Kirby\Cms\Pagination|null
|
||||
*/
|
||||
public function pagination()
|
||||
{
|
||||
return $this->pagination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent model
|
||||
*
|
||||
* @return \Kirby\Cms\Model
|
||||
*/
|
||||
public function parent()
|
||||
{
|
||||
@@ -311,7 +321,6 @@ class Collection extends BaseCollection
|
||||
* offset, limit, search and paginate on the collection.
|
||||
* Any part of the query is optional.
|
||||
*
|
||||
* @param array $arguments
|
||||
* @return static
|
||||
*/
|
||||
public function query(array $arguments = [])
|
||||
@@ -354,13 +363,11 @@ class Collection extends BaseCollection
|
||||
|
||||
/**
|
||||
* Searches the collection
|
||||
*
|
||||
* @param string|null $query
|
||||
* @param array $params
|
||||
* @return self
|
||||
*/
|
||||
public function search(string $query = null, $params = [])
|
||||
{
|
||||
public function search(
|
||||
string $query = null,
|
||||
string|array $params = []
|
||||
): static {
|
||||
return Search::collection($this, $query, $params);
|
||||
}
|
||||
|
||||
@@ -368,9 +375,6 @@ class Collection extends BaseCollection
|
||||
* Converts all objects in the collection
|
||||
* to an array. This can also take a callback
|
||||
* function to further modify the array result.
|
||||
*
|
||||
* @param \Closure|null $map
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(Closure $map = null): array
|
||||
{
|
||||
|
||||
@@ -52,8 +52,8 @@ class Collections
|
||||
* Loads a collection by name if registered
|
||||
*
|
||||
* @return \Kirby\Toolkit\Collection|null
|
||||
* @todo 4.0 Add deprecation warning when anything else than a Collection is returned
|
||||
* @todo 5.0 Add return type declaration
|
||||
* @todo 5.0 Add deprecation warning when anything else than a Collection is returned
|
||||
* @todo 6.0 Add PHP return type declaration for `Toolkit\Collection`
|
||||
*/
|
||||
public function get(string $name, array $data = [])
|
||||
{
|
||||
@@ -61,11 +61,9 @@ class Collections
|
||||
$this->collections[$name] ??= $this->load($name);
|
||||
|
||||
// if not yet cached
|
||||
if (
|
||||
isset($this->cache[$name]) === false ||
|
||||
$this->cache[$name]['data'] !== $data
|
||||
) {
|
||||
if (($this->cache[$name]['data'] ?? null) !== $data) {
|
||||
$controller = new Controller($this->collections[$name]);
|
||||
|
||||
$this->cache[$name] = [
|
||||
'result' => $controller->call(null, $data),
|
||||
'data' => $data
|
||||
@@ -82,9 +80,6 @@ class Collections
|
||||
|
||||
/**
|
||||
* Checks if a collection exists
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function has(string $name): bool
|
||||
{
|
||||
@@ -104,11 +99,9 @@ class Collections
|
||||
* Loads collection from php file in a
|
||||
* given directory or from plugin extension.
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
* @throws \Kirby\Exception\NotFoundException
|
||||
*/
|
||||
public function load(string $name)
|
||||
public function load(string $name): mixed
|
||||
{
|
||||
$kirby = App::instance();
|
||||
|
||||
@@ -126,10 +119,7 @@ class Collections
|
||||
// fallback to collections from plugins
|
||||
$collections = $kirby->extensions('collections');
|
||||
|
||||
if (isset($collections[$name]) === true) {
|
||||
return $collections[$name];
|
||||
}
|
||||
|
||||
throw new NotFoundException('The collection cannot be found');
|
||||
return $collections[$name] ??
|
||||
throw new NotFoundException('The collection cannot be found');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Exception\AuthException;
|
||||
use Kirby\Exception\DuplicateException;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Exception\PermissionException;
|
||||
|
||||
/**
|
||||
* Takes care of content lock and unlock information
|
||||
@@ -17,33 +17,16 @@ use Kirby\Exception\PermissionException;
|
||||
*/
|
||||
class ContentLock
|
||||
{
|
||||
/**
|
||||
* Lock data
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data;
|
||||
protected array $data;
|
||||
|
||||
/**
|
||||
* The model to manage locking/unlocking for
|
||||
*
|
||||
* @var ModelWithContent
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* @param \Kirby\Cms\ModelWithContent $model
|
||||
*/
|
||||
public function __construct(ModelWithContent $model)
|
||||
{
|
||||
$this->model = $model;
|
||||
$this->data = $this->kirby()->locks()->get($model);
|
||||
public function __construct(
|
||||
protected ModelWithContent $model
|
||||
) {
|
||||
$this->data = $this->kirby()->locks()->get($model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the lock unconditionally
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function clearLock(): bool
|
||||
{
|
||||
@@ -61,7 +44,6 @@ class ContentLock
|
||||
/**
|
||||
* Sets lock with the current user
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\DuplicateException
|
||||
*/
|
||||
public function create(): bool
|
||||
@@ -86,10 +68,8 @@ class ContentLock
|
||||
/**
|
||||
* Returns either `false` or array with `user`, `email`,
|
||||
* `time` and `unlockable` keys
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
public function get()
|
||||
public function get(): array|bool
|
||||
{
|
||||
$data = $this->data['lock'] ?? [];
|
||||
|
||||
@@ -114,8 +94,6 @@ class ContentLock
|
||||
|
||||
/**
|
||||
* Returns if the model is locked by another user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isLocked(): bool
|
||||
{
|
||||
@@ -130,8 +108,6 @@ class ContentLock
|
||||
|
||||
/**
|
||||
* Returns if the current user's lock has been removed by another user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isUnlocked(): bool
|
||||
{
|
||||
@@ -142,8 +118,6 @@ class ContentLock
|
||||
|
||||
/**
|
||||
* Returns the app instance
|
||||
*
|
||||
* @return \Kirby\Cms\App
|
||||
*/
|
||||
protected function kirby(): App
|
||||
{
|
||||
@@ -153,7 +127,6 @@ class ContentLock
|
||||
/**
|
||||
* Removes lock of current user
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\LogicException
|
||||
*/
|
||||
public function remove(): bool
|
||||
@@ -176,8 +149,6 @@ class ContentLock
|
||||
|
||||
/**
|
||||
* Removes unlock information for current user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function resolve(): bool
|
||||
{
|
||||
@@ -199,7 +170,7 @@ class ContentLock
|
||||
* Returns the state for the
|
||||
* form buttons in the frontend
|
||||
*/
|
||||
public function state(): ?string
|
||||
public function state(): string|null
|
||||
{
|
||||
return match (true) {
|
||||
$this->isUnlocked() => 'unlock',
|
||||
@@ -211,8 +182,6 @@ class ContentLock
|
||||
/**
|
||||
* Returns a usable lock array
|
||||
* for the frontend
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
@@ -224,8 +193,6 @@ class ContentLock
|
||||
|
||||
/**
|
||||
* Removes current lock and adds lock user to unlock data
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function unlock(): bool
|
||||
{
|
||||
@@ -245,12 +212,11 @@ class ContentLock
|
||||
* Returns currently authenticated user;
|
||||
* throws exception if none is authenticated
|
||||
*
|
||||
* @return \Kirby\Cms\User
|
||||
* @throws \Kirby\Exception\PermissionException
|
||||
*/
|
||||
protected function user(): User
|
||||
{
|
||||
return $this->kirby()->user() ??
|
||||
throw new PermissionException('No user authenticated.');
|
||||
throw new AuthException('No user authenticated.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,18 +22,14 @@ class ContentLocks
|
||||
* Data from the `.lock` files
|
||||
* that have been read so far
|
||||
* cached by `.lock` file path
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data = [];
|
||||
protected array $data = [];
|
||||
|
||||
/**
|
||||
* PHP file handles for all currently
|
||||
* open `.lock` files
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $handles = [];
|
||||
protected array $handles = [];
|
||||
|
||||
/**
|
||||
* Closes the open file handles
|
||||
@@ -50,11 +46,9 @@ class ContentLocks
|
||||
/**
|
||||
* Removes the file lock and closes the file handle
|
||||
*
|
||||
* @param string $file
|
||||
* @return void
|
||||
* @throws \Kirby\Exception\Exception
|
||||
*/
|
||||
protected function closeHandle(string $file)
|
||||
protected function closeHandle(string $file): void
|
||||
{
|
||||
if (isset($this->handles[$file]) === false) {
|
||||
return;
|
||||
@@ -72,20 +66,15 @@ class ContentLocks
|
||||
|
||||
/**
|
||||
* Returns the path to a model's lock file
|
||||
*
|
||||
* @param \Kirby\Cms\ModelWithContent $model
|
||||
* @return string
|
||||
*/
|
||||
public static function file(ModelWithContent $model): string
|
||||
{
|
||||
return $model->contentFileDirectory() . '/.lock';
|
||||
$root = $model::CLASS_ALIAS === 'file' ? dirname($model->root()) : $model->root();
|
||||
return $root . '/.lock';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lock/unlock data for the specified model
|
||||
*
|
||||
* @param \Kirby\Cms\ModelWithContent $model
|
||||
* @return array
|
||||
*/
|
||||
public function get(ModelWithContent $model): array
|
||||
{
|
||||
@@ -121,7 +110,6 @@ class ContentLocks
|
||||
/**
|
||||
* Returns the file handle to a `.lock` file
|
||||
*
|
||||
* @param string $file
|
||||
* @param bool $create Whether to create the file if it does not exist
|
||||
* @return resource|null File handle
|
||||
* @throws \Kirby\Exception\Exception
|
||||
@@ -155,9 +143,6 @@ class ContentLocks
|
||||
/**
|
||||
* Returns model ID used as the key for the data array;
|
||||
* prepended with a slash because the $site otherwise won't have an ID
|
||||
*
|
||||
* @param \Kirby\Cms\ModelWithContent $model
|
||||
* @return string
|
||||
*/
|
||||
public static function id(ModelWithContent $model): string
|
||||
{
|
||||
@@ -167,9 +152,6 @@ class ContentLocks
|
||||
/**
|
||||
* Sets and writes the lock/unlock data for the specified model
|
||||
*
|
||||
* @param \Kirby\Cms\ModelWithContent $model
|
||||
* @param array $data
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\Exception
|
||||
*/
|
||||
public function set(ModelWithContent $model, array $data): bool
|
||||
|
||||
@@ -2,6 +2,15 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Cache\ApcuCache;
|
||||
use Kirby\Cache\FileCache;
|
||||
use Kirby\Cache\MemCached;
|
||||
use Kirby\Cache\MemoryCache;
|
||||
use Kirby\Cms\Auth\EmailChallenge;
|
||||
use Kirby\Cms\Auth\TotpChallenge;
|
||||
use Kirby\Form\Field\BlocksField;
|
||||
use Kirby\Form\Field\LayoutField;
|
||||
|
||||
/**
|
||||
* The Core class lists all parts of Kirby
|
||||
* that need to be loaded or initalized in order
|
||||
@@ -22,13 +31,11 @@ namespace Kirby\Cms;
|
||||
class Core
|
||||
{
|
||||
protected array $cache = [];
|
||||
protected App $kirby;
|
||||
protected string $root;
|
||||
|
||||
public function __construct(App $kirby)
|
||||
public function __construct(protected App $kirby)
|
||||
{
|
||||
$this->kirby = $kirby;
|
||||
$this->root = dirname(__DIR__, 2) . '/config';
|
||||
$this->root = dirname(__DIR__, 2) . '/config';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,9 +59,11 @@ class Core
|
||||
return [
|
||||
'account' => $this->root . '/areas/account.php',
|
||||
'installation' => $this->root . '/areas/installation.php',
|
||||
'lab' => $this->root . '/areas/lab.php',
|
||||
'languages' => $this->root . '/areas/languages.php',
|
||||
'login' => $this->root . '/areas/login.php',
|
||||
'logout' => $this->root . '/areas/logout.php',
|
||||
'search' => $this->root . '/areas/search.php',
|
||||
'site' => $this->root . '/areas/site.php',
|
||||
'system' => $this->root . '/areas/system.php',
|
||||
'users' => $this->root . '/areas/users.php',
|
||||
@@ -67,7 +76,8 @@ class Core
|
||||
public function authChallenges(): array
|
||||
{
|
||||
return [
|
||||
'email' => 'Kirby\Cms\Auth\EmailChallenge'
|
||||
'email' => EmailChallenge::class,
|
||||
'totp' => TotpChallenge::class,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -86,9 +96,9 @@ class Core
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all paths to core blueprints
|
||||
* Returns a list of paths to core blueprints or
|
||||
* the blueprint in array form
|
||||
*
|
||||
* They are located in `/kirby/config/blueprints`.
|
||||
* Block blueprints are located in `/kirby/config/blocks`
|
||||
*/
|
||||
public function blueprints(): array
|
||||
@@ -108,13 +118,21 @@ class Core
|
||||
'blocks/video' => $this->root . '/blocks/video/video.yml',
|
||||
|
||||
// file blueprints
|
||||
'files/default' => $this->root . '/blueprints/files/default.yml',
|
||||
'files/default' => ['title' => 'File'],
|
||||
|
||||
// page blueprints
|
||||
'pages/default' => $this->root . '/blueprints/pages/default.yml',
|
||||
'pages/default' => ['title' => 'Page'],
|
||||
|
||||
// site blueprints
|
||||
'site' => $this->root . '/blueprints/site.yml'
|
||||
'site' => [
|
||||
'title' => 'Site',
|
||||
'sections' => [
|
||||
'pages' => [
|
||||
'headline' => ['*' => 'pages'],
|
||||
'type' => 'pages'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
@@ -135,10 +153,10 @@ class Core
|
||||
public function cacheTypes(): array
|
||||
{
|
||||
return [
|
||||
'apcu' => 'Kirby\Cache\ApcuCache',
|
||||
'file' => 'Kirby\Cache\FileCache',
|
||||
'memcached' => 'Kirby\Cache\MemCached',
|
||||
'memory' => 'Kirby\Cache\MemoryCache',
|
||||
'apcu' => ApcuCache::class,
|
||||
'file' => FileCache::class,
|
||||
'memcached' => MemCached::class,
|
||||
'memory' => MemoryCache::class,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -216,8 +234,9 @@ class Core
|
||||
public function fields(): array
|
||||
{
|
||||
return [
|
||||
'blocks' => 'Kirby\Form\Field\BlocksField',
|
||||
'blocks' => BlocksField::class,
|
||||
'checkboxes' => $this->root . '/fields/checkboxes.php',
|
||||
'color' => $this->root . '/fields/color.php',
|
||||
'date' => $this->root . '/fields/date.php',
|
||||
'email' => $this->root . '/fields/email.php',
|
||||
'files' => $this->root . '/fields/files.php',
|
||||
@@ -225,8 +244,9 @@ class Core
|
||||
'headline' => $this->root . '/fields/headline.php',
|
||||
'hidden' => $this->root . '/fields/hidden.php',
|
||||
'info' => $this->root . '/fields/info.php',
|
||||
'layout' => 'Kirby\Form\Field\LayoutField',
|
||||
'layout' => LayoutField::class,
|
||||
'line' => $this->root . '/fields/line.php',
|
||||
'link' => $this->root . '/fields/link.php',
|
||||
'list' => $this->root . '/fields/list.php',
|
||||
'multiselect' => $this->root . '/fields/multiselect.php',
|
||||
'number' => $this->root . '/fields/number.php',
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Template\Template;
|
||||
|
||||
/**
|
||||
* Wrapper around our Email package, which
|
||||
@@ -21,18 +22,14 @@ class Email
|
||||
{
|
||||
/**
|
||||
* Options configured through the `email` CMS option
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
protected array $options;
|
||||
|
||||
/**
|
||||
* Props for the email object; will be passed to the
|
||||
* Kirby\Email\Email class
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $props;
|
||||
protected array $props;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
@@ -40,9 +37,9 @@ class Email
|
||||
* @param string|array $preset Preset name from the config or a simple props array
|
||||
* @param array $props Props array to override the $preset
|
||||
*/
|
||||
public function __construct($preset = [], array $props = [])
|
||||
public function __construct(string|array $preset = [], array $props = [])
|
||||
{
|
||||
$this->options = App::instance()->option('email');
|
||||
$this->options = App::instance()->option('email', []);
|
||||
|
||||
// build a prop array based on preset and props
|
||||
$preset = $this->preset($preset);
|
||||
@@ -71,10 +68,9 @@ class Email
|
||||
* prop arrays in case a preset is not needed
|
||||
*
|
||||
* @param string|array $preset Preset name or simple prop array
|
||||
* @return array
|
||||
* @throws \Kirby\Exception\NotFoundException
|
||||
*/
|
||||
protected function preset($preset): array
|
||||
protected function preset(string|array $preset): array
|
||||
{
|
||||
// only passed props, not preset name
|
||||
if (is_array($preset) === true) {
|
||||
@@ -96,7 +92,6 @@ class Email
|
||||
* Renders the email template(s) and sets the body props
|
||||
* to the result
|
||||
*
|
||||
* @return void
|
||||
* @throws \Kirby\Exception\NotFoundException
|
||||
*/
|
||||
protected function template(): void
|
||||
@@ -129,20 +124,14 @@ class Email
|
||||
|
||||
/**
|
||||
* Returns an email template by name and type
|
||||
*
|
||||
* @param string $name Template name
|
||||
* @param string|null $type `html` or `text`
|
||||
* @return \Kirby\Template\Template
|
||||
*/
|
||||
protected function getTemplate(string $name, string $type = null)
|
||||
protected function getTemplate(string $name, string $type = null): Template
|
||||
{
|
||||
return App::instance()->template('emails/' . $name, $type, 'text');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the prop array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
@@ -154,11 +143,10 @@ class Email
|
||||
* supports simple strings, file objects or collections/arrays of either
|
||||
*
|
||||
* @param string $prop Prop to transform
|
||||
* @return void
|
||||
*/
|
||||
protected function transformFile(string $prop): void
|
||||
{
|
||||
$this->props[$prop] = $this->transformModel($prop, 'Kirby\Cms\File', 'root');
|
||||
$this->props[$prop] = $this->transformModel($prop, File::class, 'root');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,8 +159,12 @@ class Email
|
||||
* returns a simple value-only array if not given
|
||||
* @return array Simple key-value or just value array with the transformed prop data
|
||||
*/
|
||||
protected function transformModel(string $prop, string $class, string $contentValue, string $contentKey = null): array
|
||||
{
|
||||
protected function transformModel(
|
||||
string $prop,
|
||||
string $class,
|
||||
string $contentValue,
|
||||
string $contentKey = null
|
||||
): array {
|
||||
$value = $this->props[$prop] ?? [];
|
||||
|
||||
// ensure consistent input by making everything an iterable value
|
||||
@@ -212,11 +204,12 @@ class Email
|
||||
*
|
||||
* @param string $addressProp Prop with the email address
|
||||
* @param string $nameProp Prop with the name corresponding to the $addressProp
|
||||
* @return void
|
||||
*/
|
||||
protected function transformUserSingle(string $addressProp, string $nameProp): void
|
||||
{
|
||||
$result = $this->transformModel($addressProp, 'Kirby\Cms\User', 'name', 'email');
|
||||
protected function transformUserSingle(
|
||||
string $addressProp,
|
||||
string $nameProp
|
||||
): void {
|
||||
$result = $this->transformModel($addressProp, User::class, 'name', 'email');
|
||||
|
||||
$address = array_keys($result)[0] ?? null;
|
||||
$name = $result[$address] ?? null;
|
||||
@@ -239,10 +232,9 @@ class Email
|
||||
* supports simple strings, user objects or collections/arrays of either
|
||||
*
|
||||
* @param string $prop Prop to transform
|
||||
* @return void
|
||||
*/
|
||||
protected function transformUserMultiple(string $prop): void
|
||||
{
|
||||
$this->props[$prop] = $this->transformModel($prop, 'Kirby\Cms\User', 'name', 'email');
|
||||
$this->props[$prop] = $this->transformModel($prop, User::class, 'name', 'email');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,41 +24,31 @@ class Event
|
||||
/**
|
||||
* The full event name
|
||||
* (e.g. `page.create:after`)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
protected string $name;
|
||||
|
||||
/**
|
||||
* The event type
|
||||
* (e.g. `page` in `page.create:after`)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
protected string $type;
|
||||
|
||||
/**
|
||||
* The event action
|
||||
* (e.g. `create` in `page.create:after`)
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $action;
|
||||
protected string|null $action;
|
||||
|
||||
/**
|
||||
* The event state
|
||||
* (e.g. `after` in `page.create:after`)
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $state;
|
||||
protected string|null $state;
|
||||
|
||||
/**
|
||||
* The event arguments
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
protected array $arguments = [];
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
@@ -83,20 +73,15 @@ class Event
|
||||
|
||||
/**
|
||||
* Magic caller for event arguments
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call(string $method, array $arguments = [])
|
||||
public function __call(string $method, array $arguments = []): mixed
|
||||
{
|
||||
return $this->argument($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
*
|
||||
* @return array
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
@@ -106,8 +91,6 @@ class Event
|
||||
/**
|
||||
* Makes it possible to simply echo
|
||||
* or stringify the entire object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
@@ -117,8 +100,6 @@ class Event
|
||||
/**
|
||||
* Returns the action of the event (e.g. `create`)
|
||||
* or `null` if the event name does not include an action
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function action(): string|null
|
||||
{
|
||||
@@ -127,19 +108,14 @@ class Event
|
||||
|
||||
/**
|
||||
* Returns a specific event argument
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function argument(string $name)
|
||||
public function argument(string $name): mixed
|
||||
{
|
||||
return $this->arguments[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the arguments of the event
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function arguments(): array
|
||||
{
|
||||
@@ -151,10 +127,8 @@ class Event
|
||||
* the hook's return value
|
||||
*
|
||||
* @param object|null $bind Optional object to bind to the hook function
|
||||
* @param \Closure $hook
|
||||
* @return mixed
|
||||
*/
|
||||
public function call(object|null $bind, Closure $hook)
|
||||
public function call(object|null $bind, Closure $hook): mixed
|
||||
{
|
||||
// collect the list of possible hook arguments
|
||||
$data = $this->arguments();
|
||||
@@ -167,8 +141,6 @@ class Event
|
||||
|
||||
/**
|
||||
* Returns the full name of the event
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
@@ -178,13 +150,16 @@ class Event
|
||||
/**
|
||||
* Returns the full list of possible wildcard
|
||||
* event names based on the current event name
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function nameWildcards(): array
|
||||
{
|
||||
// if the event is already a wildcard event, no further variation is possible
|
||||
if ($this->type === '*' || $this->action === '*' || $this->state === '*') {
|
||||
// if the event is already a wildcard event,
|
||||
// no further variation is possible
|
||||
if (
|
||||
$this->type === '*' ||
|
||||
$this->action === '*' ||
|
||||
$this->state === '*'
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -228,8 +203,6 @@ class Event
|
||||
|
||||
/**
|
||||
* Returns the state of the event (e.g. `after`)
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function state(): string|null
|
||||
{
|
||||
@@ -238,8 +211,6 @@ class Event
|
||||
|
||||
/**
|
||||
* Returns the event data as array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
@@ -251,8 +222,6 @@ class Event
|
||||
|
||||
/**
|
||||
* Returns the event name as string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
@@ -261,8 +230,6 @@ class Event
|
||||
|
||||
/**
|
||||
* Returns the type of the event (e.g. `page`)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function type(): string
|
||||
{
|
||||
@@ -273,9 +240,6 @@ class Event
|
||||
* Updates a given argument with a new value
|
||||
*
|
||||
* @internal
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function updateArgument(string $name, $value): void
|
||||
|
||||
@@ -21,24 +21,21 @@ class Fieldset extends Item
|
||||
{
|
||||
public const ITEMS_CLASS = Fieldsets::class;
|
||||
|
||||
protected $disabled;
|
||||
protected $editable;
|
||||
protected $fields = [];
|
||||
protected $icon;
|
||||
protected $label;
|
||||
protected $model;
|
||||
protected $name;
|
||||
protected $preview;
|
||||
protected $tabs;
|
||||
protected $translate;
|
||||
protected $type;
|
||||
protected $unset;
|
||||
protected $wysiwyg;
|
||||
protected bool $disabled;
|
||||
protected bool $editable;
|
||||
protected array $fields = [];
|
||||
protected string|null $icon;
|
||||
protected string|null $label;
|
||||
protected string|null $name;
|
||||
protected string|bool|null $preview;
|
||||
protected array $tabs;
|
||||
protected bool $translate;
|
||||
protected string $type;
|
||||
protected bool $unset;
|
||||
protected bool $wysiwyg;
|
||||
|
||||
/**
|
||||
* Creates a new Fieldset object
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function __construct(array $params = [])
|
||||
{
|
||||
@@ -50,17 +47,17 @@ class Fieldset extends Item
|
||||
|
||||
parent::__construct($params);
|
||||
|
||||
$this->disabled = $params['disabled'] ?? false;
|
||||
$this->editable = $params['editable'] ?? true;
|
||||
$this->icon = $params['icon'] ?? null;
|
||||
$this->model = $this->parent;
|
||||
$this->name = $this->createName($params['title'] ?? $params['name'] ?? Str::ucfirst($this->type));
|
||||
$this->label = $this->createLabel($params['label'] ?? null);
|
||||
$this->preview = $params['preview'] ?? null;
|
||||
$this->tabs = $this->createTabs($params);
|
||||
$this->translate = $params['translate'] ?? true;
|
||||
$this->unset = $params['unset'] ?? false;
|
||||
$this->wysiwyg = $params['wysiwyg'] ?? false;
|
||||
$this->disabled = $params['disabled'] ?? false;
|
||||
$this->editable = $params['editable'] ?? true;
|
||||
$this->icon = $params['icon'] ?? null;
|
||||
$params['title'] ??= $params['name'] ?? Str::ucfirst($this->type);
|
||||
$this->name = $this->createName($params['title']);
|
||||
$this->label = $this->createLabel($params['label'] ?? null);
|
||||
$this->preview = $params['preview'] ?? null;
|
||||
$this->tabs = $this->createTabs($params);
|
||||
$this->translate = $params['translate'] ?? true;
|
||||
$this->unset = $params['unset'] ?? false;
|
||||
$this->wysiwyg = $params['wysiwyg'] ?? false;
|
||||
|
||||
if (
|
||||
$this->translate === false &&
|
||||
@@ -73,10 +70,6 @@ class Fieldset extends Item
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $fields
|
||||
* @return array
|
||||
*/
|
||||
protected function createFields(array $fields = []): array
|
||||
{
|
||||
$fields = Blueprint::fieldsProps($fields);
|
||||
@@ -88,28 +81,16 @@ class Fieldset extends Item
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $name
|
||||
* @return string|null
|
||||
*/
|
||||
protected function createName($name): string|null
|
||||
protected function createName(array|string $name): string|null
|
||||
{
|
||||
return I18n::translate($name, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $label
|
||||
* @return string|null
|
||||
*/
|
||||
protected function createLabel($label = null): string|null
|
||||
protected function createLabel(array|string|null $label = null): string|null
|
||||
{
|
||||
return I18n::translate($label, $label);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
protected function createTabs(array $params = []): array
|
||||
{
|
||||
$tabs = $params['tabs'] ?? [];
|
||||
@@ -133,9 +114,10 @@ class Fieldset extends Item
|
||||
|
||||
$tab = Blueprint::extend($tab);
|
||||
|
||||
$tab['fields'] = $this->createFields($tab['fields'] ?? []);
|
||||
$tab['label'] = $this->createLabel($tab['label'] ?? Str::ucfirst($name));
|
||||
$tab['name'] = $name;
|
||||
$tab['fields'] = $this->createFields($tab['fields'] ?? []);
|
||||
$tab['label'] ??= Str::ucfirst($name);
|
||||
$tab['label'] = $this->createLabel($tab['label']);
|
||||
$tab['name'] = $name;
|
||||
|
||||
$tabs[$name] = $tab;
|
||||
}
|
||||
@@ -143,17 +125,11 @@ class Fieldset extends Item
|
||||
return $tabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function disabled(): bool
|
||||
{
|
||||
return $this->disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function editable(): bool
|
||||
{
|
||||
if ($this->editable === false) {
|
||||
@@ -167,9 +143,6 @@ class Fieldset extends Item
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function fields(): array
|
||||
{
|
||||
return $this->fields;
|
||||
@@ -177,88 +150,57 @@ class Fieldset extends Item
|
||||
|
||||
/**
|
||||
* Creates a form for the given fields
|
||||
*
|
||||
* @param array $fields
|
||||
* @param array $input
|
||||
* @return \Kirby\Form\Form
|
||||
*/
|
||||
public function form(array $fields, array $input = [])
|
||||
public function form(array $fields, array $input = []): Form
|
||||
{
|
||||
return new Form([
|
||||
'fields' => $fields,
|
||||
'model' => $this->model,
|
||||
'model' => $this->parent,
|
||||
'strict' => true,
|
||||
'values' => $input,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function icon(): string|null
|
||||
{
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function label(): string|null
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Kirby\Cms\ModelWithContent
|
||||
*/
|
||||
public function model()
|
||||
public function model(): ModelWithContent
|
||||
{
|
||||
return $this->model;
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|bool
|
||||
*/
|
||||
public function preview()
|
||||
public function preview(): string|bool|null
|
||||
{
|
||||
return $this->preview;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function tabs(): array
|
||||
{
|
||||
return $this->tabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function translate(): bool
|
||||
{
|
||||
return $this->translate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function type(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
@@ -276,17 +218,11 @@ class Fieldset extends Item
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function unset(): bool
|
||||
{
|
||||
return $this->unset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function wysiwyg(): bool
|
||||
{
|
||||
return $this->wysiwyg;
|
||||
|
||||
@@ -23,15 +23,13 @@ class Fieldsets extends Items
|
||||
|
||||
/**
|
||||
* All registered fieldsets methods
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $methods = [];
|
||||
public static array $methods = [];
|
||||
|
||||
protected static function createFieldsets($params)
|
||||
protected static function createFieldsets(array $params): array
|
||||
{
|
||||
$fieldsets = [];
|
||||
$groups = [];
|
||||
$groups = [];
|
||||
|
||||
foreach ($params as $type => $fieldset) {
|
||||
if (is_int($type) === true && is_string($fieldset)) {
|
||||
@@ -75,8 +73,10 @@ class Fieldsets extends Items
|
||||
];
|
||||
}
|
||||
|
||||
public static function factory(array $items = null, array $params = [])
|
||||
{
|
||||
public static function factory(
|
||||
array $items = null,
|
||||
array $params = []
|
||||
): static {
|
||||
$items ??= App::instance()->option('blocks.fieldsets', [
|
||||
'code' => 'blocks/code',
|
||||
'gallery' => 'blocks/gallery',
|
||||
@@ -92,7 +92,10 @@ class Fieldsets extends Items
|
||||
|
||||
$result = static::createFieldsets($items);
|
||||
|
||||
return parent::factory($result['fieldsets'], ['groups' => $result['groups']] + $params);
|
||||
return parent::factory(
|
||||
$result['fieldsets'],
|
||||
['groups' => $result['groups']] + $params
|
||||
);
|
||||
}
|
||||
|
||||
public function groups(): array
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Exception;
|
||||
use IntlDateFormatter;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Filesystem\IsFile;
|
||||
use Kirby\Panel\File as Panel;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
@@ -38,61 +40,65 @@ class File extends ModelWithContent
|
||||
|
||||
public const CLASS_ALIAS = 'file';
|
||||
|
||||
/**
|
||||
* Cache for the initialized blueprint object
|
||||
*
|
||||
* @var \Kirby\Cms\FileBlueprint
|
||||
*/
|
||||
protected $blueprint;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $filename;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* All registered file methods
|
||||
*
|
||||
* @var array
|
||||
* @todo Remove when support for PHP 8.2 is dropped
|
||||
*/
|
||||
public static $methods = [];
|
||||
public static array $methods = [];
|
||||
|
||||
/**
|
||||
* Cache for the initialized blueprint object
|
||||
*/
|
||||
protected FileBlueprint|null $blueprint = null;
|
||||
|
||||
protected string $filename;
|
||||
|
||||
protected string $id;
|
||||
|
||||
/**
|
||||
* The parent object
|
||||
*
|
||||
* @var \Kirby\Cms\Model
|
||||
*/
|
||||
protected $parent;
|
||||
protected Page|Site|User|null $parent = null;
|
||||
|
||||
/**
|
||||
* The absolute path to the file
|
||||
*/
|
||||
protected string|null $root = null;
|
||||
protected string|null $root;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $template;
|
||||
protected string|null $template;
|
||||
|
||||
/**
|
||||
* The public file Url
|
||||
*/
|
||||
protected string|null $url = null;
|
||||
protected string|null $url;
|
||||
|
||||
/**
|
||||
* Creates a new File object
|
||||
*/
|
||||
public function __construct(array $props)
|
||||
{
|
||||
parent::__construct($props);
|
||||
|
||||
if (isset($props['filename'], $props['parent']) === false) {
|
||||
throw new InvalidArgumentException('The filename and parent are required');
|
||||
}
|
||||
|
||||
$this->filename = $props['filename'];
|
||||
$this->parent = $props['parent'];
|
||||
$this->template = $props['template'] ?? null;
|
||||
// Always set the root to null, to invoke
|
||||
// auto root detection
|
||||
$this->root = null;
|
||||
$this->url = $props['url'] ?? null;
|
||||
|
||||
$this->setBlueprint($props['blueprint'] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic caller for file methods
|
||||
* and content fields. (in this order)
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call(string $method, array $arguments = [])
|
||||
public function __call(string $method, array $arguments = []): mixed
|
||||
{
|
||||
// public property access
|
||||
if (isset($this->$method) === true) {
|
||||
@@ -113,25 +119,8 @@ class File extends ModelWithContent
|
||||
return $this->content()->get($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new File object
|
||||
*
|
||||
* @param array $props
|
||||
*/
|
||||
public function __construct(array $props)
|
||||
{
|
||||
// set filename as the most important prop first
|
||||
// TODO: refactor later to avoid redundant prop setting
|
||||
$this->setProperty('filename', $props['filename'] ?? null, true);
|
||||
|
||||
// set other properties
|
||||
$this->setProperties($props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
@@ -143,10 +132,7 @@ class File extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns the url to api endpoint
|
||||
*
|
||||
* @internal
|
||||
* @param bool $relative
|
||||
* @return string
|
||||
*/
|
||||
public function apiUrl(bool $relative = false): string
|
||||
{
|
||||
@@ -155,22 +141,143 @@ class File extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns the FileBlueprint object for the file
|
||||
*
|
||||
* @return \Kirby\Cms\FileBlueprint
|
||||
*/
|
||||
public function blueprint()
|
||||
public function blueprint(): FileBlueprint
|
||||
{
|
||||
if ($this->blueprint instanceof FileBlueprint) {
|
||||
return $this->blueprint;
|
||||
return $this->blueprint ??= FileBlueprint::factory(
|
||||
'files/' . $this->template(),
|
||||
'files/default',
|
||||
$this
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with all blueprints that are available for the file
|
||||
* by comparing files sections and files fields of the parent model
|
||||
*/
|
||||
public function blueprints(string $inSection = null): array
|
||||
{
|
||||
if ($inSection === null && $this->blueprints !== null) {
|
||||
return $this->blueprints; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return $this->blueprint = FileBlueprint::factory('files/' . $this->template(), 'files/default', $this);
|
||||
// always include the current template as option
|
||||
$template = $this->template() ?? 'default';
|
||||
$templates = [$template];
|
||||
$parent = $this->parent();
|
||||
|
||||
// what file templates/blueprints should be considered is
|
||||
// defined bythe parent's blueprint: which templates it allows
|
||||
// in files sections as well as files fields
|
||||
$blueprint = $parent->blueprint();
|
||||
|
||||
$fromFields = function ($fields) use (&$fromFields, $parent) {
|
||||
$templates = [];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
// files or textare field
|
||||
if (
|
||||
$field['type'] === 'files' ||
|
||||
$field['type'] === 'textarea'
|
||||
) {
|
||||
$uploads = $field['uploads'] ?? null;
|
||||
|
||||
// only if the `uploads` parent is the actual parent
|
||||
if ($target = $uploads['parent'] ?? null) {
|
||||
if ($parent->id() !== $target) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$templates[] = $uploads['template'] ?? 'default';
|
||||
continue;
|
||||
}
|
||||
|
||||
// structure field
|
||||
if ($field['type'] === 'structure') {
|
||||
$fields = $fromFields($field['fields']);
|
||||
$templates = array_merge($templates, $fields);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $templates;
|
||||
};
|
||||
|
||||
// collect all allowed templates…
|
||||
foreach ($blueprint->sections() as $section) {
|
||||
// if collecting for a specific section, skip all others
|
||||
if ($inSection !== null && $section->name() !== $inSection) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// …from files sections
|
||||
if ($section->type() === 'files') {
|
||||
$templates[] = $section->template() ?? 'default';
|
||||
continue;
|
||||
}
|
||||
|
||||
// …from fields
|
||||
if ($section->type() === 'fields') {
|
||||
$fields = $fromFields($section->fields());
|
||||
$templates = array_merge($templates, $fields);
|
||||
}
|
||||
}
|
||||
|
||||
// make sure every template is only included once
|
||||
$templates = array_unique(array_filter($templates));
|
||||
|
||||
// load the blueprint details for each collected template name
|
||||
$blueprints = [];
|
||||
|
||||
foreach ($templates as $template) {
|
||||
// default template doesn't need to exist as file
|
||||
// to be included in the list
|
||||
if ($template === 'default') {
|
||||
$blueprints[$template] = [
|
||||
'name' => 'default',
|
||||
'title' => '– (default)',
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($blueprint = FileBlueprint::factory('files/' . $template, null, $this)) {
|
||||
try {
|
||||
// ensure that file matches `accept` option,
|
||||
// if not remove template from available list
|
||||
$this->match($blueprint->accept());
|
||||
|
||||
$blueprints[$template] = [
|
||||
'name' => $name = Str::after($blueprint->name(), '/'),
|
||||
'title' => $blueprint->title() . ' (' . $name . ')',
|
||||
];
|
||||
} catch (Exception) {
|
||||
// skip when `accept` doesn't match
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$blueprints = array_values($blueprints);
|
||||
|
||||
// sort blueprints alphabetically while
|
||||
// making sure the default blueprint is on top of list
|
||||
usort($blueprints, fn ($a, $b) => match (true) {
|
||||
$a['name'] === 'default' => -1,
|
||||
$b['name'] === 'default' => 1,
|
||||
default => strnatcmp($a['title'], $b['title'])
|
||||
});
|
||||
|
||||
// no caching for when collecting for specific section
|
||||
if ($inSection !== null) {
|
||||
return $blueprints; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return $this->blueprints = $blueprints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the template in addition to the
|
||||
* other content.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function contentFileData(
|
||||
@@ -192,42 +299,41 @@ class File extends ModelWithContent
|
||||
/**
|
||||
* Returns the directory in which
|
||||
* the content file is located
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
* @deprecated 4.0.0
|
||||
* @todo Remove in v5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function contentFileDirectory(): string
|
||||
{
|
||||
Helpers::deprecated('The internal $model->contentFileDirectory() method has been deprecated. Please let us know via a GitHub issue if you need this method and tell us your use case.', 'model-content-file');
|
||||
return dirname($this->root());
|
||||
}
|
||||
|
||||
/**
|
||||
* Filename for the content file
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
* @deprecated 4.0.0
|
||||
* @todo Remove in v5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function contentFileName(): string
|
||||
{
|
||||
Helpers::deprecated('The internal $model->contentFileName() method has been deprecated. Please let us know via a GitHub issue if you need this method and tell us your use case.', 'model-content-file');
|
||||
return $this->filename();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a File object
|
||||
*
|
||||
* @internal
|
||||
* @param mixed $props
|
||||
* @return static
|
||||
*/
|
||||
public static function factory($props)
|
||||
public static function factory(array $props): static
|
||||
{
|
||||
return new static($props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filename with extension
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function filename(): string
|
||||
{
|
||||
@@ -236,19 +342,14 @@ class File extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns the parent Files collection
|
||||
*
|
||||
* @return \Kirby\Cms\Files
|
||||
*/
|
||||
public function files()
|
||||
public function files(): Files
|
||||
{
|
||||
return $this->siblingsCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the file to html
|
||||
*
|
||||
* @param array $attr
|
||||
* @return string
|
||||
*/
|
||||
public function html(array $attr = []): string
|
||||
{
|
||||
@@ -260,55 +361,91 @@ class File extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns the id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function id(): string
|
||||
{
|
||||
if ($this->id !== null) {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
if (
|
||||
$this->parent() instanceof Page ||
|
||||
$this->parent() instanceof User
|
||||
) {
|
||||
return $this->id = $this->parent()->id() . '/' . $this->filename();
|
||||
return $this->id ??= $this->parent()->id() . '/' . $this->filename();
|
||||
}
|
||||
|
||||
return $this->id = $this->filename();
|
||||
return $this->id ??= $this->filename();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the current object with the given file object
|
||||
*
|
||||
* @param \Kirby\Cms\File $file
|
||||
* @return bool
|
||||
*/
|
||||
public function is(File $file): bool
|
||||
{
|
||||
return $this->id() === $file->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the files is accessible.
|
||||
* This permission depends on the `read` option until v5
|
||||
*/
|
||||
public function isAccessible(): bool
|
||||
{
|
||||
// TODO: remove this check when `read` option deprecated in v5
|
||||
if ($this->isReadable() === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static $accessible = [];
|
||||
|
||||
if ($template = $this->template()) {
|
||||
return $accessible[$template] ??= $this->permissions()->can('access');
|
||||
}
|
||||
|
||||
return $accessible['__none__'] ??= $this->permissions()->can('access');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file can be listable by the current user
|
||||
* This permission depends on the `read` option until v5
|
||||
*/
|
||||
public function isListable(): bool
|
||||
{
|
||||
// TODO: remove this check when `read` option deprecated in v5
|
||||
if ($this->isReadable() === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// not accessible also means not listable
|
||||
if ($this->isAccessible() === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static $listable = [];
|
||||
|
||||
if ($template = $this->template()) {
|
||||
return $listable[$template] ??= $this->permissions()->can('list');
|
||||
}
|
||||
|
||||
return $listable['__none__'] ??= $this->permissions()->can('list');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file can be read by the current user
|
||||
*
|
||||
* @return bool
|
||||
* @todo Deprecate `read` option in v5 and make the necessary changes for `access` and `list` options.
|
||||
*/
|
||||
public function isReadable(): bool
|
||||
{
|
||||
static $readable = [];
|
||||
|
||||
$template = $this->template();
|
||||
if ($template = $this->template()) {
|
||||
return $readable[$template] ??= $this->permissions()->can('read');
|
||||
}
|
||||
|
||||
return $readable[$template] ??= $this->permissions()->can('read');
|
||||
return $readable['__none__'] ??= $this->permissions()->can('read');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique media hash
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function mediaHash(): string
|
||||
{
|
||||
@@ -317,9 +454,7 @@ class File extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns the absolute path to the file in the public media folder
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function mediaRoot(): string
|
||||
{
|
||||
@@ -328,9 +463,7 @@ class File extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Creates a non-guessable token string for this file
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function mediaToken(): string
|
||||
{
|
||||
@@ -340,9 +473,7 @@ class File extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns the absolute Url to the file in the public media folder
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function mediaUrl(): string
|
||||
{
|
||||
@@ -352,17 +483,16 @@ class File extends ModelWithContent
|
||||
/**
|
||||
* Get the file's last modification time.
|
||||
*
|
||||
* @param string|\IntlDateFormatter|null $format
|
||||
* @param string|null $handler date, intl or strftime
|
||||
* @param string|null $languageCode
|
||||
* @return mixed
|
||||
*/
|
||||
public function modified($format = null, string $handler = null, string $languageCode = null)
|
||||
{
|
||||
public function modified(
|
||||
string|IntlDateFormatter|null $format = null,
|
||||
string|null $handler = null,
|
||||
string|null $languageCode = null
|
||||
): string|int|false {
|
||||
$file = $this->modifiedFile();
|
||||
$content = $this->modifiedContent($languageCode);
|
||||
$modified = max($file, $content);
|
||||
$handler ??= $this->kirby()->option('date.handler', 'date');
|
||||
|
||||
return Str::date($modified, $format, $handler);
|
||||
}
|
||||
@@ -370,20 +500,15 @@ class File extends ModelWithContent
|
||||
/**
|
||||
* Timestamp of the last modification
|
||||
* of the content file
|
||||
*
|
||||
* @param string|null $languageCode
|
||||
* @return int
|
||||
*/
|
||||
protected function modifiedContent(string $languageCode = null): int
|
||||
{
|
||||
return F::modified($this->contentFile($languageCode));
|
||||
return $this->storage()->modified('published', $languageCode) ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamp of the last modification
|
||||
* of the source file
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function modifiedFile(): int
|
||||
{
|
||||
@@ -392,10 +517,8 @@ class File extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns the parent Page object
|
||||
*
|
||||
* @return \Kirby\Cms\Page|null
|
||||
*/
|
||||
public function page()
|
||||
public function page(): Page|null
|
||||
{
|
||||
if ($this->parent() instanceof Page) {
|
||||
return $this->parent();
|
||||
@@ -406,29 +529,23 @@ class File extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns the panel info object
|
||||
*
|
||||
* @return \Kirby\Panel\File
|
||||
*/
|
||||
public function panel()
|
||||
public function panel(): Panel
|
||||
{
|
||||
return new Panel($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent Model object
|
||||
*
|
||||
* @return \Kirby\Cms\Model
|
||||
* Returns the parent object
|
||||
*/
|
||||
public function parent()
|
||||
public function parent(): Page|Site|User
|
||||
{
|
||||
return $this->parent ??= $this->kirby()->site();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent id if a parent exists
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function parentId(): string
|
||||
{
|
||||
@@ -437,13 +554,14 @@ class File extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns a collection of all parent pages
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function parents()
|
||||
public function parents(): Pages
|
||||
{
|
||||
if ($this->parent() instanceof Page) {
|
||||
return $this->parent()->parents()->prepend($this->parent()->id(), $this->parent());
|
||||
return $this->parent()->parents()->prepend(
|
||||
$this->parent()->id(),
|
||||
$this->parent()
|
||||
);
|
||||
}
|
||||
|
||||
return new Pages();
|
||||
@@ -460,18 +578,14 @@ class File extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns the permissions object for this file
|
||||
*
|
||||
* @return \Kirby\Cms\FilePermissions
|
||||
*/
|
||||
public function permissions()
|
||||
public function permissions(): FilePermissions
|
||||
{
|
||||
return new FilePermissions($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute root to the file
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function root(): string|null
|
||||
{
|
||||
@@ -481,10 +595,8 @@ class File extends ModelWithContent
|
||||
/**
|
||||
* Returns the FileRules class to
|
||||
* validate any important action.
|
||||
*
|
||||
* @return \Kirby\Cms\FileRules
|
||||
*/
|
||||
protected function rules()
|
||||
protected function rules(): FileRules
|
||||
{
|
||||
return new FileRules();
|
||||
}
|
||||
@@ -492,10 +604,9 @@ class File extends ModelWithContent
|
||||
/**
|
||||
* Sets the Blueprint object
|
||||
*
|
||||
* @param array|null $blueprint
|
||||
* @return $this
|
||||
*/
|
||||
protected function setBlueprint(array $blueprint = null)
|
||||
protected function setBlueprint(array $blueprint = null): static
|
||||
{
|
||||
if ($blueprint !== null) {
|
||||
$blueprint['model'] = $this;
|
||||
@@ -505,82 +616,19 @@ class File extends ModelWithContent
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filename
|
||||
*
|
||||
* @param string $filename
|
||||
* @return $this
|
||||
*/
|
||||
protected function setFilename(string $filename)
|
||||
{
|
||||
$this->filename = $filename;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parent model object
|
||||
*
|
||||
* @param \Kirby\Cms\Model $parent
|
||||
* @return $this
|
||||
*/
|
||||
protected function setParent(Model $parent)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always set the root to null, to invoke
|
||||
* auto root detection
|
||||
*
|
||||
* @param string|null $root
|
||||
* @return $this
|
||||
*/
|
||||
protected function setRoot(string $root = null)
|
||||
{
|
||||
$this->root = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $template
|
||||
* @return $this
|
||||
*/
|
||||
protected function setTemplate(string $template = null)
|
||||
{
|
||||
$this->template = $template;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the url
|
||||
*
|
||||
* @param string|null $url
|
||||
* @return $this
|
||||
*/
|
||||
protected function setUrl(string $url = null)
|
||||
{
|
||||
$this->url = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent Files collection
|
||||
* @internal
|
||||
*
|
||||
* @return \Kirby\Cms\Files
|
||||
*/
|
||||
protected function siblingsCollection()
|
||||
protected function siblingsCollection(): Files
|
||||
{
|
||||
return $this->parent()->files();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent Site object
|
||||
*
|
||||
* @return \Kirby\Cms\Site
|
||||
*/
|
||||
public function site()
|
||||
public function site(): Site
|
||||
{
|
||||
if ($this->parent() instanceof Site) {
|
||||
return $this->parent();
|
||||
@@ -591,8 +639,6 @@ class File extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns the final template
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function template(): string|null
|
||||
{
|
||||
@@ -601,11 +647,8 @@ class File extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns siblings with the same template
|
||||
*
|
||||
* @param bool $self
|
||||
* @return \Kirby\Cms\Files
|
||||
*/
|
||||
public function templateSiblings(bool $self = true)
|
||||
public function templateSiblings(bool $self = true): Files
|
||||
{
|
||||
return $this->siblings($self)->filter('template', $this->template());
|
||||
}
|
||||
@@ -614,18 +657,17 @@ class File extends ModelWithContent
|
||||
* Extended info for the array export
|
||||
* by injecting the information from
|
||||
* the asset.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return array_merge($this->asset()->toArray(), parent::toArray());
|
||||
return array_merge(parent::toArray(), $this->asset()->toArray(), [
|
||||
'id' => $this->id(),
|
||||
'template' => $this->template(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function url(): string
|
||||
{
|
||||
@@ -636,8 +678,6 @@ class File extends ModelWithContent
|
||||
* Simplified File URL that uses the parent
|
||||
* Page URL and the filename as a more stable
|
||||
* alternative for the media URLs.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function previewUrl(): string
|
||||
{
|
||||
|
||||
@@ -21,29 +21,49 @@ use Kirby\Uuid\Uuids;
|
||||
*/
|
||||
trait FileActions
|
||||
{
|
||||
protected function changeExtension(
|
||||
File $file,
|
||||
string|null $extension = null
|
||||
): File {
|
||||
if (
|
||||
$extension === null ||
|
||||
$extension === $file->extension()
|
||||
) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return $file->changeName($file->name(), false, $extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the file without touching the extension
|
||||
* Renames the file (optionally also the extension).
|
||||
* The store is used to actually execute this.
|
||||
*
|
||||
* @param string $name
|
||||
* @param bool $sanitize
|
||||
* @return $this|static
|
||||
* @throws \Kirby\Exception\LogicException
|
||||
*/
|
||||
public function changeName(string $name, bool $sanitize = true)
|
||||
{
|
||||
public function changeName(
|
||||
string $name,
|
||||
bool $sanitize = true,
|
||||
string|null $extension = null
|
||||
): static {
|
||||
if ($sanitize === true) {
|
||||
$name = F::safeName($name);
|
||||
}
|
||||
|
||||
// if no extension is passed, make sure to maintain current one
|
||||
$extension ??= $this->extension();
|
||||
|
||||
// don't rename if not necessary
|
||||
if ($name === $this->name()) {
|
||||
if (
|
||||
$name === $this->name() &&
|
||||
$extension === $this->extension()
|
||||
) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->commit('changeName', ['file' => $this, 'name' => $name], function ($oldFile, $name) {
|
||||
return $this->commit('changeName', ['file' => $this, 'name' => $name, 'extension' => $extension], function ($oldFile, $name, $extension) {
|
||||
$newFile = $oldFile->clone([
|
||||
'filename' => $name . '.' . $oldFile->extension(),
|
||||
'filename' => $name . '.' . $extension,
|
||||
]);
|
||||
|
||||
// remove all public versions, lock and clear UUID cache
|
||||
@@ -60,16 +80,11 @@ trait FileActions
|
||||
// rename the main file
|
||||
F::move($oldFile->root(), $newFile->root());
|
||||
|
||||
if ($newFile->kirby()->multilang() === true) {
|
||||
foreach ($newFile->translations() as $translation) {
|
||||
$translationCode = $translation->code();
|
||||
|
||||
// rename the content file
|
||||
F::move($oldFile->contentFile($translationCode), $newFile->contentFile($translationCode));
|
||||
}
|
||||
} else {
|
||||
// rename the content file
|
||||
F::move($oldFile->contentFile(), $newFile->contentFile());
|
||||
// move the content storage versions
|
||||
foreach ($oldFile->storage()->all() as $version => $lang) {
|
||||
$content = $oldFile->storage()->read($version, $lang);
|
||||
$oldFile->storage()->delete($version, $lang);
|
||||
$newFile->storage()->create($version, $lang, $content);
|
||||
}
|
||||
|
||||
// update collections
|
||||
@@ -82,11 +97,8 @@ trait FileActions
|
||||
|
||||
/**
|
||||
* Changes the file's sorting number in the meta file
|
||||
*
|
||||
* @param int $sort
|
||||
* @return static
|
||||
*/
|
||||
public function changeSort(int $sort)
|
||||
public function changeSort(int $sort): static
|
||||
{
|
||||
return $this->commit(
|
||||
'changeSort',
|
||||
@@ -95,6 +107,40 @@ trait FileActions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this|static
|
||||
*/
|
||||
public function changeTemplate(string|null $template): static
|
||||
{
|
||||
if ($template === $this->template()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$arguments = [
|
||||
'file' => $this,
|
||||
'template' => $template ?? 'default'
|
||||
];
|
||||
|
||||
return $this->commit('changeTemplate', $arguments, function ($oldFile, $template) {
|
||||
// convert to new template/blueprint incl. content
|
||||
$file = $oldFile->convertTo($template);
|
||||
|
||||
// update template, prefer unset over writing `default`
|
||||
if ($template === 'default') {
|
||||
$template = null;
|
||||
}
|
||||
|
||||
$file = $file->update(['template' => $template]);
|
||||
|
||||
// rename and/or resize the file if configured by new blueprint
|
||||
$create = $file->blueprint()->create();
|
||||
$file = $file->changeExtension($file, $create['format'] ?? null);
|
||||
$file->manipulate($create);
|
||||
|
||||
return $file;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits a file action, by following these steps
|
||||
*
|
||||
@@ -103,14 +149,12 @@ trait FileActions
|
||||
* 3. commits the store action
|
||||
* 4. sends the after hook
|
||||
* 5. returns the result
|
||||
*
|
||||
* @param string $action
|
||||
* @param array $arguments
|
||||
* @param Closure $callback
|
||||
* @return mixed
|
||||
*/
|
||||
protected function commit(string $action, array $arguments, Closure $callback)
|
||||
{
|
||||
protected function commit(
|
||||
string $action,
|
||||
array $arguments,
|
||||
Closure $callback
|
||||
): mixed {
|
||||
$old = $this->hardcopy();
|
||||
$kirby = $this->kirby();
|
||||
$argumentValues = array_values($arguments);
|
||||
@@ -134,24 +178,19 @@ trait FileActions
|
||||
|
||||
/**
|
||||
* Copy the file to the given page
|
||||
*
|
||||
* @param \Kirby\Cms\Page $page
|
||||
* @return \Kirby\Cms\File
|
||||
*/
|
||||
public function copy(Page $page)
|
||||
public function copy(Page $page): static
|
||||
{
|
||||
F::copy($this->root(), $page->root() . '/' . $this->filename());
|
||||
$copy = $page->clone()->file($this->filename());
|
||||
|
||||
if ($this->kirby()->multilang() === true) {
|
||||
foreach ($this->kirby()->languages() as $language) {
|
||||
$contentFile = $this->contentFile($language->code());
|
||||
F::copy($contentFile, $page->root() . '/' . basename($contentFile));
|
||||
}
|
||||
} else {
|
||||
$contentFile = $this->contentFile();
|
||||
F::copy($contentFile, $page->root() . '/' . basename($contentFile));
|
||||
foreach ($this->storage()->all() as $version => $lang) {
|
||||
$content = $this->storage()->read($version, $lang);
|
||||
$copy->storage()->create($version, $lang, $content);
|
||||
}
|
||||
|
||||
// ensure the content is re-read after copying it
|
||||
// @todo find a more elegant way
|
||||
$copy = $page->clone()->file($this->filename());
|
||||
|
||||
// overwrite with new UUID (remove old, add new)
|
||||
@@ -168,13 +207,11 @@ trait FileActions
|
||||
* writing, so it can be replaced by any other
|
||||
* way of generating files.
|
||||
*
|
||||
* @param array $props
|
||||
* @param bool $move If set to `true`, the source will be deleted
|
||||
* @return static
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
* @throws \Kirby\Exception\LogicException
|
||||
*/
|
||||
public static function create(array $props, bool $move = false)
|
||||
public static function create(array $props, bool $move = false): File
|
||||
{
|
||||
if (isset($props['source'], $props['parent']) === false) {
|
||||
throw new InvalidArgumentException('Please provide the "source" and "parent" props for the File');
|
||||
@@ -204,9 +241,15 @@ trait FileActions
|
||||
// inject the content
|
||||
$file = $file->clone(['content' => $form->strings(true)]);
|
||||
|
||||
// if the format is different from the original,
|
||||
// we need to already rename it so that the correct file rules
|
||||
// are applied
|
||||
$create = $file->blueprint()->create();
|
||||
$file = $file->changeExtension($file, $create['format'] ?? null);
|
||||
|
||||
// run the hook
|
||||
$arguments = compact('file', 'upload');
|
||||
return $file->commit('create', $arguments, function ($file, $upload) use ($move) {
|
||||
return $file->commit('create', $arguments, function ($file, $upload) use ($create, $move) {
|
||||
// remove all public versions, lock and clear UUID cache
|
||||
$file->unpublish();
|
||||
|
||||
@@ -218,15 +261,15 @@ trait FileActions
|
||||
throw new LogicException('The file could not be created');
|
||||
}
|
||||
|
||||
// always create pages in the default language
|
||||
if ($file->kirby()->multilang() === true) {
|
||||
$languageCode = $file->kirby()->defaultLanguage()->code();
|
||||
} else {
|
||||
$languageCode = null;
|
||||
}
|
||||
// resize the file on upload if configured
|
||||
$file = $file->manipulate($create);
|
||||
|
||||
// store the content if necessary
|
||||
$file->save($file->content()->toArray(), $languageCode);
|
||||
// (always create files in the default language)
|
||||
$file->save(
|
||||
$file->content()->toArray(),
|
||||
$file->kirby()->defaultLanguage()?->code()
|
||||
);
|
||||
|
||||
// add the file to the list of siblings
|
||||
$file->siblings()->append($file->id(), $file);
|
||||
@@ -239,8 +282,6 @@ trait FileActions
|
||||
/**
|
||||
* Deletes the file. The store is used to
|
||||
* manipulate the filesystem or whatever you prefer.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete(): bool
|
||||
{
|
||||
@@ -248,12 +289,8 @@ trait FileActions
|
||||
// remove all public versions, lock and clear UUID cache
|
||||
$file->unpublish();
|
||||
|
||||
if ($file->kirby()->multilang() === true) {
|
||||
foreach ($file->translations() as $translation) {
|
||||
F::remove($file->contentFile($translation->code()));
|
||||
}
|
||||
} else {
|
||||
F::remove($file->contentFile());
|
||||
foreach ($file->storage()->all() as $version => $lang) {
|
||||
$file->storage()->delete($version, $lang);
|
||||
}
|
||||
|
||||
F::remove($file->root());
|
||||
@@ -265,13 +302,29 @@ trait FileActions
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes/crops the original file with Kirby's thumb handler
|
||||
*/
|
||||
public function manipulate(array|null $options = []): static
|
||||
{
|
||||
// nothing to process
|
||||
if (empty($options) === true || $this->isResizable() === false) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
// generate image file and overwrite it in place
|
||||
$this->kirby()->thumb($this->root(), $this->root(), $options);
|
||||
|
||||
return $this->clone([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the file to the public media folder
|
||||
* if it's not already there.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function publish()
|
||||
public function publish(): static
|
||||
{
|
||||
Media::publish($this, $this->mediaRoot());
|
||||
return $this;
|
||||
@@ -284,12 +337,10 @@ trait FileActions
|
||||
* finally decides what it will support as
|
||||
* source.
|
||||
*
|
||||
* @param string $source
|
||||
* @param bool $move If set to `true`, the source will be deleted
|
||||
* @return static
|
||||
* @throws \Kirby\Exception\LogicException
|
||||
*/
|
||||
public function replace(string $source, bool $move = false)
|
||||
public function replace(string $source, bool $move = false): static
|
||||
{
|
||||
$file = $this->clone();
|
||||
|
||||
@@ -310,6 +361,11 @@ trait FileActions
|
||||
throw new LogicException('The file could not be created');
|
||||
}
|
||||
|
||||
// apply the resizing/crop options from the blueprint
|
||||
$create = $file->blueprint()->create();
|
||||
$file = $file->changeExtension($file, $create['format'] ?? null);
|
||||
$file = $file->manipulate($create);
|
||||
|
||||
// return a fresh clone
|
||||
return $file->clone();
|
||||
});
|
||||
@@ -317,15 +373,13 @@ trait FileActions
|
||||
|
||||
/**
|
||||
* Stores the content on disk
|
||||
*
|
||||
* @internal
|
||||
* @param array|null $data
|
||||
* @param string|null $languageCode
|
||||
* @param bool $overwrite
|
||||
* @return static
|
||||
*/
|
||||
public function save(array $data = null, string $languageCode = null, bool $overwrite = false)
|
||||
{
|
||||
public function save(
|
||||
array $data = null,
|
||||
string $languageCode = null,
|
||||
bool $overwrite = false
|
||||
): static {
|
||||
$file = parent::save($data, $languageCode, $overwrite);
|
||||
|
||||
// update model in siblings collection
|
||||
@@ -339,7 +393,7 @@ trait FileActions
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function unpublish(bool $onlyMedia = false)
|
||||
public function unpublish(bool $onlyMedia = false): static
|
||||
{
|
||||
// unpublish media files
|
||||
Media::unpublish($this->parent()->mediaRoot(), $this);
|
||||
@@ -354,4 +408,23 @@ trait FileActions
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the file's data and ensures that
|
||||
* media files get wiped if `focus` changed
|
||||
*
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the input array contains invalid values
|
||||
*/
|
||||
public function update(
|
||||
array $input = null,
|
||||
string $languageCode = null,
|
||||
bool $validate = false
|
||||
): static {
|
||||
// delete all public media versions when focus field gets changed
|
||||
if (($input['focus'] ?? null) !== $this->focus()->value()) {
|
||||
$this->unpublish(true);
|
||||
}
|
||||
|
||||
return parent::update($input, $languageCode, $validate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,8 @@ class FileBlueprint extends Blueprint
|
||||
/**
|
||||
* `true` if the default accepted
|
||||
* types are being used
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $defaultTypes = false;
|
||||
protected bool $defaultTypes = false;
|
||||
|
||||
public function __construct(array $props)
|
||||
{
|
||||
@@ -35,12 +33,15 @@ class FileBlueprint extends Blueprint
|
||||
$this->props['options'] ?? true,
|
||||
// defaults
|
||||
[
|
||||
'changeName' => null,
|
||||
'create' => null,
|
||||
'delete' => null,
|
||||
'read' => null,
|
||||
'replace' => null,
|
||||
'update' => null,
|
||||
'access' => null,
|
||||
'changeName' => null,
|
||||
'changeTemplate' => null,
|
||||
'create' => null,
|
||||
'delete' => null,
|
||||
'list' => null,
|
||||
'read' => null,
|
||||
'replace' => null,
|
||||
'update' => null,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -48,9 +49,6 @@ class FileBlueprint extends Blueprint
|
||||
$this->props['accept'] = $this->normalizeAccept($this->props['accept'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function accept(): array
|
||||
{
|
||||
return $this->props['accept'];
|
||||
@@ -59,8 +57,6 @@ class FileBlueprint extends Blueprint
|
||||
/**
|
||||
* Returns the list of all accepted MIME types for
|
||||
* file upload or `*` if all MIME types are allowed
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function acceptMime(): string
|
||||
{
|
||||
@@ -120,11 +116,7 @@ class FileBlueprint extends Blueprint
|
||||
return '*';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $accept
|
||||
* @return array
|
||||
*/
|
||||
protected function normalizeAccept($accept = null): array
|
||||
protected function normalizeAccept(mixed $accept = null): array
|
||||
{
|
||||
$accept = match (true) {
|
||||
is_string($accept) => ['mime' => $accept],
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Content\Field;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Filesystem\Asset;
|
||||
|
||||
@@ -18,37 +19,30 @@ trait FileModifications
|
||||
{
|
||||
/**
|
||||
* Blurs the image by the given amount of pixels
|
||||
*
|
||||
* @param bool $pixels
|
||||
* @return \Kirby\Cms\FileVersion|\Kirby\Cms\File
|
||||
*/
|
||||
public function blur($pixels = true)
|
||||
public function blur(int|bool $pixels = true): FileVersion|File|Asset
|
||||
{
|
||||
return $this->thumb(['blur' => $pixels]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the image to black and white
|
||||
*
|
||||
* @return \Kirby\Cms\FileVersion|\Kirby\Cms\File
|
||||
*/
|
||||
public function bw()
|
||||
public function bw(): FileVersion|File|Asset
|
||||
{
|
||||
return $this->thumb(['grayscale' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crops the image by the given width and height
|
||||
*
|
||||
* @param int $width
|
||||
* @param int|null $height
|
||||
* @param string|array $options
|
||||
* @return \Kirby\Cms\FileVersion|\Kirby\Cms\File
|
||||
*/
|
||||
public function crop(int $width, int $height = null, $options = null)
|
||||
{
|
||||
public function crop(
|
||||
int $width,
|
||||
int $height = null,
|
||||
$options = null
|
||||
): FileVersion|File|Asset {
|
||||
$quality = null;
|
||||
$crop = 'center';
|
||||
$crop = true;
|
||||
|
||||
if (is_int($options) === true) {
|
||||
$quality = $options;
|
||||
@@ -71,31 +65,24 @@ trait FileModifications
|
||||
|
||||
/**
|
||||
* Alias for File::bw()
|
||||
*
|
||||
* @return \Kirby\Cms\FileVersion|\Kirby\Cms\File
|
||||
*/
|
||||
public function grayscale()
|
||||
public function grayscale(): FileVersion|File|Asset
|
||||
{
|
||||
return $this->thumb(['grayscale' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for File::bw()
|
||||
*
|
||||
* @return \Kirby\Cms\FileVersion|\Kirby\Cms\File
|
||||
*/
|
||||
public function greyscale()
|
||||
public function greyscale(): FileVersion|File|Asset
|
||||
{
|
||||
return $this->thumb(['grayscale' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the JPEG compression quality
|
||||
*
|
||||
* @param int $quality
|
||||
* @return \Kirby\Cms\FileVersion|\Kirby\Cms\File
|
||||
*/
|
||||
public function quality(int $quality)
|
||||
public function quality(int $quality): FileVersion|File|Asset
|
||||
{
|
||||
return $this->thumb(['quality' => $quality]);
|
||||
}
|
||||
@@ -104,14 +91,13 @@ trait FileModifications
|
||||
* Resizes the file with the given width and height
|
||||
* while keeping the aspect ratio.
|
||||
*
|
||||
* @param int|null $width
|
||||
* @param int|null $height
|
||||
* @param int|null $quality
|
||||
* @return \Kirby\Cms\FileVersion|\Kirby\Cms\File
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function resize(int $width = null, int $height = null, int $quality = null)
|
||||
{
|
||||
public function resize(
|
||||
int $width = null,
|
||||
int $height = null,
|
||||
int $quality = null
|
||||
): FileVersion|File|Asset {
|
||||
return $this->thumb([
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
@@ -124,11 +110,8 @@ trait FileModifications
|
||||
* Sizes can be defined as a simple array. They can
|
||||
* also be set up in the config with the thumbs.srcsets option.
|
||||
* @since 3.1.0
|
||||
*
|
||||
* @param array|string|null $sizes
|
||||
* @return string|null
|
||||
*/
|
||||
public function srcset($sizes = null): string|null
|
||||
public function srcset(array|string|null $sizes = null): string|null
|
||||
{
|
||||
if (empty($sizes) === true) {
|
||||
$sizes = $this->kirby()->option('thumbs.srcsets.default', []);
|
||||
@@ -175,12 +158,11 @@ trait FileModifications
|
||||
* could potentially also be a CDN or any other
|
||||
* place.
|
||||
*
|
||||
* @param array|null|string $options
|
||||
* @return \Kirby\Cms\FileVersion|\Kirby\Cms\File
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function thumb($options = null)
|
||||
{
|
||||
public function thumb(
|
||||
array|string|null $options = null
|
||||
): FileVersion|File|Asset {
|
||||
// thumb presets
|
||||
if (empty($options) === true) {
|
||||
$options = $this->kirby()->option('thumbs.presets.default');
|
||||
@@ -192,6 +174,11 @@ trait FileModifications
|
||||
return $this;
|
||||
}
|
||||
|
||||
// fallback to content file options
|
||||
if (($options['crop'] ?? false) === true) {
|
||||
$options['crop'] = $this->focus()->value() ?? 'center';
|
||||
}
|
||||
|
||||
// fallback to global config options
|
||||
if (isset($options['format']) === false) {
|
||||
if ($format = $this->kirby()->option('thumbs.format')) {
|
||||
|
||||
@@ -13,5 +13,14 @@ namespace Kirby\Cms;
|
||||
*/
|
||||
class FilePermissions extends ModelPermissions
|
||||
{
|
||||
protected $category = 'files';
|
||||
protected string $category = 'files';
|
||||
|
||||
protected function canChangeTemplate(): bool
|
||||
{
|
||||
if (count($this->model->blueprints()) <= 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@ class FilePicker extends Picker
|
||||
{
|
||||
/**
|
||||
* Extends the basic defaults
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function defaults(): array
|
||||
{
|
||||
@@ -33,10 +31,9 @@ class FilePicker extends Picker
|
||||
/**
|
||||
* Search all files for the picker
|
||||
*
|
||||
* @return \Kirby\Cms\Files|null
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function items()
|
||||
public function items(): Files|null
|
||||
{
|
||||
$model = $this->options['model'];
|
||||
|
||||
@@ -65,6 +62,9 @@ class FilePicker extends Picker
|
||||
default => throw new InvalidArgumentException('Your query must return a set of files')
|
||||
};
|
||||
|
||||
// filter protected and hidden pages
|
||||
$files = $files->filter('isListable', true);
|
||||
|
||||
// search
|
||||
$files = $this->search($files);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Exception\DuplicateException;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Exception\PermissionException;
|
||||
use Kirby\Filesystem\File as BaseFile;
|
||||
use Kirby\Toolkit\Str;
|
||||
@@ -23,9 +24,6 @@ class FileRules
|
||||
/**
|
||||
* Validates if the filename can be changed
|
||||
*
|
||||
* @param \Kirby\Cms\File $file
|
||||
* @param string $name
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\DuplicateException If a file with this name exists
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to rename the file
|
||||
*/
|
||||
@@ -59,22 +57,51 @@ class FileRules
|
||||
|
||||
/**
|
||||
* Validates if the file can be sorted
|
||||
*
|
||||
* @param \Kirby\Cms\File $file
|
||||
* @param int $sort
|
||||
* @return bool
|
||||
*/
|
||||
public static function changeSort(File $file, int $sort): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the template of the file can be changed
|
||||
*
|
||||
* @throws \Kirby\Exception\LogicException If the template of the page cannot be changed at all
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to change the template
|
||||
*/
|
||||
public static function changeTemplate(File $file, string $template): bool
|
||||
{
|
||||
if ($file->permissions()->changeTemplate() !== true) {
|
||||
throw new PermissionException([
|
||||
'key' => 'file.changeTemplate.permission',
|
||||
'data' => ['id' => $file->id()]
|
||||
]);
|
||||
}
|
||||
|
||||
$blueprints = $file->blueprints();
|
||||
|
||||
// ensure that the $template is a valid blueprint
|
||||
// option for this file
|
||||
if (
|
||||
count($blueprints) <= 1 ||
|
||||
in_array($template, array_column($blueprints, 'name')) === false
|
||||
) {
|
||||
throw new LogicException([
|
||||
'key' => 'file.changeTemplate.invalid',
|
||||
'data' => [
|
||||
'id' => $file->id(),
|
||||
'template' => $template,
|
||||
'blueprints' => implode(', ', array_column($blueprints, 'name'))
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the file can be created
|
||||
*
|
||||
* @param \Kirby\Cms\File $file
|
||||
* @param \Kirby\Filesystem\File $upload
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\DuplicateException If a file with the same name exists
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to create the file
|
||||
*/
|
||||
@@ -121,8 +148,6 @@ class FileRules
|
||||
/**
|
||||
* Validates if the file can be deleted
|
||||
*
|
||||
* @param \Kirby\Cms\File $file
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to delete the file
|
||||
*/
|
||||
public static function delete(File $file): bool
|
||||
@@ -137,9 +162,6 @@ class FileRules
|
||||
/**
|
||||
* Validates if the file can be replaced
|
||||
*
|
||||
* @param \Kirby\Cms\File $file
|
||||
* @param \Kirby\Filesystem\File $upload
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to replace the file
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the file type of the new file is different
|
||||
*/
|
||||
@@ -170,9 +192,6 @@ class FileRules
|
||||
/**
|
||||
* Validates if the file can be updated
|
||||
*
|
||||
* @param \Kirby\Cms\File $file
|
||||
* @param array $content
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to update the file
|
||||
*/
|
||||
public static function update(File $file, array $content = []): bool
|
||||
@@ -187,9 +206,6 @@ class FileRules
|
||||
/**
|
||||
* Validates the file extension
|
||||
*
|
||||
* @param \Kirby\Cms\File $file
|
||||
* @param string $extension
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the extension is missing or forbidden
|
||||
*/
|
||||
public static function validExtension(File $file, string $extension): bool
|
||||
@@ -235,20 +251,19 @@ class FileRules
|
||||
/**
|
||||
* Validates the extension, MIME type and filename
|
||||
*
|
||||
* @param \Kirby\Cms\File $file
|
||||
* @param string|null|false $mime If not passed, the MIME type is detected from the file,
|
||||
* if `false`, the MIME type is not validated for performance reasons
|
||||
* @return bool
|
||||
* @param $mime If not passed, the MIME type is detected from the file,
|
||||
* if `false`, the MIME type is not validated for performance reasons
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the extension, MIME type or filename is missing or forbidden
|
||||
*/
|
||||
public static function validFile(File $file, $mime = null): bool
|
||||
{
|
||||
if ($mime === false) {
|
||||
public static function validFile(
|
||||
File $file,
|
||||
string|false|null $mime = null
|
||||
): bool {
|
||||
$validMime = match ($mime) {
|
||||
// request to skip the MIME check for performance reasons
|
||||
$validMime = true;
|
||||
} else {
|
||||
$validMime = static::validMime($file, $mime ?? $file->mime());
|
||||
}
|
||||
false => true,
|
||||
default => static::validMime($file, $mime ?? $file->mime())
|
||||
};
|
||||
|
||||
return
|
||||
$validMime &&
|
||||
@@ -259,9 +274,6 @@ class FileRules
|
||||
/**
|
||||
* Validates the filename
|
||||
*
|
||||
* @param \Kirby\Cms\File $file
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the filename is missing or forbidden
|
||||
*/
|
||||
public static function validFilename(File $file, string $filename): bool
|
||||
@@ -298,9 +310,6 @@ class FileRules
|
||||
/**
|
||||
* Validates the MIME type
|
||||
*
|
||||
* @param \Kirby\Cms\File $file
|
||||
* @param string|null $mime
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the MIME type is missing or forbidden
|
||||
*/
|
||||
public static function validMime(File $file, string $mime = null): bool
|
||||
|
||||
@@ -17,18 +17,22 @@ class FileVersion
|
||||
{
|
||||
use IsFile;
|
||||
|
||||
protected $modifications;
|
||||
protected array $modifications;
|
||||
protected $original;
|
||||
|
||||
public function __construct(array $props)
|
||||
{
|
||||
$this->root = $props['root'] ?? null;
|
||||
$this->url = $props['url'] ?? null;
|
||||
$this->original = $props['original'];
|
||||
$this->modifications = $props['modifications'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy for public properties, asset methods
|
||||
* and content field getters
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call(string $method, array $arguments = [])
|
||||
public function __call(string $method, array $arguments = []): mixed
|
||||
{
|
||||
// public property access
|
||||
if (isset($this->$method) === true) {
|
||||
@@ -52,8 +56,6 @@ class FileVersion
|
||||
|
||||
/**
|
||||
* Returns the unique ID
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function id(): string
|
||||
{
|
||||
@@ -62,30 +64,24 @@ class FileVersion
|
||||
|
||||
/**
|
||||
* Returns the parent Kirby App instance
|
||||
*
|
||||
* @return \Kirby\Cms\App
|
||||
*/
|
||||
public function kirby()
|
||||
public function kirby(): App
|
||||
{
|
||||
return $this->original()->kirby();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with all applied modifications
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function modifications(): array
|
||||
{
|
||||
return $this->modifications ?? [];
|
||||
return $this->modifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the instance of the original File object
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function original()
|
||||
public function original(): mixed
|
||||
{
|
||||
return $this->original;
|
||||
}
|
||||
@@ -96,7 +92,7 @@ class FileVersion
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function save()
|
||||
public function save(): static
|
||||
{
|
||||
$this->kirby()->thumb(
|
||||
$this->original()->root(),
|
||||
@@ -106,36 +102,16 @@ class FileVersion
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for modifications
|
||||
*
|
||||
* @param array|null $modifications
|
||||
*/
|
||||
protected function setModifications(array $modifications = null)
|
||||
{
|
||||
$this->modifications = $modifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the original File object
|
||||
*
|
||||
* @param $original
|
||||
*/
|
||||
protected function setOriginal($original)
|
||||
{
|
||||
$this->original = $original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the object to an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$array = array_merge($this->asset()->toArray(), [
|
||||
'modifications' => $this->modifications(),
|
||||
]);
|
||||
$array = array_merge(
|
||||
$this->asset()->toArray(),
|
||||
['modifications' => $this->modifications()]
|
||||
);
|
||||
|
||||
ksort($array);
|
||||
|
||||
|
||||
@@ -26,10 +26,8 @@ class Files extends Collection
|
||||
|
||||
/**
|
||||
* All registered files methods
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $methods = [];
|
||||
public static array $methods = [];
|
||||
|
||||
/**
|
||||
* Adds a single file or
|
||||
@@ -40,7 +38,7 @@ class Files extends Collection
|
||||
* @return $this
|
||||
* @throws \Kirby\Exception\InvalidArgumentException When no `File` or `Files` object or an ID of an existing file is passed
|
||||
*/
|
||||
public function add($object)
|
||||
public function add($object): static
|
||||
{
|
||||
// add a files collection
|
||||
if ($object instanceof self) {
|
||||
@@ -74,7 +72,7 @@ class Files extends Collection
|
||||
* @param int $offset Sorting offset
|
||||
* @return $this
|
||||
*/
|
||||
public function changeSort(array $files, int $offset = 0)
|
||||
public function changeSort(array $files, int $offset = 0): static
|
||||
{
|
||||
foreach ($files as $filename) {
|
||||
if ($file = $this->get($filename)) {
|
||||
@@ -88,12 +86,8 @@ class Files extends Collection
|
||||
|
||||
/**
|
||||
* Creates a files collection from an array of props
|
||||
*
|
||||
* @param array $files
|
||||
* @param \Kirby\Cms\Model $parent
|
||||
* @return static
|
||||
*/
|
||||
public static function factory(array $files, Model $parent)
|
||||
public static function factory(array $files, Page|Site|User $parent): static
|
||||
{
|
||||
$collection = new static([], $parent);
|
||||
$kirby = $parent->kirby();
|
||||
@@ -114,11 +108,8 @@ class Files extends Collection
|
||||
/**
|
||||
* Finds a file by its filename
|
||||
* @internal Use `$files->find()` instead
|
||||
*
|
||||
* @param string $key
|
||||
* @return \Kirby\Cms\File|null
|
||||
*/
|
||||
public function findByKey(string $key)
|
||||
public function findByKey(string $key): File|null
|
||||
{
|
||||
if ($file = $this->findByUuid($key, 'file')) {
|
||||
return $file;
|
||||
@@ -136,7 +127,6 @@ class Files extends Collection
|
||||
* @param string|null|false $locale Locale for number formatting,
|
||||
* `null` for the current locale,
|
||||
* `false` to disable number formatting
|
||||
* @return string
|
||||
*/
|
||||
public function niceSize($locale = null): string
|
||||
{
|
||||
@@ -147,8 +137,6 @@ class Files extends Collection
|
||||
* Returns the raw size for all
|
||||
* files in the collection
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function size(): int
|
||||
{
|
||||
@@ -158,10 +146,8 @@ class Files extends Collection
|
||||
/**
|
||||
* Returns the collection sorted by
|
||||
* the sort number and the filename
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function sorted()
|
||||
public function sorted(): static
|
||||
{
|
||||
return $this->sort('sort', 'asc', 'filename', 'asc');
|
||||
}
|
||||
@@ -169,10 +155,9 @@ class Files extends Collection
|
||||
/**
|
||||
* Filter all files by the given template
|
||||
*
|
||||
* @param null|string|array $template
|
||||
* @return $this|static
|
||||
*/
|
||||
public function template($template)
|
||||
public function template(string|array|null $template): static
|
||||
{
|
||||
if (empty($template) === true) {
|
||||
return $this;
|
||||
|
||||
@@ -24,16 +24,17 @@ class Find
|
||||
* parent path and filename
|
||||
*
|
||||
* @param string $path Path to file's parent model
|
||||
* @param string $filename Filename
|
||||
* @return \Kirby\Cms\File|null
|
||||
* @throws \Kirby\Exception\NotFoundException if the file cannot be found
|
||||
*/
|
||||
public static function file(string $path, string $filename)
|
||||
{
|
||||
public static function file(
|
||||
string $path,
|
||||
string $filename
|
||||
): File|null {
|
||||
$filename = urldecode($filename);
|
||||
$file = static::parent($path)->file($filename);
|
||||
$parent = empty($path) ? null : static::parent($path);
|
||||
$file = App::instance()->file($filename, $parent);
|
||||
|
||||
if ($file?->isReadable() === true) {
|
||||
if ($file?->isAccessible() === true) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
@@ -49,10 +50,9 @@ class Find
|
||||
* Returns the language object for the given code
|
||||
*
|
||||
* @param string $code Language code
|
||||
* @return \Kirby\Cms\Language|null
|
||||
* @throws \Kirby\Exception\NotFoundException if the language cannot be found
|
||||
*/
|
||||
public static function language(string $code)
|
||||
public static function language(string $code): Language|null
|
||||
{
|
||||
if ($language = App::instance()->language($code)) {
|
||||
return $language;
|
||||
@@ -70,15 +70,16 @@ class Find
|
||||
* Returns the page object for the given id
|
||||
*
|
||||
* @param string $id Page's id
|
||||
* @return \Kirby\Cms\Page|null
|
||||
* @throws \Kirby\Exception\NotFoundException if the page cannot be found
|
||||
*/
|
||||
public static function page(string $id)
|
||||
public static function page(string $id): Page|null
|
||||
{
|
||||
$id = str_replace(['+', ' '], '/', $id);
|
||||
$page = App::instance()->page($id);
|
||||
// decode API ID encoding
|
||||
$id = str_replace(['+', ' '], '/', $id);
|
||||
$kirby = App::instance();
|
||||
$page = $kirby->page($id, null, true);
|
||||
|
||||
if ($page?->isReadable() === true) {
|
||||
if ($page?->isAccessible() === true) {
|
||||
return $page;
|
||||
}
|
||||
|
||||
@@ -94,11 +95,10 @@ class Find
|
||||
* Returns the model's object for the given path
|
||||
*
|
||||
* @param string $path Path to parent model
|
||||
* @return \Kirby\Cms\Model|null
|
||||
* @throws \Kirby\Exception\InvalidArgumentException if the model type is invalid
|
||||
* @throws \Kirby\Exception\NotFoundException if the model cannot be found
|
||||
*/
|
||||
public static function parent(string $path)
|
||||
public static function parent(string $path): ModelWithContent
|
||||
{
|
||||
$path = trim($path, '/');
|
||||
$modelType = in_array($path, ['site', 'account']) ? $path : trim(dirname($path), '/');
|
||||
@@ -140,10 +140,9 @@ class Find
|
||||
* id is passed
|
||||
*
|
||||
* @param string|null $id User's id
|
||||
* @return \Kirby\Cms\User|null
|
||||
* @throws \Kirby\Exception\NotFoundException if the user for the given id cannot be found
|
||||
*/
|
||||
public static function user(string $id = null)
|
||||
public static function user(string $id = null): User|null
|
||||
{
|
||||
// account is a reserved word to find the current
|
||||
// user. It's used in various API and area routes.
|
||||
|
||||
@@ -18,72 +18,40 @@ trait HasChildren
|
||||
{
|
||||
/**
|
||||
* The list of available published children
|
||||
*
|
||||
* @var \Kirby\Cms\Pages|null
|
||||
*/
|
||||
public $children;
|
||||
public Pages|null $children = null;
|
||||
|
||||
/**
|
||||
* The list of available draft children
|
||||
*
|
||||
* @var \Kirby\Cms\Pages|null
|
||||
*/
|
||||
public $drafts;
|
||||
public Pages|null $drafts = null;
|
||||
|
||||
/**
|
||||
* The combined list of available published
|
||||
* and draft children
|
||||
*
|
||||
* @var \Kirby\Cms\Pages|null
|
||||
*/
|
||||
public $childrenAndDrafts;
|
||||
public Pages|null $childrenAndDrafts = null;
|
||||
|
||||
/**
|
||||
* Returns all published children
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function children()
|
||||
public function children(): Pages
|
||||
{
|
||||
if ($this->children instanceof Pages) {
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
return $this->children = Pages::factory($this->inventory()['children'], $this);
|
||||
return $this->children ??= Pages::factory($this->inventory()['children'], $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all published and draft children at the same time
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function childrenAndDrafts()
|
||||
public function childrenAndDrafts(): Pages
|
||||
{
|
||||
if ($this->childrenAndDrafts instanceof Pages) {
|
||||
return $this->childrenAndDrafts;
|
||||
}
|
||||
|
||||
return $this->childrenAndDrafts = $this->children()->merge($this->drafts());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of IDs for the model's
|
||||
* `toArray` method
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function convertChildrenToArray(): array
|
||||
{
|
||||
return $this->children()->keys();
|
||||
return $this->childrenAndDrafts ??= $this->children()->merge($this->drafts());
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a draft child by ID
|
||||
*
|
||||
* @param string $path
|
||||
* @return \Kirby\Cms\Page|null
|
||||
*/
|
||||
public function draft(string $path)
|
||||
public function draft(string $path): Page|null
|
||||
{
|
||||
$path = str_replace('_drafts/', '', $path);
|
||||
|
||||
@@ -113,10 +81,8 @@ trait HasChildren
|
||||
|
||||
/**
|
||||
* Returns all draft children
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function drafts()
|
||||
public function drafts(): Pages
|
||||
{
|
||||
if ($this->drafts instanceof Pages) {
|
||||
return $this->drafts;
|
||||
@@ -137,40 +103,30 @@ trait HasChildren
|
||||
|
||||
/**
|
||||
* Finds one or multiple published children by ID
|
||||
*
|
||||
* @param string ...$arguments
|
||||
* @return \Kirby\Cms\Page|\Kirby\Cms\Pages|null
|
||||
*/
|
||||
public function find(...$arguments)
|
||||
public function find(string|array ...$arguments): Page|Pages|null
|
||||
{
|
||||
return $this->children()->find(...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a single published or draft child
|
||||
*
|
||||
* @param string $path
|
||||
* @return \Kirby\Cms\Page|null
|
||||
*/
|
||||
public function findPageOrDraft(string $path)
|
||||
public function findPageOrDraft(string $path): Page|null
|
||||
{
|
||||
return $this->children()->find($path) ?? $this->drafts()->find($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of all published children of published children
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function grandChildren()
|
||||
public function grandChildren(): Pages
|
||||
{
|
||||
return $this->children()->children();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the model has any published children
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasChildren(): bool
|
||||
{
|
||||
@@ -179,8 +135,6 @@ trait HasChildren
|
||||
|
||||
/**
|
||||
* Checks if the model has any draft children
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDrafts(): bool
|
||||
{
|
||||
@@ -189,8 +143,6 @@ trait HasChildren
|
||||
|
||||
/**
|
||||
* Checks if the page has any listed children
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasListedChildren(): bool
|
||||
{
|
||||
@@ -199,8 +151,6 @@ trait HasChildren
|
||||
|
||||
/**
|
||||
* Checks if the page has any unlisted children
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasUnlistedChildren(): bool
|
||||
{
|
||||
@@ -211,9 +161,8 @@ trait HasChildren
|
||||
* Creates a flat child index
|
||||
*
|
||||
* @param bool $drafts If set to `true`, draft children are included
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function index(bool $drafts = false)
|
||||
public function index(bool $drafts = false): Pages
|
||||
{
|
||||
if ($drafts === true) {
|
||||
return $this->childrenAndDrafts()->index($drafts);
|
||||
@@ -225,10 +174,9 @@ trait HasChildren
|
||||
/**
|
||||
* Sets the published children collection
|
||||
*
|
||||
* @param array|null $children
|
||||
* @return $this
|
||||
*/
|
||||
protected function setChildren(array $children = null)
|
||||
protected function setChildren(array $children = null): static
|
||||
{
|
||||
if ($children !== null) {
|
||||
$this->children = Pages::factory($children, $this);
|
||||
@@ -240,10 +188,9 @@ trait HasChildren
|
||||
/**
|
||||
* Sets the draft children collection
|
||||
*
|
||||
* @param array|null $drafts
|
||||
* @return $this
|
||||
*/
|
||||
protected function setDrafts(array $drafts = null)
|
||||
protected function setDrafts(array $drafts = null): static
|
||||
{
|
||||
if ($drafts !== null) {
|
||||
$this->drafts = Pages::factory($drafts, $this, true);
|
||||
|
||||
@@ -17,50 +17,31 @@ trait HasFiles
|
||||
{
|
||||
/**
|
||||
* The Files collection
|
||||
*
|
||||
* @var \Kirby\Cms\Files
|
||||
*/
|
||||
protected $files;
|
||||
protected Files|array|null $files = null;
|
||||
|
||||
/**
|
||||
* Filters the Files collection by type audio
|
||||
*
|
||||
* @return \Kirby\Cms\Files
|
||||
*/
|
||||
public function audio()
|
||||
public function audio(): Files
|
||||
{
|
||||
return $this->files()->filter('type', '==', 'audio');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the Files collection by type code
|
||||
*
|
||||
* @return \Kirby\Cms\Files
|
||||
*/
|
||||
public function code()
|
||||
public function code(): Files
|
||||
{
|
||||
return $this->files()->filter('type', '==', 'code');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of file ids
|
||||
* for the toArray method of the model
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function convertFilesToArray(): array
|
||||
{
|
||||
return $this->files()->keys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new file
|
||||
*
|
||||
* @param array $props
|
||||
* @param bool $move If set to `true`, the source will be deleted
|
||||
* @return \Kirby\Cms\File
|
||||
*/
|
||||
public function createFile(array $props, bool $move = false)
|
||||
public function createFile(array $props, bool $move = false): File
|
||||
{
|
||||
$props = array_merge($props, [
|
||||
'parent' => $this,
|
||||
@@ -72,23 +53,19 @@ trait HasFiles
|
||||
|
||||
/**
|
||||
* Filters the Files collection by type documents
|
||||
*
|
||||
* @return \Kirby\Cms\Files
|
||||
*/
|
||||
public function documents()
|
||||
public function documents(): Files
|
||||
{
|
||||
return $this->files()->filter('type', '==', 'document');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific file by filename or the first one
|
||||
*
|
||||
* @param string|null $filename
|
||||
* @param string $in
|
||||
* @return \Kirby\Cms\File|null
|
||||
*/
|
||||
public function file(string $filename = null, string $in = 'files')
|
||||
{
|
||||
public function file(
|
||||
string $filename = null,
|
||||
string $in = 'files'
|
||||
): File|null {
|
||||
if ($filename === null) {
|
||||
return $this->$in()->first();
|
||||
}
|
||||
@@ -114,10 +91,8 @@ trait HasFiles
|
||||
|
||||
/**
|
||||
* Returns the Files collection
|
||||
*
|
||||
* @return \Kirby\Cms\Files
|
||||
*/
|
||||
public function files()
|
||||
public function files(): Files
|
||||
{
|
||||
if ($this->files instanceof Files) {
|
||||
return $this->files;
|
||||
@@ -128,8 +103,6 @@ trait HasFiles
|
||||
|
||||
/**
|
||||
* Checks if the Files collection has any audio files
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAudio(): bool
|
||||
{
|
||||
@@ -138,8 +111,6 @@ trait HasFiles
|
||||
|
||||
/**
|
||||
* Checks if the Files collection has any code files
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCode(): bool
|
||||
{
|
||||
@@ -148,8 +119,6 @@ trait HasFiles
|
||||
|
||||
/**
|
||||
* Checks if the Files collection has any document files
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDocuments(): bool
|
||||
{
|
||||
@@ -158,8 +127,6 @@ trait HasFiles
|
||||
|
||||
/**
|
||||
* Checks if the Files collection has any files
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasFiles(): bool
|
||||
{
|
||||
@@ -168,8 +135,6 @@ trait HasFiles
|
||||
|
||||
/**
|
||||
* Checks if the Files collection has any images
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasImages(): bool
|
||||
{
|
||||
@@ -178,8 +143,6 @@ trait HasFiles
|
||||
|
||||
/**
|
||||
* Checks if the Files collection has any videos
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasVideos(): bool
|
||||
{
|
||||
@@ -188,21 +151,16 @@ trait HasFiles
|
||||
|
||||
/**
|
||||
* Returns a specific image by filename or the first one
|
||||
*
|
||||
* @param string|null $filename
|
||||
* @return \Kirby\Cms\File|null
|
||||
*/
|
||||
public function image(string $filename = null)
|
||||
public function image(string $filename = null): File|null
|
||||
{
|
||||
return $this->file($filename, 'images');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the Files collection by type image
|
||||
*
|
||||
* @return \Kirby\Cms\Files
|
||||
*/
|
||||
public function images()
|
||||
public function images(): Files
|
||||
{
|
||||
return $this->files()->filter('type', '==', 'image');
|
||||
}
|
||||
@@ -210,10 +168,9 @@ trait HasFiles
|
||||
/**
|
||||
* Sets the Files collection
|
||||
*
|
||||
* @param \Kirby\Cms\Files|null $files
|
||||
* @return $this
|
||||
*/
|
||||
protected function setFiles(array $files = null)
|
||||
protected function setFiles(array $files = null): static
|
||||
{
|
||||
if ($files !== null) {
|
||||
$this->files = Files::factory($files, $this);
|
||||
@@ -224,10 +181,8 @@ trait HasFiles
|
||||
|
||||
/**
|
||||
* Filters the Files collection by type videos
|
||||
*
|
||||
* @return \Kirby\Cms\Files
|
||||
*/
|
||||
public function videos()
|
||||
public function videos(): Files
|
||||
{
|
||||
return $this->files()->filter('type', '==', 'video');
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Closure;
|
||||
use Kirby\Exception\BadMethodCallException;
|
||||
|
||||
/**
|
||||
@@ -17,22 +18,17 @@ trait HasMethods
|
||||
{
|
||||
/**
|
||||
* All registered methods
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $methods = [];
|
||||
public static array $methods = [];
|
||||
|
||||
/**
|
||||
* Calls a registered method class with the
|
||||
* passed arguments
|
||||
*
|
||||
* @internal
|
||||
* @param string $method
|
||||
* @param array $args
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Kirby\Exception\BadMethodCallException
|
||||
*/
|
||||
public function callMethod(string $method, array $args = [])
|
||||
public function callMethod(string $method, array $args = []): mixed
|
||||
{
|
||||
$closure = $this->getMethod($method);
|
||||
|
||||
@@ -45,10 +41,7 @@ trait HasMethods
|
||||
|
||||
/**
|
||||
* Checks if the object has a registered method
|
||||
*
|
||||
* @internal
|
||||
* @param string $method
|
||||
* @return bool
|
||||
*/
|
||||
public function hasMethod(string $method): bool
|
||||
{
|
||||
@@ -59,11 +52,8 @@ trait HasMethods
|
||||
* Returns a registered method by name, either from
|
||||
* the current class or from a parent class ordered by
|
||||
* inheritance order (top to bottom)
|
||||
*
|
||||
* @param string $method
|
||||
* @return \Closure|null
|
||||
*/
|
||||
protected function getMethod(string $method)
|
||||
protected function getMethod(string $method): Closure|null
|
||||
{
|
||||
if (isset(static::$methods[$method]) === true) {
|
||||
return static::$methods[$method];
|
||||
|
||||
@@ -18,8 +18,6 @@ trait HasSiblings
|
||||
* Returns the position / index in the collection
|
||||
*
|
||||
* @param \Kirby\Cms\Collection|null $collection
|
||||
*
|
||||
* @return int|false
|
||||
*/
|
||||
public function indexOf($collection = null): int|false
|
||||
{
|
||||
@@ -88,7 +86,6 @@ trait HasSiblings
|
||||
/**
|
||||
* Returns all sibling elements
|
||||
*
|
||||
* @param bool $self
|
||||
* @return \Kirby\Cms\Collection
|
||||
*/
|
||||
public function siblings(bool $self = true)
|
||||
@@ -106,8 +103,6 @@ trait HasSiblings
|
||||
* Checks if there's a next item in the collection
|
||||
*
|
||||
* @param \Kirby\Cms\Collection|null $collection
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasNext($collection = null): bool
|
||||
{
|
||||
@@ -118,8 +113,6 @@ trait HasSiblings
|
||||
* Checks if there's a previous item in the collection
|
||||
*
|
||||
* @param \Kirby\Cms\Collection|null $collection
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPrev($collection = null): bool
|
||||
{
|
||||
@@ -130,8 +123,6 @@ trait HasSiblings
|
||||
* Checks if the item is the first in the collection
|
||||
*
|
||||
* @param \Kirby\Cms\Collection|null $collection
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFirst($collection = null): bool
|
||||
{
|
||||
@@ -143,8 +134,6 @@ trait HasSiblings
|
||||
* Checks if the item is the last in the collection
|
||||
*
|
||||
* @param \Kirby\Cms\Collection|null $collection
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isLast($collection = null): bool
|
||||
{
|
||||
@@ -156,9 +145,6 @@ trait HasSiblings
|
||||
* Checks if the item is at a certain position
|
||||
*
|
||||
* @param \Kirby\Cms\Collection|null $collection
|
||||
* @param int $n
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isNth(int $n, $collection = null): bool
|
||||
{
|
||||
|
||||
@@ -29,20 +29,14 @@ class Helpers
|
||||
* ```
|
||||
*/
|
||||
public static $deprecations = [
|
||||
// Passing the $slot or $slots variables to snippets is
|
||||
// deprecated and will break in a future version.
|
||||
'snippet-pass-slots' => true,
|
||||
// Passing a single space as value to `Xml::attr()` has been
|
||||
// deprecated. In a future version, passing a single space won't
|
||||
// render an empty value anymore but a single space.
|
||||
// To render an empty value, please pass an empty string.
|
||||
'xml-attr-single-space' => true,
|
||||
|
||||
// The `Toolkit\Query` class has been deprecated and will
|
||||
// be removed in a future version. Use `Query\Query` instead:
|
||||
// Kirby\Query\Query::factory($query)->resolve($data).
|
||||
'toolkit-query-class' => true,
|
||||
|
||||
// Passing an empty string as value to `Xml::attr()` has been
|
||||
// deprecated. In a future version, passing an empty string won't
|
||||
// omit the attribute anymore but render it with an empty value.
|
||||
// To omit the attribute, please pass `null`.
|
||||
'xml-attr-empty-string' => false,
|
||||
// The internal `$model->contentFile*()` methods have been deprecated
|
||||
'model-content-file' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -52,8 +46,10 @@ class Helpers
|
||||
* @param string|null $key If given, the key will be checked against the static array
|
||||
* @return bool Whether the warning was triggered
|
||||
*/
|
||||
public static function deprecated(string $message, string|null $key = null): bool
|
||||
{
|
||||
public static function deprecated(
|
||||
string $message,
|
||||
string|null $key = null
|
||||
): bool {
|
||||
// only trigger warning in debug mode or when running PHPUnit tests
|
||||
// @codeCoverageIgnoreStart
|
||||
if (
|
||||
@@ -75,19 +71,16 @@ class Helpers
|
||||
/**
|
||||
* Simple object and variable dumper
|
||||
* to help with debugging.
|
||||
*
|
||||
* @param mixed $variable
|
||||
* @param bool $echo
|
||||
* @return string
|
||||
*/
|
||||
public static function dump($variable, bool $echo = true): string
|
||||
public static function dump(mixed $variable, bool $echo = true): string
|
||||
{
|
||||
$kirby = App::instance();
|
||||
$kirby = App::instance();
|
||||
$output = print_r($variable, true);
|
||||
|
||||
if ($kirby->environment()->cli() === true) {
|
||||
$output = print_r($variable, true) . PHP_EOL;
|
||||
$output .= PHP_EOL;
|
||||
} else {
|
||||
$output = '<pre>' . print_r($variable, true) . '</pre>';
|
||||
$output = Str::wrap($output, '<pre>', '</pre>');
|
||||
}
|
||||
|
||||
if ($echo === true) {
|
||||
@@ -110,8 +103,11 @@ class Helpers
|
||||
* @return mixed Return value of the `$action` closure,
|
||||
* possibly overridden by `$fallback`
|
||||
*/
|
||||
public static function handleErrors(Closure $action, Closure $condition, $fallback = null)
|
||||
{
|
||||
public static function handleErrors(
|
||||
Closure $action,
|
||||
Closure $condition,
|
||||
$fallback = null
|
||||
) {
|
||||
$override = null;
|
||||
|
||||
$handler = set_error_handler(function () use (&$override, &$handler, $condition, $fallback) {
|
||||
@@ -152,7 +148,6 @@ class Helpers
|
||||
* @internal
|
||||
*
|
||||
* @param string $name Name of the helper
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasOverride(string $name): bool
|
||||
{
|
||||
@@ -164,11 +159,9 @@ class Helpers
|
||||
* Determines the size/length of numbers,
|
||||
* strings, arrays and countable objects
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return int
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function size($value): int
|
||||
public static function size(mixed $value): int
|
||||
{
|
||||
if (is_numeric($value)) {
|
||||
return (int)$value;
|
||||
|
||||
@@ -23,11 +23,20 @@ class Html extends \Kirby\Toolkit\Html
|
||||
* @since 3.7.0
|
||||
*
|
||||
* @param string|array $url Relative or absolute URLs, an array of URLs or `@auto` for automatic template css loading
|
||||
* @param string|array $options Pass an array of attributes for the link tag or a media attribute string
|
||||
* @return string|null
|
||||
* @param string|array|null $options Pass an array of attributes for the link tag or a media attribute string
|
||||
*/
|
||||
public static function css($url, $options = null): string|null
|
||||
{
|
||||
public static function css(
|
||||
string|array|Plugin|PluginAssets $url,
|
||||
string|array|null $options = null
|
||||
): string|null {
|
||||
if ($url instanceof Plugin) {
|
||||
$url = $url->assets();
|
||||
}
|
||||
|
||||
if ($url instanceof PluginAssets) {
|
||||
$url = $url->css()->values(fn ($asset) => $asset->url());
|
||||
}
|
||||
|
||||
if (is_array($url) === true) {
|
||||
$links = A::map($url, fn ($url) => static::css($url, $options));
|
||||
return implode(PHP_EOL, $links);
|
||||
@@ -69,23 +78,31 @@ class Html extends \Kirby\Toolkit\Html
|
||||
* @param string|null $href Relative or absolute Url
|
||||
* @param string|array|null $text If `null`, the link will be used as link text. If an array is passed, each element will be added unencoded
|
||||
* @param array $attr Additional attributes for the a tag.
|
||||
* @return string
|
||||
*/
|
||||
public static function link(string $href = null, $text = null, array $attr = []): string
|
||||
{
|
||||
public static function link(
|
||||
string|null $href = null,
|
||||
string|array $text = null,
|
||||
array $attr = []
|
||||
): string {
|
||||
return parent::link(Url::to($href), $text, $attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a script tag to load a javascript file
|
||||
* @since 3.7.0
|
||||
*
|
||||
* @param string|array $url
|
||||
* @param string|array $options
|
||||
* @return string|null
|
||||
*/
|
||||
public static function js($url, $options = null): string|null
|
||||
{
|
||||
public static function js(
|
||||
string|array|Plugin|PluginAssets $url,
|
||||
string|array|bool|null $options = null
|
||||
): string|null {
|
||||
if ($url instanceof Plugin) {
|
||||
$url = $url->assets();
|
||||
}
|
||||
|
||||
if ($url instanceof PluginAssets) {
|
||||
$url = $url->js()->values(fn ($asset) => $asset->url());
|
||||
}
|
||||
|
||||
if (is_array($url) === true) {
|
||||
$scripts = A::map($url, fn ($url) => static::js($url, $options));
|
||||
return implode(PHP_EOL, $scripts);
|
||||
@@ -114,11 +131,8 @@ class Html extends \Kirby\Toolkit\Html
|
||||
* Includes an SVG file by absolute or
|
||||
* relative file path.
|
||||
* @since 3.7.0
|
||||
*
|
||||
* @param string|\Kirby\Cms\File $file
|
||||
* @return string|false
|
||||
*/
|
||||
public static function svg($file)
|
||||
public static function svg(string|File $file): string|false
|
||||
{
|
||||
// support for Kirby's file objects
|
||||
if (
|
||||
|
||||
@@ -25,8 +25,6 @@ class Ingredients
|
||||
|
||||
/**
|
||||
* Creates a new ingredient collection
|
||||
*
|
||||
* @param array $ingredients
|
||||
*/
|
||||
public function __construct(array $ingredients)
|
||||
{
|
||||
@@ -35,20 +33,15 @@ class Ingredients
|
||||
|
||||
/**
|
||||
* Magic getter for single ingredients
|
||||
*
|
||||
* @param string $method
|
||||
* @param array|null $args
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call(string $method, array $args = null)
|
||||
public function __call(string $method, array $args = null): mixed
|
||||
{
|
||||
return $this->ingredients[$method] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
*
|
||||
* @return array
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
@@ -57,9 +50,6 @@ class Ingredients
|
||||
|
||||
/**
|
||||
* Get a single ingredient by key
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get(string $key)
|
||||
{
|
||||
@@ -69,12 +59,9 @@ class Ingredients
|
||||
/**
|
||||
* Resolves all ingredient callbacks
|
||||
* and creates a plain array
|
||||
*
|
||||
* @internal
|
||||
* @param array $ingredients
|
||||
* @return static
|
||||
*/
|
||||
public static function bake(array $ingredients)
|
||||
public static function bake(array $ingredients): static
|
||||
{
|
||||
foreach ($ingredients as $name => $ingredient) {
|
||||
if ($ingredient instanceof Closure) {
|
||||
@@ -87,8 +74,6 @@ class Ingredients
|
||||
|
||||
/**
|
||||
* Returns all ingredients as plain array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Content\Field;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
@@ -28,49 +29,28 @@ class Item
|
||||
|
||||
protected Field|null $field;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $params;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Cms\Page|\Kirby\Cms\Site|\Kirby\Cms\File|\Kirby\Cms\User
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Cms\Items
|
||||
*/
|
||||
protected $siblings;
|
||||
protected string $id;
|
||||
protected array $params;
|
||||
protected ModelWithContent $parent;
|
||||
protected Items $siblings;
|
||||
|
||||
/**
|
||||
* Creates a new item
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function __construct(array $params = [])
|
||||
{
|
||||
$siblingsClass = static::ITEMS_CLASS;
|
||||
|
||||
$class = static::ITEMS_CLASS;
|
||||
$this->id = $params['id'] ?? Str::uuid();
|
||||
$this->params = $params;
|
||||
$this->field = $params['field'] ?? null;
|
||||
$this->parent = $params['parent'] ?? App::instance()->site();
|
||||
$this->siblings = $params['siblings'] ?? new $siblingsClass();
|
||||
$this->siblings = $params['siblings'] ?? new $class();
|
||||
}
|
||||
|
||||
/**
|
||||
* Static Item factory
|
||||
*
|
||||
* @param array $params
|
||||
* @return \Kirby\Cms\Item
|
||||
*/
|
||||
public static function factory(array $params)
|
||||
public static function factory(array $params): static
|
||||
{
|
||||
return new static($params);
|
||||
}
|
||||
@@ -85,8 +65,6 @@ class Item
|
||||
|
||||
/**
|
||||
* Returns the unique item id (UUID v4)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function id(): string
|
||||
{
|
||||
@@ -95,9 +73,6 @@ class Item
|
||||
|
||||
/**
|
||||
* Compares the item to another one
|
||||
*
|
||||
* @param \Kirby\Cms\Item $item
|
||||
* @return bool
|
||||
*/
|
||||
public function is(Item $item): bool
|
||||
{
|
||||
@@ -106,20 +81,16 @@ class Item
|
||||
|
||||
/**
|
||||
* Returns the Kirby instance
|
||||
*
|
||||
* @return \Kirby\Cms\App
|
||||
*/
|
||||
public function kirby()
|
||||
public function kirby(): App
|
||||
{
|
||||
return $this->parent()->kirby();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent model
|
||||
*
|
||||
* @return \Kirby\Cms\Page|\Kirby\Cms\Site|\Kirby\Cms\File|\Kirby\Cms\User
|
||||
*/
|
||||
public function parent()
|
||||
public function parent(): ModelWithContent
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
@@ -128,18 +99,15 @@ class Item
|
||||
* Returns the sibling collection
|
||||
* This is required by the HasSiblings trait
|
||||
*
|
||||
* @return \Kirby\Cms\Items
|
||||
* @psalm-return self::ITEMS_CLASS
|
||||
*/
|
||||
protected function siblingsCollection()
|
||||
protected function siblingsCollection(): Items
|
||||
{
|
||||
return $this->siblings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the item to an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Kirby\Content\Field;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* A collection of items
|
||||
@@ -23,27 +24,16 @@ class Items extends Collection
|
||||
|
||||
/**
|
||||
* All registered items methods
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $methods = [];
|
||||
public static array $methods = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
protected array $options;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Cms\ModelWithContent
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $objects
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($objects = [], array $options = [])
|
||||
{
|
||||
$this->options = $options;
|
||||
@@ -56,41 +46,36 @@ class Items extends Collection
|
||||
/**
|
||||
* Creates a new item collection from a
|
||||
* an array of item props
|
||||
*
|
||||
* @param array $items
|
||||
* @param array $params
|
||||
* @return \Kirby\Cms\Items
|
||||
*/
|
||||
public static function factory(array $items = null, array $params = [])
|
||||
{
|
||||
$options = array_merge([
|
||||
'field' => null,
|
||||
'options' => [],
|
||||
'parent' => App::instance()->site(),
|
||||
], $params);
|
||||
|
||||
public static function factory(
|
||||
array $items = null,
|
||||
array $params = []
|
||||
): static {
|
||||
if (empty($items) === true || is_array($items) === false) {
|
||||
return new static();
|
||||
}
|
||||
|
||||
if (is_array($options) === false) {
|
||||
throw new Exception('Invalid item options');
|
||||
if (is_array($params) === false) {
|
||||
throw new InvalidArgumentException('Invalid item options');
|
||||
}
|
||||
|
||||
// create a new collection of blocks
|
||||
$collection = new static([], $options);
|
||||
$collection = new static([], $params);
|
||||
|
||||
foreach ($items as $params) {
|
||||
if (is_array($params) === false) {
|
||||
continue;
|
||||
foreach ($items as $item) {
|
||||
if (is_array($item) === false) {
|
||||
throw new InvalidArgumentException('Invalid data for ' . static::ITEM_CLASS);
|
||||
}
|
||||
|
||||
$params['field'] = $options['field'];
|
||||
$params['options'] = $options['options'];
|
||||
$params['parent'] = $options['parent'];
|
||||
$params['siblings'] = $collection;
|
||||
// inject properties from the parent
|
||||
$item['field'] = $collection->field();
|
||||
$item['options'] = $params['options'] ?? [];
|
||||
$item['parent'] = $collection->parent();
|
||||
$item['siblings'] = $collection;
|
||||
$item['params'] = $item;
|
||||
|
||||
$class = static::ITEM_CLASS;
|
||||
$item = $class::factory($params);
|
||||
$item = $class::factory($item);
|
||||
$collection->append($item->id(), $item);
|
||||
}
|
||||
|
||||
@@ -107,8 +92,6 @@ class Items extends Collection
|
||||
|
||||
/**
|
||||
* Convert the items to an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(Closure $map = null): array
|
||||
{
|
||||
|
||||
@@ -4,7 +4,8 @@ namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Exception\PermissionException;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Toolkit\Locale;
|
||||
use Kirby\Toolkit\Str;
|
||||
@@ -26,80 +27,54 @@ use Throwable;
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class Language extends Model
|
||||
class Language
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $code;
|
||||
use HasSiblings;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* The parent Kirby instance
|
||||
*/
|
||||
protected $default;
|
||||
public static App|null $kirby;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $direction;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $locale;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
protected $slugs;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
protected $smartypants;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
protected $translations;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $url;
|
||||
protected string $code;
|
||||
protected bool $default;
|
||||
protected string $direction;
|
||||
protected array $locale;
|
||||
protected string $name;
|
||||
protected array $slugs;
|
||||
protected array $smartypants;
|
||||
protected array $translations;
|
||||
protected string|null $url;
|
||||
|
||||
/**
|
||||
* Creates a new language object
|
||||
*
|
||||
* @param array $props
|
||||
*/
|
||||
public function __construct(array $props)
|
||||
{
|
||||
$this->setRequiredProperties($props, [
|
||||
'code'
|
||||
]);
|
||||
if (isset($props['code']) === false) {
|
||||
throw new InvalidArgumentException('The property "code" is required');
|
||||
}
|
||||
|
||||
$this->setOptionalProperties($props, [
|
||||
'default',
|
||||
'direction',
|
||||
'locale',
|
||||
'name',
|
||||
'slugs',
|
||||
'smartypants',
|
||||
'translations',
|
||||
'url',
|
||||
]);
|
||||
static::$kirby = $props['kirby'] ?? null;
|
||||
$this->code = trim($props['code']);
|
||||
$this->default = ($props['default'] ?? false) === true;
|
||||
$this->direction = ($props['direction'] ?? null) === 'rtl' ? 'rtl' : 'ltr';
|
||||
$this->name = trim($props['name'] ?? $this->code);
|
||||
$this->slugs = $props['slugs'] ?? [];
|
||||
$this->smartypants = $props['smartypants'] ?? [];
|
||||
$this->translations = $props['translations'] ?? [];
|
||||
$this->url = $props['url'] ?? null;
|
||||
|
||||
if ($locale = $props['locale'] ?? null) {
|
||||
$this->locale = Locale::normalize($locale);
|
||||
} else {
|
||||
$this->locale = [LC_ALL => $this->code];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
*
|
||||
* @return array
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
@@ -109,8 +84,6 @@ class Language extends Model
|
||||
/**
|
||||
* Returns the language code
|
||||
* when the language is converted to a string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
@@ -120,8 +93,6 @@ class Language extends Model
|
||||
/**
|
||||
* Returns the base Url for the language
|
||||
* without the path or other cruft
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function baseUrl(): string
|
||||
{
|
||||
@@ -139,67 +110,40 @@ class Language extends Model
|
||||
return Url::base($languageUrl) ?? $kirbyUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance with the same
|
||||
* initial properties.
|
||||
*/
|
||||
public function clone(array $props = []): static
|
||||
{
|
||||
return new static(array_replace_recursive([
|
||||
'code' => $this->code,
|
||||
'default' => $this->default,
|
||||
'direction' => $this->direction,
|
||||
'locale' => $this->locale,
|
||||
'name' => $this->name,
|
||||
'slugs' => $this->slugs,
|
||||
'smartypants' => $this->smartypants,
|
||||
'translations' => $this->translations,
|
||||
'url' => $this->url,
|
||||
], $props));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the language code/id.
|
||||
* The language code is used in
|
||||
* text file names as appendix.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function code(): string
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal converter to create or remove
|
||||
* translation files.
|
||||
*
|
||||
* @param string $from
|
||||
* @param string $to
|
||||
* @return bool
|
||||
*/
|
||||
protected static function converter(string $from, string $to): bool
|
||||
{
|
||||
$kirby = App::instance();
|
||||
$site = $kirby->site();
|
||||
|
||||
// convert site
|
||||
foreach ($site->files() as $file) {
|
||||
F::move($file->contentFile($from, true), $file->contentFile($to, true));
|
||||
}
|
||||
|
||||
F::move($site->contentFile($from, true), $site->contentFile($to, true));
|
||||
|
||||
// convert all pages
|
||||
foreach ($kirby->site()->index(true) as $page) {
|
||||
foreach ($page->files() as $file) {
|
||||
F::move($file->contentFile($from, true), $file->contentFile($to, true));
|
||||
}
|
||||
|
||||
F::move($page->contentFile($from, true), $page->contentFile($to, true));
|
||||
}
|
||||
|
||||
// convert all users
|
||||
foreach ($kirby->users() as $user) {
|
||||
foreach ($user->files() as $file) {
|
||||
F::move($file->contentFile($from, true), $file->contentFile($to, true));
|
||||
}
|
||||
|
||||
F::move($user->contentFile($from, true), $user->contentFile($to, true));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new language object
|
||||
*
|
||||
* @internal
|
||||
* @param array $props
|
||||
* @return static
|
||||
*/
|
||||
public static function create(array $props)
|
||||
public static function create(array $props): static
|
||||
{
|
||||
$props['code'] = Str::slug($props['code'] ?? null);
|
||||
$kirby = App::instance();
|
||||
@@ -227,7 +171,12 @@ class Language extends Model
|
||||
$language->save();
|
||||
|
||||
if ($languages->count() === 0) {
|
||||
static::converter('', $language->code());
|
||||
foreach ($kirby->models() as $model) {
|
||||
$model->storage()->convertLanguage(
|
||||
'default',
|
||||
$language->code()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// update the main languages collection in the app instance
|
||||
@@ -248,17 +197,18 @@ class Language extends Model
|
||||
/**
|
||||
* Delete the current language and
|
||||
* all its translation files
|
||||
*
|
||||
* @internal
|
||||
* @return bool
|
||||
*
|
||||
* @throws \Kirby\Exception\Exception
|
||||
*/
|
||||
public function delete(): bool
|
||||
{
|
||||
$kirby = App::instance();
|
||||
$languages = $kirby->languages();
|
||||
$code = $this->code();
|
||||
$isLast = $languages->count() === 1;
|
||||
$kirby = App::instance();
|
||||
$code = $this->code();
|
||||
|
||||
if ($this->isDeletable() === false) {
|
||||
throw new Exception('The language cannot be deleted');
|
||||
}
|
||||
|
||||
// trigger before hook
|
||||
$kirby->trigger('language.delete:before', [
|
||||
@@ -269,10 +219,12 @@ class Language extends Model
|
||||
throw new Exception('The language could not be deleted');
|
||||
}
|
||||
|
||||
if ($isLast === true) {
|
||||
$this->converter($code, '');
|
||||
} else {
|
||||
$this->deleteContentFiles($code);
|
||||
foreach ($kirby->models() as $model) {
|
||||
if ($this->isLast() === true) {
|
||||
$model->storage()->convertLanguage($code, 'default');
|
||||
} else {
|
||||
$model->storage()->deleteLanguage($code);
|
||||
}
|
||||
}
|
||||
|
||||
// get the original language collection and remove the current language
|
||||
@@ -286,43 +238,8 @@ class Language extends Model
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the language is deleted, all content files with
|
||||
* the language code must be removed as well.
|
||||
*
|
||||
* @param mixed $code
|
||||
* @return bool
|
||||
*/
|
||||
protected function deleteContentFiles($code): bool
|
||||
{
|
||||
$kirby = App::instance();
|
||||
$site = $kirby->site();
|
||||
|
||||
F::remove($site->contentFile($code, true));
|
||||
|
||||
foreach ($kirby->site()->index(true) as $page) {
|
||||
foreach ($page->files() as $file) {
|
||||
F::remove($file->contentFile($code, true));
|
||||
}
|
||||
|
||||
F::remove($page->contentFile($code, true));
|
||||
}
|
||||
|
||||
foreach ($kirby->users() as $user) {
|
||||
foreach ($user->files() as $file) {
|
||||
F::remove($file->contentFile($code, true));
|
||||
}
|
||||
|
||||
F::remove($user->contentFile($code, true));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reading direction of this language
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function direction(): string
|
||||
{
|
||||
@@ -331,8 +248,6 @@ class Language extends Model
|
||||
|
||||
/**
|
||||
* Check if the language file exists
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
@@ -342,19 +257,36 @@ class Language extends Model
|
||||
/**
|
||||
* Checks if this is the default language
|
||||
* for the site.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDefault(): bool
|
||||
{
|
||||
return $this->default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the language can be deleted
|
||||
*/
|
||||
public function isDeletable(): bool
|
||||
{
|
||||
// the default language can only be deleted if it's the last
|
||||
if ($this->isDefault() === true && $this->isLast() === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this is the last language
|
||||
*/
|
||||
public function isLast(): bool
|
||||
{
|
||||
return App::instance()->languages()->count() === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The id is required for collections
|
||||
* to work properly. The code is used as id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function id(): string
|
||||
{
|
||||
@@ -362,11 +294,17 @@ class Language extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the language rules for provided locale code
|
||||
*
|
||||
* @param string $code
|
||||
* Returns the parent Kirby instance
|
||||
*/
|
||||
public static function loadRules(string $code)
|
||||
public function kirby(): App
|
||||
{
|
||||
return static::$kirby ??= App::instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the language rules for provided locale code
|
||||
*/
|
||||
public static function loadRules(string $code): array
|
||||
{
|
||||
$kirby = App::instance();
|
||||
$code = Str::contains($code, '.') ? Str::before($code, '.') : $code;
|
||||
@@ -387,9 +325,8 @@ class Language extends Model
|
||||
* Returns the PHP locale setting array
|
||||
*
|
||||
* @param int $category If passed, returns the locale for the specified category (e.g. LC_ALL) as string
|
||||
* @return array|string
|
||||
*/
|
||||
public function locale(int $category = null)
|
||||
public function locale(int $category = null): array|string|null
|
||||
{
|
||||
if ($category !== null) {
|
||||
return $this->locale[$category] ?? $this->locale[LC_ALL] ?? null;
|
||||
@@ -401,8 +338,6 @@ class Language extends Model
|
||||
/**
|
||||
* Returns the human-readable name
|
||||
* of the language
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
@@ -411,8 +346,6 @@ class Language extends Model
|
||||
|
||||
/**
|
||||
* Returns the URL path for the language
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function path(): string
|
||||
{
|
||||
@@ -425,8 +358,6 @@ class Language extends Model
|
||||
|
||||
/**
|
||||
* Returns the routing pattern for the language
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function pattern(): string
|
||||
{
|
||||
@@ -441,8 +372,6 @@ class Language extends Model
|
||||
|
||||
/**
|
||||
* Returns the absolute path to the language file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function root(): string
|
||||
{
|
||||
@@ -453,19 +382,15 @@ class Language extends Model
|
||||
* Returns the LanguageRouter instance
|
||||
* which is used to handle language specific
|
||||
* routes.
|
||||
*
|
||||
* @return \Kirby\Cms\LanguageRouter
|
||||
*/
|
||||
public function router()
|
||||
public function router(): LanguageRouter
|
||||
{
|
||||
return new LanguageRouter($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get slug rules for language
|
||||
*
|
||||
* @internal
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
@@ -476,11 +401,11 @@ class Language extends Model
|
||||
|
||||
/**
|
||||
* Saves the language settings in the languages folder
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function save()
|
||||
public function save(): static
|
||||
{
|
||||
try {
|
||||
$existingData = Data::read($this->root());
|
||||
@@ -508,104 +433,15 @@ class Language extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $code
|
||||
* @return $this
|
||||
* Private siblings collector
|
||||
*/
|
||||
protected function setCode(string $code)
|
||||
protected function siblingsCollection(): Collection
|
||||
{
|
||||
$this->code = trim($code);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $default
|
||||
* @return $this
|
||||
*/
|
||||
protected function setDefault(bool $default = false)
|
||||
{
|
||||
$this->default = $default;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $direction
|
||||
* @return $this
|
||||
*/
|
||||
protected function setDirection(string $direction = 'ltr')
|
||||
{
|
||||
$this->direction = $direction === 'rtl' ? 'rtl' : 'ltr';
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $locale
|
||||
* @return $this
|
||||
*/
|
||||
protected function setLocale($locale = null)
|
||||
{
|
||||
if ($locale === null) {
|
||||
$this->locale = [LC_ALL => $this->code];
|
||||
} else {
|
||||
$this->locale = Locale::normalize($locale);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return $this
|
||||
*/
|
||||
protected function setName(string $name = null)
|
||||
{
|
||||
$this->name = trim($name ?? $this->code);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $slugs
|
||||
* @return $this
|
||||
*/
|
||||
protected function setSlugs(array $slugs = null)
|
||||
{
|
||||
$this->slugs = $slugs ?? [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $smartypants
|
||||
* @return $this
|
||||
*/
|
||||
protected function setSmartypants(array $smartypants = null)
|
||||
{
|
||||
$this->smartypants = $smartypants ?? [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $translations
|
||||
* @return $this
|
||||
*/
|
||||
protected function setTranslations(array $translations = null)
|
||||
{
|
||||
$this->translations = $translations ?? [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return $this
|
||||
*/
|
||||
protected function setUrl(string $url = null)
|
||||
{
|
||||
$this->url = $url;
|
||||
return $this;
|
||||
return App::instance()->languages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the custom slug rules for this language
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function slugs(): array
|
||||
{
|
||||
@@ -614,8 +450,6 @@ class Language extends Model
|
||||
|
||||
/**
|
||||
* Returns the custom SmartyPants options for this language
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function smartypants(): array
|
||||
{
|
||||
@@ -625,8 +459,6 @@ class Language extends Model
|
||||
/**
|
||||
* Returns the most important
|
||||
* properties as array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
@@ -643,8 +475,6 @@ class Language extends Model
|
||||
|
||||
/**
|
||||
* Returns the translation strings for this language
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function translations(): array
|
||||
{
|
||||
@@ -653,8 +483,6 @@ class Language extends Model
|
||||
|
||||
/**
|
||||
* Returns the absolute Url for the language
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function url(): string
|
||||
{
|
||||
@@ -665,12 +493,9 @@ class Language extends Model
|
||||
|
||||
/**
|
||||
* Update language properties and save them
|
||||
*
|
||||
* @internal
|
||||
* @param array $props
|
||||
* @return static
|
||||
*/
|
||||
public function update(array $props = null)
|
||||
public function update(array $props = null): static
|
||||
{
|
||||
// don't change the language code
|
||||
unset($props['code']);
|
||||
@@ -681,6 +506,10 @@ class Language extends Model
|
||||
$kirby = App::instance();
|
||||
$updated = $this->clone($props);
|
||||
|
||||
if (isset($props['translations']) === true) {
|
||||
$updated->translations = $props['translations'];
|
||||
}
|
||||
|
||||
// validate the updated language
|
||||
LanguageRules::update($updated);
|
||||
|
||||
@@ -690,32 +519,31 @@ class Language extends Model
|
||||
'input' => $props
|
||||
]);
|
||||
|
||||
// convert the current default to a non-default language
|
||||
if ($updated->isDefault() === true) {
|
||||
$kirby->defaultLanguage()?->clone(['default' => false])->save();
|
||||
// if language just got promoted to be the new default language…
|
||||
if ($this->isDefault() === false && $updated->isDefault() === true) {
|
||||
// convert the current default to a non-default language
|
||||
$previous = $kirby->defaultLanguage()?->clone(['default' => false])->save();
|
||||
$kirby->languages(false)->set($previous->code(), $previous);
|
||||
|
||||
$code = $this->code();
|
||||
$site = $kirby->site();
|
||||
|
||||
touch($site->contentFile($code));
|
||||
|
||||
foreach ($kirby->site()->index(true) as $page) {
|
||||
$files = $page->files();
|
||||
|
||||
foreach ($files as $file) {
|
||||
touch($file->contentFile($code));
|
||||
}
|
||||
|
||||
touch($page->contentFile($code));
|
||||
foreach ($kirby->models() as $model) {
|
||||
$model->storage()->touchLanguage($this);
|
||||
}
|
||||
} elseif ($this->isDefault() === true) {
|
||||
throw new PermissionException('Please select another language to be the primary language');
|
||||
}
|
||||
|
||||
// if language was the default language and got demoted…
|
||||
if (
|
||||
$this->isDefault() === true &&
|
||||
$updated->isDefault() === false &&
|
||||
$kirby->defaultLanguage()->code() === $this->code()
|
||||
) {
|
||||
// ensure another language has already been set as default
|
||||
throw new LogicException('Please select another language to be the primary language');
|
||||
}
|
||||
|
||||
$language = $updated->save();
|
||||
|
||||
// make sure the language is also updated in the Kirby language collection
|
||||
App::instance()->languages(false)->set($language->code(), $language);
|
||||
// make sure the language is also updated in the languages collection
|
||||
$kirby->languages(false)->set($language->code(), $language);
|
||||
|
||||
// trigger after hook
|
||||
$kirby->trigger('language.update:after', [
|
||||
@@ -726,4 +554,19 @@ class Language extends Model
|
||||
|
||||
return $language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a language variable object
|
||||
* for the key in the translations array
|
||||
*/
|
||||
public function variable(string $key, bool $decode = false): LanguageVariable
|
||||
{
|
||||
// allows decoding if base64-url encoded url is sent
|
||||
// for compatibility of different environments
|
||||
if ($decode === true) {
|
||||
$key = rawurldecode(base64_decode($key));
|
||||
}
|
||||
|
||||
return new LanguageVariable($this, $key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,36 +20,21 @@ use Kirby\Toolkit\Str;
|
||||
*/
|
||||
class LanguageRouter
|
||||
{
|
||||
/**
|
||||
* The parent language
|
||||
*
|
||||
* @var Language
|
||||
*/
|
||||
protected $language;
|
||||
|
||||
/**
|
||||
* The router instance
|
||||
*
|
||||
* @var Router
|
||||
*/
|
||||
protected $router;
|
||||
protected Router $router;
|
||||
|
||||
/**
|
||||
* Creates a new language router instance
|
||||
* for the given language
|
||||
*
|
||||
* @param \Kirby\Cms\Language $language
|
||||
*/
|
||||
public function __construct(Language $language)
|
||||
{
|
||||
$this->language = $language;
|
||||
public function __construct(
|
||||
protected Language $language
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all scoped routes for the
|
||||
* current language from the Kirby instance
|
||||
*
|
||||
* @return array
|
||||
* @throws \Kirby\Exception\NotFoundException
|
||||
*/
|
||||
public function routes(): array
|
||||
@@ -106,26 +91,32 @@ class LanguageRouter
|
||||
* Wrapper around the Router::call method
|
||||
* that injects the Language instance and
|
||||
* if needed also the Page as arguments.
|
||||
*
|
||||
* @param string|null $path
|
||||
* @return mixed
|
||||
*/
|
||||
public function call(string $path = null)
|
||||
public function call(string|null $path = null): mixed
|
||||
{
|
||||
$language = $this->language;
|
||||
$kirby = $language->kirby();
|
||||
$router = new Router($this->routes());
|
||||
$language = $this->language;
|
||||
$kirby = $language->kirby();
|
||||
$this->router ??= new Router($this->routes());
|
||||
|
||||
try {
|
||||
return $router->call($path, $kirby->request()->method(), function ($route) use ($kirby, $language) {
|
||||
return $this->router->call($path, $kirby->request()->method(), function ($route) use ($kirby, $language) {
|
||||
$kirby->setCurrentTranslation($language);
|
||||
$kirby->setCurrentLanguage($language);
|
||||
|
||||
if ($page = $route->page()) {
|
||||
return $route->action()->call($route, $language, $page, ...$route->arguments());
|
||||
return $route->action()->call(
|
||||
$route,
|
||||
$language,
|
||||
$page,
|
||||
...$route->arguments()
|
||||
);
|
||||
}
|
||||
|
||||
return $route->action()->call($route, $language, ...$route->arguments());
|
||||
return $route->action()->call(
|
||||
$route,
|
||||
$language,
|
||||
...$route->arguments()
|
||||
);
|
||||
});
|
||||
} catch (Exception) {
|
||||
return $kirby->resolve($path, $language->code());
|
||||
|
||||
@@ -8,9 +8,6 @@ class LanguageRoutes
|
||||
{
|
||||
/**
|
||||
* Creates all multi-language routes
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby
|
||||
* @return array
|
||||
*/
|
||||
public static function create(App $kirby): array
|
||||
{
|
||||
@@ -58,9 +55,6 @@ class LanguageRoutes
|
||||
/**
|
||||
* Create the fallback route
|
||||
* for unprefixed default language URLs.
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby
|
||||
* @return array
|
||||
*/
|
||||
public static function fallback(App $kirby): array
|
||||
{
|
||||
@@ -73,7 +67,10 @@ class LanguageRoutes
|
||||
$extension = F::extension($path);
|
||||
|
||||
// try to redirect prefixed pages
|
||||
if (empty($extension) === true && $page = $kirby->page($path)) {
|
||||
if (
|
||||
empty($extension) === true &&
|
||||
$page = $kirby->page($path)
|
||||
) {
|
||||
$url = $kirby->request()->url([
|
||||
'query' => null,
|
||||
'params' => null,
|
||||
@@ -81,15 +78,17 @@ class LanguageRoutes
|
||||
]);
|
||||
|
||||
if ($url->toString() !== $page->url()) {
|
||||
// redirect to translated page directly
|
||||
// if translation is exists and languages detect is enabled
|
||||
// redirect to translated page directly if translation
|
||||
// is exists and languages detect is enabled
|
||||
$lang = $kirby->detectedLanguage()->code();
|
||||
|
||||
if (
|
||||
$kirby->option('languages.detect') === true &&
|
||||
$page->translation($kirby->detectedLanguage()->code())->exists() === true
|
||||
$page->translation($lang)->exists() === true
|
||||
) {
|
||||
return $kirby
|
||||
->response()
|
||||
->redirect($page->url($kirby->detectedLanguage()->code()));
|
||||
->redirect($page->url($lang));
|
||||
}
|
||||
|
||||
return $kirby
|
||||
@@ -105,9 +104,6 @@ class LanguageRoutes
|
||||
|
||||
/**
|
||||
* Create the multi-language home page route
|
||||
*
|
||||
* @param \Kirby\Cms\App $kirby
|
||||
* @return array
|
||||
*/
|
||||
public static function home(App $kirby): array
|
||||
{
|
||||
@@ -118,7 +114,10 @@ class LanguageRoutes
|
||||
'env' => 'site',
|
||||
'action' => function () use ($kirby) {
|
||||
// find all languages with the same base url as the current installation
|
||||
$languages = $kirby->languages()->filter('baseurl', $kirby->url());
|
||||
$languages = $kirby->languages()->filter(
|
||||
'baseurl',
|
||||
$kirby->url()
|
||||
);
|
||||
|
||||
// if there's no language with a matching base url,
|
||||
// redirect to the default language
|
||||
@@ -128,7 +127,8 @@ class LanguageRoutes
|
||||
->redirect($kirby->defaultLanguage()->url());
|
||||
}
|
||||
|
||||
// if there's just one language, we take that to render the home page
|
||||
// if there's just one language,
|
||||
// we take that to render the home page
|
||||
if ($languages->count() === 1) {
|
||||
$currentLanguage = $languages->first();
|
||||
} else {
|
||||
|
||||
@@ -20,8 +20,6 @@ class LanguageRules
|
||||
/**
|
||||
* Validates if the language can be created
|
||||
*
|
||||
* @param \Kirby\Cms\Language $language
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\DuplicateException If the language already exists
|
||||
*/
|
||||
public static function create(Language $language): bool
|
||||
@@ -43,8 +41,6 @@ class LanguageRules
|
||||
|
||||
/**
|
||||
* Validates if the language can be updated
|
||||
*
|
||||
* @param \Kirby\Cms\Language $language
|
||||
*/
|
||||
public static function update(Language $language)
|
||||
{
|
||||
@@ -55,8 +51,6 @@ class LanguageRules
|
||||
/**
|
||||
* Validates if the language code is formatted correctly
|
||||
*
|
||||
* @param \Kirby\Cms\Language $language
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the language code is not valid
|
||||
*/
|
||||
public static function validLanguageCode(Language $language): bool
|
||||
@@ -77,8 +71,6 @@ class LanguageRules
|
||||
/**
|
||||
* Validates if the language name is formatted correctly
|
||||
*
|
||||
* @param \Kirby\Cms\Language $language
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the language name is invalid
|
||||
*/
|
||||
public static function validLanguageName(Language $language): bool
|
||||
|
||||
122
kirby/src/Cms/LanguageVariable.php
Normal file
122
kirby/src/Cms/LanguageVariable.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Exception\DuplicateException;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* A language variable is a custom translation string
|
||||
* Those are stored in /site/languages/$code.php in the
|
||||
* translations array
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class LanguageVariable
|
||||
{
|
||||
protected App $kirby;
|
||||
|
||||
public function __construct(
|
||||
protected Language $language,
|
||||
protected string $key
|
||||
) {
|
||||
$this->kirby = App::instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new language variable. This will
|
||||
* be added to the default language first and
|
||||
* can then be translated in other languages.
|
||||
*/
|
||||
public static function create(
|
||||
string $key,
|
||||
string|null $value = null
|
||||
): static {
|
||||
if (is_numeric($key) === true) {
|
||||
throw new InvalidArgumentException('The variable key must not be numeric');
|
||||
}
|
||||
|
||||
if (empty($key) === true) {
|
||||
throw new InvalidArgumentException('The variable needs a valid key');
|
||||
}
|
||||
|
||||
$kirby = App::instance();
|
||||
$language = $kirby->defaultLanguage();
|
||||
$translations = $language->translations();
|
||||
|
||||
if ($kirby->translation()->get($key) !== null) {
|
||||
if (isset($translations[$key]) === true) {
|
||||
throw new DuplicateException('The variable already exists');
|
||||
}
|
||||
|
||||
throw new DuplicateException('The variable is part of the core translation and cannot be overwritten');
|
||||
}
|
||||
|
||||
$translations[$key] = trim($value ?? '');
|
||||
|
||||
$language->update(['translations' => $translations]);
|
||||
|
||||
return $language->variable($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a language variable from the translations array.
|
||||
* This will go through all language files and delete the
|
||||
* key from all translation arrays to keep them clean.
|
||||
*/
|
||||
public function delete(): bool
|
||||
{
|
||||
// go through all languages and remove the variable
|
||||
foreach ($this->kirby->languages() as $language) {
|
||||
$variables = $language->translations();
|
||||
|
||||
unset($variables[$this->key]);
|
||||
|
||||
$language->update(['translations' => $variables]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a language variable exists in the default language
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
$language = $this->kirby->defaultLanguage();
|
||||
return isset($language->translations()[$this->key]) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique key for the variable
|
||||
*/
|
||||
public function key(): string
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new value for the language variable
|
||||
*/
|
||||
public function update(string $value): static
|
||||
{
|
||||
$translations = $this->language->translations();
|
||||
$translations[$this->key] = $value;
|
||||
|
||||
$language = $this->language->update(['translations' => $translations]);
|
||||
|
||||
return $language->variable($this->key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value if the variable has been translated.
|
||||
*/
|
||||
public function value(): string|null
|
||||
{
|
||||
return $this->language->translations()[$this->key] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -18,20 +18,19 @@ class Languages extends Collection
|
||||
{
|
||||
/**
|
||||
* All registered languages methods
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $methods = [];
|
||||
public static array $methods = [];
|
||||
|
||||
/**
|
||||
* Creates a new collection with the given language objects
|
||||
*
|
||||
* @param array $objects `Kirby\Cms\Language` objects
|
||||
* @param null $parent
|
||||
* @throws \Kirby\Exception\DuplicateException
|
||||
*/
|
||||
public function __construct($objects = [], $parent = null)
|
||||
{
|
||||
public function __construct(
|
||||
array $objects = [],
|
||||
$parent = null
|
||||
) {
|
||||
$defaults = array_filter(
|
||||
$objects,
|
||||
fn ($language) => $language->isDefault() === true
|
||||
@@ -41,48 +40,39 @@ class Languages extends Collection
|
||||
throw new DuplicateException('You cannot have multiple default languages. Please check your language config files.');
|
||||
}
|
||||
|
||||
parent::__construct($objects, $parent);
|
||||
parent::__construct($objects, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all language codes as array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function codes(): array
|
||||
{
|
||||
return $this->keys();
|
||||
return App::instance()->multilang() ? $this->keys() : ['default'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new language with the given props
|
||||
*
|
||||
* @internal
|
||||
* @param array $props
|
||||
* @return \Kirby\Cms\Language
|
||||
*/
|
||||
public function create(array $props)
|
||||
public function create(array $props): Language
|
||||
{
|
||||
return Language::create($props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default language
|
||||
*
|
||||
* @return \Kirby\Cms\Language|null
|
||||
*/
|
||||
public function default()
|
||||
public function default(): Language|null
|
||||
{
|
||||
return $this->findBy('isDefault', true) ?? $this->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert all defined languages to a collection
|
||||
*
|
||||
* @internal
|
||||
* @return static
|
||||
*/
|
||||
public static function load()
|
||||
public static function load(): static
|
||||
{
|
||||
$languages = [];
|
||||
$files = glob(App::instance()->root('languages') . '/*.php');
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Content\Content;
|
||||
|
||||
/**
|
||||
* Represents a single Layout with
|
||||
* multiple columns
|
||||
@@ -19,24 +21,13 @@ class Layout extends Item
|
||||
|
||||
public const ITEMS_CLASS = Layouts::class;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Cms\Content
|
||||
*/
|
||||
protected $attrs;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Cms\LayoutColumns
|
||||
*/
|
||||
protected $columns;
|
||||
protected Content $attrs;
|
||||
protected LayoutColumns $columns;
|
||||
|
||||
/**
|
||||
* Proxy for attrs
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $args
|
||||
* @return \Kirby\Cms\Field
|
||||
*/
|
||||
public function __call(string $method, array $args = [])
|
||||
public function __call(string $method, array $args = []): mixed
|
||||
{
|
||||
// layout methods
|
||||
if ($this->hasMethod($method) === true) {
|
||||
@@ -48,8 +39,6 @@ class Layout extends Item
|
||||
|
||||
/**
|
||||
* Creates a new Layout object
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function __construct(array $params = [])
|
||||
{
|
||||
@@ -66,20 +55,16 @@ class Layout extends Item
|
||||
|
||||
/**
|
||||
* Returns the attrs object
|
||||
*
|
||||
* @return \Kirby\Cms\Content
|
||||
*/
|
||||
public function attrs()
|
||||
public function attrs(): Content
|
||||
{
|
||||
return $this->attrs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the columns in this layout
|
||||
*
|
||||
* @return \Kirby\Cms\LayoutColumns
|
||||
*/
|
||||
public function columns()
|
||||
public function columns(): LayoutColumns
|
||||
{
|
||||
return $this->columns;
|
||||
}
|
||||
@@ -87,24 +72,18 @@ class Layout extends Item
|
||||
/**
|
||||
* Checks if the layout is empty
|
||||
* @since 3.5.2
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return $this
|
||||
->columns()
|
||||
->filter(function ($column) {
|
||||
return $column->isNotEmpty();
|
||||
})
|
||||
->filter('isEmpty', false)
|
||||
->count() === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the layout is not empty
|
||||
* @since 3.5.2
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isNotEmpty(): bool
|
||||
{
|
||||
@@ -114,8 +93,6 @@ class Layout extends Item
|
||||
/**
|
||||
* The result is being sent to the editor
|
||||
* via the API in the panel
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
|
||||
@@ -21,20 +21,11 @@ class LayoutColumn extends Item
|
||||
|
||||
public const ITEMS_CLASS = LayoutColumns::class;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Cms\Blocks
|
||||
*/
|
||||
protected $blocks;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $width;
|
||||
protected Blocks $blocks;
|
||||
protected string $width;
|
||||
|
||||
/**
|
||||
* Creates a new LayoutColumn object
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function __construct(array $params = [])
|
||||
{
|
||||
@@ -50,12 +41,8 @@ class LayoutColumn extends Item
|
||||
|
||||
/**
|
||||
* Magic getter function
|
||||
*
|
||||
* @param string $method
|
||||
* @param mixed $args
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call(string $method, $args)
|
||||
public function __call(string $method, mixed $args): mixed
|
||||
{
|
||||
// layout column methods
|
||||
if ($this->hasMethod($method) === true) {
|
||||
@@ -67,9 +54,8 @@ class LayoutColumn extends Item
|
||||
* Returns the blocks collection
|
||||
*
|
||||
* @param bool $includeHidden Sets whether to include hidden blocks
|
||||
* @return \Kirby\Cms\Blocks
|
||||
*/
|
||||
public function blocks(bool $includeHidden = false)
|
||||
public function blocks(bool $includeHidden = false): Blocks
|
||||
{
|
||||
if ($includeHidden === false) {
|
||||
return $this->blocks->filter('isHidden', false);
|
||||
@@ -81,8 +67,6 @@ class LayoutColumn extends Item
|
||||
/**
|
||||
* Checks if the column is empty
|
||||
* @since 3.5.2
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
@@ -95,8 +79,6 @@ class LayoutColumn extends Item
|
||||
/**
|
||||
* Checks if the column is not empty
|
||||
* @since 3.5.2
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isNotEmpty(): bool
|
||||
{
|
||||
@@ -105,9 +87,6 @@ class LayoutColumn extends Item
|
||||
|
||||
/**
|
||||
* Returns the number of columns this column spans
|
||||
*
|
||||
* @param int $columns
|
||||
* @return int
|
||||
*/
|
||||
public function span(int $columns = 12): int
|
||||
{
|
||||
@@ -121,8 +100,6 @@ class LayoutColumn extends Item
|
||||
/**
|
||||
* The result is being sent to the editor
|
||||
* via the API in the panel
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
@@ -135,8 +112,6 @@ class LayoutColumn extends Item
|
||||
|
||||
/**
|
||||
* Returns the width of the column
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function width(): string
|
||||
{
|
||||
|
||||
@@ -18,8 +18,6 @@ class LayoutColumns extends Items
|
||||
|
||||
/**
|
||||
* All registered layout columns methods
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $methods = [];
|
||||
public static array $methods = [];
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Data\Json;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Throwable;
|
||||
|
||||
@@ -22,17 +22,28 @@ class Layouts extends Items
|
||||
|
||||
/**
|
||||
* All registered layouts methods
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $methods = [];
|
||||
public static array $methods = [];
|
||||
|
||||
public static function factory(
|
||||
array $items = null,
|
||||
array $params = []
|
||||
): static {
|
||||
// convert single layout to layouts array
|
||||
if (
|
||||
isset($items['columns']) === true ||
|
||||
isset($items['id']) === true
|
||||
) {
|
||||
$items = [$items];
|
||||
}
|
||||
|
||||
public static function factory(array $items = null, array $params = [])
|
||||
{
|
||||
$first = $items[0] ?? [];
|
||||
|
||||
// if there are no wrapping layouts for blocks yet …
|
||||
if (array_key_exists('content', $first) === true || array_key_exists('type', $first) === true) {
|
||||
if (
|
||||
isset($first['content']) === true ||
|
||||
isset($first['type']) === true
|
||||
) {
|
||||
$items = [
|
||||
[
|
||||
'id' => Str::uuid(),
|
||||
@@ -52,9 +63,6 @@ class Layouts extends Items
|
||||
/**
|
||||
* Checks if a given block type exists in the layouts collection
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @param string $type
|
||||
* @return bool
|
||||
*/
|
||||
public function hasBlockType(string $type): bool
|
||||
{
|
||||
@@ -63,15 +71,15 @@ class Layouts extends Items
|
||||
|
||||
/**
|
||||
* Parse layouts data
|
||||
*
|
||||
* @param array|string $input
|
||||
* @return array
|
||||
*/
|
||||
public static function parse($input): array
|
||||
public static function parse(array|string|null $input): array
|
||||
{
|
||||
if (empty($input) === false && is_array($input) === false) {
|
||||
if (
|
||||
empty($input) === false &&
|
||||
is_array($input) === false
|
||||
) {
|
||||
try {
|
||||
$input = Data::decode($input, 'json');
|
||||
$input = Json::decode((string)$input);
|
||||
} catch (Throwable) {
|
||||
return [];
|
||||
}
|
||||
@@ -89,9 +97,8 @@ class Layouts extends Items
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @param bool $includeHidden Sets whether to include hidden blocks
|
||||
* @return \Kirby\Cms\Blocks
|
||||
*/
|
||||
public function toBlocks(bool $includeHidden = false)
|
||||
public function toBlocks(bool $includeHidden = false): Blocks
|
||||
{
|
||||
$blocks = [];
|
||||
|
||||
|
||||
523
kirby/src/Cms/License.php
Normal file
523
kirby/src/Cms/License.php
Normal file
@@ -0,0 +1,523 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use IntlDateFormatter;
|
||||
use Kirby\Data\Json;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Http\Remote;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Kirby\Toolkit\V;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class License
|
||||
{
|
||||
protected const HISTORY = [
|
||||
'3' => '2019-02-05',
|
||||
'4' => '2023-11-28'
|
||||
];
|
||||
|
||||
protected const SALT = 'kwAHMLyLPBnHEskzH9pPbJsBxQhKXZnX';
|
||||
|
||||
// cache
|
||||
protected LicenseStatus $status;
|
||||
protected LicenseType $type;
|
||||
|
||||
public function __construct(
|
||||
protected string|null $activation = null,
|
||||
protected string|null $code = null,
|
||||
protected string|null $domain = null,
|
||||
protected string|null $email = null,
|
||||
protected string|null $order = null,
|
||||
protected string|null $date = null,
|
||||
protected string|null $signature = null,
|
||||
) {
|
||||
// normalize the email address
|
||||
$this->email = $this->email === null ? null : $this->normalizeEmail($this->email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the activation date if available
|
||||
*/
|
||||
public function activation(string|IntlDateFormatter|null $format = null): int|string|null
|
||||
{
|
||||
return $this->activation !== null ? Str::date(strtotime($this->activation), $format) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the license code if available
|
||||
*/
|
||||
public function code(bool $obfuscated = false): string|null
|
||||
{
|
||||
if ($this->code !== null && $obfuscated === true) {
|
||||
return Str::substr($this->code, 0, 10) . str_repeat('X', 22);
|
||||
}
|
||||
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Content for the license file
|
||||
*/
|
||||
public function content(): array
|
||||
{
|
||||
return [
|
||||
'activation' => $this->activation,
|
||||
'code' => $this->code,
|
||||
'date' => $this->date,
|
||||
'domain' => $this->domain,
|
||||
'email' => $this->email,
|
||||
'order' => $this->order,
|
||||
'signature' => $this->signature,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the purchase date if available
|
||||
*/
|
||||
public function date(string|IntlDateFormatter|null $format = null): int|string|null
|
||||
{
|
||||
return $this->date !== null ? Str::date(strtotime($this->date), $format) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the activation domain if available
|
||||
*/
|
||||
public function domain(): string|null
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the activation email if available
|
||||
*/
|
||||
public function email(): string|null
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the email address of the license
|
||||
*/
|
||||
public function hasValidEmailAddress(): bool
|
||||
{
|
||||
return V::email($this->email) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hub address
|
||||
*/
|
||||
public static function hub(): string
|
||||
{
|
||||
return App::instance()->option('hub', 'https://hub.getkirby.com');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for all required components of a valid license
|
||||
*/
|
||||
public function isComplete(): bool
|
||||
{
|
||||
if (
|
||||
$this->code !== null &&
|
||||
$this->date !== null &&
|
||||
$this->domain !== null &&
|
||||
$this->email !== null &&
|
||||
$this->order !== null &&
|
||||
$this->signature !== null &&
|
||||
$this->hasValidEmailAddress() === true &&
|
||||
$this->type() !== LicenseType::Invalid
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The license is still valid for the currently
|
||||
* installed version, but it passed the 3 year period.
|
||||
*/
|
||||
public function isInactive(): bool
|
||||
{
|
||||
return $this->renewal() < time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for licenses beyond their 3 year period
|
||||
*/
|
||||
public function isLegacy(): bool
|
||||
{
|
||||
if ($this->type() === LicenseType::Legacy) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// without an activation date, the license
|
||||
// renewal cannot be evaluated and the license
|
||||
// has to be marked as expired
|
||||
if ($this->activation === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// get release date of current major version
|
||||
$major = Str::before(App::instance()->version(), '.');
|
||||
$release = strtotime(static::HISTORY[$major] ?? '');
|
||||
|
||||
// if there's no matching version in the history
|
||||
// rather throw an exception to avoid further issues
|
||||
// @codeCoverageIgnoreStart
|
||||
if ($release === false) {
|
||||
throw new InvalidArgumentException('The version for your license could not be found');
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
// If the renewal date is older than the version launch
|
||||
// date, the license is expired
|
||||
return $this->renewal() < $release;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs multiple checks to find out if the license is
|
||||
* installed and verifiable
|
||||
*/
|
||||
public function isMissing(): bool
|
||||
{
|
||||
return
|
||||
$this->isComplete() === false ||
|
||||
$this->isOnCorrectDomain() === false ||
|
||||
$this->isSigned() === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the license is on the correct domain
|
||||
*/
|
||||
public function isOnCorrectDomain(): bool
|
||||
{
|
||||
if ($this->domain === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// compare domains
|
||||
if ($this->normalizeDomain(App::instance()->system()->indexUrl()) !== $this->normalizeDomain($this->domain)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the signature with all ingredients
|
||||
*/
|
||||
public function isSigned(): bool
|
||||
{
|
||||
if ($this->signature === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// get the public key
|
||||
$pubKey = F::read(App::instance()->root('kirby') . '/kirby.pub');
|
||||
|
||||
// verify the license signature
|
||||
$data = json_encode($this->signatureData());
|
||||
$signature = hex2bin($this->signature);
|
||||
|
||||
return openssl_verify($data, $signature, $pubKey, 'RSA-SHA256') === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reliable label for the license type
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
if ($this->status() === LicenseStatus::Missing) {
|
||||
return LicenseType::Invalid->label();
|
||||
}
|
||||
|
||||
return $this->type()->label();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the email address to be make sure it
|
||||
* does not have trailing spaces and is lowercase.
|
||||
*/
|
||||
protected function normalizeEmail(string $email): string
|
||||
{
|
||||
return Str::lower(trim($email));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the domain to be comparable
|
||||
*/
|
||||
protected function normalizeDomain(string $domain): string
|
||||
{
|
||||
// remove common "testing" subdomains as well as www.
|
||||
// to ensure that installations of the same site have
|
||||
// the same license URL; only for installations at /,
|
||||
// subdirectory installations are difficult to normalize
|
||||
if (Str::contains($domain, '/') === false) {
|
||||
if (Str::startsWith($domain, 'www.')) {
|
||||
return substr($domain, 4);
|
||||
}
|
||||
|
||||
if (Str::startsWith($domain, 'dev.')) {
|
||||
return substr($domain, 4);
|
||||
}
|
||||
|
||||
if (Str::startsWith($domain, 'test.')) {
|
||||
return substr($domain, 5);
|
||||
}
|
||||
|
||||
if (Str::startsWith($domain, 'staging.')) {
|
||||
return substr($domain, 8);
|
||||
}
|
||||
}
|
||||
|
||||
return $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the order id if available
|
||||
*/
|
||||
public function order(): string|null
|
||||
{
|
||||
return $this->order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support the old license file dataset
|
||||
* from older licenses
|
||||
*/
|
||||
public static function polyfill(array $license): array
|
||||
{
|
||||
return [
|
||||
'activation' => $license['activation'] ?? null,
|
||||
'code' => $license['code'] ?? $license['license'] ?? null,
|
||||
'date' => $license['date'] ?? null,
|
||||
'domain' => $license['domain'] ?? null,
|
||||
'email' => $license['email'] ?? null,
|
||||
'order' => $license['order'] ?? null,
|
||||
'signature' => $license['signature'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the license file in the config folder
|
||||
* and creates a new license instance for it.
|
||||
*/
|
||||
public static function read(): static
|
||||
{
|
||||
try {
|
||||
$license = Json::read(App::instance()->root('license'));
|
||||
} catch (Throwable) {
|
||||
return new static();
|
||||
}
|
||||
|
||||
return new static(...static::polyfill($license));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the hub to register the license
|
||||
*/
|
||||
public function register(): static
|
||||
{
|
||||
if ($this->type() === LicenseType::Invalid) {
|
||||
throw new InvalidArgumentException(['key' => 'license.format']);
|
||||
}
|
||||
|
||||
if ($this->hasValidEmailAddress() === false) {
|
||||
throw new InvalidArgumentException(['key' => 'license.email']);
|
||||
}
|
||||
|
||||
if ($this->domain === null) {
|
||||
throw new InvalidArgumentException(['key' => 'license.domain']);
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
$response = $this->request('register', [
|
||||
'license' => $this->code,
|
||||
'email' => $this->email,
|
||||
'domain' => $this->domain
|
||||
]);
|
||||
|
||||
return $this->update($response);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the renewal date
|
||||
*/
|
||||
public function renewal(string|IntlDateFormatter|null $format = null): int|string|null
|
||||
{
|
||||
if ($this->activation === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$time = strtotime('+3 years', $this->activation());
|
||||
return Str::date($time, $format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a hub request
|
||||
*/
|
||||
public function request(string $path, array $data): array
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
$response = Remote::get(static::hub() . '/' . $path, [
|
||||
'data' => $data
|
||||
]);
|
||||
|
||||
// handle request errors
|
||||
if ($response->code() !== 200) {
|
||||
$message = $response->json()['message'] ?? 'The request failed';
|
||||
|
||||
throw new LogicException($message, $response->code());
|
||||
}
|
||||
|
||||
return $response->json();
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the license in the config folder
|
||||
*/
|
||||
public function save(): bool
|
||||
{
|
||||
if ($this->status() !== LicenseStatus::Active) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'license.verification'
|
||||
]);
|
||||
}
|
||||
|
||||
// where to store the license file
|
||||
$file = App::instance()->root('license');
|
||||
|
||||
// save the license information
|
||||
return Json::write($file, $this->content());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the signature if available
|
||||
*/
|
||||
public function signature(): string|null
|
||||
{
|
||||
return $this->signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the signature data array to compare
|
||||
* with the signature in ::isSigned
|
||||
*/
|
||||
public function signatureData(): array
|
||||
{
|
||||
if ($this->type() === LicenseType::Legacy) {
|
||||
return [
|
||||
'license' => $this->code,
|
||||
'order' => $this->order,
|
||||
'email' => hash('sha256', $this->email . static::SALT),
|
||||
'domain' => $this->domain,
|
||||
'date' => $this->date,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'activation' => $this->activation,
|
||||
'code' => $this->code,
|
||||
'date' => $this->date,
|
||||
'domain' => $this->domain,
|
||||
'email' => hash('sha256', $this->email . static::SALT),
|
||||
'order' => $this->order,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the license status as string
|
||||
* This is used to build the proper UI elements
|
||||
* for the license activation
|
||||
*/
|
||||
public function status(): LicenseStatus
|
||||
{
|
||||
return $this->status ??= match (true) {
|
||||
$this->isMissing() === true => LicenseStatus::Missing,
|
||||
$this->isLegacy() === true => LicenseStatus::Legacy,
|
||||
$this->isInactive() === true => LicenseStatus::Inactive,
|
||||
default => LicenseStatus::Active
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the license type if the license key is available
|
||||
*/
|
||||
public function type(): LicenseType
|
||||
{
|
||||
return $this->type ??= LicenseType::detect($this->code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the license file
|
||||
*/
|
||||
public function update(array $data): static
|
||||
{
|
||||
// decode the response
|
||||
$data = static::polyfill($data);
|
||||
|
||||
$this->activation = $data['activation'];
|
||||
$this->code = $data['code'];
|
||||
$this->date = $data['date'];
|
||||
$this->order = $data['order'];
|
||||
$this->signature = $data['signature'];
|
||||
|
||||
// clear the caches
|
||||
unset($this->status, $this->type);
|
||||
|
||||
// save the new state of the license
|
||||
$this->save();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an upgrade request to the hub in order
|
||||
* to either redirect to the upgrade form or
|
||||
* sync the new license state
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function upgrade(): array
|
||||
{
|
||||
$response = $this->request('upgrade', [
|
||||
'domain' => $this->domain,
|
||||
'email' => $this->email,
|
||||
'license' => $this->code,
|
||||
]);
|
||||
|
||||
// the license still needs an upgrade
|
||||
if (empty($response['url']) === false) {
|
||||
// validate the redirect URL
|
||||
if (Str::startsWith($response['url'], static::hub()) === false) {
|
||||
throw new Exception('We couldn’t redirect you to the Hub');
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => 'upgrade',
|
||||
'url' => $response['url']
|
||||
];
|
||||
}
|
||||
|
||||
// the license has already been upgraded
|
||||
// and can now be replaced
|
||||
$this->update($response);
|
||||
|
||||
return [
|
||||
'status' => 'complete',
|
||||
];
|
||||
}
|
||||
}
|
||||
127
kirby/src/Cms/LicenseStatus.php
Normal file
127
kirby/src/Cms/LicenseStatus.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
/**
|
||||
* @package Kirby Cms
|
||||
* @author Nico Hoffmann <nico@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
enum LicenseStatus: string
|
||||
{
|
||||
/**
|
||||
* The license is valid and active
|
||||
*/
|
||||
case Active = 'active';
|
||||
|
||||
/**
|
||||
* Only used for the demo instance
|
||||
*/
|
||||
case Demo = 'demo';
|
||||
|
||||
/**
|
||||
* The included updates period of
|
||||
* the license is over.
|
||||
*/
|
||||
case Inactive = 'inactive';
|
||||
|
||||
/**
|
||||
* The installation has an old
|
||||
* license (v1, v2, v3)
|
||||
*/
|
||||
case Legacy = 'legacy';
|
||||
|
||||
/**
|
||||
* The installation has no license or
|
||||
* the license cannot be validated
|
||||
*/
|
||||
case Missing = 'missing';
|
||||
|
||||
/**
|
||||
* Returns the dialog according to the status
|
||||
*/
|
||||
public function dialog(): string
|
||||
{
|
||||
return match ($this) {
|
||||
static::Missing => 'registration',
|
||||
default => 'license'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the icon according to the status.
|
||||
* The icon is used for the system view and
|
||||
* in the license dialog.
|
||||
*/
|
||||
public function icon(): string
|
||||
{
|
||||
return match ($this) {
|
||||
static::Missing => 'key',
|
||||
static::Legacy => 'alert',
|
||||
static::Inactive => 'clock',
|
||||
static::Active => 'check',
|
||||
static::Demo => 'preview',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The info text is shown in the license dialog
|
||||
* in the status row.
|
||||
*/
|
||||
public function info(string|null $end = null): string
|
||||
{
|
||||
return I18n::template('license.status.' . $this->value . '.info', ['date' => $end]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Label for the system view
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return I18n::translate('license.status.' . $this->value . '.label');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the license can be renewed
|
||||
* The license dialog will show the renew
|
||||
* button in this case and redirect to the hub
|
||||
*/
|
||||
public function renewable(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
static::Demo => false,
|
||||
static::Active => false,
|
||||
default => true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the theme according to the status
|
||||
* The theme is used for the label in the system
|
||||
* view and the status icon in the license dialog.
|
||||
*/
|
||||
public function theme(): string
|
||||
{
|
||||
return match ($this) {
|
||||
static::Missing => 'love',
|
||||
static::Legacy => 'negative',
|
||||
static::Inactive => 'notice',
|
||||
static::Active => 'positive',
|
||||
static::Demo => 'notice',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status as string value
|
||||
*/
|
||||
public function value(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
111
kirby/src/Cms/LicenseType.php
Normal file
111
kirby/src/Cms/LicenseType.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Toolkit\I18n;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
enum LicenseType: string
|
||||
{
|
||||
/**
|
||||
* New basic licenses
|
||||
*/
|
||||
case Basic = 'basic';
|
||||
|
||||
/**
|
||||
* New enterprise licenses
|
||||
*/
|
||||
case Enterprise = 'enterprise';
|
||||
|
||||
/**
|
||||
* Invalid license codes
|
||||
*/
|
||||
case Invalid = 'invalid';
|
||||
|
||||
/**
|
||||
* Old Kirby 3 licenses
|
||||
*/
|
||||
case Legacy = 'legacy';
|
||||
|
||||
/**
|
||||
* Detects the correct LicenseType based on the code
|
||||
*/
|
||||
public static function detect(string|null $code): static
|
||||
{
|
||||
return match (true) {
|
||||
static::Basic->isValidCode($code) => static::Basic,
|
||||
static::Enterprise->isValidCode($code) => static::Enterprise,
|
||||
static::Legacy->isValidCode($code) => static::Legacy,
|
||||
default => static::Invalid
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for a valid license code
|
||||
* by prefix and length. This is just a
|
||||
* rough validation.
|
||||
*/
|
||||
public function isValidCode(string|null $code): bool
|
||||
{
|
||||
return
|
||||
$code !== null &&
|
||||
Str::length($code) === $this->length() &&
|
||||
Str::startsWith($code, $this->prefix()) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The expected lengths of the license code
|
||||
*/
|
||||
public function length(): int
|
||||
{
|
||||
return match ($this) {
|
||||
static::Basic => 38,
|
||||
static::Enterprise => 38,
|
||||
static::Legacy => 39,
|
||||
static::Invalid => 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A human-readable license type label
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return match ($this) {
|
||||
static::Basic => 'Kirby Basic',
|
||||
static::Enterprise => 'Kirby Enterprise',
|
||||
static::Legacy => 'Kirby 3',
|
||||
static::Invalid => I18n::translate('license.unregistered.label'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The expected prefix for the license code
|
||||
*/
|
||||
public function prefix(): string|null
|
||||
{
|
||||
return match ($this) {
|
||||
static::Basic => 'K-BAS-',
|
||||
static::Enterprise => 'K-ENT-',
|
||||
static::Legacy => 'K3-PRO-',
|
||||
static::Invalid => null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the enum value
|
||||
*/
|
||||
public function value(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
@@ -50,9 +50,6 @@ class Loader
|
||||
|
||||
/**
|
||||
* Loads the area definition
|
||||
*
|
||||
* @param string $name
|
||||
* @return array|null
|
||||
*/
|
||||
public function area(string $name): array|null
|
||||
{
|
||||
@@ -62,15 +59,14 @@ class Loader
|
||||
/**
|
||||
* Loads all areas and makes sure that plugins
|
||||
* are injected properly
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function areas(): array
|
||||
{
|
||||
$areas = [];
|
||||
$extensions = $this->withPlugins === true ? $this->kirby->extensions('areas') : [];
|
||||
|
||||
// load core areas and extend them with elements from plugins if they exist
|
||||
// load core areas and extend them with elements
|
||||
// from plugins if they exist
|
||||
foreach ($this->kirby->core()->areas() as $id => $area) {
|
||||
$area = $this->resolveArea($area);
|
||||
|
||||
@@ -98,9 +94,6 @@ class Loader
|
||||
|
||||
/**
|
||||
* Loads a core component closure
|
||||
*
|
||||
* @param string $name
|
||||
* @return \Closure|null
|
||||
*/
|
||||
public function component(string $name): Closure|null
|
||||
{
|
||||
@@ -109,8 +102,6 @@ class Loader
|
||||
|
||||
/**
|
||||
* Loads all core component closures
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function components(): array
|
||||
{
|
||||
@@ -119,21 +110,14 @@ class Loader
|
||||
|
||||
/**
|
||||
* Loads a particular extension
|
||||
*
|
||||
* @param string $type
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function extension(string $type, string $name)
|
||||
public function extension(string $type, string $name): mixed
|
||||
{
|
||||
return $this->extensions($type)[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all defined extensions
|
||||
*
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
public function extensions(string $type): array
|
||||
{
|
||||
@@ -152,11 +136,8 @@ class Loader
|
||||
*
|
||||
* 3.) closures will be called and the Kirby instance will be
|
||||
* passed as first argument
|
||||
*
|
||||
* @param mixed $item
|
||||
* @return mixed
|
||||
*/
|
||||
public function resolve($item)
|
||||
public function resolve(mixed $item): mixed
|
||||
{
|
||||
if (is_string($item) === true) {
|
||||
$item = match (F::extension($item)) {
|
||||
@@ -175,9 +156,6 @@ class Loader
|
||||
/**
|
||||
* Calls `static::resolve()` on all items
|
||||
* in the given array
|
||||
*
|
||||
* @param array $items
|
||||
* @return array
|
||||
*/
|
||||
public function resolveAll(array $items): array
|
||||
{
|
||||
@@ -193,11 +171,8 @@ class Loader
|
||||
/**
|
||||
* Areas need a bit of special treatment
|
||||
* when they are being loaded
|
||||
*
|
||||
* @param string|array|Closure $area
|
||||
* @return array
|
||||
*/
|
||||
public function resolveArea($area): array
|
||||
public function resolveArea(string|array|Closure $area): array
|
||||
{
|
||||
$area = $this->resolve($area);
|
||||
$dropdowns = $area['dropdowns'] ?? [];
|
||||
@@ -217,9 +192,6 @@ class Loader
|
||||
|
||||
/**
|
||||
* Loads a particular section definition
|
||||
*
|
||||
* @param string $name
|
||||
* @return array|null
|
||||
*/
|
||||
public function section(string $name): array|null
|
||||
{
|
||||
@@ -228,8 +200,6 @@ class Loader
|
||||
|
||||
/**
|
||||
* Loads all section defintions
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function sections(): array
|
||||
{
|
||||
@@ -239,8 +209,6 @@ class Loader
|
||||
/**
|
||||
* Returns the status flag, which shows
|
||||
* if plugins are loaded as well.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function withPlugins(): bool
|
||||
{
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Toolkit\Str;
|
||||
@@ -23,14 +25,12 @@ class Media
|
||||
/**
|
||||
* Tries to find a file by model and filename
|
||||
* and to copy it to the media folder.
|
||||
*
|
||||
* @param \Kirby\Cms\Model|null $model
|
||||
* @param string $hash
|
||||
* @param string $filename
|
||||
* @return \Kirby\Cms\Response|false
|
||||
*/
|
||||
public static function link(Model $model = null, string $hash, string $filename)
|
||||
{
|
||||
public static function link(
|
||||
Page|Site|User $model = null,
|
||||
string $hash,
|
||||
string $filename
|
||||
): Response|false {
|
||||
if ($model === null) {
|
||||
return false;
|
||||
}
|
||||
@@ -62,10 +62,6 @@ class Media
|
||||
|
||||
/**
|
||||
* Copy the file to the final media folder location
|
||||
*
|
||||
* @param \Kirby\Cms\File $file
|
||||
* @param string $dest
|
||||
* @return bool
|
||||
*/
|
||||
public static function publish(File $file, string $dest): bool
|
||||
{
|
||||
@@ -87,14 +83,12 @@ class Media
|
||||
* Tries to find a job file for the
|
||||
* given filename and then calls the thumb
|
||||
* component to create a thumbnail accordingly
|
||||
*
|
||||
* @param \Kirby\Cms\Model|string $model
|
||||
* @param string $hash
|
||||
* @param string $filename
|
||||
* @return \Kirby\Cms\Response|false
|
||||
*/
|
||||
public static function thumb($model, string $hash, string $filename)
|
||||
{
|
||||
public static function thumb(
|
||||
File|Page|Site|User|string $model,
|
||||
string $hash,
|
||||
string $filename
|
||||
): Response|false {
|
||||
$kirby = App::instance();
|
||||
|
||||
$root = match (true) {
|
||||
@@ -109,15 +103,23 @@ class Media
|
||||
=> $model->mediaRoot() . '/' . $hash
|
||||
};
|
||||
|
||||
$thumb = $root . '/' . $filename;
|
||||
$job = $root . '/.jobs/' . $filename . '.json';
|
||||
|
||||
try {
|
||||
$thumb = $root . '/' . $filename;
|
||||
$job = $root . '/.jobs/' . $filename . '.json';
|
||||
$options = Data::read($job);
|
||||
} catch (Throwable) {
|
||||
// send a customized error message to make clearer what happened here
|
||||
throw new NotFoundException('The thumbnail configuration could not be found');
|
||||
}
|
||||
|
||||
if (empty($options) === true) {
|
||||
return false;
|
||||
}
|
||||
if (empty($options['filename']) === true) {
|
||||
throw new InvalidArgumentException('Incomplete thumbnail configuration');
|
||||
}
|
||||
|
||||
try {
|
||||
// find the correct source file depending on the model
|
||||
// this adds support for custom assets
|
||||
$source = match (true) {
|
||||
is_string($model) === true
|
||||
=> $kirby->root('index') . '/' . $model . '/' . $options['filename'],
|
||||
@@ -125,30 +127,30 @@ class Media
|
||||
=> $model->file($options['filename'])->root()
|
||||
};
|
||||
|
||||
try {
|
||||
$kirby->thumb($source, $thumb, $options);
|
||||
F::remove($job);
|
||||
return Response::file($thumb);
|
||||
} catch (Throwable) {
|
||||
F::remove($thumb);
|
||||
return Response::file($source);
|
||||
}
|
||||
} catch (Throwable) {
|
||||
return false;
|
||||
// generate the thumbnail and save it in the media folder
|
||||
$kirby->thumb($source, $thumb, $options);
|
||||
|
||||
// remove the job file once the thumbnail has been created
|
||||
F::remove($job);
|
||||
|
||||
// read the file and send it to the browser
|
||||
return Response::file($thumb);
|
||||
} catch (Throwable $e) {
|
||||
// remove potentially broken thumbnails
|
||||
F::remove($thumb);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all versions of the given file
|
||||
* within the parent directory
|
||||
*
|
||||
* @param string $directory
|
||||
* @param \Kirby\Cms\File $file
|
||||
* @param string|null $ignore
|
||||
* @return bool
|
||||
*/
|
||||
public static function unpublish(string $directory, File $file, string $ignore = null): bool
|
||||
{
|
||||
public static function unpublish(
|
||||
string $directory,
|
||||
File $file,
|
||||
string|null $ignore = null
|
||||
): bool {
|
||||
if (is_dir($directory) === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Kirby\Cms;
|
||||
use Kirby\Toolkit\Properties;
|
||||
|
||||
/**
|
||||
* Foundation for Page, Site, File and User models.
|
||||
* @deprecated 4.0.0 will be removed in Kirby 5.0
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
|
||||
@@ -15,28 +15,13 @@ use Kirby\Toolkit\A;
|
||||
*/
|
||||
abstract class ModelPermissions
|
||||
{
|
||||
protected $category;
|
||||
protected $model;
|
||||
protected $options;
|
||||
protected $permissions;
|
||||
protected $user;
|
||||
protected string $category;
|
||||
protected ModelWithContent $model;
|
||||
protected array $options;
|
||||
protected Permissions $permissions;
|
||||
protected User $user;
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param array $arguments
|
||||
* @return bool
|
||||
*/
|
||||
public function __call(string $method, array $arguments = []): bool
|
||||
{
|
||||
return $this->can($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* ModelPermissions constructor
|
||||
*
|
||||
* @param \Kirby\Cms\Model $model
|
||||
*/
|
||||
public function __construct(Model $model)
|
||||
public function __construct(ModelWithContent $model)
|
||||
{
|
||||
$this->model = $model;
|
||||
$this->options = $model->blueprint()->options();
|
||||
@@ -44,29 +29,34 @@ abstract class ModelPermissions
|
||||
$this->permissions = $this->user->role()->permissions();
|
||||
}
|
||||
|
||||
public function __call(string $method, array $arguments = []): bool
|
||||
{
|
||||
return $this->can($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
*
|
||||
* @return array
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $action
|
||||
* @return bool
|
||||
*/
|
||||
public function can(string $action): bool
|
||||
{
|
||||
$user = $this->user->id();
|
||||
$role = $this->user->role()->id();
|
||||
|
||||
// users with the `nobody` role can do nothing
|
||||
// that needs a permission check
|
||||
if ($role === 'nobody') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for a custom overall can method
|
||||
// check for a custom `can` method
|
||||
// which would take priority over any other
|
||||
// role-based permission rules
|
||||
if (
|
||||
method_exists($this, 'can' . $action) === true &&
|
||||
$this->{'can' . $action}() === false
|
||||
@@ -74,6 +64,11 @@ abstract class ModelPermissions
|
||||
return false;
|
||||
}
|
||||
|
||||
// the almighty `kirby` user can do anything
|
||||
if ($user === 'kirby' && $role === 'admin') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// evaluate the blueprint options block
|
||||
if (isset($this->options[$action]) === true) {
|
||||
$options = $this->options[$action];
|
||||
@@ -90,25 +85,24 @@ abstract class ModelPermissions
|
||||
is_array($options) === true &&
|
||||
A::isAssociative($options) === true
|
||||
) {
|
||||
return $options[$role] ?? $options['*'] ?? false;
|
||||
if (isset($options[$role]) === true) {
|
||||
return $options[$role];
|
||||
}
|
||||
|
||||
if (isset($options['*']) === true) {
|
||||
return $options['*'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->permissions->for($this->category, $action);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $action
|
||||
* @return bool
|
||||
*/
|
||||
public function cannot(string $action): bool
|
||||
{
|
||||
return $this->can($action) === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$array = [];
|
||||
|
||||
@@ -3,9 +3,14 @@
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Closure;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Content\Content;
|
||||
use Kirby\Content\ContentStorage;
|
||||
use Kirby\Content\ContentTranslation;
|
||||
use Kirby\Content\PlainTextContentStorageHandler;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Form\Form;
|
||||
use Kirby\Panel\Model;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Kirby\Uuid\Identifiable;
|
||||
use Kirby\Uuid\Uuid;
|
||||
@@ -21,70 +26,106 @@ use Throwable;
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
abstract class ModelWithContent extends Model implements Identifiable
|
||||
abstract class ModelWithContent implements Identifiable
|
||||
{
|
||||
/**
|
||||
* The content
|
||||
*
|
||||
* @var \Kirby\Cms\Content
|
||||
* Each model must define a CLASS_ALIAS
|
||||
* which will be used in template queries.
|
||||
* The CLASS_ALIAS is a short human-readable
|
||||
* version of the class name, i.e. page.
|
||||
*/
|
||||
public $content;
|
||||
public const CLASS_ALIAS = null;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Cms\Translations
|
||||
* Cached array of valid blueprints
|
||||
* that could be used for the model
|
||||
*/
|
||||
public $translations;
|
||||
public array|null $blueprints = null;
|
||||
|
||||
public Content|null $content;
|
||||
public static App $kirby;
|
||||
protected Site|null $site;
|
||||
protected ContentStorage $storage;
|
||||
public Collection|null $translations;
|
||||
|
||||
/**
|
||||
* Store values used to initilaize object
|
||||
*/
|
||||
protected array $propertyData = [];
|
||||
|
||||
public function __construct(array $props = [])
|
||||
{
|
||||
$this->site = $props['site'] ?? null;
|
||||
|
||||
$this->setContent($props['content'] ?? null);
|
||||
$this->setTranslations($props['translations'] ?? null);
|
||||
|
||||
$this->propertyData = $props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the blueprint of the model
|
||||
*
|
||||
* @return \Kirby\Cms\Blueprint
|
||||
*/
|
||||
abstract public function blueprint();
|
||||
abstract public function blueprint(): Blueprint;
|
||||
|
||||
/**
|
||||
* Returns an array with all blueprints that are available
|
||||
*
|
||||
* @param string|null $inSection
|
||||
* @return array
|
||||
*/
|
||||
public function blueprints(string $inSection = null): array
|
||||
{
|
||||
$blueprints = [];
|
||||
$blueprint = $this->blueprint();
|
||||
$sections = $inSection !== null ? [$blueprint->section($inSection)] : $blueprint->sections();
|
||||
// helper function
|
||||
$toBlueprints = function (array $sections): array {
|
||||
$blueprints = [];
|
||||
|
||||
foreach ($sections as $section) {
|
||||
if ($section === null) {
|
||||
continue;
|
||||
foreach ($sections as $section) {
|
||||
if ($section === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ((array)$section->blueprints() as $blueprint) {
|
||||
$blueprints[$blueprint['name']] = $blueprint;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ((array)$section->blueprints() as $blueprint) {
|
||||
$blueprints[$blueprint['name']] = $blueprint;
|
||||
}
|
||||
return array_values($blueprints);
|
||||
};
|
||||
|
||||
$blueprint = $this->blueprint();
|
||||
|
||||
// no caching for when collecting for specific section
|
||||
if ($inSection !== null) {
|
||||
return $toBlueprints([$blueprint->section($inSection)]);
|
||||
}
|
||||
|
||||
return array_values($blueprints);
|
||||
return $this->blueprints ??= $toBlueprints($blueprint->sections());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the same
|
||||
* initial properties
|
||||
*
|
||||
* @todo eventually refactor without need of propertyData
|
||||
*/
|
||||
public function clone(array $props = []): static
|
||||
{
|
||||
return new static(array_replace_recursive($this->propertyData, $props));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes any given model action
|
||||
*
|
||||
* @param string $action
|
||||
* @param array $arguments
|
||||
* @param \Closure $callback
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function commit(string $action, array $arguments, Closure $callback);
|
||||
abstract protected function commit(
|
||||
string $action,
|
||||
array $arguments,
|
||||
Closure $callback
|
||||
): mixed;
|
||||
|
||||
/**
|
||||
* Returns the content
|
||||
*
|
||||
* @param string|null $languageCode
|
||||
* @return \Kirby\Cms\Content
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the language for the given code does not exist
|
||||
*/
|
||||
public function content(string $languageCode = null)
|
||||
public function content(string|null $languageCode = null): Content
|
||||
{
|
||||
// single language support
|
||||
if ($this->kirby()->multilang() === false) {
|
||||
@@ -124,72 +165,55 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute path to the content file
|
||||
*
|
||||
* Returns the absolute path to the content file;
|
||||
* NOTE: only supports the published content file
|
||||
* (use `$model->storage()->contentFile()` for other versions)
|
||||
* @internal
|
||||
* @param string|null $languageCode
|
||||
* @param bool $force
|
||||
* @return string
|
||||
* @deprecated 4.0.0
|
||||
* @todo Remove in v5
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the language for the given code does not exist
|
||||
*/
|
||||
public function contentFile(string $languageCode = null, bool $force = false): string
|
||||
{
|
||||
$extension = $this->contentFileExtension();
|
||||
$directory = $this->contentFileDirectory();
|
||||
$filename = $this->contentFileName();
|
||||
public function contentFile(
|
||||
string $languageCode = null,
|
||||
bool $force = false
|
||||
): string {
|
||||
Helpers::deprecated('The internal $model->contentFile() method has been deprecated. You can use $model->storage()->contentFile() instead, however please note that this method is also internal and may be removed in the future.', 'model-content-file');
|
||||
|
||||
// overwrite the language code
|
||||
if ($force === true) {
|
||||
if (empty($languageCode) === false) {
|
||||
return $directory . '/' . $filename . '.' . $languageCode . '.' . $extension;
|
||||
}
|
||||
|
||||
return $directory . '/' . $filename . '.' . $extension;
|
||||
}
|
||||
|
||||
// add and validate the language code in multi language mode
|
||||
if ($this->kirby()->multilang() === true) {
|
||||
if ($language = $this->kirby()->languageCode($languageCode)) {
|
||||
return $directory . '/' . $filename . '.' . $language . '.' . $extension;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Invalid language: ' . $languageCode);
|
||||
}
|
||||
|
||||
return $directory . '/' . $filename . '.' . $extension;
|
||||
return $this->storage()->contentFile(
|
||||
$this->storage()->defaultVersion(),
|
||||
$languageCode,
|
||||
$force
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with all content files
|
||||
*
|
||||
* @return array
|
||||
* Returns an array with all content files;
|
||||
* NOTE: only supports the published content file
|
||||
* (use `$model->storage()->contentFiles()` for other versions)
|
||||
* @deprecated 4.0.0
|
||||
* @todo Remove in v5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function contentFiles(): array
|
||||
{
|
||||
if ($this->kirby()->multilang() === true) {
|
||||
$files = [];
|
||||
foreach ($this->kirby()->languages()->codes() as $code) {
|
||||
$files[] = $this->contentFile($code);
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
Helpers::deprecated('The internal $model->contentFiles() method has been deprecated. You can use $model->storage()->contentFiles() instead, however please note that this method is also internal and may be removed in the future.', 'model-content-file');
|
||||
|
||||
return [
|
||||
$this->contentFile()
|
||||
];
|
||||
return $this->storage()->contentFiles(
|
||||
$this->storage()->defaultVersion()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the content that should be written
|
||||
* to the text file
|
||||
*
|
||||
* @internal
|
||||
* @param array $data
|
||||
* @param string|null $languageCode
|
||||
* @return array
|
||||
*/
|
||||
public function contentFileData(array $data, string $languageCode = null): array
|
||||
{
|
||||
public function contentFileData(
|
||||
array $data,
|
||||
string $languageCode = null
|
||||
): array {
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -197,44 +221,102 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
* Returns the absolute path to the
|
||||
* folder in which the content file is
|
||||
* located
|
||||
*
|
||||
* @internal
|
||||
* @return string|null
|
||||
* @deprecated 4.0.0
|
||||
* @todo Remove in v5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function contentFileDirectory(): string|null
|
||||
{
|
||||
Helpers::deprecated('The internal $model->contentFileDirectory() method has been deprecated. Please let us know via a GitHub issue if you need this method and tell us your use case.', 'model-content-file');
|
||||
return $this->root();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extension of the content file
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
* @deprecated 4.0.0
|
||||
* @todo Remove in v5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function contentFileExtension(): string
|
||||
{
|
||||
Helpers::deprecated('The internal $model->contentFileName() method has been deprecated. Please let us know via a GitHub issue if you need this method and tell us your use case.', 'model-content-file');
|
||||
return $this->kirby()->contentExtension();
|
||||
}
|
||||
|
||||
/**
|
||||
* Needs to be declared by the final model
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
* @deprecated 4.0.0
|
||||
* @todo Remove in v5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
abstract public function contentFileName(): string;
|
||||
|
||||
/**
|
||||
* Decrement a given field value
|
||||
*
|
||||
* @param string $field
|
||||
* @param int $by
|
||||
* @param int $min
|
||||
* @return static
|
||||
* Converts model to new blueprint
|
||||
* incl. its content for all translations
|
||||
*/
|
||||
public function decrement(string $field, int $by = 1, int $min = 0)
|
||||
protected function convertTo(string $blueprint): static
|
||||
{
|
||||
// first close object with new blueprint as template
|
||||
$new = $this->clone(['template' => $blueprint]);
|
||||
|
||||
// temporary compatibility change (TODO: also convert changes)
|
||||
$identifier = $this->storage()->defaultVersion();
|
||||
|
||||
// for multilang, we go through all translations and
|
||||
// covnert the content for each of them, remove the content file
|
||||
// to rewrite it with converted content afterwards
|
||||
if ($this->kirby()->multilang() === true) {
|
||||
$translations = [];
|
||||
|
||||
foreach ($this->kirby()->languages()->codes() as $code) {
|
||||
if ($this->translation($code)?->exists() === true) {
|
||||
$content = $this->content($code)->convertTo($blueprint);
|
||||
|
||||
// delete the old text file
|
||||
$this->storage()->delete(
|
||||
$identifier,
|
||||
$code
|
||||
);
|
||||
|
||||
// save to re-create the translation content file
|
||||
// with the converted/updated content
|
||||
$new->save($content, $code);
|
||||
}
|
||||
|
||||
$translations[] = [
|
||||
'code' => $code,
|
||||
'content' => $content ?? null
|
||||
];
|
||||
}
|
||||
|
||||
// cloning the object with the new translations content ensures
|
||||
// that `propertyData` prop does not hold any old translations
|
||||
// content that could surface on subsequent cloning
|
||||
return $new->clone(['translations' => $translations]);
|
||||
}
|
||||
|
||||
// for single language setups, we do the same,
|
||||
// just once for the main content
|
||||
$content = $this->content()->convertTo($blueprint);
|
||||
|
||||
// delete the old text file
|
||||
$this->storage()->delete($identifier, 'default');
|
||||
|
||||
return $new->save($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement a given field value
|
||||
*/
|
||||
public function decrement(
|
||||
string $field,
|
||||
int $by = 1,
|
||||
int $min = 0
|
||||
): static {
|
||||
$value = (int)$this->content()->get($field)->value() - $by;
|
||||
|
||||
if ($value < $min) {
|
||||
@@ -246,8 +328,6 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
|
||||
/**
|
||||
* Returns all content validation errors
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function errors(): array
|
||||
{
|
||||
@@ -261,15 +341,38 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment a given field value
|
||||
*
|
||||
* @param string $field
|
||||
* @param int $by
|
||||
* @param int|null $max
|
||||
* @return static
|
||||
* Creates a clone and fetches all
|
||||
* lazy-loaded getters to get a full copy
|
||||
*/
|
||||
public function increment(string $field, int $by = 1, int $max = null)
|
||||
public function hardcopy(): static
|
||||
{
|
||||
$clone = $this->clone();
|
||||
|
||||
foreach (get_object_vars($clone) as $name => $default) {
|
||||
if (method_exists($clone, $name) === true) {
|
||||
$clone->$name();
|
||||
}
|
||||
}
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Each model must return a unique id
|
||||
*/
|
||||
public function id(): string|null
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment a given field value
|
||||
*/
|
||||
public function increment(
|
||||
string $field,
|
||||
int $by = 1,
|
||||
int $max = null
|
||||
): static {
|
||||
$value = (int)$this->content()->get($field)->value() + $by;
|
||||
|
||||
if ($max && $value > $max) {
|
||||
@@ -281,8 +384,6 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
|
||||
/**
|
||||
* Checks if the model is locked for the current user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isLocked(): bool
|
||||
{
|
||||
@@ -292,12 +393,18 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
|
||||
/**
|
||||
* Checks if the data has any errors
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid(): bool
|
||||
{
|
||||
return Form::for($this)->hasErrors() === false;
|
||||
return Form::for($this)->isValid() === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent Kirby instance
|
||||
*/
|
||||
public function kirby(): App
|
||||
{
|
||||
return static::$kirby ??= App::instance();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -305,12 +412,14 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
*
|
||||
* Only if a content directory exists,
|
||||
* virtual pages will need to overwrite this method
|
||||
*
|
||||
* @return \Kirby\Cms\ContentLock|null
|
||||
*/
|
||||
public function lock()
|
||||
public function lock(): ContentLock|null
|
||||
{
|
||||
$dir = $this->contentFileDirectory();
|
||||
$dir = $this->root();
|
||||
|
||||
if ($this::CLASS_ALIAS === 'file') {
|
||||
$dir = dirname($dir);
|
||||
}
|
||||
|
||||
if (
|
||||
$this->kirby()->option('content.locking', true) &&
|
||||
@@ -319,33 +428,43 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
) {
|
||||
return new ContentLock($this);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the panel info of the model
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @return \Kirby\Panel\Model
|
||||
*/
|
||||
abstract public function panel();
|
||||
abstract public function panel(): Model;
|
||||
|
||||
/**
|
||||
* Must return the permissions object for the model
|
||||
*
|
||||
* @return \Kirby\Cms\ModelPermissions
|
||||
*/
|
||||
abstract public function permissions();
|
||||
abstract public function permissions(): ModelPermissions;
|
||||
|
||||
/**
|
||||
* Clean internal caches
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function purge(): static
|
||||
{
|
||||
$this->blueprints = null;
|
||||
$this->content = null;
|
||||
$this->translations = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string query, starting from the model
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $query
|
||||
* @param string|null $expect
|
||||
* @return mixed
|
||||
*/
|
||||
public function query(string $query = null, string $expect = null)
|
||||
{
|
||||
public function query(
|
||||
string $query = null,
|
||||
string $expect = null
|
||||
): mixed {
|
||||
if ($query === null) {
|
||||
return null;
|
||||
}
|
||||
@@ -370,43 +489,37 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
|
||||
/**
|
||||
* Read the content from the content file
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $languageCode
|
||||
* @return array
|
||||
*/
|
||||
public function readContent(string $languageCode = null): array
|
||||
{
|
||||
$file = $this->contentFile($languageCode);
|
||||
|
||||
// only if the content file really does not exist, it's ok
|
||||
// to return empty content. Otherwise this could lead to
|
||||
// content loss in case of file reading issues
|
||||
if (file_exists($file) === false) {
|
||||
try {
|
||||
return $this->storage()->read(
|
||||
$this->storage()->defaultVersion(),
|
||||
$languageCode
|
||||
);
|
||||
} catch (NotFoundException) {
|
||||
// only if the content file really does not exist, it's ok
|
||||
// to return empty content. Otherwise this could lead to
|
||||
// content loss in case of file reading issues
|
||||
return [];
|
||||
}
|
||||
|
||||
return Data::read($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute path to the model
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
abstract public function root(): string|null;
|
||||
|
||||
/**
|
||||
* Stores the content on disk
|
||||
*
|
||||
* @internal
|
||||
* @param array|null $data
|
||||
* @param string|null $languageCode
|
||||
* @param bool $overwrite
|
||||
* @return static
|
||||
*/
|
||||
public function save(array $data = null, string $languageCode = null, bool $overwrite = false)
|
||||
{
|
||||
public function save(
|
||||
array|null $data = null,
|
||||
string|null $languageCode = null,
|
||||
bool $overwrite = false
|
||||
): static {
|
||||
if ($this->kirby()->multilang() === true) {
|
||||
return $this->saveTranslation($data, $languageCode, $overwrite);
|
||||
}
|
||||
@@ -416,13 +529,11 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
|
||||
/**
|
||||
* Save the single language content
|
||||
*
|
||||
* @param array|null $data
|
||||
* @param bool $overwrite
|
||||
* @return static
|
||||
*/
|
||||
protected function saveContent(array $data = null, bool $overwrite = false)
|
||||
{
|
||||
protected function saveContent(
|
||||
array $data = null,
|
||||
bool $overwrite = false
|
||||
): static {
|
||||
// create a clone to avoid modifying the original
|
||||
$clone = $this->clone();
|
||||
|
||||
@@ -438,14 +549,13 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
/**
|
||||
* Save a translation
|
||||
*
|
||||
* @param array|null $data
|
||||
* @param string|null $languageCode
|
||||
* @param bool $overwrite
|
||||
* @return static
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the language for the given code does not exist
|
||||
*/
|
||||
protected function saveTranslation(array $data = null, string $languageCode = null, bool $overwrite = false)
|
||||
{
|
||||
protected function saveTranslation(
|
||||
array $data = null,
|
||||
string $languageCode = null,
|
||||
bool $overwrite = false
|
||||
): static {
|
||||
// create a clone to not touch the original
|
||||
$clone = $this->clone();
|
||||
|
||||
@@ -491,10 +601,9 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
/**
|
||||
* Sets the Content object
|
||||
*
|
||||
* @param array|null $content
|
||||
* @return $this
|
||||
*/
|
||||
protected function setContent(array $content = null)
|
||||
protected function setContent(array $content = null): static
|
||||
{
|
||||
if ($content !== null) {
|
||||
$content = new Content($content, $this);
|
||||
@@ -507,10 +616,9 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
/**
|
||||
* Create the translations collection from an array
|
||||
*
|
||||
* @param array|null $translations
|
||||
* @return $this
|
||||
*/
|
||||
protected function setTranslations(array $translations = null)
|
||||
protected function setTranslations(array $translations = null): static
|
||||
{
|
||||
if ($translations !== null) {
|
||||
$this->translations = new Collection();
|
||||
@@ -520,23 +628,57 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
$translation = new ContentTranslation($props);
|
||||
$this->translations->data[$translation->code()] = $translation;
|
||||
}
|
||||
} else {
|
||||
$this->translations = null;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent Site instance
|
||||
*/
|
||||
public function site(): Site
|
||||
{
|
||||
return $this->site ??= $this->kirby()->site();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content storage handler
|
||||
* @internal
|
||||
*/
|
||||
public function storage(): ContentStorage
|
||||
{
|
||||
return $this->storage ??= new ContentStorage(
|
||||
model: $this,
|
||||
handler: PlainTextContentStorageHandler::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the model to a simple array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'content' => $this->content()->toArray(),
|
||||
'translations' => $this->translations()->toArray()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* String template builder with automatic HTML escaping
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @param string|null $template Template string or `null` to use the model ID
|
||||
* @param array $data
|
||||
* @param string|null $fallback Fallback for tokens in the template that cannot be replaced
|
||||
* (`null` to keep the original token)
|
||||
* @return string
|
||||
*/
|
||||
public function toSafeString(string $template = null, array $data = [], string|null $fallback = ''): string
|
||||
{
|
||||
public function toSafeString(
|
||||
string $template = null,
|
||||
array $data = [],
|
||||
string|null $fallback = ''
|
||||
): string {
|
||||
return $this->toString($template, $data, $fallback, 'safeTemplate');
|
||||
}
|
||||
|
||||
@@ -544,14 +686,16 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
* String template builder
|
||||
*
|
||||
* @param string|null $template Template string or `null` to use the model ID
|
||||
* @param array $data
|
||||
* @param string|null $fallback Fallback for tokens in the template that cannot be replaced
|
||||
* (`null` to keep the original token)
|
||||
* @param string $handler For internal use
|
||||
* @return string
|
||||
*/
|
||||
public function toString(string $template = null, array $data = [], string|null $fallback = '', string $handler = 'template'): string
|
||||
{
|
||||
public function toString(
|
||||
string $template = null,
|
||||
array $data = [],
|
||||
string|null $fallback = '',
|
||||
string $handler = 'template'
|
||||
): string {
|
||||
if ($template === null) {
|
||||
return $this->id() ?? '';
|
||||
}
|
||||
@@ -570,15 +714,22 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes it possible to convert the entire model
|
||||
* to a string. Mostly useful for debugging
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single translation by language code
|
||||
* If no code is specified the current translation is returned
|
||||
*
|
||||
* @param string|null $languageCode
|
||||
* @return \Kirby\Cms\ContentTranslation|null
|
||||
*/
|
||||
public function translation(string $languageCode = null)
|
||||
{
|
||||
public function translation(
|
||||
string $languageCode = null
|
||||
): ContentTranslation|null {
|
||||
if ($language = $this->kirby()->language($languageCode)) {
|
||||
return $this->translations()->find($language->code());
|
||||
}
|
||||
@@ -588,10 +739,8 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
|
||||
/**
|
||||
* Returns the translations collection
|
||||
*
|
||||
* @return \Kirby\Cms\Collection
|
||||
*/
|
||||
public function translations()
|
||||
public function translations(): Collection
|
||||
{
|
||||
if ($this->translations !== null) {
|
||||
return $this->translations;
|
||||
@@ -614,14 +763,13 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
/**
|
||||
* Updates the model data
|
||||
*
|
||||
* @param array|null $input
|
||||
* @param string|null $languageCode
|
||||
* @param bool $validate
|
||||
* @return static
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the input array contains invalid values
|
||||
*/
|
||||
public function update(array $input = null, string $languageCode = null, bool $validate = false)
|
||||
{
|
||||
public function update(
|
||||
array $input = null,
|
||||
string $languageCode = null,
|
||||
bool $validate = false
|
||||
): static {
|
||||
$form = Form::for($this, [
|
||||
'ignoreDisabled' => $validate === false,
|
||||
'input' => $input,
|
||||
@@ -654,17 +802,21 @@ abstract class ModelWithContent extends Model implements Identifiable
|
||||
/**
|
||||
* Low level data writer method
|
||||
* to store the given data on disk or anywhere else
|
||||
*
|
||||
* @internal
|
||||
* @param array $data
|
||||
* @param string|null $languageCode
|
||||
* @return bool
|
||||
*/
|
||||
public function writeContent(array $data, string $languageCode = null): bool
|
||||
{
|
||||
return Data::write(
|
||||
$this->contentFile($languageCode),
|
||||
$this->contentFileData($data, $languageCode)
|
||||
);
|
||||
$data = $this->contentFileData($data, $languageCode);
|
||||
$id = $this->storage()->defaultVersion();
|
||||
|
||||
try {
|
||||
// we can only update if the version already exists
|
||||
$this->storage()->update($id, $languageCode, $data);
|
||||
} catch (NotFoundException) {
|
||||
// otherwise create a new version
|
||||
$this->storage()->create($id, $languageCode, $data);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Content\Field;
|
||||
|
||||
/**
|
||||
* The Nest class converts any array type
|
||||
* into a Kirby style collection/object. This
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Content\Field;
|
||||
use Kirby\Toolkit\Obj;
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,6 @@ use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Form\Form;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\I18n;
|
||||
@@ -30,6 +29,7 @@ trait PageActions
|
||||
/**
|
||||
* Adapts necessary modifications which page uuid, page slug and files uuid
|
||||
* of copy objects for single or multilang environments
|
||||
* @internal
|
||||
*/
|
||||
protected function adaptCopy(Page $copy, bool $files = false, bool $children = false): Page
|
||||
{
|
||||
@@ -103,11 +103,10 @@ trait PageActions
|
||||
* This only affects this page,
|
||||
* siblings will not be resorted.
|
||||
*
|
||||
* @param int|null $num
|
||||
* @return $this|static
|
||||
* @throws \Kirby\Exception\LogicException If a draft is being sorted or the directory cannot be moved
|
||||
*/
|
||||
public function changeNum(int $num = null)
|
||||
public function changeNum(int|null $num = null): static
|
||||
{
|
||||
if ($this->isDraft() === true) {
|
||||
throw new LogicException('Drafts cannot change their sorting number');
|
||||
@@ -130,7 +129,7 @@ trait PageActions
|
||||
if (Dir::move($oldPage->root(), $newPage->root()) === true) {
|
||||
// Updates the root path of the old page with the root path
|
||||
// of the moved new page to use fly actions on old page in loop
|
||||
$oldPage->setRoot($newPage->root());
|
||||
$oldPage->root = $newPage->root();
|
||||
} else {
|
||||
throw new LogicException('The page directory cannot be moved');
|
||||
}
|
||||
@@ -146,13 +145,13 @@ trait PageActions
|
||||
/**
|
||||
* Changes the slug/uid of the page
|
||||
*
|
||||
* @param string $slug
|
||||
* @param string|null $languageCode
|
||||
* @return $this|static
|
||||
* @throws \Kirby\Exception\LogicException If the directory cannot be moved
|
||||
*/
|
||||
public function changeSlug(string $slug, string $languageCode = null)
|
||||
{
|
||||
public function changeSlug(
|
||||
string $slug,
|
||||
string|null $languageCode = null
|
||||
): static {
|
||||
// always sanitize the slug
|
||||
$slug = Str::slug($slug);
|
||||
|
||||
@@ -205,14 +204,13 @@ trait PageActions
|
||||
/**
|
||||
* Change the slug for a specific language
|
||||
*
|
||||
* @param string $slug
|
||||
* @param string|null $languageCode
|
||||
* @return static
|
||||
* @throws \Kirby\Exception\NotFoundException If the language for the given language code cannot be found
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the slug for the default language is being changed
|
||||
*/
|
||||
protected function changeSlugForLanguage(string $slug, string $languageCode = null)
|
||||
{
|
||||
protected function changeSlugForLanguage(
|
||||
string $slug,
|
||||
string|null $languageCode = null
|
||||
): static {
|
||||
$language = $this->kirby()->language($languageCode);
|
||||
|
||||
if (!$language) {
|
||||
@@ -247,10 +245,9 @@ trait PageActions
|
||||
*
|
||||
* @param string $status "draft", "listed" or "unlisted"
|
||||
* @param int|null $position Optional sorting number
|
||||
* @return static
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If an invalid status is being passed
|
||||
*/
|
||||
public function changeStatus(string $status, int $position = null)
|
||||
public function changeStatus(string $status, int|null $position = null): static
|
||||
{
|
||||
return match ($status) {
|
||||
'draft' => $this->changeStatusToDraft(),
|
||||
@@ -260,10 +257,7 @@ trait PageActions
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
protected function changeStatusToDraft()
|
||||
protected function changeStatusToDraft(): static
|
||||
{
|
||||
$arguments = ['page' => $this, 'status' => 'draft', 'position' => null];
|
||||
$page = $this->commit(
|
||||
@@ -276,10 +270,9 @@ trait PageActions
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $position
|
||||
* @return $this|static
|
||||
*/
|
||||
protected function changeStatusToListed(int $position = null)
|
||||
protected function changeStatusToListed(int|null $position = null): static
|
||||
{
|
||||
// create a sorting number for the page
|
||||
$num = $this->createNum($position);
|
||||
@@ -304,7 +297,7 @@ trait PageActions
|
||||
/**
|
||||
* @return $this|static
|
||||
*/
|
||||
protected function changeStatusToUnlisted()
|
||||
protected function changeStatusToUnlisted(): static
|
||||
{
|
||||
if ($this->status() === 'unlisted') {
|
||||
return $this;
|
||||
@@ -325,10 +318,9 @@ trait PageActions
|
||||
* collection. Siblings will be resorted. If the page
|
||||
* status isn't yet `listed`, it will be changed to it.
|
||||
*
|
||||
* @param int|null $position
|
||||
* @return $this|static
|
||||
*/
|
||||
public function changeSort(int $position = null)
|
||||
public function changeSort(int|null $position = null): static
|
||||
{
|
||||
return $this->changeStatus('listed', $position);
|
||||
}
|
||||
@@ -336,51 +328,18 @@ trait PageActions
|
||||
/**
|
||||
* Changes the page template
|
||||
*
|
||||
* @param string $template
|
||||
* @return $this|static
|
||||
* @throws \Kirby\Exception\LogicException If the textfile cannot be renamed/moved
|
||||
*/
|
||||
public function changeTemplate(string $template)
|
||||
public function changeTemplate(string $template): static
|
||||
{
|
||||
if ($template === $this->intendedTemplate()->name()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->commit('changeTemplate', ['page' => $this, 'template' => $template], function ($oldPage, $template) {
|
||||
if ($this->kirby()->multilang() === true) {
|
||||
$newPage = $this->clone([
|
||||
'template' => $template
|
||||
]);
|
||||
|
||||
foreach ($this->kirby()->languages()->codes() as $code) {
|
||||
if ($oldPage->translation($code)->exists() !== true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = $oldPage->content($code)->convertTo($template);
|
||||
|
||||
if (F::remove($oldPage->contentFile($code)) !== true) {
|
||||
throw new LogicException('The old text file could not be removed');
|
||||
}
|
||||
|
||||
// save the language file
|
||||
$newPage->save($content, $code);
|
||||
}
|
||||
|
||||
// return a fresh copy of the object
|
||||
$page = $newPage->clone();
|
||||
} else {
|
||||
$newPage = $this->clone([
|
||||
'content' => $this->content()->convertTo($template),
|
||||
'template' => $template
|
||||
]);
|
||||
|
||||
if (F::remove($oldPage->contentFile()) !== true) {
|
||||
throw new LogicException('The old text file could not be removed');
|
||||
}
|
||||
|
||||
$page = $newPage->save();
|
||||
}
|
||||
// convert for new template/blueprint
|
||||
$page = $oldPage->convertTo($template);
|
||||
|
||||
// update the parent collection
|
||||
static::updateParentCollections($page, 'set');
|
||||
@@ -391,13 +350,11 @@ trait PageActions
|
||||
|
||||
/**
|
||||
* Change the page title
|
||||
*
|
||||
* @param string $title
|
||||
* @param string|null $languageCode
|
||||
* @return static
|
||||
*/
|
||||
public function changeTitle(string $title, string $languageCode = null)
|
||||
{
|
||||
public function changeTitle(
|
||||
string $title,
|
||||
string|null $languageCode = null
|
||||
): static {
|
||||
$arguments = ['page' => $this, 'title' => $title, 'languageCode' => $languageCode];
|
||||
return $this->commit('changeTitle', $arguments, function ($page, $title, $languageCode) {
|
||||
$page = $page->save(['title' => $title], $languageCode);
|
||||
@@ -417,14 +374,12 @@ trait PageActions
|
||||
* 3. commits the store action
|
||||
* 4. sends the after hook
|
||||
* 5. returns the result
|
||||
*
|
||||
* @param string $action
|
||||
* @param array $arguments
|
||||
* @param \Closure $callback
|
||||
* @return mixed
|
||||
*/
|
||||
protected function commit(string $action, array $arguments, Closure $callback)
|
||||
{
|
||||
protected function commit(
|
||||
string $action,
|
||||
array $arguments,
|
||||
Closure $callback
|
||||
): mixed {
|
||||
$old = $this->hardcopy();
|
||||
$kirby = $this->kirby();
|
||||
$argumentValues = array_values($arguments);
|
||||
@@ -452,11 +407,9 @@ trait PageActions
|
||||
/**
|
||||
* Copies the page to a new parent
|
||||
*
|
||||
* @param array $options
|
||||
* @return \Kirby\Cms\Page
|
||||
* @throws \Kirby\Exception\DuplicateException If the page already exists
|
||||
*/
|
||||
public function copy(array $options = [])
|
||||
public function copy(array $options = []): static
|
||||
{
|
||||
$slug = $options['slug'] ?? $this->slug();
|
||||
$isDraft = $options['isDraft'] ?? $this->isDraft();
|
||||
@@ -495,7 +448,8 @@ trait PageActions
|
||||
$ignore[] = $file->root();
|
||||
|
||||
// append all content files
|
||||
array_push($ignore, ...$file->contentFiles());
|
||||
array_push($ignore, ...$file->storage()->contentFiles('published'));
|
||||
array_push($ignore, ...$file->storage()->contentFiles('changes'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,11 +468,8 @@ trait PageActions
|
||||
|
||||
/**
|
||||
* Creates and stores a new page
|
||||
*
|
||||
* @param array $props
|
||||
* @return static
|
||||
*/
|
||||
public static function create(array $props)
|
||||
public static function create(array $props): Page
|
||||
{
|
||||
// clean up the slug
|
||||
$props['slug'] = Str::slug($props['slug'] ?? $props['content']['title'] ?? null);
|
||||
@@ -584,11 +535,8 @@ trait PageActions
|
||||
|
||||
/**
|
||||
* Creates a child of the current page
|
||||
*
|
||||
* @param array $props
|
||||
* @return static
|
||||
*/
|
||||
public function createChild(array $props)
|
||||
public function createChild(array $props): Page
|
||||
{
|
||||
$props = array_merge($props, [
|
||||
'url' => null,
|
||||
@@ -597,16 +545,13 @@ trait PageActions
|
||||
'site' => $this->site(),
|
||||
]);
|
||||
|
||||
$modelClass = Page::$models[$props['template']] ?? Page::class;
|
||||
$modelClass = Page::$models[$props['template'] ?? null] ?? Page::class;
|
||||
return $modelClass::create($props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the sorting number for the page
|
||||
* depending on the blueprint settings
|
||||
*
|
||||
* @param int|null $num
|
||||
* @return int
|
||||
*/
|
||||
public function createNum(int $num = null): int
|
||||
{
|
||||
@@ -664,9 +609,6 @@ trait PageActions
|
||||
|
||||
/**
|
||||
* Deletes the page
|
||||
*
|
||||
* @param bool $force
|
||||
* @return bool
|
||||
*/
|
||||
public function delete(bool $force = false): bool
|
||||
{
|
||||
@@ -716,12 +658,8 @@ trait PageActions
|
||||
/**
|
||||
* Duplicates the page with the given
|
||||
* slug and optionally copies all files
|
||||
*
|
||||
* @param string|null $slug
|
||||
* @param array $options
|
||||
* @return \Kirby\Cms\Page
|
||||
*/
|
||||
public function duplicate(string $slug = null, array $options = [])
|
||||
public function duplicate(string|null $slug = null, array $options = []): static
|
||||
{
|
||||
// create the slug for the duplicate
|
||||
$slug = Str::slug($slug ?? $this->slug() . '-' . Str::slug(I18n::translate('page.duplicate.appendix')));
|
||||
@@ -749,11 +687,60 @@ trait PageActions
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the page to a new parent if the
|
||||
* new parent accepts the page type
|
||||
*/
|
||||
public function move(Site|Page $parent): Page
|
||||
{
|
||||
// nothing to move
|
||||
if ($this->parentModel()->is($parent) === true) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$arguments = [
|
||||
'page' => $this,
|
||||
'parent' => $parent
|
||||
];
|
||||
|
||||
return $this->commit('move', $arguments, function ($page, $parent) {
|
||||
// remove the uuid cache for this page
|
||||
$page->uuid()?->clear(true);
|
||||
|
||||
// move drafts into the drafts folder of the parent
|
||||
if ($page->isDraft() === true) {
|
||||
$newRoot = $parent->root() . '/_drafts/' . $page->dirname();
|
||||
} else {
|
||||
$newRoot = $parent->root() . '/' . $page->dirname();
|
||||
}
|
||||
|
||||
// try to move the page directory on disk
|
||||
if (Dir::move($page->root(), $newRoot) !== true) {
|
||||
throw new LogicException([
|
||||
'key' => 'page.move.directory'
|
||||
]);
|
||||
}
|
||||
|
||||
// flush all collection caches to be sure that
|
||||
// the new child is included afterwards
|
||||
$parent->purge();
|
||||
|
||||
// double-check if the new child can actually be found
|
||||
if (!$newPage = $parent->childrenAndDrafts()->find($page->slug())) {
|
||||
throw new LogicException([
|
||||
'key' => 'page.move.notFound'
|
||||
]);
|
||||
}
|
||||
|
||||
return $newPage;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this|static
|
||||
* @throws \Kirby\Exception\LogicException If the folder cannot be moved
|
||||
*/
|
||||
public function publish()
|
||||
public function publish(): static
|
||||
{
|
||||
if ($this->isDraft() === false) {
|
||||
return $this;
|
||||
@@ -794,25 +781,24 @@ trait PageActions
|
||||
|
||||
/**
|
||||
* Clean internal caches
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function purge()
|
||||
public function purge(): static
|
||||
{
|
||||
parent::purge();
|
||||
|
||||
$this->blueprint = null;
|
||||
$this->children = null;
|
||||
$this->childrenAndDrafts = null;
|
||||
$this->content = null;
|
||||
$this->drafts = null;
|
||||
$this->files = null;
|
||||
$this->inventory = null;
|
||||
$this->translations = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $position
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\LogicException If the page is not included in the siblings collection
|
||||
*/
|
||||
protected function resortSiblingsAfterListing(int $position = null): bool
|
||||
@@ -859,7 +845,7 @@ trait PageActions
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
public function resortSiblingsAfterUnlisting(): bool
|
||||
{
|
||||
@@ -886,15 +872,13 @@ trait PageActions
|
||||
|
||||
/**
|
||||
* Stores the content on disk
|
||||
*
|
||||
* @internal
|
||||
* @param array|null $data
|
||||
* @param string|null $languageCode
|
||||
* @param bool $overwrite
|
||||
* @return static
|
||||
*/
|
||||
public function save(array $data = null, string $languageCode = null, bool $overwrite = false)
|
||||
{
|
||||
public function save(
|
||||
array|null $data = null,
|
||||
string|null $languageCode = null,
|
||||
bool $overwrite = false
|
||||
): static {
|
||||
$page = parent::save($data, $languageCode, $overwrite);
|
||||
|
||||
// overwrite the updated page in the parent collection
|
||||
@@ -910,7 +894,7 @@ trait PageActions
|
||||
* @return $this|static
|
||||
* @throws \Kirby\Exception\LogicException If the folder cannot be moved
|
||||
*/
|
||||
public function unpublish()
|
||||
public function unpublish(): static
|
||||
{
|
||||
if ($this->isDraft() === true) {
|
||||
return $this;
|
||||
@@ -947,14 +931,12 @@ trait PageActions
|
||||
|
||||
/**
|
||||
* Updates the page data
|
||||
*
|
||||
* @param array|null $input
|
||||
* @param string|null $languageCode
|
||||
* @param bool $validate
|
||||
* @return static
|
||||
*/
|
||||
public function update(array $input = null, string $languageCode = null, bool $validate = false)
|
||||
{
|
||||
public function update(
|
||||
array|null $input = null,
|
||||
string|null $languageCode = null,
|
||||
bool $validate = false
|
||||
): static {
|
||||
if ($this->isDraft() === true) {
|
||||
$validate = false;
|
||||
}
|
||||
@@ -982,10 +964,12 @@ trait PageActions
|
||||
* @param \Kirby\Cms\Page $page
|
||||
* @param string $method Method to call on the parent collections
|
||||
* @param \Kirby\Cms\Page|null $parentMdel
|
||||
* @return void
|
||||
*/
|
||||
protected static function updateParentCollections($page, string $method, $parentModel = null): void
|
||||
{
|
||||
protected static function updateParentCollections(
|
||||
$page,
|
||||
string $method,
|
||||
$parentModel = null
|
||||
): void {
|
||||
$parentModel ??= $page->parentModel();
|
||||
|
||||
// method arguments depending on the called method
|
||||
|
||||
@@ -16,8 +16,6 @@ class PageBlueprint extends Blueprint
|
||||
/**
|
||||
* Creates a new page blueprint object
|
||||
* with the given props
|
||||
*
|
||||
* @param array $props
|
||||
*/
|
||||
public function __construct(array $props)
|
||||
{
|
||||
@@ -28,6 +26,7 @@ class PageBlueprint extends Blueprint
|
||||
$this->props['options'] ?? true,
|
||||
// defaults
|
||||
[
|
||||
'access' => null,
|
||||
'changeSlug' => null,
|
||||
'changeStatus' => null,
|
||||
'changeTemplate' => null,
|
||||
@@ -35,8 +34,10 @@ class PageBlueprint extends Blueprint
|
||||
'create' => null,
|
||||
'delete' => null,
|
||||
'duplicate' => null,
|
||||
'read' => null,
|
||||
'list' => null,
|
||||
'move' => null,
|
||||
'preview' => null,
|
||||
'read' => null,
|
||||
'sort' => null,
|
||||
'update' => null,
|
||||
],
|
||||
@@ -58,8 +59,6 @@ class PageBlueprint extends Blueprint
|
||||
|
||||
/**
|
||||
* Returns the page numbering mode
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function num(): string
|
||||
{
|
||||
@@ -70,7 +69,6 @@ class PageBlueprint extends Blueprint
|
||||
* Normalizes the ordering number
|
||||
*
|
||||
* @param mixed $num
|
||||
* @return string
|
||||
*/
|
||||
protected function normalizeNum($num): string
|
||||
{
|
||||
@@ -86,7 +84,6 @@ class PageBlueprint extends Blueprint
|
||||
* Normalizes the available status options for the page
|
||||
*
|
||||
* @param mixed $status
|
||||
* @return array
|
||||
*/
|
||||
protected function normalizeStatus($status): array
|
||||
{
|
||||
@@ -163,8 +160,6 @@ class PageBlueprint extends Blueprint
|
||||
/**
|
||||
* Returns the options object
|
||||
* that handles page options and permissions
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function options(): array
|
||||
{
|
||||
@@ -176,10 +171,8 @@ class PageBlueprint extends Blueprint
|
||||
* The preview setting controls the "Open"
|
||||
* button in the panel and redirects it to a
|
||||
* different URL if necessary.
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public function preview()
|
||||
public function preview(): string|bool
|
||||
{
|
||||
$preview = $this->props['options']['preview'] ?? true;
|
||||
|
||||
@@ -192,8 +185,6 @@ class PageBlueprint extends Blueprint
|
||||
|
||||
/**
|
||||
* Returns the status array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function status(): array
|
||||
{
|
||||
|
||||
@@ -13,30 +13,18 @@ namespace Kirby\Cms;
|
||||
*/
|
||||
class PagePermissions extends ModelPermissions
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $category = 'pages';
|
||||
protected string $category = 'pages';
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function canChangeSlug(): bool
|
||||
{
|
||||
return $this->model->isHomeOrErrorPage() !== true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function canChangeStatus(): bool
|
||||
{
|
||||
return $this->model->isErrorPage() !== true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function canChangeTemplate(): bool
|
||||
{
|
||||
if ($this->model->isErrorPage() === true) {
|
||||
@@ -50,17 +38,16 @@ class PagePermissions extends ModelPermissions
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function canDelete(): bool
|
||||
{
|
||||
return $this->model->isHomeOrErrorPage() !== true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function canMove(): bool
|
||||
{
|
||||
return $this->model->isHomeOrErrorPage() !== true;
|
||||
}
|
||||
|
||||
protected function canSort(): bool
|
||||
{
|
||||
if ($this->model->isErrorPage() === true) {
|
||||
|
||||
@@ -18,25 +18,14 @@ use Kirby\Exception\InvalidArgumentException;
|
||||
*/
|
||||
class PagePicker extends Picker
|
||||
{
|
||||
/**
|
||||
* @var \Kirby\Cms\Pages
|
||||
*/
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Cms\Pages
|
||||
*/
|
||||
protected $itemsForQuery;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Cms\Page|\Kirby\Cms\Site|null
|
||||
*/
|
||||
protected $parent;
|
||||
// TODO: null only due to our Properties setters,
|
||||
// remove once our implementation is better
|
||||
protected Pages|null $items = null;
|
||||
protected Pages|null $itemsForQuery = null;
|
||||
protected Page|Site|null $parent;
|
||||
|
||||
/**
|
||||
* Extends the basic defaults
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function defaults(): array
|
||||
{
|
||||
@@ -55,10 +44,8 @@ class PagePicker extends Picker
|
||||
* also be any subpage. When a query is given
|
||||
* and subpage navigation is deactivated,
|
||||
* there will be no model available at all.
|
||||
*
|
||||
* @return \Kirby\Cms\Page|\Kirby\Cms\Site|null
|
||||
*/
|
||||
public function model()
|
||||
public function model(): Page|Site|null
|
||||
{
|
||||
// no subpages navigation = no model
|
||||
if ($this->options['subpages'] === false) {
|
||||
@@ -77,10 +64,8 @@ class PagePicker extends Picker
|
||||
* Returns a model object for the given
|
||||
* query, depending on the parent and subpages
|
||||
* options.
|
||||
*
|
||||
* @return \Kirby\Cms\Page|\Kirby\Cms\Site|null
|
||||
*/
|
||||
public function modelForQuery()
|
||||
public function modelForQuery(): Page|Site|null
|
||||
{
|
||||
if ($this->options['subpages'] === true && empty($this->options['parent']) === false) {
|
||||
return $this->parent();
|
||||
@@ -93,11 +78,8 @@ class PagePicker extends Picker
|
||||
* Returns basic information about the
|
||||
* parent model that is currently selected
|
||||
* in the page picker.
|
||||
*
|
||||
* @param \Kirby\Cms\Site|\Kirby\Cms\Page|null
|
||||
* @return array|null
|
||||
*/
|
||||
public function modelToArray($model = null): array|null
|
||||
public function modelToArray(Page|Site $model = null): array|null
|
||||
{
|
||||
if ($model === null) {
|
||||
return null;
|
||||
@@ -132,10 +114,8 @@ class PagePicker extends Picker
|
||||
|
||||
/**
|
||||
* Search all pages for the picker
|
||||
*
|
||||
* @return \Kirby\Cms\Pages|null
|
||||
*/
|
||||
public function items()
|
||||
public function items(): Pages|null
|
||||
{
|
||||
// cache
|
||||
if ($this->items !== null) {
|
||||
@@ -157,8 +137,8 @@ class PagePicker extends Picker
|
||||
$items = $this->itemsForQuery();
|
||||
}
|
||||
|
||||
// filter protected pages
|
||||
$items = $items->filter('isReadable', true);
|
||||
// filter protected and hidden pages
|
||||
$items = $items->filter('isListable', true);
|
||||
|
||||
// search
|
||||
$items = $this->search($items);
|
||||
@@ -169,10 +149,8 @@ class PagePicker extends Picker
|
||||
|
||||
/**
|
||||
* Search for pages by parent
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function itemsForParent()
|
||||
public function itemsForParent(): Pages
|
||||
{
|
||||
return $this->parent()->children();
|
||||
}
|
||||
@@ -180,10 +158,9 @@ class PagePicker extends Picker
|
||||
/**
|
||||
* Search for pages by query string
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function itemsForQuery()
|
||||
public function itemsForQuery(): Pages
|
||||
{
|
||||
// cache
|
||||
if ($this->itemsForQuery !== null) {
|
||||
@@ -212,26 +189,18 @@ class PagePicker extends Picker
|
||||
* The model will be used to fetch
|
||||
* subpages unless there's a specific
|
||||
* query to find pages instead.
|
||||
*
|
||||
* @return \Kirby\Cms\Page|\Kirby\Cms\Site
|
||||
*/
|
||||
public function parent()
|
||||
public function parent(): Page|Site
|
||||
{
|
||||
if ($this->parent !== null) {
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
return $this->parent = $this->kirby->page($this->options['parent']) ?? $this->site;
|
||||
return $this->parent ??= $this->kirby->page($this->options['parent']) ?? $this->site;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the top-most model (page or site)
|
||||
* that can be accessed when navigating
|
||||
* through pages.
|
||||
*
|
||||
* @return \Kirby\Cms\Page|\Kirby\Cms\Site
|
||||
*/
|
||||
public function start()
|
||||
public function start(): Page|Site
|
||||
{
|
||||
if (empty($this->options['query']) === false) {
|
||||
return $this->itemsForQuery()?->parent() ?? $this->site;
|
||||
@@ -244,8 +213,6 @@ class PagePicker extends Picker
|
||||
* Returns an associative array
|
||||
* with all information for the picker.
|
||||
* This will be passed directly to the API.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ use Kirby\Exception\DuplicateException;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Exception\PermissionException;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
@@ -22,9 +23,6 @@ class PageRules
|
||||
/**
|
||||
* Validates if the sorting number of the page can be changed
|
||||
*
|
||||
* @param \Kirby\Cms\Page $page
|
||||
* @param int|null $num
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the given number is invalid
|
||||
*/
|
||||
public static function changeNum(Page $page, int $num = null): bool
|
||||
@@ -39,9 +37,6 @@ class PageRules
|
||||
/**
|
||||
* Validates if the slug for the page can be changed
|
||||
*
|
||||
* @param \Kirby\Cms\Page $page
|
||||
* @param string $slug
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\DuplicateException If a page with this slug already exists
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to change the slug
|
||||
*/
|
||||
@@ -57,6 +52,7 @@ class PageRules
|
||||
}
|
||||
|
||||
self::validateSlugLength($slug);
|
||||
self::validateSlugProtectedPaths($page, $slug);
|
||||
|
||||
$siblings = $page->parentModel()->children();
|
||||
$drafts = $page->parentModel()->drafts();
|
||||
@@ -85,14 +81,13 @@ class PageRules
|
||||
/**
|
||||
* Validates if the status for the page can be changed
|
||||
*
|
||||
* @param \Kirby\Cms\Page $page
|
||||
* @param string $status
|
||||
* @param int|null $position
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the given status is invalid
|
||||
*/
|
||||
public static function changeStatus(Page $page, string $status, int $position = null): bool
|
||||
{
|
||||
public static function changeStatus(
|
||||
Page $page,
|
||||
string $status,
|
||||
int $position = null
|
||||
): bool {
|
||||
if (isset($page->blueprint()->status()[$status]) === false) {
|
||||
throw new InvalidArgumentException(['key' => 'page.status.invalid']);
|
||||
}
|
||||
@@ -108,11 +103,9 @@ class PageRules
|
||||
/**
|
||||
* Validates if a page can be converted to a draft
|
||||
*
|
||||
* @param \Kirby\Cms\Page $page
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to change the status or the page cannot be converted to a draft
|
||||
*/
|
||||
public static function changeStatusToDraft(Page $page)
|
||||
public static function changeStatusToDraft(Page $page): bool
|
||||
{
|
||||
if ($page->permissions()->changeStatus() !== true) {
|
||||
throw new PermissionException([
|
||||
@@ -138,13 +131,10 @@ class PageRules
|
||||
/**
|
||||
* Validates if the status of a page can be changed to listed
|
||||
*
|
||||
* @param \Kirby\Cms\Page $page
|
||||
* @param int $position
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the given position is invalid
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to change the status or the status for the page cannot be changed by any user
|
||||
*/
|
||||
public static function changeStatusToListed(Page $page, int $position)
|
||||
public static function changeStatusToListed(Page $page, int $position): bool
|
||||
{
|
||||
// no need to check for status changing permissions,
|
||||
// instead we need to check for sorting permissions
|
||||
@@ -173,8 +163,6 @@ class PageRules
|
||||
/**
|
||||
* Validates if the status of a page can be changed to unlisted
|
||||
*
|
||||
* @param \Kirby\Cms\Page $page
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to change the status
|
||||
*/
|
||||
public static function changeStatusToUnlisted(Page $page)
|
||||
@@ -187,9 +175,6 @@ class PageRules
|
||||
/**
|
||||
* Validates if the template of the page can be changed
|
||||
*
|
||||
* @param \Kirby\Cms\Page $page
|
||||
* @param string $template
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\LogicException If the template of the page cannot be changed at all
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to change the template
|
||||
*/
|
||||
@@ -222,9 +207,6 @@ class PageRules
|
||||
/**
|
||||
* Validates if the title of the page can be changed
|
||||
*
|
||||
* @param \Kirby\Cms\Page $page
|
||||
* @param string $title
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the new title is empty
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to change the title
|
||||
*/
|
||||
@@ -239,11 +221,7 @@ class PageRules
|
||||
]);
|
||||
}
|
||||
|
||||
if (Str::length($title) === 0) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'page.changeTitle.empty',
|
||||
]);
|
||||
}
|
||||
static::validateTitleLength($title);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -251,8 +229,6 @@ class PageRules
|
||||
/**
|
||||
* Validates if the page can be created
|
||||
*
|
||||
* @param \Kirby\Cms\Page $page
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\DuplicateException If the same page or a draft already exists
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the slug is invalid
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to create this page
|
||||
@@ -269,6 +245,7 @@ class PageRules
|
||||
}
|
||||
|
||||
self::validateSlugLength($page->slug());
|
||||
self::validateSlugProtectedPaths($page, $page->slug());
|
||||
|
||||
if ($page->exists() === true) {
|
||||
throw new DuplicateException([
|
||||
@@ -303,9 +280,6 @@ class PageRules
|
||||
/**
|
||||
* Validates if the page can be deleted
|
||||
*
|
||||
* @param \Kirby\Cms\Page $page
|
||||
* @param bool $force
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\LogicException If the page has children and should not be force-deleted
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to delete the page
|
||||
*/
|
||||
@@ -330,14 +304,13 @@ class PageRules
|
||||
/**
|
||||
* Validates if the page can be duplicated
|
||||
*
|
||||
* @param \Kirby\Cms\Page $page
|
||||
* @param string $slug
|
||||
* @param array $options
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to duplicate the page
|
||||
*/
|
||||
public static function duplicate(Page $page, string $slug, array $options = []): bool
|
||||
{
|
||||
public static function duplicate(
|
||||
Page $page,
|
||||
string $slug,
|
||||
array $options = []
|
||||
): bool {
|
||||
if ($page->permissions()->duplicate() !== true) {
|
||||
throw new PermissionException([
|
||||
'key' => 'page.duplicate.permission',
|
||||
@@ -352,12 +325,82 @@ class PageRules
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the page can be moved
|
||||
* to the given parent
|
||||
*/
|
||||
public static function move(Page $page, Site|Page $parent): bool
|
||||
{
|
||||
// if nothing changes, there's no need for checks
|
||||
if ($parent->is($page->parent()) === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($page->permissions()->move() !== true) {
|
||||
throw new PermissionException([
|
||||
'key' => 'page.move.permission',
|
||||
'data' => [
|
||||
'slug' => $page->slug()
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// the page cannot be moved into itself
|
||||
if ($parent instanceof Page && ($page->is($parent) === true || $page->isAncestorOf($parent) === true)) {
|
||||
throw new LogicException([
|
||||
'key' => 'page.move.ancestor',
|
||||
]);
|
||||
}
|
||||
|
||||
// check for duplicates
|
||||
if ($parent->childrenAndDrafts()->find($page->slug())) {
|
||||
throw new DuplicateException([
|
||||
'key' => 'page.move.duplicate',
|
||||
'data' => [
|
||||
'slug' => $page->slug(),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
$allowed = [];
|
||||
|
||||
// collect all allowed subpage templates
|
||||
foreach ($parent->blueprint()->sections() as $section) {
|
||||
// only take pages sections into consideration
|
||||
if ($section->type() !== 'pages') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// only consider page sections that list pages
|
||||
// of the targeted new parent page
|
||||
if ($section->parent() !== $parent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// go through all allowed blueprints and
|
||||
// add the name to the allow list
|
||||
foreach ($section->blueprints() as $blueprint) {
|
||||
$allowed[] = $blueprint['name'];
|
||||
}
|
||||
}
|
||||
|
||||
// check if the template of this page is allowed as subpage type
|
||||
if (in_array($page->intendedTemplate()->name(), $allowed) === false) {
|
||||
throw new PermissionException([
|
||||
'key' => 'page.move.template',
|
||||
'data' => [
|
||||
'template' => $page->intendedTemplate()->name(),
|
||||
'parent' => $parent->id() ?? '/',
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the page can be published
|
||||
* (status change from draft to listed or unlisted)
|
||||
*
|
||||
* @param Page $page
|
||||
* @return bool
|
||||
*/
|
||||
public static function publish(Page $page): bool
|
||||
{
|
||||
@@ -383,9 +426,6 @@ class PageRules
|
||||
/**
|
||||
* Validates if the page can be updated
|
||||
*
|
||||
* @param \Kirby\Cms\Page $page
|
||||
* @param array $content
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\PermissionException If the user is not allowed to update the page
|
||||
*/
|
||||
public static function update(Page $page, array $content = []): bool
|
||||
@@ -406,11 +446,9 @@ class PageRules
|
||||
* Ensures that the slug is not empty and doesn't exceed the maximum length
|
||||
* to make sure that the directory name will be accepted by the filesystem
|
||||
*
|
||||
* @param string $slug New slug to check
|
||||
* @return void
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the slug is empty or too long
|
||||
*/
|
||||
protected static function validateSlugLength(string $slug): void
|
||||
public static function validateSlugLength(string $slug): void
|
||||
{
|
||||
$slugLength = Str::length($slug);
|
||||
|
||||
@@ -433,4 +471,48 @@ class PageRules
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ensure that a top-level page path does not start with one of
|
||||
* the reserved URL paths, e.g. for API or the Panel
|
||||
*
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the page ID starts as one of the disallowed paths
|
||||
*/
|
||||
protected static function validateSlugProtectedPaths(
|
||||
Page $page,
|
||||
string $slug
|
||||
): void {
|
||||
if ($page->parent() === null) {
|
||||
$paths = A::map(
|
||||
['api', 'assets', 'media', 'panel'],
|
||||
fn ($url) => $page->kirby()->url($url, true)->path()->toString()
|
||||
);
|
||||
|
||||
$index = array_search($slug, $paths);
|
||||
|
||||
if ($index !== false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'page.changeSlug.reserved',
|
||||
'data' => [
|
||||
'path' => $paths[$index]
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the page title is not empty
|
||||
*
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the title is empty
|
||||
*/
|
||||
public static function validateTitleLength(string $title): void
|
||||
{
|
||||
if (Str::length($title) === 0) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'page.changeTitle.empty',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@ trait PageSiblings
|
||||
* page in the siblings collection
|
||||
*
|
||||
* @param \Kirby\Cms\Collection|null $collection
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasNextListed($collection = null): bool
|
||||
{
|
||||
@@ -31,8 +29,6 @@ trait PageSiblings
|
||||
* page in the siblings collection
|
||||
*
|
||||
* @param \Kirby\Cms\Collection|null $collection
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasNextUnlisted($collection = null): bool
|
||||
{
|
||||
@@ -44,8 +40,6 @@ trait PageSiblings
|
||||
* page in the siblings collection
|
||||
*
|
||||
* @param \Kirby\Cms\Collection|null $collection
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPrevListed($collection = null): bool
|
||||
{
|
||||
@@ -57,8 +51,6 @@ trait PageSiblings
|
||||
* page in the siblings collection
|
||||
*
|
||||
* @param \Kirby\Cms\Collection|null $collection
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPrevUnlisted($collection = null): bool
|
||||
{
|
||||
@@ -130,7 +122,6 @@ trait PageSiblings
|
||||
/**
|
||||
* Returns siblings with the same template
|
||||
*
|
||||
* @param bool $self
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function templateSiblings(bool $self = true)
|
||||
|
||||
@@ -41,10 +41,8 @@ class Pages extends Collection
|
||||
|
||||
/**
|
||||
* All registered pages methods
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $methods = [];
|
||||
public static array $methods = [];
|
||||
|
||||
/**
|
||||
* Adds a single page or
|
||||
@@ -55,7 +53,7 @@ class Pages extends Collection
|
||||
* @return $this
|
||||
* @throws \Kirby\Exception\InvalidArgumentException When no `Page` or `Pages` object or an ID of an existing page is passed
|
||||
*/
|
||||
public function add($object)
|
||||
public function add($object): static
|
||||
{
|
||||
$site = App::instance()->site();
|
||||
|
||||
@@ -85,20 +83,16 @@ class Pages extends Collection
|
||||
|
||||
/**
|
||||
* Returns all audio files of all children
|
||||
*
|
||||
* @return \Kirby\Cms\Files
|
||||
*/
|
||||
public function audio()
|
||||
public function audio(): Files
|
||||
{
|
||||
return $this->files()->filter('type', 'audio');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all children for each page in the array
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function children()
|
||||
public function children(): Pages
|
||||
{
|
||||
$children = new Pages([]);
|
||||
|
||||
@@ -113,30 +107,24 @@ class Pages extends Collection
|
||||
|
||||
/**
|
||||
* Returns all code files of all children
|
||||
*
|
||||
* @return \Kirby\Cms\Files
|
||||
*/
|
||||
public function code()
|
||||
public function code(): Files
|
||||
{
|
||||
return $this->files()->filter('type', 'code');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all documents of all children
|
||||
*
|
||||
* @return \Kirby\Cms\Files
|
||||
*/
|
||||
public function documents()
|
||||
public function documents(): Files
|
||||
{
|
||||
return $this->files()->filter('type', 'document');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all drafts for all pages in the collection
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function drafts()
|
||||
public function drafts(): Pages
|
||||
{
|
||||
$drafts = new Pages([]);
|
||||
|
||||
@@ -151,14 +139,12 @@ class Pages extends Collection
|
||||
|
||||
/**
|
||||
* Creates a pages collection from an array of props
|
||||
*
|
||||
* @param array $pages
|
||||
* @param \Kirby\Cms\Model|null $model
|
||||
* @param bool|null $draft
|
||||
* @return static
|
||||
*/
|
||||
public static function factory(array $pages, Model $model = null, bool $draft = null)
|
||||
{
|
||||
public static function factory(
|
||||
array $pages,
|
||||
Page|Site $model = null,
|
||||
bool $draft = null
|
||||
): static {
|
||||
$model ??= App::instance()->site();
|
||||
$children = new static([], $model);
|
||||
$kirby = $model->kirby();
|
||||
@@ -187,10 +173,8 @@ class Pages extends Collection
|
||||
|
||||
/**
|
||||
* Returns all files of all children
|
||||
*
|
||||
* @return \Kirby\Cms\Files
|
||||
*/
|
||||
public function files()
|
||||
public function files(): Files
|
||||
{
|
||||
$files = new Files([], $this->parent);
|
||||
|
||||
@@ -206,11 +190,8 @@ class Pages extends Collection
|
||||
/**
|
||||
* Finds a page by its ID or URI
|
||||
* @internal Use `$pages->find()` instead
|
||||
*
|
||||
* @param string|null $key
|
||||
* @return \Kirby\Cms\Page|null
|
||||
*/
|
||||
public function findByKey(string|null $key = null)
|
||||
public function findByKey(string|null $key = null): Page|null
|
||||
{
|
||||
if ($key === null) {
|
||||
return null;
|
||||
@@ -239,17 +220,21 @@ class Pages extends Collection
|
||||
return $page;
|
||||
}
|
||||
|
||||
// try to find the page by its (translated) URI by stepping through the page tree
|
||||
$kirby = App::instance();
|
||||
$multiLang = $kirby->multilang();
|
||||
|
||||
// try to find the page by its (translated) URI
|
||||
// by stepping through the page tree
|
||||
$start = $this->parent instanceof Page ? $this->parent->id() : '';
|
||||
if ($page = $this->findByKeyRecursive($key, $start, App::instance()->multilang())) {
|
||||
if ($page = $this->findByKeyRecursive($key, $start, $multiLang)) {
|
||||
return $page;
|
||||
}
|
||||
|
||||
// for secondary languages, try the full translated URI
|
||||
// (for collections without parent that won't have a result above)
|
||||
if (
|
||||
App::instance()->multilang() === true &&
|
||||
App::instance()->language()->isDefault() === false &&
|
||||
$multiLang === true &&
|
||||
$kirby->language()->isDefault() === false &&
|
||||
$page = $this->findBy('uri', $key)
|
||||
) {
|
||||
return $page;
|
||||
@@ -263,8 +248,11 @@ class Pages extends Collection
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function findByKeyRecursive(string $id, string $startAt = null, bool $multiLang = false)
|
||||
{
|
||||
protected function findByKeyRecursive(
|
||||
string $id,
|
||||
string $startAt = null,
|
||||
bool $multiLang = false
|
||||
) {
|
||||
$path = explode('/', $id);
|
||||
$item = null;
|
||||
$query = $startAt;
|
||||
@@ -274,9 +262,14 @@ class Pages extends Collection
|
||||
$query = ltrim($query . '/' . $key, '/');
|
||||
$item = $collection->get($query) ?? null;
|
||||
|
||||
if ($item === null && $multiLang === true && !App::instance()->language()->isDefault()) {
|
||||
if (
|
||||
$item === null &&
|
||||
$multiLang === true &&
|
||||
App::instance()->language()->isDefault() === false
|
||||
) {
|
||||
if (count($path) > 1 || $collection->parent()) {
|
||||
// either the desired path is definitely not a slug, or collection is the children of another collection
|
||||
// either the desired path is definitely not a slug,
|
||||
// or collection is the children of another collection
|
||||
$item = $collection->findBy('slug', $key);
|
||||
} else {
|
||||
// desired path _could_ be a slug or a "top level" uri
|
||||
@@ -294,10 +287,8 @@ class Pages extends Collection
|
||||
|
||||
/**
|
||||
* Finds the currently open page
|
||||
*
|
||||
* @return \Kirby\Cms\Page|null
|
||||
*/
|
||||
public function findOpen()
|
||||
public function findOpen(): Page|null
|
||||
{
|
||||
return $this->findBy('isOpen', true);
|
||||
}
|
||||
@@ -325,10 +316,8 @@ class Pages extends Collection
|
||||
|
||||
/**
|
||||
* Returns all images of all children
|
||||
*
|
||||
* @return \Kirby\Cms\Files
|
||||
*/
|
||||
public function images()
|
||||
public function images(): Files
|
||||
{
|
||||
return $this->files()->filter('type', 'image');
|
||||
}
|
||||
@@ -337,7 +326,6 @@ class Pages extends Collection
|
||||
* Create a recursive flat index of all
|
||||
* pages and subpages, etc.
|
||||
*
|
||||
* @param bool $drafts
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function index(bool $drafts = false)
|
||||
@@ -371,20 +359,16 @@ class Pages extends Collection
|
||||
|
||||
/**
|
||||
* Returns all listed pages in the collection
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function listed()
|
||||
public function listed(): static
|
||||
{
|
||||
return $this->filter('isListed', '==', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all unlisted pages in the collection
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function unlisted()
|
||||
public function unlisted(): static
|
||||
{
|
||||
return $this->filter('isUnlisted', '==', true);
|
||||
}
|
||||
@@ -468,20 +452,14 @@ class Pages extends Collection
|
||||
|
||||
/**
|
||||
* Returns an array with all page numbers
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function nums(): array
|
||||
{
|
||||
return $this->pluck('num');
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns all listed and unlisted pages in the collection
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function published()
|
||||
// Returns all listed and unlisted pages in the collection
|
||||
public function published(): static
|
||||
{
|
||||
return $this->filter('isDraft', '==', false);
|
||||
}
|
||||
@@ -509,10 +487,8 @@ class Pages extends Collection
|
||||
|
||||
/**
|
||||
* Returns all video files of all children
|
||||
*
|
||||
* @return \Kirby\Cms\Files
|
||||
*/
|
||||
public function videos()
|
||||
public function videos(): Files
|
||||
{
|
||||
return $this->files()->filter('type', 'video');
|
||||
}
|
||||
|
||||
@@ -60,8 +60,6 @@ class Pagination extends BasePagination
|
||||
* 'url' => new Uri('https://getkirby.com/blog')
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function __construct(array $params = [])
|
||||
{
|
||||
@@ -95,8 +93,6 @@ class Pagination extends BasePagination
|
||||
|
||||
/**
|
||||
* Returns the Url for the first page
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function firstPageUrl(): string|null
|
||||
{
|
||||
@@ -105,8 +101,6 @@ class Pagination extends BasePagination
|
||||
|
||||
/**
|
||||
* Returns the Url for the last page
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function lastPageUrl(): string|null
|
||||
{
|
||||
@@ -116,8 +110,6 @@ class Pagination extends BasePagination
|
||||
/**
|
||||
* Returns the Url for the next page.
|
||||
* Returns null if there's no next page.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function nextPageUrl(): string|null
|
||||
{
|
||||
@@ -132,9 +124,6 @@ class Pagination extends BasePagination
|
||||
* Returns the URL of the current page.
|
||||
* If the `$page` variable is set, the URL
|
||||
* for that page will be returned.
|
||||
*
|
||||
* @param int|null $page
|
||||
* @return string|null
|
||||
*/
|
||||
public function pageUrl(int $page = null): string|null
|
||||
{
|
||||
@@ -165,8 +154,6 @@ class Pagination extends BasePagination
|
||||
/**
|
||||
* Returns the Url for the previous page.
|
||||
* Returns null if there's no previous page.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function prevPageUrl(): string|null
|
||||
{
|
||||
|
||||
@@ -17,15 +17,9 @@ use Kirby\Exception\InvalidArgumentException;
|
||||
*/
|
||||
class Permissions
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $extendedActions = [];
|
||||
public static array $extendedActions = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $actions = [
|
||||
protected array $actions = [
|
||||
'access' => [
|
||||
'account' => true,
|
||||
'languages' => true,
|
||||
@@ -35,18 +29,22 @@ class Permissions
|
||||
'users' => true,
|
||||
],
|
||||
'files' => [
|
||||
'changeName' => true,
|
||||
'create' => true,
|
||||
'delete' => true,
|
||||
'read' => true,
|
||||
'replace' => true,
|
||||
'update' => true
|
||||
'access' => true,
|
||||
'changeName' => true,
|
||||
'changeTemplate' => true,
|
||||
'create' => true,
|
||||
'delete' => true,
|
||||
'list' => true,
|
||||
'read' => true,
|
||||
'replace' => true,
|
||||
'update' => true
|
||||
],
|
||||
'languages' => [
|
||||
'create' => true,
|
||||
'delete' => true
|
||||
],
|
||||
'pages' => [
|
||||
'access' => true,
|
||||
'changeSlug' => true,
|
||||
'changeStatus' => true,
|
||||
'changeTemplate' => true,
|
||||
@@ -54,6 +52,8 @@ class Permissions
|
||||
'create' => true,
|
||||
'delete' => true,
|
||||
'duplicate' => true,
|
||||
'list' => true,
|
||||
'move' => true,
|
||||
'preview' => true,
|
||||
'read' => true,
|
||||
'sort' => true,
|
||||
@@ -87,10 +87,9 @@ class Permissions
|
||||
/**
|
||||
* Permissions constructor
|
||||
*
|
||||
* @param array $settings
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function __construct($settings = [])
|
||||
public function __construct(array|bool|null $settings = [])
|
||||
{
|
||||
// dynamically register the extended actions
|
||||
foreach (static::$extendedActions as $key => $actions) {
|
||||
@@ -110,11 +109,6 @@ class Permissions
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $category
|
||||
* @param string|null $action
|
||||
* @return bool
|
||||
*/
|
||||
public function for(string $category = null, string $action = null): bool
|
||||
{
|
||||
if ($action === null) {
|
||||
@@ -132,33 +126,26 @@ class Permissions
|
||||
return $this->actions[$category][$action];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $category
|
||||
* @param string $action
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasAction(string $category, string $action): bool
|
||||
{
|
||||
return $this->hasCategory($category) === true && array_key_exists($action, $this->actions[$category]) === true;
|
||||
return
|
||||
$this->hasCategory($category) === true &&
|
||||
array_key_exists($action, $this->actions[$category]) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $category
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasCategory(string $category): bool
|
||||
{
|
||||
return array_key_exists($category, $this->actions) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $category
|
||||
* @param string $action
|
||||
* @param $setting
|
||||
* @return $this
|
||||
*/
|
||||
protected function setAction(string $category, string $action, $setting)
|
||||
{
|
||||
protected function setAction(
|
||||
string $category,
|
||||
string $action,
|
||||
$setting
|
||||
): static {
|
||||
// wildcard to overwrite the entire category
|
||||
if ($action === '*') {
|
||||
return $this->setCategory($category, $setting);
|
||||
@@ -170,10 +157,9 @@ class Permissions
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $setting
|
||||
* @return $this
|
||||
*/
|
||||
protected function setAll(bool $setting)
|
||||
protected function setAll(bool $setting): static
|
||||
{
|
||||
foreach ($this->actions as $categoryName => $actions) {
|
||||
$this->setCategory($categoryName, $setting);
|
||||
@@ -183,10 +169,9 @@ class Permissions
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $settings
|
||||
* @return $this
|
||||
*/
|
||||
protected function setCategories(array $settings)
|
||||
protected function setCategories(array $settings): static
|
||||
{
|
||||
foreach ($settings as $categoryName => $categoryActions) {
|
||||
if (is_bool($categoryActions) === true) {
|
||||
@@ -204,12 +189,10 @@ class Permissions
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $category
|
||||
* @param bool $setting
|
||||
* @return $this
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
protected function setCategory(string $category, bool $setting)
|
||||
protected function setCategory(string $category, bool $setting): static
|
||||
{
|
||||
if ($this->hasCategory($category) === false) {
|
||||
throw new InvalidArgumentException('Invalid permissions category');
|
||||
@@ -222,9 +205,6 @@ class Permissions
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->actions;
|
||||
|
||||
@@ -14,25 +14,12 @@ namespace Kirby\Cms;
|
||||
*/
|
||||
abstract class Picker
|
||||
{
|
||||
/**
|
||||
* @var \Kirby\Cms\App
|
||||
*/
|
||||
protected $kirby;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Cms\Site
|
||||
*/
|
||||
protected $site;
|
||||
protected App $kirby;
|
||||
protected array $options;
|
||||
protected Site $site;
|
||||
|
||||
/**
|
||||
* Creates a new Picker instance
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function __construct(array $params = [])
|
||||
{
|
||||
@@ -43,8 +30,6 @@ abstract class Picker
|
||||
|
||||
/**
|
||||
* Return the array of default values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function defaults(): array
|
||||
{
|
||||
@@ -55,7 +40,7 @@ abstract class Picker
|
||||
// query template for the info field
|
||||
'info' => false,
|
||||
// listing style: list, cards, cardlets
|
||||
'layout' =>'list',
|
||||
'layout' => 'list',
|
||||
// number of users displayed per pagination page
|
||||
'limit' => 20,
|
||||
// optional mapping function for the result array
|
||||
@@ -75,20 +60,15 @@ abstract class Picker
|
||||
|
||||
/**
|
||||
* Fetches all items for the picker
|
||||
*
|
||||
* @return \Kirby\Cms\Collection|null
|
||||
*/
|
||||
abstract public function items();
|
||||
abstract public function items(): Collection|null;
|
||||
|
||||
/**
|
||||
* Converts all given items to an associative
|
||||
* array that is already optimized for the
|
||||
* panel picker component.
|
||||
*
|
||||
* @param \Kirby\Cms\Collection|null $items
|
||||
* @return array
|
||||
*/
|
||||
public function itemsToArray($items = null): array
|
||||
public function itemsToArray(Collection $items = null): array
|
||||
{
|
||||
if ($items === null) {
|
||||
return [];
|
||||
@@ -116,11 +96,8 @@ abstract class Picker
|
||||
/**
|
||||
* Apply pagination to the collection
|
||||
* of items according to the options.
|
||||
*
|
||||
* @param \Kirby\Cms\Collection $items
|
||||
* @return \Kirby\Cms\Collection
|
||||
*/
|
||||
public function paginate(Collection $items)
|
||||
public function paginate(Collection $items): Collection
|
||||
{
|
||||
return $items->paginate([
|
||||
'limit' => $this->options['limit'],
|
||||
@@ -131,9 +108,6 @@ abstract class Picker
|
||||
/**
|
||||
* Return the most relevant pagination
|
||||
* info as array
|
||||
*
|
||||
* @param \Kirby\Cms\Pagination $pagination
|
||||
* @return array
|
||||
*/
|
||||
public function paginationToArray(Pagination $pagination): array
|
||||
{
|
||||
@@ -147,11 +121,8 @@ abstract class Picker
|
||||
/**
|
||||
* Search through the collection of items
|
||||
* if not deactivate in the options
|
||||
*
|
||||
* @param \Kirby\Cms\Collection $items
|
||||
* @return \Kirby\Cms\Collection
|
||||
*/
|
||||
public function search(Collection $items)
|
||||
public function search(Collection $items): Collection
|
||||
{
|
||||
if (empty($this->options['search']) === false) {
|
||||
return $items->search($this->options['search']);
|
||||
@@ -164,8 +135,6 @@ abstract class Picker
|
||||
* Returns an associative array
|
||||
* with all information for the picker.
|
||||
* This will be passed directly to the API.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
|
||||
@@ -23,8 +23,9 @@ use Throwable;
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class Plugin extends Model
|
||||
class Plugin
|
||||
{
|
||||
protected PluginAssets $assets;
|
||||
protected array $extends;
|
||||
protected string $name;
|
||||
protected string $root;
|
||||
@@ -33,21 +34,17 @@ class Plugin extends Model
|
||||
protected array|null $info = null;
|
||||
protected UpdateStatus|null $updateStatus = null;
|
||||
|
||||
/**
|
||||
* Allows access to any composer.json field by method call
|
||||
*/
|
||||
public function __call(string $key, array $arguments = null)
|
||||
{
|
||||
return $this->info()[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name Plugin name within Kirby (`vendor/plugin`)
|
||||
* @param array $extends Associative array of plugin extensions
|
||||
*
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the plugin name has an invalid format
|
||||
*/
|
||||
public function __construct(string $name, array $extends = [])
|
||||
{
|
||||
$this->setName($name);
|
||||
static::validateName($name);
|
||||
|
||||
$this->name = $name;
|
||||
$this->extends = $extends;
|
||||
$this->root = $extends['root'] ?? dirname(debug_backtrace()[0]['file']);
|
||||
$this->info = empty($extends['info']) === false && is_array($extends['info']) ? $extends['info'] : null;
|
||||
@@ -55,6 +52,30 @@ class Plugin extends Model
|
||||
unset($this->extends['root'], $this->extends['info']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows access to any composer.json field by method call
|
||||
*/
|
||||
public function __call(string $key, array $arguments = null): mixed
|
||||
{
|
||||
return $this->info()[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin asset object for a specific asset
|
||||
*/
|
||||
public function asset(string $path): PluginAsset|null
|
||||
{
|
||||
return $this->assets()->get($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin assets collection
|
||||
*/
|
||||
public function assets(): PluginAssets
|
||||
{
|
||||
return $this->assets ??= PluginAssets::factory($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array with author information
|
||||
* from the composer.json file
|
||||
@@ -114,6 +135,14 @@ class Plugin extends Model
|
||||
return $this->info = $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current $kirby instance
|
||||
*/
|
||||
public function kirby(): App
|
||||
{
|
||||
return App::instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link to the plugin homepage
|
||||
*/
|
||||
@@ -185,23 +214,6 @@ class Plugin extends Model
|
||||
return $this->root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and sets the plugin name
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the plugin name has an invalid format
|
||||
*/
|
||||
protected function setName(string $name): static
|
||||
{
|
||||
if (preg_match('!^[a-z0-9-]+\/[a-z0-9-]+$!i', $name) !== 1) {
|
||||
throw new InvalidArgumentException('The plugin name must follow the format "a-z0-9-/a-z0-9-"');
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available plugin metadata
|
||||
*/
|
||||
@@ -264,6 +276,19 @@ class Plugin extends Model
|
||||
return $this->updateStatus = new UpdateStatus($this, false, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the name follows the required pattern
|
||||
* and throws an exception if not
|
||||
*
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function validateName(string $name): void
|
||||
{
|
||||
if (preg_match('!^[a-z0-9-]+\/[a-z0-9-]+$!i', $name) !== 1) {
|
||||
throw new InvalidArgumentException('The plugin name must follow the format "a-z0-9-/a-z0-9-"');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the normalized version number
|
||||
* from the composer.json file
|
||||
|
||||
120
kirby/src/Cms/PluginAsset.php
Normal file
120
kirby/src/Cms/PluginAsset.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Filesystem\F;
|
||||
|
||||
/**
|
||||
* Representing a plugin asset with methods
|
||||
* to manage the asset file between the plugin
|
||||
* and media folder
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Nico Hoffmann <nico@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class PluginAsset
|
||||
{
|
||||
public function __construct(
|
||||
protected string $path,
|
||||
protected string $root,
|
||||
protected Plugin $plugin
|
||||
) {
|
||||
}
|
||||
|
||||
public function extension(): string
|
||||
{
|
||||
return F::extension($this->path());
|
||||
}
|
||||
|
||||
public function filename(): string
|
||||
{
|
||||
return F::filename($this->path());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a unique media hash
|
||||
*/
|
||||
public function mediaHash(): string
|
||||
{
|
||||
return crc32($this->filename()) . '-' . $this->modified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute path to the asset file in the media folder
|
||||
*/
|
||||
public function mediaRoot(): string
|
||||
{
|
||||
return $this->plugin()->mediaRoot() . '/' . $this->mediaHash() . '/' . $this->path();
|
||||
}
|
||||
|
||||
/**
|
||||
* Public accessible url path for the asset
|
||||
*/
|
||||
public function mediaUrl(): string
|
||||
{
|
||||
return $this->plugin()->mediaUrl() . '/' . $this->mediaHash() . '/' . $this->path();
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamp when asset file was last modified
|
||||
*/
|
||||
public function modified(): int|false
|
||||
{
|
||||
return F::modified($this->root());
|
||||
}
|
||||
|
||||
public function path(): string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function plugin(): Plugin
|
||||
{
|
||||
return $this->plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes the asset file to the plugin's media folder
|
||||
* by creating a symlink
|
||||
*/
|
||||
public function publish(): void
|
||||
{
|
||||
F::link($this->root(), $this->mediaRoot(), 'symlink');
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @since 4.0.0
|
||||
* @deprecated 4.0.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function publishAt(string $path): void
|
||||
{
|
||||
$media = $this->plugin()->mediaRoot() . '/' . $path;
|
||||
F::link($this->root(), $media, 'symlink');
|
||||
}
|
||||
|
||||
public function root(): string
|
||||
{
|
||||
return $this->root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ::mediaUrl
|
||||
*/
|
||||
public function url(): string
|
||||
{
|
||||
return $this->mediaUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ::url
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->url();
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Closure;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Http\Response;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
* Plugin assets are automatically copied/linked
|
||||
@@ -13,65 +15,167 @@ use Kirby\Http\Response;
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @author Nico Hoffmann <nico@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class PluginAssets
|
||||
class PluginAssets extends Collection
|
||||
{
|
||||
/**
|
||||
* Clean old/deprecated assets on every resolve
|
||||
*
|
||||
* @param string $pluginName
|
||||
* @return void
|
||||
*/
|
||||
public static function clean(string $pluginName): void
|
||||
{
|
||||
if ($plugin = App::instance()->plugin($pluginName)) {
|
||||
$root = $plugin->root() . '/assets';
|
||||
$media = $plugin->mediaRoot();
|
||||
$assets = Dir::index($media, true);
|
||||
$assets = $plugin->assets();
|
||||
|
||||
foreach ($assets as $asset) {
|
||||
$original = $root . '/' . $asset;
|
||||
// get all media files
|
||||
$files = Dir::index($media, true);
|
||||
|
||||
if (file_exists($original) === false) {
|
||||
$assetRoot = $media . '/' . $asset;
|
||||
// get all active assets' paths from the plugin
|
||||
$active = $assets->values(
|
||||
function ($asset) {
|
||||
$path = $asset->mediaHash() . '/' . $asset->path();
|
||||
$paths = [];
|
||||
$parts = explode('/', $path);
|
||||
|
||||
if (is_file($assetRoot) === true) {
|
||||
F::remove($assetRoot);
|
||||
} else {
|
||||
Dir::remove($assetRoot);
|
||||
// collect all path segments
|
||||
// (e.g. foo/, foo/bar/, foo/bar/baz.css) for the asset
|
||||
for ($i = 1, $max = count($parts); $i <= $max; $i++) {
|
||||
$paths[] = implode('/', array_slice($parts, 0, $i));
|
||||
|
||||
// TODO: remove when media hash is enforced as mandatory
|
||||
$paths[] = implode('/', array_slice($parts, 1, $i));
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
);
|
||||
|
||||
// flatten the array and remove duplicates
|
||||
$active = array_unique(array_merge(...array_values($active)));
|
||||
|
||||
// get outdated media files by comparing all
|
||||
// files in the media folder against the set of asset paths
|
||||
$stale = array_diff($files, $active);
|
||||
|
||||
foreach ($stale as $file) {
|
||||
$root = $media . '/' . $file;
|
||||
|
||||
if (is_file($root) === true) {
|
||||
F::remove($root);
|
||||
} else {
|
||||
Dir::remove($root);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters assets collection by CSS files
|
||||
*/
|
||||
public function css(): static
|
||||
{
|
||||
return $this->filter(fn ($asset) => $asset->extension() === 'css');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new collection for the plugin's assets
|
||||
* by considering the plugin's `asset` extension
|
||||
* (and `assets` directory as fallback)
|
||||
*/
|
||||
public static function factory(Plugin $plugin): static
|
||||
{
|
||||
// get assets defined in the plugin extension
|
||||
if ($assets = $plugin->extends()['assets'] ?? null) {
|
||||
if ($assets instanceof Closure) {
|
||||
$assets = $assets();
|
||||
}
|
||||
|
||||
// normalize array: use relative path as
|
||||
// key when no key is defined
|
||||
foreach ($assets as $key => $root) {
|
||||
if (is_int($key) === true) {
|
||||
unset($assets[$key]);
|
||||
$path = Str::after($root, $plugin->root() . '/');
|
||||
$assets[$path] = $root;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fallback: if no assets are defined in the plugin extension,
|
||||
// use all files in the plugin's `assets` directory
|
||||
if ($assets === null) {
|
||||
$assets = [];
|
||||
$root = $plugin->root() . '/assets';
|
||||
|
||||
foreach (Dir::index($root, true) as $path) {
|
||||
if (is_file($root . '/' . $path) === true) {
|
||||
$assets[$path] = $root . '/' . $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$collection = new static([], $plugin);
|
||||
|
||||
foreach ($assets as $path => $root) {
|
||||
$collection->data[$path] = new PluginAsset($path, $root, $plugin);
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters assets collection by JavaScript files
|
||||
*/
|
||||
public function js(): static
|
||||
{
|
||||
return $this->filter(fn ($asset) => $asset->extension() === 'js');
|
||||
}
|
||||
|
||||
public function plugin(): Plugin
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a symlink for a plugin asset and
|
||||
* return the public URL
|
||||
*
|
||||
* @param string $pluginName
|
||||
* @param string $filename
|
||||
* @return \Kirby\Cms\Response|null
|
||||
*/
|
||||
public static function resolve(string $pluginName, string $filename)
|
||||
{
|
||||
public static function resolve(
|
||||
string $pluginName,
|
||||
string $hash,
|
||||
string $path
|
||||
): Response|null {
|
||||
if ($plugin = App::instance()->plugin($pluginName)) {
|
||||
$source = $plugin->root() . '/assets/' . $filename;
|
||||
// do some spring cleaning for older files
|
||||
static::clean($pluginName);
|
||||
|
||||
if (F::exists($source, $plugin->root()) === true) {
|
||||
// do some spring cleaning for older files
|
||||
static::clean($pluginName);
|
||||
// @codeCoverageIgnoreStart
|
||||
// TODO: deprecated media URL without hash
|
||||
if (empty($hash) === true) {
|
||||
$asset = $plugin->asset($path);
|
||||
$asset->publishAt($path);
|
||||
return Response::file($asset->root());
|
||||
}
|
||||
|
||||
$target = $plugin->mediaRoot() . '/' . $filename;
|
||||
// TODO: deprecated media URL with hash (but path)
|
||||
if ($asset = $plugin->asset($hash . '/' . $path)) {
|
||||
$asset->publishAt($hash . '/' . $path);
|
||||
return Response::file($asset->root());
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
// create a symlink if possible
|
||||
F::link($source, $target, 'symlink');
|
||||
if ($asset = $plugin->asset($path)) {
|
||||
if ($asset->mediaHash() === $hash) {
|
||||
// create a symlink if possible
|
||||
$asset->publish();
|
||||
|
||||
// return the file response
|
||||
return Response::file($source);
|
||||
// return the file response
|
||||
return Response::file($asset->root());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Http\Request;
|
||||
use Kirby\Toolkit\Facade;
|
||||
|
||||
/**
|
||||
@@ -15,10 +16,7 @@ use Kirby\Toolkit\Facade;
|
||||
*/
|
||||
class R extends Facade
|
||||
{
|
||||
/**
|
||||
* @return \Kirby\Http\Request
|
||||
*/
|
||||
public static function instance()
|
||||
public static function instance(): Request
|
||||
{
|
||||
return App::instance()->request();
|
||||
}
|
||||
|
||||
@@ -20,68 +20,50 @@ class Responder
|
||||
/**
|
||||
* Timestamp when the response expires
|
||||
* in Kirby's cache
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected $expires = null;
|
||||
protected int|null $expires = null;
|
||||
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $code = null;
|
||||
protected int|null $code = null;
|
||||
|
||||
/**
|
||||
* Response body
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $body = null;
|
||||
protected string|null $body = null;
|
||||
|
||||
/**
|
||||
* Flag that defines whether the current
|
||||
* response can be cached by Kirby's cache
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $cache = true;
|
||||
protected bool $cache = true;
|
||||
|
||||
/**
|
||||
* HTTP headers
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $headers = [];
|
||||
protected array $headers = [];
|
||||
|
||||
/**
|
||||
* Content type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = null;
|
||||
protected string|null $type = null;
|
||||
|
||||
/**
|
||||
* Flag that defines whether the current
|
||||
* response uses the HTTP `Authorization`
|
||||
* request header
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $usesAuth = false;
|
||||
protected bool $usesAuth = false;
|
||||
|
||||
/**
|
||||
* List of cookie names the response
|
||||
* relies on
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $usesCookies = [];
|
||||
protected array $usesCookies = [];
|
||||
|
||||
/**
|
||||
* Creates and sends the response
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
@@ -91,10 +73,9 @@ class Responder
|
||||
/**
|
||||
* Setter and getter for the response body
|
||||
*
|
||||
* @param string|null $body
|
||||
* @return string|$this
|
||||
* @return $this|string|null
|
||||
*/
|
||||
public function body(string $body = null)
|
||||
public function body(string $body = null): static|string|null
|
||||
{
|
||||
if ($body === null) {
|
||||
return $this->body;
|
||||
@@ -110,10 +91,9 @@ class Responder
|
||||
* by Kirby's cache
|
||||
* @since 3.5.5
|
||||
*
|
||||
* @param bool|null $cache
|
||||
* @return bool|$this
|
||||
*/
|
||||
public function cache(bool|null $cache = null)
|
||||
public function cache(bool|null $cache = null): bool|static
|
||||
{
|
||||
if ($cache === null) {
|
||||
// never ever cache private responses
|
||||
@@ -134,10 +114,9 @@ class Responder
|
||||
* `Authorization` request header
|
||||
* @since 3.7.0
|
||||
*
|
||||
* @param bool|null $usesAuth
|
||||
* @return bool|$this
|
||||
*/
|
||||
public function usesAuth(bool|null $usesAuth = null)
|
||||
public function usesAuth(bool|null $usesAuth = null): bool|static
|
||||
{
|
||||
if ($usesAuth === null) {
|
||||
return $this->usesAuth;
|
||||
@@ -151,9 +130,6 @@ class Responder
|
||||
* Setter for a cookie name that is
|
||||
* used by the response
|
||||
* @since 3.7.0
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function usesCookie(string $name): void
|
||||
{
|
||||
@@ -168,7 +144,6 @@ class Responder
|
||||
* names the response relies on
|
||||
* @since 3.7.0
|
||||
*
|
||||
* @param array|null $usesCookies
|
||||
* @return array|$this
|
||||
*/
|
||||
public function usesCookies(array|null $usesCookies = null)
|
||||
@@ -233,7 +208,6 @@ class Responder
|
||||
/**
|
||||
* Setter and getter for the status code
|
||||
*
|
||||
* @param int|null $code
|
||||
* @return int|$this
|
||||
*/
|
||||
public function code(int $code = null)
|
||||
@@ -248,8 +222,6 @@ class Responder
|
||||
|
||||
/**
|
||||
* Construct response from an array
|
||||
*
|
||||
* @param array $response
|
||||
*/
|
||||
public function fromArray(array $response): void
|
||||
{
|
||||
@@ -266,7 +238,6 @@ class Responder
|
||||
/**
|
||||
* Setter and getter for a single header
|
||||
*
|
||||
* @param string $key
|
||||
* @param string|false|null $value
|
||||
* @param bool $lazy If `true`, an existing header value is not overridden
|
||||
* @return string|$this
|
||||
@@ -293,7 +264,6 @@ class Responder
|
||||
/**
|
||||
* Setter and getter for all headers
|
||||
*
|
||||
* @param array|null $headers
|
||||
* @return array|$this
|
||||
*/
|
||||
public function headers(array $headers = null)
|
||||
@@ -333,7 +303,6 @@ class Responder
|
||||
/**
|
||||
* Shortcut to configure a json response
|
||||
*
|
||||
* @param array|null $json
|
||||
* @return string|$this
|
||||
*/
|
||||
public function json(array $json = null)
|
||||
@@ -348,12 +317,12 @@ class Responder
|
||||
/**
|
||||
* Shortcut to create a redirect response
|
||||
*
|
||||
* @param string|null $location
|
||||
* @param int|null $code
|
||||
* @return $this
|
||||
*/
|
||||
public function redirect(string|null $location = null, int|null $code = null)
|
||||
{
|
||||
public function redirect(
|
||||
string|null $location = null,
|
||||
int|null $code = null
|
||||
) {
|
||||
$location = Url::to($location ?? '/');
|
||||
$location = Url::unIdn($location);
|
||||
|
||||
@@ -364,11 +333,8 @@ class Responder
|
||||
|
||||
/**
|
||||
* Creates and returns the response object from the config
|
||||
*
|
||||
* @param string|null $body
|
||||
* @return \Kirby\Cms\Response
|
||||
*/
|
||||
public function send(string $body = null)
|
||||
public function send(string $body = null): Response
|
||||
{
|
||||
if ($body !== null) {
|
||||
$this->body($body);
|
||||
@@ -380,8 +346,6 @@ class Responder
|
||||
/**
|
||||
* Converts the response configuration
|
||||
* to an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
@@ -399,7 +363,6 @@ class Responder
|
||||
/**
|
||||
* Setter and getter for the content type
|
||||
*
|
||||
* @param string|null $type
|
||||
* @return string|$this
|
||||
*/
|
||||
public function type(string $type = null)
|
||||
@@ -423,10 +386,6 @@ class Responder
|
||||
* is actually used/relied on by the response
|
||||
* @since 3.7.0
|
||||
* @internal
|
||||
*
|
||||
* @param bool $usesAuth
|
||||
* @param array $usesCookies
|
||||
* @return bool
|
||||
*/
|
||||
public static function isPrivate(bool $usesAuth, array $usesCookies): bool
|
||||
{
|
||||
|
||||
@@ -19,8 +19,10 @@ class Response extends \Kirby\Http\Response
|
||||
* parses locations with the Url::to method
|
||||
* first.
|
||||
*/
|
||||
public static function redirect(string $location = '/', int $code = 302): static
|
||||
{
|
||||
public static function redirect(
|
||||
string $location = '/',
|
||||
int $code = 302
|
||||
): static {
|
||||
return parent::redirect(Url::to($location), $code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,41 +17,38 @@ use Kirby\Toolkit\I18n;
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class Role extends Model
|
||||
class Role
|
||||
{
|
||||
protected $description;
|
||||
protected $name;
|
||||
protected $permissions;
|
||||
protected $title;
|
||||
protected string|null $description;
|
||||
protected string $name;
|
||||
protected Permissions $permissions;
|
||||
protected string|null $title;
|
||||
|
||||
public function __construct(array $props)
|
||||
{
|
||||
$this->setProperties($props);
|
||||
$this->name = $props['name'];
|
||||
$this->permissions = new Permissions($props['permissions'] ?? null);
|
||||
$title = $props['title'] ?? null;
|
||||
$this->title = I18n::translate($title) ?? $title;
|
||||
$description = $props['description'] ?? null;
|
||||
$this->description = I18n::translate($description) ?? $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
*
|
||||
* @return array
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->name();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $inject
|
||||
* @return static
|
||||
*/
|
||||
public static function admin(array $inject = [])
|
||||
public static function admin(array $inject = []): static
|
||||
{
|
||||
try {
|
||||
return static::load('admin');
|
||||
@@ -60,9 +57,6 @@ class Role extends Model
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected static function defaults(): array
|
||||
{
|
||||
return [
|
||||
@@ -81,54 +75,32 @@ class Role extends Model
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function description()
|
||||
public function description(): string|null
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $props
|
||||
* @param array $inject
|
||||
* @return static
|
||||
*/
|
||||
public static function factory(array $props, array $inject = [])
|
||||
public static function factory(array $props, array $inject = []): static
|
||||
{
|
||||
return new static($props + $inject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function id(): string
|
||||
{
|
||||
return $this->name();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isAdmin(): bool
|
||||
{
|
||||
return $this->name() === 'admin';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isNobody(): bool
|
||||
{
|
||||
return $this->name() === 'nobody';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @param array $inject
|
||||
* @return static
|
||||
*/
|
||||
public static function load(string $file, array $inject = [])
|
||||
public static function load(string $file, array $inject = []): static
|
||||
{
|
||||
$data = Data::read($file);
|
||||
$data['name'] = F::name($file);
|
||||
@@ -136,19 +108,12 @@ class Role extends Model
|
||||
return static::factory($data, $inject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $inject
|
||||
* @return static
|
||||
*/
|
||||
public static function nobody(array $inject = [])
|
||||
public static function nobody(array $inject = []): static
|
||||
{
|
||||
try {
|
||||
return static::load('nobody');
|
||||
@@ -157,57 +122,11 @@ class Role extends Model
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Kirby\Cms\Permissions
|
||||
*/
|
||||
public function permissions()
|
||||
public function permissions(): Permissions
|
||||
{
|
||||
return $this->permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $description
|
||||
* @return $this
|
||||
*/
|
||||
protected function setDescription($description = null)
|
||||
{
|
||||
$this->description = I18n::translate($description, $description);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return $this
|
||||
*/
|
||||
protected function setName(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $permissions
|
||||
* @return $this
|
||||
*/
|
||||
protected function setPermissions($permissions = null)
|
||||
{
|
||||
$this->permissions = new Permissions($permissions);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $title
|
||||
* @return $this
|
||||
*/
|
||||
protected function setTitle($title = null)
|
||||
{
|
||||
$this->title = I18n::translate($title, $title);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function title(): string
|
||||
{
|
||||
return $this->title ??= ucfirst($this->name());
|
||||
@@ -216,8 +135,6 @@ class Role extends Model
|
||||
/**
|
||||
* Converts the most important role
|
||||
* properties to an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
|
||||
@@ -20,10 +20,8 @@ class Roles extends Collection
|
||||
{
|
||||
/**
|
||||
* All registered roles methods
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $methods = [];
|
||||
public static array $methods = [];
|
||||
|
||||
/**
|
||||
* Returns a filtered list of all
|
||||
@@ -33,7 +31,7 @@ class Roles extends Collection
|
||||
* @return $this|static
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function canBeChanged()
|
||||
public function canBeChanged(): static
|
||||
{
|
||||
if (App::instance()->user()) {
|
||||
return $this->filter(function ($role) {
|
||||
@@ -57,7 +55,7 @@ class Roles extends Collection
|
||||
* @return $this|static
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function canBeCreated()
|
||||
public function canBeCreated(): static
|
||||
{
|
||||
if (App::instance()->user()) {
|
||||
return $this->filter(function ($role) {
|
||||
@@ -73,12 +71,7 @@ class Roles extends Collection
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $roles
|
||||
* @param array $inject
|
||||
* @return static
|
||||
*/
|
||||
public static function factory(array $roles, array $inject = [])
|
||||
public static function factory(array $roles, array $inject = []): static
|
||||
{
|
||||
$collection = new static();
|
||||
|
||||
@@ -97,12 +90,7 @@ class Roles extends Collection
|
||||
return $collection->sort('name', 'asc');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $root
|
||||
* @param array $inject
|
||||
* @return static
|
||||
*/
|
||||
public static function load(string $root = null, array $inject = [])
|
||||
public static function load(string $root = null, array $inject = []): static
|
||||
{
|
||||
$kirby = App::instance();
|
||||
$roles = new static();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Session\Session;
|
||||
use Kirby\Toolkit\Facade;
|
||||
|
||||
/**
|
||||
@@ -15,10 +16,7 @@ use Kirby\Toolkit\Facade;
|
||||
*/
|
||||
class S extends Facade
|
||||
{
|
||||
/**
|
||||
* @return \Kirby\Session\Session
|
||||
*/
|
||||
public static function instance()
|
||||
public static function instance(): Session
|
||||
{
|
||||
return App::instance()->session();
|
||||
}
|
||||
|
||||
@@ -16,47 +16,36 @@ namespace Kirby\Cms;
|
||||
*/
|
||||
class Search
|
||||
{
|
||||
/**
|
||||
* @param string|null $query
|
||||
* @param array $params
|
||||
* @return \Kirby\Cms\Files
|
||||
*/
|
||||
public static function files(string $query = null, $params = [])
|
||||
{
|
||||
public static function files(
|
||||
string $query = null,
|
||||
array $params = []
|
||||
): Files {
|
||||
return App::instance()->site()->index()->files()->search($query, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Native search method to search for anything within the collection
|
||||
*
|
||||
* @param \Kirby\Cms\Collection $collection
|
||||
* @param string|null $query
|
||||
* @param mixed $params
|
||||
* @return \Kirby\Cms\Collection|bool
|
||||
*/
|
||||
public static function collection(Collection $collection, string $query = null, $params = [])
|
||||
{
|
||||
public static function collection(
|
||||
Collection $collection,
|
||||
string|null $query = null,
|
||||
string|array $params = []
|
||||
): Collection {
|
||||
$kirby = App::instance();
|
||||
return ($kirby->component('search'))($kirby, $collection, $query, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $query
|
||||
* @param array $params
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public static function pages(string $query = null, $params = [])
|
||||
{
|
||||
public static function pages(
|
||||
string $query = null,
|
||||
array $params = []
|
||||
): Pages {
|
||||
return App::instance()->site()->index()->search($query, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $query
|
||||
* @param array $params
|
||||
* @return \Kirby\Cms\Users
|
||||
*/
|
||||
public static function users(string $query = null, $params = [])
|
||||
{
|
||||
public static function users(
|
||||
string $query = null,
|
||||
array $params = []
|
||||
): Users {
|
||||
return App::instance()->users()->search($query, $params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,24 +18,15 @@ class Section extends Component
|
||||
{
|
||||
/**
|
||||
* Registry for all component mixins
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $mixins = [];
|
||||
public static array $mixins = [];
|
||||
|
||||
/**
|
||||
* Registry for all component types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $types = [];
|
||||
|
||||
public static array $types = [];
|
||||
|
||||
/**
|
||||
* Section constructor.
|
||||
*
|
||||
* @param string $type
|
||||
* @param array $attrs
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function __construct(string $type, array $attrs = [])
|
||||
@@ -44,7 +35,7 @@ class Section extends Component
|
||||
throw new InvalidArgumentException('Undefined section model');
|
||||
}
|
||||
|
||||
if ($attrs['model'] instanceof Model === false) {
|
||||
if ($attrs['model'] instanceof ModelWithContent === false) {
|
||||
throw new InvalidArgumentException('Invalid section model');
|
||||
}
|
||||
|
||||
@@ -64,25 +55,16 @@ class Section extends Component
|
||||
return $this->errors ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Kirby\Cms\App
|
||||
*/
|
||||
public function kirby()
|
||||
public function kirby(): App
|
||||
{
|
||||
return $this->model()->kirby();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Kirby\Cms\Model
|
||||
*/
|
||||
public function model()
|
||||
public function model(): ModelWithContent
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$array = parent::toArray();
|
||||
@@ -92,9 +74,6 @@ class Section extends Component
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toResponse(): array
|
||||
{
|
||||
return array_merge([
|
||||
|
||||
@@ -22,87 +22,83 @@ use Kirby\Toolkit\A;
|
||||
*/
|
||||
class Site extends ModelWithContent
|
||||
{
|
||||
use SiteActions;
|
||||
use HasChildren;
|
||||
use HasFiles;
|
||||
use HasMethods;
|
||||
use SiteActions;
|
||||
|
||||
public const CLASS_ALIAS = 'site';
|
||||
|
||||
/**
|
||||
* The SiteBlueprint object
|
||||
*
|
||||
* @var \Kirby\Cms\SiteBlueprint
|
||||
*/
|
||||
protected $blueprint;
|
||||
protected SiteBlueprint|null $blueprint = null;
|
||||
|
||||
/**
|
||||
* The error page object
|
||||
*
|
||||
* @var \Kirby\Cms\Page
|
||||
*/
|
||||
protected $errorPage;
|
||||
protected Page|null $errorPage = null;
|
||||
|
||||
/**
|
||||
* The id of the error page, which is
|
||||
* fetched in the errorPage method
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $errorPageId = 'error';
|
||||
protected string $errorPageId;
|
||||
|
||||
/**
|
||||
* The home page object
|
||||
*
|
||||
* @var \Kirby\Cms\Page
|
||||
*/
|
||||
protected $homePage;
|
||||
protected Page|null $homePage = null;
|
||||
|
||||
/**
|
||||
* The id of the home page, which is
|
||||
* fetched in the errorPage method
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $homePageId = 'home';
|
||||
protected string $homePageId;
|
||||
|
||||
/**
|
||||
* Cache for the inventory array
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $inventory;
|
||||
protected array|null $inventory = null;
|
||||
|
||||
/**
|
||||
* The current page object
|
||||
*
|
||||
* @var \Kirby\Cms\Page
|
||||
*/
|
||||
protected $page;
|
||||
protected Page|null $page;
|
||||
|
||||
/**
|
||||
* The absolute path to the site directory
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $root;
|
||||
protected string $root;
|
||||
|
||||
/**
|
||||
* The page url
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url;
|
||||
protected string|null $url;
|
||||
|
||||
/**
|
||||
* Creates a new Site object
|
||||
*/
|
||||
public function __construct(array $props = [])
|
||||
{
|
||||
parent::__construct($props);
|
||||
|
||||
$this->errorPageId = $props['errorPageId'] ?? 'error';
|
||||
$this->homePageId = $props['homePageId'] ?? 'home';
|
||||
$this->page = $props['page'] ?? null;
|
||||
$this->url = $props['url'] ?? null;
|
||||
|
||||
$this->setBlueprint($props['blueprint'] ?? null);
|
||||
$this->setChildren($props['children'] ?? null);
|
||||
$this->setDrafts($props['drafts'] ?? null);
|
||||
$this->setFiles($props['files'] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modified getter to also return fields
|
||||
* from the content
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call(string $method, array $arguments = [])
|
||||
public function __call(string $method, array $arguments = []): mixed
|
||||
{
|
||||
// public property access
|
||||
if (isset($this->$method) === true) {
|
||||
@@ -118,20 +114,9 @@ class Site extends ModelWithContent
|
||||
return $this->content()->get($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Site object
|
||||
*
|
||||
* @param array $props
|
||||
*/
|
||||
public function __construct(array $props = [])
|
||||
{
|
||||
$this->setProperties($props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
*
|
||||
* @return array
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
@@ -145,8 +130,6 @@ class Site extends ModelWithContent
|
||||
/**
|
||||
* Makes it possible to convert the site model
|
||||
* to a string. Mostly useful for debugging.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
@@ -155,10 +138,7 @@ class Site extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns the url to the api endpoint
|
||||
*
|
||||
* @internal
|
||||
* @param bool $relative
|
||||
* @return string
|
||||
*/
|
||||
public function apiUrl(bool $relative = false): string
|
||||
{
|
||||
@@ -171,10 +151,8 @@ class Site extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns the blueprint object
|
||||
*
|
||||
* @return \Kirby\Cms\SiteBlueprint
|
||||
*/
|
||||
public function blueprint()
|
||||
public function blueprint(): SiteBlueprint
|
||||
{
|
||||
if ($this->blueprint instanceof SiteBlueprint) {
|
||||
return $this->blueprint;
|
||||
@@ -185,10 +163,8 @@ class Site extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Builds a breadcrumb collection
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function breadcrumb()
|
||||
public function breadcrumb(): Pages
|
||||
{
|
||||
// get all parents and flip the order
|
||||
$crumb = $this->page()->parents()->flip();
|
||||
@@ -204,53 +180,39 @@ class Site extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Prepares the content for the write method
|
||||
*
|
||||
* @internal
|
||||
* @param array $data
|
||||
* @param string|null $languageCode
|
||||
* @return array
|
||||
*/
|
||||
public function contentFileData(array $data, string|null $languageCode = null): array
|
||||
{
|
||||
return A::prepend($data, [
|
||||
'title' => $data['title'] ?? null,
|
||||
]);
|
||||
public function contentFileData(
|
||||
array $data,
|
||||
string|null $languageCode = null
|
||||
): array {
|
||||
return A::prepend($data, ['title' => $data['title'] ?? null]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filename for the content file
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
* @deprecated 4.0.0
|
||||
* @todo Remove in v5
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function contentFileName(): string
|
||||
{
|
||||
Helpers::deprecated('The internal $model->contentFileName() method has been deprecated. Please let us know via a GitHub issue if you need this method and tell us your use case.', 'model-content-file');
|
||||
return 'site';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the error page object
|
||||
*
|
||||
* @return \Kirby\Cms\Page|null
|
||||
*/
|
||||
public function errorPage()
|
||||
public function errorPage(): Page|null
|
||||
{
|
||||
if ($this->errorPage instanceof Page) {
|
||||
return $this->errorPage;
|
||||
}
|
||||
|
||||
if ($error = $this->find($this->errorPageId())) {
|
||||
return $this->errorPage = $error;
|
||||
}
|
||||
|
||||
return null;
|
||||
return $this->errorPage ??= $this->find($this->errorPageId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the global error page id
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function errorPageId(): string
|
||||
{
|
||||
@@ -259,8 +221,6 @@ class Site extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Checks if the site exists on disk
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
@@ -269,27 +229,15 @@ class Site extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns the home page object
|
||||
*
|
||||
* @return \Kirby\Cms\Page|null
|
||||
*/
|
||||
public function homePage()
|
||||
public function homePage(): Page|null
|
||||
{
|
||||
if ($this->homePage instanceof Page) {
|
||||
return $this->homePage;
|
||||
}
|
||||
|
||||
if ($home = $this->find($this->homePageId())) {
|
||||
return $this->homePage = $home;
|
||||
}
|
||||
|
||||
return null;
|
||||
return $this->homePage ??= $this->find($this->homePageId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the global home page id
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function homePageId(): string
|
||||
{
|
||||
@@ -299,9 +247,7 @@ class Site extends ModelWithContent
|
||||
/**
|
||||
* Creates an inventory of all files
|
||||
* and children in the site directory
|
||||
*
|
||||
* @internal
|
||||
* @return array
|
||||
*/
|
||||
public function inventory(): array
|
||||
{
|
||||
@@ -323,7 +269,6 @@ class Site extends ModelWithContent
|
||||
* Compares the current object with the given site object
|
||||
*
|
||||
* @param mixed $site
|
||||
* @return bool
|
||||
*/
|
||||
public function is($site): bool
|
||||
{
|
||||
@@ -336,9 +281,7 @@ class Site extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns the root to the media folder for the site
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function mediaRoot(): string
|
||||
{
|
||||
@@ -347,9 +290,7 @@ class Site extends ModelWithContent
|
||||
|
||||
/**
|
||||
* The site's base url for any files
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function mediaUrl(): string
|
||||
{
|
||||
@@ -359,18 +300,12 @@ class Site extends ModelWithContent
|
||||
/**
|
||||
* Gets the last modification date of all pages
|
||||
* in the content folder.
|
||||
*
|
||||
* @param string|null $format
|
||||
* @param string|null $handler
|
||||
* @return int|string
|
||||
*/
|
||||
public function modified(string|null $format = null, string|null $handler = null)
|
||||
{
|
||||
return Dir::modified(
|
||||
$this->root(),
|
||||
$format,
|
||||
$handler ?? $this->kirby()->option('date.handler', 'date')
|
||||
);
|
||||
public function modified(
|
||||
string|null $format = null,
|
||||
string|null $handler = null
|
||||
): int|string {
|
||||
return Dir::modified($this->root(), $format, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,9 +319,8 @@ class Site extends ModelWithContent
|
||||
*
|
||||
* @param string|null $path omit for current page,
|
||||
* otherwise e.g. `notes/across-the-ocean`
|
||||
* @return \Kirby\Cms\Page|null
|
||||
*/
|
||||
public function page(string|null $path = null)
|
||||
public function page(string|null $path = null): Page|null
|
||||
{
|
||||
if ($path !== null) {
|
||||
return $this->find($path);
|
||||
@@ -405,39 +339,31 @@ class Site extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Alias for `Site::children()`
|
||||
*
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function pages()
|
||||
public function pages(): Pages
|
||||
{
|
||||
return $this->children();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the panel info object
|
||||
*
|
||||
* @return \Kirby\Panel\Site
|
||||
*/
|
||||
public function panel()
|
||||
public function panel(): Panel
|
||||
{
|
||||
return new Panel($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the permissions object for this site
|
||||
*
|
||||
* @return \Kirby\Cms\SitePermissions
|
||||
*/
|
||||
public function permissions()
|
||||
public function permissions(): SitePermissions
|
||||
{
|
||||
return new SitePermissions($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview Url
|
||||
*
|
||||
* @internal
|
||||
* @return string|null
|
||||
*/
|
||||
public function previewUrl(): string|null
|
||||
{
|
||||
@@ -458,8 +384,6 @@ class Site extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns the absolute path to the content directory
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function root(): string
|
||||
{
|
||||
@@ -470,22 +394,16 @@ class Site extends ModelWithContent
|
||||
* Returns the SiteRules class instance
|
||||
* which is being used in various methods
|
||||
* to check for valid actions and input.
|
||||
*
|
||||
* @return \Kirby\Cms\SiteRules
|
||||
*/
|
||||
protected function rules()
|
||||
protected function rules(): SiteRules
|
||||
{
|
||||
return new SiteRules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search all pages in the site
|
||||
*
|
||||
* @param string|null $query
|
||||
* @param array $params
|
||||
* @return \Kirby\Cms\Pages
|
||||
*/
|
||||
public function search(string|null $query = null, $params = [])
|
||||
public function search(string|null $query = null, string|array $params = []): Pages
|
||||
{
|
||||
return $this->index()->search($query, $params);
|
||||
}
|
||||
@@ -493,10 +411,9 @@ class Site extends ModelWithContent
|
||||
/**
|
||||
* Sets the Blueprint object
|
||||
*
|
||||
* @param array|null $blueprint
|
||||
* @return $this
|
||||
*/
|
||||
protected function setBlueprint(array|null $blueprint = null)
|
||||
protected function setBlueprint(array|null $blueprint = null): static
|
||||
{
|
||||
if ($blueprint !== null) {
|
||||
$blueprint['model'] = $this;
|
||||
@@ -506,86 +423,25 @@ class Site extends ModelWithContent
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the id of the error page, which
|
||||
* is used in the errorPage method
|
||||
* to get the default error page if nothing
|
||||
* else is set.
|
||||
*
|
||||
* @param string $id
|
||||
* @return $this
|
||||
*/
|
||||
protected function setErrorPageId(string $id = 'error')
|
||||
{
|
||||
$this->errorPageId = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the id of the home page, which
|
||||
* is used in the homePage method
|
||||
* to get the default home page if nothing
|
||||
* else is set.
|
||||
*
|
||||
* @param string $id
|
||||
* @return $this
|
||||
*/
|
||||
protected function setHomePageId(string $id = 'home')
|
||||
{
|
||||
$this->homePageId = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current page object
|
||||
*
|
||||
* @internal
|
||||
* @param \Kirby\Cms\Page|null $page
|
||||
* @return $this
|
||||
*/
|
||||
public function setPage(?Page $page = null)
|
||||
{
|
||||
$this->page = $page;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Url
|
||||
*
|
||||
* @param string|null $url
|
||||
* @return $this
|
||||
*/
|
||||
protected function setUrl(string|null $url = null)
|
||||
{
|
||||
$this->url = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the most important site
|
||||
* properties to an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'children' => $this->children()->keys(),
|
||||
'content' => $this->content()->toArray(),
|
||||
'errorPage' => $this->errorPage() ? $this->errorPage()->id() : false,
|
||||
'files' => $this->files()->keys(),
|
||||
'homePage' => $this->homePage() ? $this->homePage()->id() : false,
|
||||
'page' => $this->page() ? $this->page()->id() : false,
|
||||
'title' => $this->title()->value(),
|
||||
'url' => $this->url(),
|
||||
];
|
||||
return array_merge(parent::toArray(), [
|
||||
'children' => $this->children()->keys(),
|
||||
'errorPage' => $this->errorPage()?->id() ?? false,
|
||||
'files' => $this->files()->keys(),
|
||||
'homePage' => $this->homePage()?->id() ?? false,
|
||||
'page' => $this->page()?->id() ?? false,
|
||||
'title' => $this->title()->value(),
|
||||
'url' => $this->url(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Url
|
||||
*
|
||||
* @param string|null $language
|
||||
* @return string
|
||||
*/
|
||||
public function url(string|null $language = null): string
|
||||
{
|
||||
@@ -598,14 +454,12 @@ class Site extends ModelWithContent
|
||||
|
||||
/**
|
||||
* Returns the translated url
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $languageCode
|
||||
* @param array|null $options
|
||||
* @return string
|
||||
*/
|
||||
public function urlForLanguage(string|null $languageCode = null, array|null $options = null): string
|
||||
{
|
||||
public function urlForLanguage(
|
||||
string|null $languageCode = null,
|
||||
array|null $options = null
|
||||
): string {
|
||||
if ($language = $this->kirby()->language($languageCode)) {
|
||||
return $language->url();
|
||||
}
|
||||
@@ -617,21 +471,19 @@ class Site extends ModelWithContent
|
||||
* Sets the current page by
|
||||
* id or page object and
|
||||
* returns the current page
|
||||
*
|
||||
* @internal
|
||||
* @param string|\Kirby\Cms\Page $page
|
||||
* @param string|null $languageCode
|
||||
* @return \Kirby\Cms\Page
|
||||
*/
|
||||
public function visit($page, string|null $languageCode = null)
|
||||
{
|
||||
public function visit(
|
||||
string|Page $page,
|
||||
string|null $languageCode = null
|
||||
): Page {
|
||||
if ($languageCode !== null) {
|
||||
$this->kirby()->setCurrentTranslation($languageCode);
|
||||
$this->kirby()->setCurrentLanguage($languageCode);
|
||||
}
|
||||
|
||||
// convert ids to a Page object
|
||||
if (is_string($page)) {
|
||||
if (is_string($page) === true) {
|
||||
$page = $this->find($page);
|
||||
}
|
||||
|
||||
@@ -640,22 +492,16 @@ class Site extends ModelWithContent
|
||||
throw new InvalidArgumentException('Invalid page object');
|
||||
}
|
||||
|
||||
// set the current active page
|
||||
$this->setPage($page);
|
||||
|
||||
// return the page
|
||||
return $page;
|
||||
// set and return the current active page
|
||||
return $this->page = $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any content of the site has been
|
||||
* modified after the given unix timestamp
|
||||
* This is mainly used to auto-update the cache
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return bool
|
||||
*/
|
||||
public function wasModifiedAfter($time): bool
|
||||
public function wasModifiedAfter(int $time): bool
|
||||
{
|
||||
return Dir::wasModifiedAfter($this->root(), $time);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user