Upgrade to 3.2.0

This commit is contained in:
Bastian Allgeier
2019-06-25 09:56:08 +02:00
parent 9e18cf635d
commit 9c89153d35
296 changed files with 14408 additions and 2504 deletions

View File

@@ -11,12 +11,19 @@ use Kirby\Http\Router;
use Kirby\Http\Response;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Properties;
use Kirby\Toolkit\Str;
/**
* The API class is a generic container
* for API routes, models and collections and is used
* to run our REST API. You can find our API setup
* in kirby/config/api.php
* in `kirby/config/api.php`.
*
* @package Kirby Api
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Api
{
@@ -179,7 +186,9 @@ class Api
*
* @param string $name
* @param array|null $collection
* @return Collection
* @return Kirby\Api\Collection
*
* @throws NotFoundException If no collection for `$name` exists
*/
public function collection(string $name, $collection = null)
{
@@ -207,6 +216,8 @@ class Api
* @param string|null $key
* @param mixed ...$args
* @return mixed
*
* @throws NotFoundException If no data for `$key` exists
*/
public function data($key = null, ...$args)
{
@@ -252,7 +263,9 @@ class Api
*
* @param string $name
* @param mixed $object
* @return Model
* @return Kirby\Api\Model
*
* @throws NotFoundException If no model for `$name` exists
*/
public function model(string $name, $object = null)
{
@@ -362,7 +375,9 @@ class Api
* API model or collection representation
*
* @param mixed $object
* @return Model|Collection
* @return Kirby\Api\Model|Kirby\Api\Collection
*
* @throws NotFoundException If `$object` cannot be resolved
*/
public function resolve($object)
{
@@ -531,11 +546,17 @@ class Api
'route' => $this->route->pattern()
] + $e->toArray();
} else {
// remove the document root from the file path
$file = $e->getFile();
if (empty($_SERVER['DOCUMENT_ROOT']) === false) {
$file = ltrim(Str::after($file, $_SERVER['DOCUMENT_ROOT']), '/');
}
$result = [
'status' => 'error',
'exception' => get_class($e),
'message' => $e->getMessage(),
'file' => ltrim($e->getFile(), $_SERVER['DOCUMENT_ROOT'] ?? null),
'file' => $file,
'line' => $e->getLine(),
'code' => empty($e->getCode()) === false ? $e->getCode() : 500,
'route' => $this->route ? $this->route->pattern() : null
@@ -578,7 +599,8 @@ class Api
unset(
$result['file'],
$result['exception'],
$result['line']
$result['line'],
$result['route']
);
}
@@ -602,6 +624,9 @@ class Api
* @param Closure $callback
* @param boolean $single
* @return array
*
* @throws Exception If request has no files
* @throws Exception If there was an error with the upload
*/
public function upload(Closure $callback, $single = false): array
{

View File

@@ -2,7 +2,6 @@
namespace Kirby\Api;
use Closure;
use Exception;
use Kirby\Toolkit\Str;
@@ -11,6 +10,12 @@ use Kirby\Toolkit\Str;
* around our Kirby Collections and handles
* stuff like pagination and proper JSON output
* for collections in REST calls.
*
* @package Kirby Api
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Collection
{

View File

@@ -2,7 +2,6 @@
namespace Kirby\Api;
use Closure;
use Exception;
use Kirby\Toolkit\Str;
@@ -15,6 +14,11 @@ use Kirby\Toolkit\Str;
* by GraphQLs architecture and makes it possible to load
* only the model data that is needed for the current API call.
*
* @package Kirby Api
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Model
{

View File

@@ -9,17 +9,17 @@ use APCUIterator;
*
* @package Kirby Cache
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license MIT
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://opensource.org/licenses/MIT
*/
class ApcuCache extends Cache
{
/**
* Checks if the current key exists in cache
* Determines if an item exists in the cache
*
* @param string $key
* @param string $key
* @return boolean
*/
public function exists(string $key): bool
@@ -28,7 +28,8 @@ class ApcuCache extends Cache
}
/**
* Flush the entire cache directory
* Flushes the entire cache and returns
* whether the operation was successful
*
* @return boolean
*/
@@ -42,9 +43,10 @@ class ApcuCache extends Cache
}
/**
* Remove an item from the cache
* Removes an item from the cache and returns
* whether the operation was successful
*
* @param string $key
* @param string $key
* @return boolean
*/
public function remove(string $key): bool
@@ -53,10 +55,11 @@ class ApcuCache extends Cache
}
/**
* Retrieve an item from the cache.
* Internal method to retrieve the raw cache value;
* needs to return a Value object or null if not found
*
* @param string $key
* @return mixed
* @param string $key
* @return Kirby\Cache\Value|null
*/
public function retrieve(string $key)
{
@@ -64,20 +67,21 @@ class ApcuCache extends Cache
}
/**
* Write an item to the cache for a given number of minutes.
* Writes an item to the cache for a given number of minutes and
* returns whether the operation was successful
*
* <code>
* // Put an item in the cache for 15 minutes
* Cache::set('value', 'my value', 15);
* // put an item in the cache for 15 minutes
* $cache->set('value', 'my value', 15);
* </code>
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
* @param string $key
* @param mixed $value
* @param int $minutes
* @return boolean
*/
public function set(string $key, $value, int $minutes = 0)
public function set(string $key, $value, int $minutes = 0): bool
{
return apcu_store($this->key($key), $this->value($value, $minutes)->toJson(), $this->expiration($minutes));
return apcu_store($this->key($key), (new Value($value, $minutes))->toJson(), $this->expiration($minutes));
}
}

View File

@@ -4,28 +4,27 @@ namespace Kirby\Cache;
/**
* Cache foundation
* This class doesn't do anything
* and is perfect as foundation for
* other cache drivers and to be used
* when the cache is disabled
* This abstract class is used as
* foundation for other cache drivers
* by extending it
*
* @package Kirby Cache
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license MIT
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://opensource.org/licenses/MIT
*/
class Cache
abstract class Cache
{
/**
* stores all options for the driver
* Stores all options for the driver
* @var array
*/
protected $options = [];
/**
* Set all parameters which are needed to connect to the cache storage
* Sets all parameters which are needed to connect to the cache storage
*
* @param array $options
*/
@@ -35,27 +34,26 @@ class Cache
}
/**
* Write an item to the cache for a given number of minutes.
* Writes an item to the cache for a given number of minutes and
* returns whether the operation was successful;
* this needs to be defined by the driver
*
* <code>
* // Put an item in the cache for 15 minutes
* Cache::set('value', 'my value', 15);
* // put an item in the cache for 15 minutes
* $cache->set('value', 'my value', 15);
* </code>
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
* @param string $key
* @param mixed $value
* @param int $minutes
* @return boolean
*/
public function set(string $key, $value, int $minutes = 0)
{
return null;
}
abstract public function set(string $key, $value, int $minutes = 0): bool;
/**
* Adds the prefix to the key if given
*
* @param string $key
* @param string $key
* @return string
*/
protected function key(string $key): string
@@ -68,30 +66,28 @@ class Cache
}
/**
* Private method to retrieve the cache value
* This needs to be defined by the driver
* Internal method to retrieve the raw cache value;
* needs to return a Value object or null if not found;
* this needs to be defined by the driver
*
* @param string $key
* @return mixed
* @param string $key
* @return Kirby\Cache\Value|null
*/
public function retrieve(string $key)
{
return null;
}
abstract public function retrieve(string $key);
/**
* Get an item from the cache.
* Gets an item from the cache
*
* <code>
* // Get an item from the cache driver
* $value = Cache::get('value');
* // get an item from the cache driver
* $value = $cache->get('value');
*
* // Return a default value if the requested item isn't cached
* $value = Cache::get('value', 'default value');
* // return a default value if the requested item isn't cached
* $value = $cache->get('value', 'default value');
* </code>
*
* @param string $key
* @param mixed $default
* @param string $key
* @param mixed $default
* @return mixed
*/
public function get(string $key, $default = null)
@@ -100,34 +96,31 @@ class Cache
$value = $this->retrieve($key);
// check for a valid cache value
if (!is_a($value, 'Kirby\Cache\Value')) {
if (!is_a($value, Value::class)) {
return $default;
}
// remove the item if it is expired
if (time() >= $value->expires()) {
if ($value->expires() > 0 && time() >= $value->expires()) {
$this->remove($key);
return $default;
}
// get the pure value
$cache = $value->value();
// return the cache value or the default
return $cache ?? $default;
// return the pure value
return $value->value();
}
/**
* Calculates the expiration timestamp
*
* @param int $minutes
* @param int $minutes
* @return int
*/
protected function expiration(int $minutes = 0): int
{
// keep forever if minutes are not defined
// 0 = keep forever
if ($minutes === 0) {
$minutes = 2628000;
return 0;
}
// calculate the time
@@ -135,10 +128,12 @@ class Cache
}
/**
* Checks when an item in the cache expires
* Checks when an item in the cache expires;
* returns the expiry timestamp on success, null if the
* item never expires and false if the item does not exist
*
* @param string $key
* @return mixed
* @param string $key
* @return int|null|false
*/
public function expires(string $key)
{
@@ -146,7 +141,7 @@ class Cache
$value = $this->retrieve($key);
// check for a valid Value object
if (!is_a($value, 'Kirby\Cache\Value')) {
if (!is_a($value, Value::class)) {
return false;
}
@@ -157,19 +152,29 @@ class Cache
/**
* Checks if an item in the cache is expired
*
* @param string $key
* @param string $key
* @return boolean
*/
public function expired(string $key): bool
{
return $this->expires($key) <= time();
$expires = $this->expires($key);
if ($expires === null) {
return false;
} elseif (!is_int($expires)) {
return true;
} else {
return time() >= $expires;
}
}
/**
* Checks when the cache has been created
* Checks when the cache has been created;
* returns the creation timestamp on success
* and false if the item does not exist
*
* @param string $key
* @return mixed
* @param string $key
* @return int|false
*/
public function created(string $key)
{
@@ -177,7 +182,7 @@ class Cache
$value = $this->retrieve($key);
// check for a valid Value object
if (!is_a($value, 'Kirby\Cache\Value')) {
if (!is_a($value, Value::class)) {
return false;
}
@@ -188,8 +193,8 @@ class Cache
/**
* Alternate version for Cache::created($key)
*
* @param string $key
* @return mixed
* @param string $key
* @return int|false
*/
public function modified(string $key)
{
@@ -197,48 +202,34 @@ class Cache
}
/**
* Returns Value object
* Determines if an item exists in the cache
*
* @param mixed $value The value, which should be cached
* @param int $minutes The number of minutes before expiration
* @return Value
*/
protected function value($value, int $minutes): Value
{
return new Value($value, $minutes);
}
/**
* Determine if an item exists in the cache.
*
* @param string $key
* @param string $key
* @return boolean
*/
public function exists(string $key): bool
{
return !$this->expired($key);
return $this->expired($key) === false;
}
/**
* Remove an item from the cache
* Removes an item from the cache and returns
* whether the operation was successful;
* this needs to be defined by the driver
*
* @param string $key
* @param string $key
* @return boolean
*/
public function remove(string $key): bool
{
return true;
}
abstract public function remove(string $key): bool;
/**
* Flush the entire cache
* Flushes the entire cache and returns
* whether the operation was successful;
* this needs to be defined by the driver
*
* @return boolean
*/
public function flush(): bool
{
return true;
}
abstract public function flush(): bool;
/**
* Returns all passed cache options

View File

@@ -2,7 +2,6 @@
namespace Kirby\Cache;
use Exception;
use Kirby\Toolkit\Dir;
use Kirby\Toolkit\F;
@@ -11,80 +10,107 @@ use Kirby\Toolkit\F;
*
* @package Kirby Cache
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license MIT
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://opensource.org/licenses/MIT
*/
class FileCache extends Cache
{
/**
* Set all parameters which are needed for the file cache
* see defaults for available parameters
*
* @param array $params
* Full root including prefix
* @var string
*/
public function __construct(array $params)
protected $root;
/**
* Sets all parameters which are needed for the file cache
*
* @param array $options 'root' (required)
* 'prefix' (default: none)
* 'extension' (file extension for cache files, default: none)
*/
public function __construct(array $options)
{
$defaults = [
'root' => null,
'prefix' => null,
'extension' => null
];
parent::__construct(array_merge($defaults, $params));
parent::__construct(array_merge($defaults, $options));
// build the full root including prefix
$this->root = $this->options['root'];
if (empty($this->options['prefix']) === false) {
$this->root .= '/' . $this->options['prefix'];
}
// try to create the directory
Dir::make($this->options['root'], true);
Dir::make($this->root, true);
}
/**
* Returns the full path to a file for a given key
*
* @param string $key
* @param string $key
* @return string
*/
protected function file(string $key): string
{
$extension = isset($this->options['extension']) ? '.' . $this->options['extension'] : '';
$file = $this->root . '/' . $key;
return $this->options['root'] . '/' . $this->key($key) . $extension;
if (isset($this->options['extension'])) {
return $file . '.' . $this->options['extension'];
} else {
return $file;
}
}
/**
* Write an item to the cache for a given number of minutes.
* Writes an item to the cache for a given number of minutes and
* returns whether the operation was successful
*
* <code>
* // Put an item in the cache for 15 minutes
* Cache::set('value', 'my value', 15);
* // put an item in the cache for 15 minutes
* $cache->set('value', 'my value', 15);
* </code>
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @param string $key
* @param mixed $value
* @param int $minutes
* @return boolean
*/
public function set(string $key, $value, int $minutes = 0)
public function set(string $key, $value, int $minutes = 0): bool
{
return F::write($this->file($key), $this->value($value, $minutes)->toJson());
$file = $this->file($key);
return F::write($file, (new Value($value, $minutes))->toJson());
}
/**
* Retrieve an item from the cache.
* Internal method to retrieve the raw cache value;
* needs to return a Value object or null if not found
*
* @param string $key
* @return mixed
* @param string $key
* @return Kirby\Cache\Value|null
*/
public function retrieve(string $key)
{
return Value::fromJson(F::read($this->file($key)));
$file = $this->file($key);
return Value::fromJson(F::read($file));
}
/**
* Checks when the cache has been created
* Checks when the cache has been created;
* returns the creation timestamp on success
* and false if the item does not exist
*
* @param string $key
* @return int
* @return mixed
*/
public function created(string $key): int
public function created(string $key)
{
// use the modification timestamp
// as indicator when the cache has been created/overwritten
@@ -92,37 +118,39 @@ class FileCache extends Cache
// get the file for this cache key
$file = $this->file($key);
return file_exists($file) ? filemtime($this->file($key)) : 0;
return file_exists($file) ? filemtime($this->file($key)) : false;
}
/**
* Remove an item from the cache
* Removes an item from the cache and returns
* whether the operation was successful
*
* @param string $key
* @param string $key
* @return boolean
*/
public function remove(string $key): bool
{
return F::remove($this->file($key));
$file = $this->file($key);
if (is_file($file) === true) {
return F::remove($file);
} else {
return false;
}
}
/**
* Flush the entire cache directory
* Flushes the entire cache and returns
* whether the operation was successful
*
* @return boolean
*/
public function flush(): bool
{
$root = $this->options['root'];
if (empty($this->options['prefix']) === false) {
$root = $root . '/' . $this->options['prefix'];
}
if (Dir::remove($root) === true && Dir::make($root) === true) {
if (Dir::remove($this->root) === true && Dir::make($this->root) === true) {
return true;
}
return false;
return false; // @codeCoverageIgnore
}
}

View File

@@ -3,14 +3,14 @@
namespace Kirby\Cache;
/**
* Memcached Driver
*
* @package Kirby Cache
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license MIT
*/
* Memcached Driver
*
* @package Kirby Cache
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://opensource.org/licenses/MIT
*/
class MemCached extends Cache
{
@@ -21,12 +21,13 @@ class MemCached extends Cache
protected $connection;
/**
* Set all parameters which are needed for the memcache client
* see defaults for available parameters
* Sets all parameters which are needed to connect to Memcached
*
* @param array $params
* @param array $options 'host' (default: localhost)
* 'port' (default: 11211)
* 'prefix' (default: null)
*/
public function __construct(array $params = [])
public function __construct(array $options = [])
{
$defaults = [
'host' => 'localhost',
@@ -34,47 +35,37 @@ class MemCached extends Cache
'prefix' => null,
];
parent::__construct(array_merge($defaults, $params));
parent::__construct(array_merge($defaults, $options));
$this->connection = new \Memcached();
$this->connection->addServer($this->options['host'], $this->options['port']);
}
/**
* Write an item to the cache for a given number of minutes.
* Writes an item to the cache for a given number of minutes and
* returns whether the operation was successful
*
* <code>
* // Put an item in the cache for 15 minutes
* Cache::set('value', 'my value', 15);
* // put an item in the cache for 15 minutes
* $cache->set('value', 'my value', 15);
* </code>
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
* @param string $key
* @param mixed $value
* @param int $minutes
* @return boolean
*/
public function set(string $key, $value, int $minutes = 0)
public function set(string $key, $value, int $minutes = 0): bool
{
return $this->connection->set($this->key($key), $this->value($value, $minutes)->toJson(), $this->expiration($minutes));
return $this->connection->set($this->key($key), (new Value($value, $minutes))->toJson(), $this->expiration($minutes));
}
/**
* Returns the full keyname
* including the prefix (if set)
* Internal method to retrieve the raw cache value;
* needs to return a Value object or null if not found
*
* @param string $key
* @return string
*/
public function key(string $key): string
{
return $this->options['prefix'] . $key;
}
/**
* Retrieve the CacheValue object from the cache.
*
* @param string $key
* @return object CacheValue
* @param string $key
* @return Kirby\Cache\Value|null
*/
public function retrieve(string $key)
{
@@ -82,9 +73,10 @@ class MemCached extends Cache
}
/**
* Remove an item from the cache
* Removes an item from the cache and returns
* whether the operation was successful
*
* @param string $key
* @param string $key
* @return boolean
*/
public function remove(string $key): bool
@@ -93,40 +85,9 @@ class MemCached extends Cache
}
/**
* Checks when an item in the cache expires
*
* @param string $key
* @return int
*/
public function expires(string $key): int
{
return parent::expires($this->key($key));
}
/**
* Checks if an item in the cache is expired
*
* @param string $key
* @return boolean
*/
public function expired(string $key): bool
{
return parent::expired($this->key($key));
}
/**
* Checks when the cache has been created
*
* @param string $key
* @return int
*/
public function created(string $key): int
{
return parent::created($this->key($key));
}
/**
* Flush the entire cache directory
* Flushes the entire cache and returns
* whether the operation was successful;
* WARNING: Memcached only supports flushing the whole cache at once!
*
* @return boolean
*/

83
kirby/src/Cache/MemoryCache.php Executable file
View File

@@ -0,0 +1,83 @@
<?php
namespace Kirby\Cache;
/**
* Memory Cache Driver (cache in memory for current request only)
*
* @package Kirby Cache
* @author Lukas Bestle <lukas@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://opensource.org/licenses/MIT
*/
class MemoryCache extends Cache
{
/**
* Cache data
* @var array
*/
protected $store = [];
/**
* Writes an item to the cache for a given number of minutes and
* returns whether the operation was successful
*
* <code>
* // put an item in the cache for 15 minutes
* $cache->set('value', 'my value', 15);
* </code>
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return boolean
*/
public function set(string $key, $value, int $minutes = 0): bool
{
$this->store[$key] = new Value($value, $minutes);
return true;
}
/**
* Internal method to retrieve the raw cache value;
* needs to return a Value object or null if not found
*
* @param string $key
* @return Kirby\Cache\Value|null
*/
public function retrieve(string $key)
{
return $this->store[$key] ?? null;
}
/**
* Removes an item from the cache and returns
* whether the operation was successful
*
* @param string $key
* @return boolean
*/
public function remove(string $key): bool
{
if (isset($this->store[$key])) {
unset($this->store[$key]);
return true;
} else {
return false;
}
}
/**
* Flushes the entire cache and returns
* whether the operation was successful
*
* @return boolean
*/
public function flush(): bool
{
$this->store = [];
return true;
}
}

70
kirby/src/Cache/NullCache.php Executable file
View File

@@ -0,0 +1,70 @@
<?php
namespace Kirby\Cache;
/**
* Dummy Cache Driver (does not do any caching)
*
* @package Kirby Cache
* @author Lukas Bestle <lukas@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://opensource.org/licenses/MIT
*/
class NullCache extends Cache
{
/**
* Writes an item to the cache for a given number of minutes and
* returns whether the operation was successful
*
* <code>
* // put an item in the cache for 15 minutes
* $cache->set('value', 'my value', 15);
* </code>
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return boolean
*/
public function set(string $key, $value, int $minutes = 0): bool
{
return true;
}
/**
* Internal method to retrieve the raw cache value;
* needs to return a Value object or null if not found
*
* @param string $key
* @return Kirby\Cache\Value|null
*/
public function retrieve(string $key)
{
return null;
}
/**
* Removes an item from the cache and returns
* whether the operation was successful
*
* @param string $key
* @return boolean
*/
public function remove(string $key): bool
{
return true;
}
/**
* Flushes the entire cache and returns
* whether the operation was successful
*
* @return boolean
*/
public function flush(): bool
{
return true;
}
}

View File

@@ -7,31 +7,31 @@ use Throwable;
/**
* Cache Value
* Stores the value, creation timestamp and expiration timestamp
* and makes it possible to store all three with a single cache key.
* and makes it possible to store all three with a single cache key
*
* @package Kirby Cache
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license MIT
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://opensource.org/licenses/MIT
*/
class Value
{
/**
* the cached value
* Cached value
* @var mixed
*/
protected $value;
/**
* the expiration timestamp
* the number of minutes until the value expires
* @var int
*/
protected $expires;
protected $minutes;
/**
* the creation timestamp
* Creation timestamp
* @var int
*/
protected $created;
@@ -40,18 +40,13 @@ class Value
* Constructor
*
* @param mixed $value
* @param int $minutes the number of minutes until the value expires
* @param int $created the unix timestamp when the value has been created
* @param int $minutes the number of minutes until the value expires
* @param int $created the unix timestamp when the value has been created
*/
public function __construct($value, int $minutes = 0, $created = null)
public function __construct($value, int $minutes = 0, int $created = null)
{
// keep forever if minutes are not defined
if ($minutes === 0) {
$minutes = 2628000;
}
$this->value = $value;
$this->minutes = $minutes;
$this->minutes = $minutes ?? 0;
$this->created = $created ?? time();
}
@@ -66,12 +61,18 @@ class Value
}
/**
* Returns the expiration date as UNIX timestamp
* Returns the expiration date as UNIX timestamp or
* null if the value never expires
*
* @return int
* @return int|null
*/
public function expires(): int
public function expires(): ?int
{
// 0 = keep forever
if ($this->minutes === 0) {
return null;
}
return $this->created + ($this->minutes * 60);
}
@@ -79,32 +80,37 @@ class Value
* Creates a value object from an array
*
* @param array $array
* @return array
* @return self
*/
public static function fromArray(array $array): self
public static function fromArray(array $array)
{
return new static($array['value'] ?? null, $array['minutes'] ?? 0, $array['created'] ?? null);
}
/**
* Creates a value object from a json string
* Creates a value object from a JSON string;
* returns null on error
*
* @param string $json
* @return array
* @return self|null
*/
public static function fromJson($json): self
public static function fromJson(string $json)
{
try {
$array = json_decode($json, true) ?? [];
} catch (Throwable $e) {
$array = [];
}
$array = json_decode($json, true);
return static::fromArray($array);
if (is_array($array)) {
return static::fromArray($array);
} else {
return null;
}
} catch (Throwable $e) {
return null;
}
}
/**
* Convert the object to a json string
* Converts the object to a JSON string
*
* @return string
*/
@@ -114,7 +120,7 @@ class Value
}
/**
* Convert the object to an array
* Converts the object to an array
*
* @return array
*/
@@ -128,7 +134,7 @@ class Value
}
/**
* Returns the value
* Returns the pure value
*
* @return mixed
*/

View File

@@ -7,16 +7,37 @@ use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\NotFoundException;
use Kirby\Toolkit\Str;
/**
* Api
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Api extends BaseApi
{
/**
* @var App
*/
protected $kirby;
/**
* Execute an API call for the given path,
* request method and optional request data
*
* @param string $path
* @param string $method
* @param array $requestData
* @return mixed
*/
public function call(string $path = null, string $method = 'GET', array $requestData = [])
{
$this->setRequestMethod($method);
$this->setRequestData($requestData);
if ($languageCode = $this->requestHeaders('x-language')) {
if ($languageCode = $this->language()) {
$this->kirby->setCurrentLanguage($languageCode);
}
@@ -27,6 +48,12 @@ class Api extends BaseApi
return parent::call($path, $method, $requestData);
}
/**
* @param mixed $model
* @param string $name
* @param string $path
* @return mixed
*/
public function fieldApi($model, string $name, string $path = null)
{
$form = Form::for($model);
@@ -59,6 +86,14 @@ class Api extends BaseApi
return $fieldApi->call($path, $this->requestMethod(), $this->requestData());
}
/**
* Returns the file object for the given
* parent path and filename
*
* @param string $path Path to file's parent model
* @param string $filename Filename
* @return Kirby\Cms\File|null
*/
public function file(string $path = null, string $filename)
{
$filename = urldecode($filename);
@@ -75,12 +110,22 @@ class Api extends BaseApi
]);
}
/**
* Returns the model's object for the given path
*
* @param string $path Path to parent model
* @return Kirby\Cms\Model|null
*/
public function parent(string $path)
{
$modelType = $path === 'site' ? 'site' : dirname($path);
$modelType = $path === 'site' ? 'site' : trim(dirname($path), '/');
$modelTypes = ['site' => 'site', 'users' => 'user', 'pages' => 'page'];
$modelName = $modelTypes[$modelType] ?? null;
if (Str::endsWith($modelType, '/files') === true) {
$modelName = 'file';
}
if ($modelName === null) {
throw new InvalidArgumentException('Invalid file model type');
}
@@ -91,7 +136,13 @@ class Api extends BaseApi
$modelId = basename($path);
if ($modelName === 'page') {
$modelId = str_replace('+', '/', $modelId);
$modelId = str_replace(['+', ' '], '/', $modelId);
}
if ($modelName === 'file') {
if ($model = $this->file(...explode('/files/', $path))) {
return $model;
}
}
}
@@ -104,16 +155,32 @@ class Api extends BaseApi
]);
}
/**
* Returns the Kirby instance
*
* @return Kirby\Cms\App
*/
public function kirby()
{
return $this->kirby;
}
public function language()
/**
* Returns the language request header
*
* @return string|null
*/
public function language(): ?string
{
return $this->requestHeaders('x-language');
return get('language') ?? $this->requestHeaders('x-language');
}
/**
* Returns the page object for the given id
*
* @param string $id Page's id
* @return Kirby\Cms\Page|null
*/
public function page(string $id)
{
$id = str_replace('+', '/', $id);
@@ -138,17 +205,33 @@ class Api extends BaseApi
], $options));
}
/**
* @param Kirby\Cms\App $kirby
*/
protected function setKirby(App $kirby)
{
$this->kirby = $kirby;
return $this;
}
/**
* Returns the site object
*
* @return Kirby\Cms\Site
*/
public function site()
{
return $this->kirby->site();
}
/**
* Returns the user object for the given id or
* returns the current authenticated user if no
* id is passed
*
* @param string $id User's id
* @return Kirby\Cms\User|null
*/
public function user(string $id = null)
{
// get the authenticated user
@@ -169,6 +252,11 @@ class Api extends BaseApi
]);
}
/**
* Returns the users collection
*
* @return Kirby\Cms\Users
*/
public function users()
{
return $this->kirby->users();

View File

@@ -2,22 +2,15 @@
namespace Kirby\Cms;
use Closure;
use Exception;
use Throwable;
use Kirby\Data\Data;
use Kirby\Email\PHPMailer as Emailer;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\LogicException;
use Kirby\Exception\NotFoundException;
use Kirby\Form\Field;
use Kirby\Http\Route;
use Kirby\Http\Router;
use Kirby\Http\Request;
use Kirby\Http\Server;
use Kirby\Http\Visitor;
use Kirby\Image\Darkroom;
use Kirby\Session\AutoSession as Session;
use Kirby\Session\AutoSession;
use Kirby\Text\KirbyTag;
use Kirby\Toolkit\A;
use Kirby\Toolkit\Config;
@@ -25,8 +18,6 @@ use Kirby\Toolkit\Controller;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Dir;
use Kirby\Toolkit\Properties;
use Kirby\Toolkit\Str;
use Kirby\Toolkit\Url;
/**
* The `$kirby` object is the app instance of
@@ -37,8 +28,9 @@ use Kirby\Toolkit\Url;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class App
{
@@ -62,6 +54,7 @@ class App
protected $defaultLanguage;
protected $language;
protected $languages;
protected $locks;
protected $multilang;
protected $options;
protected $path;
@@ -135,7 +128,7 @@ class App
}
/**
* Improved var_dump output
* Improved `var_dump` output
*
* @return array
*/
@@ -156,9 +149,9 @@ class App
* Returns the Api instance
*
* @internal
* @return Api
* @return Kirby\Cms\Api
*/
public function api(): Api
public function api()
{
if ($this->api !== null) {
return $this->api;
@@ -182,19 +175,33 @@ class App
}
/**
* Apply a hook to the given value
* Applies a hook to the given value;
* the value that gets modified by the hooks
* is always the last argument
*
* @internal
* @param string $name
* @param mixed $value
* @return mixed
* @param string $name Hook name
* @param mixed $args Arguments to pass to the hooks
* @return mixed Resulting value as modified by the hooks
*/
public function apply(string $name, $value)
public function apply(string $name, ...$args)
{
// split up args into "passive" args and the value
$value = array_pop($args);
if ($functions = $this->extension('hooks', $name)) {
foreach ($functions as $function) {
// re-assemble args
$hookArgs = $args;
$hookArgs[] = $value;
// bind the App object to the hook
$value = $function->call($this, $value);
$newValue = $function->call($this, ...$hookArgs);
// update value if one was returned
if ($newValue !== null) {
$value = $newValue;
}
}
}
@@ -238,7 +245,7 @@ class App
* @param string $type
* @return array
*/
public function blueprints(string $type = 'pages')
public function blueprints(string $type = 'pages'): array
{
$blueprints = [];
@@ -262,6 +269,8 @@ class App
/**
* Calls any Kirby route
*
* @param string $path
* @param string $method
* @return mixed
*/
public function call(string $path = null, string $method = null)
@@ -273,7 +282,7 @@ class App
};
$router::$afterEach = function ($route, $path, $method, $result) {
$this->trigger('route:after', $route, $path, $method, $result);
return $this->apply('route:after', $route, $path, $method, $result);
};
return $router->call($path ?? $this->path(), $method ?? $this->request()->method());
@@ -300,9 +309,9 @@ class App
/**
* Returns all user-defined collections
*
* @return Collections
* @return Kirby\Cms\Collections
*/
public function collections(): Collections
public function collections()
{
return $this->collections = $this->collections ?? new Collections;
}
@@ -379,9 +388,9 @@ class App
* Try to find a controller by name
*
* @param string $name
* @return Closure|null
* @return Kirby\Toolkit\Controller|null
*/
protected function controllerLookup(string $name, string $contentType = 'html'): ?Controller
protected function controllerLookup(string $name, string $contentType = 'html')
{
if ($contentType !== null && $contentType !== 'html') {
$name .= '.' . $contentType;
@@ -403,9 +412,9 @@ class App
/**
* Returns the default language object
*
* @return Language|null
* @return Kirby\Cms\Language|null
*/
public function defaultLanguage(): ?Language
public function defaultLanguage()
{
return $this->defaultLanguage = $this->defaultLanguage ?? $this->languages()->default();
}
@@ -416,7 +425,7 @@ class App
*
* @internal
*/
public static function destroy()
public static function destroy(): void
{
static::$plugins = [];
static::$instance = null;
@@ -425,7 +434,7 @@ class App
/**
* Detect the prefered language from the visitor object
*
* @return Language
* @return Kirby\Cms\Language
*/
public function detectedLanguage()
{
@@ -450,9 +459,9 @@ class App
/**
* Returns the Email singleton
*
* @return Email
* @return Kirby\Email\PHPMailer
*/
public function email($preset = [], array $props = []): Emailer
public function email($preset = [], array $props = [])
{
return new Emailer((new Email($preset, $props))->toArray(), $props['debug'] ?? false);
}
@@ -462,7 +471,7 @@ class App
*
* @param string $path
* @param boolean $drafts
* @return File|null
* @return Kirby\Cms\File|null
*/
public function file(string $path, $parent = null, bool $drafts = true)
{
@@ -502,10 +511,10 @@ class App
/**
* Returns the current App instance
*
* @param self $instance
* @param Kirby\Cms\App $instance
* @return self
*/
public static function instance(self $instance = null): self
public static function instance(self $instance = null)
{
if ($instance === null) {
return static::$instance ?? new static;
@@ -520,7 +529,7 @@ class App
*
* @internal
* @param mixed $input
* @return Response
* @return Kirby\Http\Response
*/
public function io($input)
{
@@ -664,9 +673,9 @@ class App
* Returns the current language
*
* @param string|null $code
* @return Language|null
* @return Kirby\Cms\Language|null
*/
public function language(string $code = null): ?Language
public function language(string $code = null)
{
if ($this->multilang() === false) {
return null;
@@ -701,9 +710,9 @@ class App
/**
* Returns all available site languages
*
* @return Languages
* @return Kirby\Cms\Languages
*/
public function languages(): Languages
public function languages()
{
if ($this->languages !== null) {
return clone $this->languages;
@@ -712,6 +721,20 @@ class App
return $this->languages = Languages::load();
}
/**
* Returns the app's locks object
*
* @return Kirby\Cms\ContentLocks
*/
public function locks(): ContentLocks
{
if ($this->locks !== null) {
return $this->locks;
}
return $this->locks = new ContentLocks;
}
/**
* Parses Markdown
*
@@ -766,7 +789,7 @@ class App
*
* @return array
*/
protected function optionsFromProps(array $options = [])
protected function optionsFromProps(array $options = []): array
{
return $this->options = array_replace_recursive($this->options, $options);
}
@@ -796,9 +819,9 @@ class App
* Returns any page from the content folder
*
* @param string $id
* @param Page|null $parent
* @param Kirby\Cms\Page|Kirby\Cms\Site|null $parent
* @param bool $drafts
* @return Page|null
* @return Kirby\Cms\Page|null
*/
public function page(string $id, $parent = null, bool $drafts = true)
{
@@ -820,7 +843,7 @@ class App
*
* @return string
*/
public function path()
public function path(): string
{
if (is_string($this->path) === true) {
return $this->path;
@@ -840,7 +863,7 @@ class App
* Returns the Response object for the
* current request
*
* @return Response
* @return Kirby\Http\Response
*/
public function render(string $path = null, string $method = null)
{
@@ -850,9 +873,9 @@ class App
/**
* Returns the Request singleton
*
* @return Request
* @return Kirby\Http\Request
*/
public function request(): Request
public function request()
{
return $this->request = $this->request ?? new Request;
}
@@ -931,7 +954,7 @@ class App
/**
* Response configuration
*
* @return Responder
* @return Kirby\Cms\Responder
*/
public function response()
{
@@ -941,9 +964,9 @@ class App
/**
* Returns all user roles
*
* @return Roles
* @return Kirby\Cms\Roles
*/
public function roles(): Roles
public function roles()
{
return $this->roles = $this->roles ?? Roles::load($this->root('roles'));
}
@@ -954,7 +977,7 @@ class App
* @param string $type
* @return string
*/
public function root($type = 'index'): string
public function root(string $type = 'index'): string
{
return $this->roots->__get($type);
}
@@ -962,9 +985,9 @@ class App
/**
* Returns the directory structure
*
* @return Ingredients
* @return Kirby\Cms\Ingredients
*/
public function roots(): Ingredients
public function roots()
{
return $this->roots;
}
@@ -972,7 +995,7 @@ class App
/**
* Returns the currently active route
*
* @return Route|null
* @return Kirby\Http\Route|null
*/
public function route()
{
@@ -983,11 +1006,21 @@ class App
* Returns the Router singleton
*
* @internal
* @return Router
* @return Kirby\Http\Router
*/
public function router(): Router
public function router()
{
return $this->router = $this->router ?? new Router($this->routes());
$routes = $this->routes();
if ($this->multilang() === true) {
foreach ($routes as $index => $route) {
if (empty($route['language']) === false) {
unset($routes[$index]);
}
}
}
return $this->router = $this->router ?? new Router($routes);
}
/**
@@ -1004,19 +1037,20 @@ class App
$registry = $this->extensions('routes');
$system = (include static::$root . '/config/routes.php')($this);
$routes = array_merge($system['before'], $registry, $system['after']);
return $this->routes = array_merge($system['before'], $registry, $system['after']);
return $this->routes = $routes;
}
/**
* Returns the current session object
*
* @param array $options Additional options, see the session component
* @return Session
* @return Kirby\Session\Session
*/
public function session(array $options = [])
{
$this->session = $this->session ?? new Session($this->root('sessions'), $this->options['session'] ?? []);
$this->session = $this->session ?? new AutoSession($this->root('sessions'), $this->options['session'] ?? []);
return $this->session->get($options);
}
@@ -1026,7 +1060,7 @@ class App
* @param array $languages
* @return self
*/
protected function setLanguages(array $languages = null): self
protected function setLanguages(array $languages = null)
{
if ($languages !== null) {
$this->languages = new Languages();
@@ -1053,7 +1087,13 @@ class App
return $this;
}
protected function setRequest(array $request = null): self
/**
* Sets the request
*
* @param array $request
* @return self
*/
protected function setRequest(array $request = null)
{
if ($request !== null) {
$this->request = new Request($request);
@@ -1068,7 +1108,7 @@ class App
* @param array $roles
* @return self
*/
protected function setRoles(array $roles = null): self
protected function setRoles(array $roles = null)
{
if ($roles !== null) {
$this->roles = Roles::factory($roles, [
@@ -1082,7 +1122,7 @@ class App
/**
* Sets a custom Site object
*
* @param array|Site $site
* @param Kirby\Cms\Site|array $site
* @return self
*/
protected function setSite($site = null)
@@ -1100,9 +1140,9 @@ class App
/**
* Returns the Server object
*
* @return Server
* @return Kirby\Http\Server
*/
public function server(): Server
public function server()
{
return $this->server = $this->server ?? new Server;
}
@@ -1110,9 +1150,9 @@ class App
/**
* Initializes and returns the Site object
*
* @return Site
* @return Kirby\Cms\Site
*/
public function site(): Site
public function site()
{
return $this->site = $this->site ?? new Site([
'errorPageId' => $this->options['error'] ?? 'error',
@@ -1145,9 +1185,9 @@ class App
* and return a template snippet
*
* @internal
* @return Snippet
* @return string
*/
public function snippet(string $name, array $data = []): ?string
public function snippet($name, array $data = []): ?string
{
return $this->component('snippet')($this, $name, array_merge($this->data, $data));
}
@@ -1155,9 +1195,9 @@ class App
/**
* System check class
*
* @return System
* @return Kirby\Cms\System
*/
public function system(): System
public function system()
{
return $this->system = $this->system ?? new System($this);
}
@@ -1167,9 +1207,9 @@ class App
* and return the Template object
*
* @internal
* @return Template
* @return Kirby\Cms\Template
*/
public function template(string $name, string $type = 'html', string $defaultType = 'html'): Template
public function template(string $name, string $type = 'html', string $defaultType = 'html')
{
return $this->component('template')($this, $name, $type, $defaultType);
}
@@ -1228,7 +1268,7 @@ class App
* @param string $type
* @return string
*/
public function url($type = 'index'): string
public function url(string $type = 'index'): string
{
return $this->urls->__get($type);
}
@@ -1236,9 +1276,9 @@ class App
/**
* Returns the url structure
*
* @return Ingredients
* @return Kirby\Cms\Ingredients
*/
public function urls(): Ingredients
public function urls()
{
return $this->urls;
}
@@ -1249,7 +1289,7 @@ class App
*
* @return string|null
*/
public static function version()
public static function version(): ?string
{
return static::$version = static::$version ?? Data::read(static::$root . '/composer.json')['version'] ?? null;
}
@@ -1267,9 +1307,9 @@ class App
/**
* Returns the visitor object
*
* @return Visitor
* @return Kirby\Cms\Visitor
*/
public function visitor(): Visitor
public function visitor()
{
return $this->visitor = $this->visitor ?? new Visitor();
}

View File

@@ -3,8 +3,18 @@
namespace Kirby\Cms;
use Kirby\Cache\Cache;
use Kirby\Cache\NullCache;
use Kirby\Exception\InvalidArgumentException;
/**
* AppCaches
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
trait AppCaches
{
protected $caches = [];
@@ -13,7 +23,7 @@ trait AppCaches
* Returns a cache instance by key
*
* @param string $key
* @return Cache
* @return Kirby\Cache\Cache
*/
public function cache(string $key)
{
@@ -25,7 +35,8 @@ trait AppCaches
$options = $this->cacheOptions($key);
if ($options['active'] === false) {
return $this->caches[$key] = new Cache;
// use a dummy cache that does nothing
return $this->caches[$key] = new NullCache;
}
$type = strtolower($options['type']);
@@ -41,7 +52,17 @@ trait AppCaches
$className = $types[$type];
// initialize the cache class
return $this->caches[$key] = new $className($options);
$cache = new $className($options);
// check if it is a useable cache object
if (is_a($cache, Cache::class) !== true) {
throw new InvalidArgumentException([
'key' => 'app.invalid.cacheType',
'data' => ['type' => $type]
]);
}
return $this->caches[$key] = $cache;
}
/**
@@ -60,11 +81,16 @@ trait AppCaches
];
}
$prefix = str_replace('/', '_', $this->system()->indexUrl()) .
'/' .
str_replace('.', '/', $key);
$defaults = [
'active' => true,
'type' => 'file',
'extension' => 'cache',
'root' => $this->root('cache') . '/' . str_replace('.', '/', $key)
'root' => $this->root('cache'),
'prefix' => $prefix
];
if ($options === true) {
@@ -103,7 +129,7 @@ trait AppCaches
$cacheName = implode('.', array_slice($parts, 2));
// check if such a plugin exists
if ($plugin = $this->plugin($pluginName)) {
if ($this->plugin($pluginName)) {
return empty($cacheName) === true ? $pluginPrefix . '.cache' : $pluginPrefix . '.cache.' . $cacheName;
}

View File

@@ -2,8 +2,6 @@
namespace Kirby\Cms;
use Closure;
use Kirby\Exception\Exception;
use Kirby\Http\Response;
use Whoops\Run as Whoops;
use Whoops\Handler\Handler;
@@ -11,9 +9,18 @@ use Whoops\Handler\PrettyPageHandler;
use Whoops\Handler\PlainTextHandler;
use Whoops\Handler\CallbackHandler;
/**
* AppErrors
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
trait AppErrors
{
protected function handleCliErrors()
protected function handleCliErrors(): void
{
$whoops = new Whoops;
$whoops->pushHandler(new PlainTextHandler);

View File

@@ -4,7 +4,6 @@ namespace Kirby\Cms;
use Closure;
use Kirby\Exception\DuplicateException;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Form\Field as FormField;
use Kirby\Text\KirbyTag;
use Kirby\Toolkit\A;
@@ -13,6 +12,15 @@ use Kirby\Toolkit\Dir;
use Kirby\Toolkit\F;
use Kirby\Toolkit\V;
/**
* AppPlugins
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
trait AppPlugins
{
@@ -29,6 +37,10 @@ trait AppPlugins
* @var array
*/
protected $extensions = [
// load options first to make them available for the rest
'options' => [],
// other plugin types
'api' => [],
'blueprints' => [],
'cacheTypes' => [],
@@ -39,13 +51,13 @@ trait AppPlugins
'fieldMethods' => [],
'fileMethods' => [],
'filesMethods' => [],
'fileModels' => [],
'fields' => [],
'hooks' => [],
'options' => [],
'pages' => [],
'pageMethods' => [],
'pageModels' => [],
'pagesMethods' => [],
'pageModels' => [],
'routes' => [],
'sections' => [],
'siteMethods' => [],
@@ -53,6 +65,9 @@ trait AppPlugins
'tags' => [],
'templates' => [],
'translations' => [],
'userMethods' => [],
'userModels' => [],
'usersMethods' => [],
'validators' => []
];
@@ -69,7 +84,7 @@ trait AppPlugins
*
* @internal
* @param array $extensions
* @param Plugin $plugin The plugin which defined those extensions
* @param Kirby\Cms\Plugin $plugin The plugin which defined those extensions
* @return array
*/
public function extend(array $extensions, Plugin $plugin = null): array
@@ -83,6 +98,12 @@ trait AppPlugins
return $this->extensions;
}
/**
* Registers API extensions
*
* @param array|bool $api
* @return array
*/
protected function extendApi($api): array
{
if (is_array($api) === true) {
@@ -96,56 +117,133 @@ trait AppPlugins
}
}
/**
* Registers additional blueprints
*
* @param array $blueprints
* @return array
*/
protected function extendBlueprints(array $blueprints): array
{
return $this->extensions['blueprints'] = array_merge($this->extensions['blueprints'], $blueprints);
}
/**
* Registers additional cache types
*
* @param array $cacheTypes
* @return array
*/
protected function extendCacheTypes(array $cacheTypes): array
{
return $this->extensions['cacheTypes'] = array_merge($this->extensions['cacheTypes'], $cacheTypes);
}
/**
* Registers additional collection filters
*
* @param array $filters
* @return array
*/
protected function extendCollectionFilters(array $filters): array
{
return $this->extensions['collectionFilters'] = Collection::$filters = array_merge(Collection::$filters, $filters);
}
/**
* Registers additional collections
*
* @param array $collections
* @return array
*/
protected function extendCollections(array $collections): array
{
return $this->extensions['collections'] = array_merge($this->extensions['collections'], $collections);
}
/**
* Registers core components
*
* @param array $components
* @return array
*/
protected function extendComponents(array $components): array
{
return $this->extensions['components'] = array_merge($this->extensions['components'], $components);
}
/**
* Registers additional controllers
*
* @param array $controllers
* @return array
*/
protected function extendControllers(array $controllers): array
{
return $this->extensions['controllers'] = array_merge($this->extensions['controllers'], $controllers);
}
/**
* Registers additional file methods
*
* @param array $methods
* @return array
*/
protected function extendFileMethods(array $methods): array
{
return $this->extensions['fileMethods'] = File::$methods = array_merge(File::$methods, $methods);
}
/**
* Registers additional files methods
*
* @param array $methods
* @return array
*/
protected function extendFilesMethods(array $methods): array
{
return $this->extensions['filesMethods'] = Files::$methods = array_merge(Files::$methods, $methods);
}
/**
* Registers additional file models
*
* @param array $models
* @return array
*/
protected function extendFileModels(array $models): array
{
return $this->extensions['fileModels'] = File::$models = array_merge(File::$models, $models);
}
/**
* Registers additional field methods
*
* @param array $methods
* @return array
*/
protected function extendFieldMethods(array $methods): array
{
return $this->extensions['fieldMethods'] = Field::$methods = array_merge(Field::$methods, array_change_key_case($methods));
}
/**
* Registers Panel fields
*
* @param array $fields
* @return array
*/
protected function extendFields(array $fields): array
{
return $this->extensions['fields'] = FormField::$types = array_merge(FormField::$types, $fields);
}
/**
* Registers hooks
*
* @param array $hooks
* @return array
*/
protected function extendHooks(array $hooks): array
{
foreach ($hooks as $name => $callbacks) {
@@ -165,11 +263,24 @@ trait AppPlugins
return $this->extensions['hooks'];
}
protected function extendMarkdown(Closure $markdown): array
/**
* Registers markdown component
*
* @param Closure $blueprints
* @return Closure
*/
protected function extendMarkdown(Closure $markdown)
{
return $this->extensions['markdown'] = $markdown;
}
/**
* Registers additional options
*
* @param array $options
* @param Kirby\Cms\Plugin|null $plugin
* @return array
*/
protected function extendOptions(array $options, Plugin $plugin = null): array
{
if ($plugin !== null) {
@@ -185,21 +296,45 @@ trait AppPlugins
return $this->extensions['options'] = $this->options = A::merge($options, $this->options, A::MERGE_REPLACE);
}
/**
* Registers additional page methods
*
* @param array $methods
* @return array
*/
protected function extendPageMethods(array $methods): array
{
return $this->extensions['pageMethods'] = Page::$methods = array_merge(Page::$methods, $methods);
}
/**
* Registers additional pages methods
*
* @param array $methods
* @return array
*/
protected function extendPagesMethods(array $methods): array
{
return $this->extensions['pagesMethods'] = Pages::$methods = array_merge(Pages::$methods, $methods);
}
/**
* Registers additional page models
*
* @param array $models
* @return array
*/
protected function extendPageModels(array $models): array
{
return $this->extensions['pageModels'] = Page::$models = array_merge(Page::$models, $models);
}
/**
* Registers pages
*
* @param array $pages
* @return array
*/
protected function extendPages(array $pages): array
{
return $this->extensions['pages'] = array_merge($this->extensions['pages'], $pages);
@@ -220,41 +355,122 @@ trait AppPlugins
return $this->extensions['routes'] = array_merge($this->extensions['routes'], $routes);
}
/**
* Registers Panel sections
*
* @param array $sections
* @return array
*/
protected function extendSections(array $sections): array
{
return $this->extensions['sections'] = Section::$types = array_merge(Section::$types, $sections);
}
/**
* Registers additional site methods
*
* @param array $methods
* @return array
*/
protected function extendSiteMethods(array $methods): array
{
return $this->extensions['siteMethods'] = Site::$methods = array_merge(Site::$methods, $methods);
}
protected function extendSmartypants(Closure $smartypants): array
/**
* Registers SmartyPants component
*
* @param Closure $smartypants
* @return Closure
*/
protected function extendSmartypants(Closure $smartypants)
{
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 KirbyTags
*
* @param array $tags
* @return array
*/
protected function extendTags(array $tags): array
{
return $this->extensions['tags'] = KirbyTag::$types = array_merge(KirbyTag::$types, $tags);
return $this->extensions['tags'] = KirbyTag::$types = array_merge(KirbyTag::$types, array_change_key_case($tags));
}
/**
* Registers additional templates
*
* @param array $templates
* @return array
*/
protected function extendTemplates(array $templates): array
{
return $this->extensions['templates'] = array_merge($this->extensions['templates'], $templates);
}
/**
* Registers translations
*
* @param array $translations
* @return array
*/
protected function extendTranslations(array $translations): array
{
return $this->extensions['translations'] = array_replace_recursive($this->extensions['translations'], $translations);
}
/**
* Registers additional user methods
*
* @param array $methods
* @return array
*/
protected function extendUserMethods(array $methods): array
{
return $this->extensions['userMethods'] = User::$methods = array_merge(User::$methods, $methods);
}
/**
* Registers additional user models
*
* @param array $models
* @return array
*/
protected function extendUserModels(array $models): array
{
return $this->extensions['userModels'] = User::$models = array_merge(User::$models, $models);
}
/**
* Registers additional users methods
*
* @param array $methods
* @return array
*/
protected function extendUsersMethods(array $methods): array
{
return $this->extensions['usersMethods'] = Users::$methods = array_merge(Users::$methods, $methods);
}
/**
* Registers additional custom validators
*
* @param array $validators
* @return array
*/
protected function extendValidators(array $validators): array
{
return $this->extensions['validators'] = V::$validators = array_merge(V::$validators, $validators);
@@ -367,8 +583,13 @@ trait AppPlugins
protected function extensionsFromSystem()
{
// Form Field Mixins
FormField::$mixins['min'] = include static::$root . '/config/fields/mixins/min.php';
FormField::$mixins['options'] = include static::$root . '/config/fields/mixins/options.php';
FormField::$mixins['filepicker'] = include static::$root . '/config/fields/mixins/filepicker.php';
FormField::$mixins['min'] = include static::$root . '/config/fields/mixins/min.php';
FormField::$mixins['options'] = include static::$root . '/config/fields/mixins/options.php';
FormField::$mixins['pagepicker'] = include static::$root . '/config/fields/mixins/pagepicker.php';
FormField::$mixins['picker'] = include static::$root . '/config/fields/mixins/picker.php';
FormField::$mixins['upload'] = include static::$root . '/config/fields/mixins/upload.php';
FormField::$mixins['userpicker'] = include static::$root . '/config/fields/mixins/userpicker.php';
// Tag Aliases
KirbyTag::$aliases = [
@@ -398,6 +619,7 @@ trait AppPlugins
'apcu' => 'Kirby\Cache\ApcuCache',
'file' => 'Kirby\Cache\FileCache',
'memcached' => 'Kirby\Cache\MemCached',
'memory' => 'Kirby\Cache\MemoryCache',
]);
$this->extendComponents(include static::$root . '/config/components.php');
@@ -433,7 +655,7 @@ trait AppPlugins
*
* @param string $name
* @param array|null $extends If null is passed it will be used as getter. Otherwise as factory.
* @return Plugin|null
* @return Kirby\Cms\Plugin|null
*/
public static function plugin(string $name, array $extends = null)
{

View File

@@ -2,8 +2,20 @@
namespace Kirby\Cms;
use Kirby\Data\Data;
use Kirby\Toolkit\F;
use Kirby\Toolkit\I18n;
use Kirby\Toolkit\Str;
/**
* AppTranslations
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
trait AppTranslations
{
protected $translations;
@@ -13,9 +25,9 @@ trait AppTranslations
*
* @return void
*/
protected function i18n()
protected function i18n(): void
{
I18n::$load = function ($locale) {
I18n::$load = function ($locale): array {
$data = [];
if ($translation = $this->translation($locale)) {
@@ -25,12 +37,16 @@ trait AppTranslations
// inject translations from the current language
if ($this->multilang() === true && $language = $this->languages()->find($locale)) {
$data = array_merge($data, $language->translations());
// Add language slug rules to Str class
Str::$language = $language->rules();
}
return $data;
};
I18n::$locale = function () {
I18n::$locale = function (): string {
if ($this->multilang() === true) {
return $this->defaultLanguage()->code();
} else {
@@ -38,7 +54,7 @@ trait AppTranslations
}
};
I18n::$fallback = function () {
I18n::$fallback = function (): string {
if ($this->multilang() === true) {
return $this->defaultLanguage()->code();
} else {
@@ -47,6 +63,20 @@ trait AppTranslations
};
I18n::$translations = [];
if (isset($this->options['slugs']) === true) {
$file = $this->root('i18n:rules') . '/' . $this->options['slugs'] . '.json';
if (F::exists($file) === true) {
try {
$data = Data::read($file);
} catch (\Exception $e) {
$data = [];
}
Str::$language = $data;
}
}
}
/**
@@ -55,7 +85,7 @@ trait AppTranslations
*
* @internal
* @param string $languageCode
* @return Language|null
* @return Kirby\Cms\Language|null
*/
public function setCurrentLanguage(string $languageCode = null)
{
@@ -84,7 +114,7 @@ trait AppTranslations
* @param string $translationCode
* @return void
*/
public function setCurrentTranslation(string $translationCode = null)
public function setCurrentTranslation(string $translationCode = null): void
{
I18n::$locale = $translationCode ?? 'en';
}
@@ -95,7 +125,7 @@ trait AppTranslations
* @internal
* @param string|array $locale
*/
public function setLocale($locale)
public function setLocale($locale): void
{
if (is_array($locale) === true) {
foreach ($locale as $key => $value) {
@@ -110,7 +140,7 @@ trait AppTranslations
* Load a specific translation by locale
*
* @param string|null $locale
* @return Translation|null
* @return Kirby\Cms\Translation|null
*/
public function translation(string $locale = null)
{
@@ -134,7 +164,7 @@ trait AppTranslations
/**
* Returns all available translations
*
* @return Translations
* @return Kirby\Cms\Translations
*/
public function translations()
{

View File

@@ -2,6 +2,15 @@
namespace Kirby\Cms;
/**
* AppUsers
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
trait AppUsers
{
@@ -16,7 +25,7 @@ trait AppUsers
* Returns the Authentication layer class
*
* @internal
* @return Auth
* @return Kirby\Cms\Auth
*/
public function auth()
{
@@ -27,7 +36,7 @@ trait AppUsers
* Become any existing user
*
* @param string|null $who
* @return self
* @return Kirby\Cms\User|null
*/
public function impersonate(string $who = null)
{
@@ -37,10 +46,10 @@ trait AppUsers
/**
* Set the currently active user id
*
* @param User|string $user
* @return self
* @param Kirby\Cms\User|string $user
* @return Kirby\Cms\App
*/
protected function setUser($user = null): self
protected function setUser($user = null)
{
$this->user = $user;
return $this;
@@ -50,9 +59,9 @@ trait AppUsers
* Create your own set of app users
*
* @param array $users
* @return self
* @return Kirby\Cms\App
*/
protected function setUsers(array $users = null): self
protected function setUsers(array $users = null)
{
if ($users !== null) {
$this->users = Users::factory($users, [
@@ -67,11 +76,10 @@ trait AppUsers
* Returns a specific user by id
* or the current user if no id is given
*
* @param string $id
* @param \Kirby\Session\Session|array $session Session options or session object for getting the current user
* @return User|null
* @param string $id
* @return Kirby\Cms\User|null
*/
public function user(string $id = null, $session = null)
public function user(string $id = null)
{
if ($id !== null) {
return $this->users()->find($id);
@@ -87,9 +95,9 @@ trait AppUsers
/**
* Returns all users
*
* @return Users
* @return Kirby\Cms\Users
*/
public function users(): Users
public function users()
{
if (is_a($this->users, 'Kirby\Cms\Users') === true) {
return $this->users;

View File

@@ -10,6 +10,12 @@ use Kirby\Toolkit\Properties;
* methods and thumbnail generation as for any other
* Kirby files. Pass a relative path to the Asset
* object to create the asset.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Asset
{

View File

@@ -7,11 +7,16 @@ use Kirby\Exception\PermissionException;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\NotFoundException;
use Kirby\Http\Request\Auth\BasicAuth;
use Kirby\Session\Session;
use Throwable;
/**
* Authentication layer
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Auth
{
@@ -20,7 +25,7 @@ class Auth
protected $user;
/**
* @param App $kirby
* @param Kirby\Cms\App $kirby
*/
public function __construct(App $kirby)
{
@@ -53,8 +58,8 @@ class Auth
* for a basic authentication header with
* valid credentials
*
* @param BasicAuth|null $auth
* @return User|null
* @param Kirby\Http\Request\Auth\BasicAuth|null $auth
* @return Kirby\Cms\User|null
*/
public function currentUserFromBasicAuth(BasicAuth $auth = null)
{
@@ -88,13 +93,13 @@ class Auth
* the current session and finding a valid
* valid user id in there
*
* @param Session|null $session
* @return User|null
* @param Kirby\Cms\Session|array|null $session
* @return Kirby\Cms\User|null
*/
public function currentUserFromSession($session = null)
{
// use passed session options or session object if set
if (is_array($session)) {
if (is_array($session) === true) {
$session = $this->kirby->session($session);
}
@@ -123,7 +128,7 @@ class Auth
* Become any existing user
*
* @param string|null $who
* @return User|null
* @return Kirby\Cms\User|null
*/
public function impersonate(string $who = null)
{
@@ -185,7 +190,7 @@ class Auth
* @param string $email
* @param string $password
* @param boolean $long
* @return User|false
* @return Kirby\Cms\User|false
*/
public function login(string $email, string $password, bool $long = false)
{
@@ -311,10 +316,10 @@ class Auth
/**
* Validates the currently logged in user
*
* @param array|Session|null $session
* @return User|null
* @param Kirby\Session\Sessionarray||null $session
* @return Kirby\Cms\User|null
*/
public function user($session = null): ?User
public function user($session = null)
{
if ($this->impersonate !== null) {
return $this->impersonate;

View File

@@ -10,13 +10,18 @@ use Kirby\Form\Field;
use Kirby\Toolkit\A;
use Kirby\Toolkit\F;
use Kirby\Toolkit\I18n;
use Kirby\Toolkit\Obj;
use Throwable;
/**
* The Blueprint class normalizes an array from a
* blueprint file and converts sections, columns, fields
* etc. into a correct tab layout.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Blueprint
{
@@ -81,13 +86,13 @@ class Blueprint
}
/**
* Improved var_dump output
* Improved `var_dump` output
*
* @return array
*/
public function __debuginfo(): array
{
return $this->props;
return $this->props ?? [];
}
/**
@@ -217,7 +222,7 @@ class Blueprint
*
* @param string $name
* @param string $fallback
* @param Model $model
* @param Kirby\Cms\Model $model
* @return self
*/
public static function factory(string $name, string $fallback = null, Model $model)
@@ -311,11 +316,9 @@ class Blueprint
* Loads a blueprint from file or array
*
* @param string $name
* @param string $fallback
* @param Model $model
* @return array
*/
public static function load(string $name)
public static function load(string $name): array
{
if (isset(static::$loaded[$name]) === true) {
return static::$loaded[$name];
@@ -349,7 +352,7 @@ class Blueprint
/**
* Returns the parent model
*
* @return Model
* @return Kirby\Cms\Model
*/
public function model()
{
@@ -402,7 +405,7 @@ class Blueprint
return $columns;
}
public static function helpList(array $items)
public static function helpList(array $items): string
{
$md = [];
@@ -503,6 +506,12 @@ class Blueprint
$fieldProps = [];
}
// unset / remove field if its propperty is false
if ($fieldProps === false) {
unset($fields[$fieldName]);
continue;
}
// inject the name
$fieldProps['name'] = $fieldName;
@@ -580,6 +589,12 @@ class Blueprint
{
foreach ($sections as $sectionName => $sectionProps) {
// unset / remove section if its propperty is false
if ($sectionProps === false) {
unset($sections[$sectionName]);
continue;
}
// inject all section extensions
$sectionProps = $this->extend($sectionProps);
@@ -648,6 +663,12 @@ class Blueprint
foreach ($tabs as $tabName => $tabProps) {
// unset / remove tab if its propperty is false
if ($tabProps === false) {
unset($tabs[$tabName]);
continue;
}
// inject all tab extensions
$tabProps = $this->extend($tabProps);
@@ -691,9 +712,9 @@ class Blueprint
* Returns a single section by name
*
* @param string $name
* @return Section|null
* @return Kirby\Cms\Section|null
*/
public function section(string $name): ?Section
public function section(string $name)
{
if (empty($this->sections[$name]) === true) {
return null;

View File

@@ -19,8 +19,9 @@ use Kirby\Toolkit\Str;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Collection extends BaseCollection
{
@@ -100,9 +101,9 @@ class Collection extends BaseCollection
/**
* Appends an element to the data array
*
* @param mixed $key
* @param mixed $item
* @return Collection
* @param mixed $key
* @param mixed $item
* @return Kirby\Cms\Collection
*/
public function append(...$args)
{
@@ -120,11 +121,12 @@ class Collection extends BaseCollection
}
/**
* Groups the items by a given field
* Groups the items by a given field. Returns a collection
* with an item for each group and a collection for each group.
*
* @param string $field
* @param bool $i (ignore upper/lowercase for group names)
* @return Collection A collection with an item for each group and a Collection for each group
* @param bool $i Ignore upper/lowercase for group names
* @return Kirby\Cms\Collection
*/
public function groupBy($field, bool $i = true)
{
@@ -195,8 +197,8 @@ class Collection extends BaseCollection
/**
* Returns a Collection without the given element(s)
*
* @param args any number of keys, passed as individual arguments
* @return Collection
* @param mixxed[] $keys any number of keys, passed as individual arguments
* @return Kirby\Cms\Collection
*/
public function not(...$keys)
{
@@ -213,9 +215,9 @@ class Collection extends BaseCollection
}
/**
* Add pagination
* Add pagination and return a sliced set of data.
*
* @return Collection a sliced set of data
* @return Kirby\Cms\Collection
*/
public function paginate(...$arguments)
{
@@ -228,7 +230,7 @@ class Collection extends BaseCollection
/**
* Returns the parent model
*
* @return Model
* @return Kirby\Cms\Model
*/
public function parent()
{

View File

@@ -2,7 +2,6 @@
namespace Kirby\Cms;
use Closure;
use Kirby\Exception\NotFoundException;
use Kirby\Toolkit\Controller;
@@ -16,8 +15,9 @@ use Kirby\Toolkit\Controller;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Collections
{
@@ -45,7 +45,7 @@ class Collections
*
* @param string $name
* @param array $arguments
* @return Collection|null
* @return Kirby\Cms\Collection|null
*/
public function __call(string $name, array $arguments = [])
{
@@ -57,7 +57,7 @@ class Collections
*
* @param string $name
* @param array $data
* @return Collection|null
* @return Kirby\Cms\Collection|null
*/
public function get(string $name, array $data = [])
{

View File

@@ -2,16 +2,15 @@
namespace Kirby\Cms;
use Closure;
/**
* The Content class handles all fields
* for content from pages, the site and users
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Content
{
@@ -39,7 +38,7 @@ class Content
* for testing, but field methods might
* need it.
*
* @var Page|File|User|Site
* @var Model
*/
protected $parent;
@@ -48,9 +47,9 @@ class Content
*
* @param string $name
* @param array $arguments
* @return Field
* @return Kirby\Cms\Field
*/
public function __call(string $name, array $arguments = []): Field
public function __call(string $name, array $arguments = [])
{
return $this->get($name);
}
@@ -58,10 +57,10 @@ class Content
/**
* Creates a new Content object
*
* @param array $data
* @param object $parent
* @param array|null $data
* @param object|null $parent
*/
public function __construct($data = [], $parent = null)
public function __construct(array $data = [], $parent = null)
{
$this->data = $data;
$this->parent = $parent;
@@ -69,7 +68,7 @@ class Content
/**
* Same as `self::data()` to improve
* var_dump output
* `var_dump` output
*
* @see self::data()
* @return array
@@ -149,7 +148,7 @@ class Content
* or all registered fields
*
* @param string $key
* @return Field|array
* @return Kirby\Cms\Field|array
*/
public function get(string $key = null)
{
@@ -202,7 +201,7 @@ class Content
* @param string ...$keys
* @return self
*/
public function not(...$keys): self
public function not(...$keys)
{
$copy = clone $this;
$copy->fields = null;
@@ -218,7 +217,7 @@ class Content
* Returns the parent
* Site, Page, File or User object
*
* @return Site|Page|File|User
* @return Kirby\Cms\Model
*/
public function parent()
{
@@ -228,10 +227,10 @@ class Content
/**
* Set the parent model
*
* @param Model $parent
* @param Kirby\Cms\Model $parent
* @return self
*/
public function setParent(Model $parent): self
public function setParent(Model $parent)
{
$this->parent = $parent;
return $this;
@@ -256,7 +255,7 @@ class Content
* @param bool $overwrite
* @return self
*/
public function update(array $content = null, bool $overwrite = false): self
public function update(array $content = null, bool $overwrite = false)
{
$this->data = $overwrite === true ? (array)$content : array_merge($this->data, (array)$content);
return $this;

220
kirby/src/Cms/ContentLock.php Executable file
View File

@@ -0,0 +1,220 @@
<?php
namespace Kirby\Cms;
use Kirby\Exception\DuplicateException;
use Kirby\Exception\LogicException;
use Kirby\Exception\PermissionException;
/**
* Takes care of content lock and unlock information
*
* @package Kirby Cms
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class ContentLock
{
/**
* Lock data
*
* @var array
*/
protected $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);
}
/**
* Sets lock with the current user
*
* @return bool
*/
public function create(): bool
{
// check if model is already locked by another user
if (
isset($this->data['lock']) === true &&
$this->data['lock']['user'] !== $this->user()->id()
) {
$id = ContentLocks::id($this->model);
throw new DuplicateException($id . ' is already locked');
}
$this->data['lock'] = [
'user' => $this->user()->id(),
'time' => time()
];
return $this->kirby()->locks()->set($this->model, $this->data);
}
/**
* Returns array with `locked` flag and,
* if needed, `user`, `email`, `time`, `canUnlock`
*
* @return array
*/
public function get(): array
{
$data = $this->data['lock'] ?? [];
if (
empty($data) === false &&
$data['user'] !== $this->user()->id() &&
$user = $this->kirby()->user($data['user'])
) {
$time = intval($data['time']);
return [
'locked' => true,
'user' => $user->id(),
'email' => $user->email(),
'time' => $time,
'canUnlock' => $time + $this->kirby()->option('lock.duration', 60 * 2) <= time()
];
}
return [
'locked' => false
];
}
/**
* Returns if the model is locked by another user
*
* @return bool
*/
public function isLocked(): bool
{
$lock = $this->get();
if (
$lock['locked'] === true &&
$lock['user'] !== $this->user()->id()
) {
return true;
}
return false;
}
/**
* Returns if the current user's lock has been removed by another user
*
* @return bool
*/
public function isUnlocked(): bool
{
$data = $this->data['unlock'] ?? [];
return in_array($this->user()->id(), $data) === true;
}
/**
* Returns the app instance
*
* @return Kirby\Cms\App
*/
protected function kirby(): App
{
return $this->model->kirby();
}
/**
* Removes lock of current user
*
* @return bool
*/
public function remove(): bool
{
// if no lock exists, skip
if (isset($this->data['lock']) === false) {
return true;
}
// check if lock was set by another user
if ($this->data['lock']['user'] !== $this->user()->id()) {
throw new LogicException('The content lock can only be removed by the user who created it. Use unlock instead.', 409);
}
// remove lock
unset($this->data['lock']);
return $this->kirby()->locks()->set($this->model, $this->data);
}
/**
* Removes unlock information for current user
*
* @return bool
*/
public function resolve(): bool
{
// if no unlocks exist, skip
if (isset($this->data['unlock']) === false) {
return true;
}
// remove user from unlock array
$this->data['unlock'] = array_diff(
$this->data['unlock'],
[$this->user()->id()]
);
return $this->kirby()->locks()->set($this->model, $this->data);
}
/**
* Removes current lock and adds lock user to unlock data
*
* @return bool
*/
public function unlock(): bool
{
// if no lock exists, skip
if (isset($this->data['lock']) === false) {
return true;
}
// add lock user to unlocked data
$this->data['unlock'] = $this->data['unlock'] ?? [];
$this->data['unlock'][] = $this->data['lock']['user'];
// remove lock
unset($this->data['lock']);
return $this->kirby()->locks()->set($this->model, $this->data);
}
/**
* Returns currently authenticated user;
* throws exception if none is authenticated
*
* @return Kirby\Cms\User
*/
protected function user(): User
{
if ($user = $this->kirby()->user()) {
return $user;
}
throw new PermissionException('No user authenticated.');
}
}

226
kirby/src/Cms/ContentLocks.php Executable file
View File

@@ -0,0 +1,226 @@
<?php
namespace Kirby\Cms;
use Kirby\Data\Yaml;
use Kirby\Exception\Exception;
use Kirby\Toolkit\F;
/**
* Manages all content lock files
*
* @package Kirby Cms
* @author Nico Hoffmann <nico@getkirby.com>,
* Lukas Bestle <lukas@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class ContentLocks
{
/**
* Data from the `.lock` files
* that have been read so far
* cached by `.lock` file path
*
* @var array
*/
protected $data = [];
/**
* PHP file handles for all currently
* open `.lock` files
*
* @var array
*/
protected $handles = [];
/**
* Closes the open file handles
*
* @codeCoverageIgnore
*/
public function __destruct()
{
foreach ($this->handles as $file => $handle) {
$this->closeHandle($file);
}
}
/**
* Removes the file lock and closes the file handle
*
* @param string $file
* @return void
*/
protected function closeHandle(string $file)
{
if (isset($this->handles[$file]) === false) {
return;
}
$handle = $this->handles[$file];
$result = flock($handle, LOCK_UN) && fclose($handle);
if ($result !== true) {
throw new Exception('Unexpected file system error.'); // @codeCoverageIgnore
}
unset($this->handles[$file]);
}
/**
* 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';
}
/**
* Returns the lock/unlock data for the specified model
*
* @param Kirby\Cms\ModelWithContent $model
* @return array
*/
public function get(ModelWithContent $model): array
{
$file = static::file($model);
$id = static::id($model);
// return from cache if file was already loaded
if (isset($this->data[$file]) === true) {
return $this->data[$file][$id] ?? [];
}
// first get a handle to ensure a file system lock
$handle = $this->handle($file);
if (is_resource($handle) === true) {
// read data from file
clearstatcache();
$filesize = filesize($file);
if ($filesize > 0) {
// always read the whole file
rewind($handle);
$string = fread($handle, $filesize);
$data = Yaml::decode($string);
}
}
$this->data[$file] = $data ?? [];
return $this->data[$file][$id] ?? [];
}
/**
* Returns the file handle to a `.lock` file
*
* @param string $file
* @param boolean $create Whether to create the file if it does not exist
* @return resource|null File handle
*/
protected function handle(string $file, bool $create = false)
{
// check for an already open handle
if (isset($this->handles[$file]) === true) {
return $this->handles[$file];
}
// don't create a file if not requested
if (is_file($file) !== true && $create !== true) {
return null;
}
$handle = @fopen($file, 'c+b');
if (is_resource($handle) === false) {
throw new Exception('Lock file ' . $file . ' could not be opened.'); // @codeCoverageIgnore
}
// lock the lock file exclusively to prevent changes by other threads
$result = flock($handle, LOCK_EX);
if ($result !== true) {
throw new Exception('Unexpected file system error.'); // @codeCoverageIgnore
}
return $this->handles[$file] = $handle;
}
/**
* 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
{
return '/' . $model->id();
}
/**
* Sets and writes the lock/unlock data for the specified model
*
* @param Kirby\Cms\ModelWithContent $model
* @param array $data
* @return boolean
*/
public function set(ModelWithContent $model, array $data): bool
{
$file = static::file($model);
$id = static::id($model);
$handle = $this->handle($file, true);
$this->data[$file][$id] = $data;
// make sure to unset model id entries,
// if no lock data for the model exists
foreach ($this->data[$file] as $id => $data) {
// there is no data for that model whatsoever
if (
isset($data['lock']) === false &&
(isset($data['unlock']) === false ||
count($data['unlock']) === 0)
) {
unset($this->data[$file][$id]);
// there is empty unlock data, but still lock data
} elseif (
isset($data['unlock']) === true &&
count($data['unlock']) === 0
) {
unset($this->data[$file][$id]['unlock']);
}
}
// there is no data left in the file whatsoever, delete the file
if (count($this->data[$file]) === 0) {
unset($this->data[$file]);
// close the file handle, otherwise we can't delete it on Windows
$this->closeHandle($file);
return F::remove($file);
}
$yaml = Yaml::encode($this->data[$file]);
// delete all file contents first
if (rewind($handle) !== true || ftruncate($handle, 0) !== true) {
throw new Exception('Could not write lock file ' . $file . '.'); // @codeCoverageIgnore
}
// write the new contents
$result = fwrite($handle, $yaml);
if (is_int($result) === false || $result === 0) {
throw new Exception('Could not write lock file ' . $file . '.'); // @codeCoverageIgnore
}
return true;
}
}

View File

@@ -2,16 +2,18 @@
namespace Kirby\Cms;
use Exception;
use Kirby\Data\Data;
use Kirby\Exception\PermissionException;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Properties;
/**
* Each page, file or site can have multiple
* translated versions of their content,
* represented by this class
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class ContentTranslation
{
@@ -33,7 +35,7 @@ class ContentTranslation
protected $contentFile;
/**
* @var Page|Site|File
* @var Model
*/
protected $parent;
@@ -54,7 +56,7 @@ class ContentTranslation
}
/**
* Improve var_dump() output
* Improve `var_dump` output
*
* @return array
*/
@@ -127,9 +129,9 @@ class ContentTranslation
/**
* Returns the translation code as id
*
* @return void
* @return string
*/
public function id()
public function id(): string
{
return $this->code();
}
@@ -150,9 +152,9 @@ class ContentTranslation
}
/**
* Returns the parent Page, File or Site object
* Returns the parent page, file or site object
*
* @return Page|File|Site
* @return Kirby\Cms\Model
*/
public function parent()
{
@@ -163,7 +165,7 @@ class ContentTranslation
* @param string $code
* @return self
*/
protected function setCode(string $code): self
protected function setCode(string $code)
{
$this->code = $code;
return $this;
@@ -173,17 +175,17 @@ class ContentTranslation
* @param array $content
* @return self
*/
protected function setContent(array $content = null): self
protected function setContent(array $content = null)
{
$this->content = $content;
return $this;
}
/**
* @param Model $parent
* @param Kirby\Cms\Model $parent
* @return self
*/
protected function setParent(Model $parent): self
protected function setParent(Model $parent)
{
$this->parent = $parent;
return $this;
@@ -193,7 +195,7 @@ class ContentTranslation
* @param string $slug
* @return self
*/
protected function setSlug(string $slug = null): self
protected function setSlug(string $slug = null)
{
$this->slug = $slug;
return $this;

View File

@@ -2,14 +2,17 @@
namespace Kirby\Cms;
use Exception;
use Kirby\Toolkit\F;
/**
* Extension of the Toolkit Dir class with a new
* Dir::inventory method, that handles scanning directories
* Extension of the Toolkit `Dir` class with a new
* `Dir::inventory` method, that handles scanning directories
* and converts the results into our children, files and
* other page stuff.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Dir extends \Kirby\Toolkit\Dir
{
@@ -107,7 +110,7 @@ class Dir extends \Kirby\Toolkit\Dir
$content = array_unique($content);
}
$inventory = static::inventoryContent($dir, $inventory, $content);
$inventory = static::inventoryContent($inventory, $content);
$inventory = static::inventoryModels($inventory, $contentExtension, $multilang);
return $inventory;
@@ -122,7 +125,7 @@ class Dir extends \Kirby\Toolkit\Dir
* @param array $content
* @return array
*/
protected static function inventoryContent(string $dir, array $inventory, array $content): array
protected static function inventoryContent(array $inventory, array $content): array
{
// filter meta files from the content file

View File

@@ -3,13 +3,18 @@
namespace Kirby\Cms;
use Kirby\Exception\NotFoundException;
use Kirby\Toolkit\Tpl;
/**
* Wrapper around our PHPMailer package, which
* handles all the magic connections between Kirby
* and sending emails, like email templates, file
* attachments, etc.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Email
{
@@ -28,7 +33,7 @@ class Email
public function __construct($preset = [], array $props = [])
{
$this->options = $options = App::instance()->option('email');
$this->options = App::instance()->option('email');
// load presets from options
$this->preset = $this->preset($preset);
@@ -46,7 +51,11 @@ class Email
$this->template();
}
protected function preset($preset)
/**
* @param string|array $preset
* @return array
*/
protected function preset($preset): array
{
// only passed props, not preset name
if (is_string($preset) !== true) {
@@ -64,7 +73,7 @@ class Email
return $this->options['presets'][$preset];
}
protected function template()
protected function template(): void
{
if (isset($this->props['template']) === true) {
@@ -93,6 +102,13 @@ class Email
}
}
/**
* Undocumented function
*
* @param string $name
* @param string|null $type
* @return Kirby\Cms\Template
*/
protected function getTemplate(string $name, string $type = null)
{
return App::instance()->template('emails/' . $name, $type, 'text');
@@ -130,7 +146,7 @@ class Email
}
}
protected function transformProp($prop, $model)
protected function transformProp(string $prop, string $model): void
{
if (isset($this->props[$prop]) === true) {
$this->props[$prop] = $this->{'transform' . ucfirst($model)}($this->props[$prop]);

View File

@@ -21,8 +21,9 @@ use Kirby\Exception\InvalidArgumentException;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Field
{
@@ -53,7 +54,7 @@ class Field
* This will be the page, site, user or file
* to which the content belongs
*
* @var Site|Page|File|User
* @var Model
*/
protected $parent;
@@ -169,7 +170,7 @@ class Field
/**
* @see Field::parent()
* @return Page|File|Site|User
* @return Kirby\Cms\Model|null
*/
public function model()
{
@@ -200,7 +201,7 @@ class Field
/**
* Returns the parent object of the field
*
* @return Page|File|Site|User
* @return Kirby\Cms\Model|null
*/
public function parent()
{
@@ -228,12 +229,12 @@ class Field
}
/**
* Returns the field content
* Returns the field content. If a new value is passed,
* the modified field will be returned. Otherwise it
* will return the field value.
*
* @param string|Closure $value
* @return mixed If a new value is passed, the modified
* field will be returned. Otherwise it
* will return the field value.
* @return mixed
*/
public function value($value = null)
{

View File

@@ -2,14 +2,10 @@
namespace Kirby\Cms;
use Kirby\Data\Data;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Image\Image;
use Kirby\Toolkit\A;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Str;
use Throwable;
/**
* The `$file` object provides a set
@@ -27,8 +23,9 @@ use Throwable;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class File extends ModelWithContent
{
@@ -45,14 +42,14 @@ class File extends ModelWithContent
* This is used to do actual file
* method calls, like size, mime, etc.
*
* @var Image
* @var Kirby\Image\Image
*/
protected $asset;
/**
* Cache for the initialized blueprint object
*
* @var FileBlueprint
* @var Kirby\Cms\FileBlueprint
*/
protected $blueprint;
@@ -73,10 +70,18 @@ class File extends ModelWithContent
*/
public static $methods = [];
/**
* Registry with all File models
*
* @var array
*/
public static $models = [];
/**
* The parent object
*
* @var Model
* @var Kirby\Cms\Model
*/
protected $parent;
@@ -140,7 +145,7 @@ class File extends ModelWithContent
}
/**
* Improved var_dump() output
* Improved `var_dump` output
*
* @return array
*/
@@ -168,9 +173,9 @@ class File extends ModelWithContent
* Returns the Image object
*
* @internal
* @return Image
* @return Kirby\Image\Image
*/
public function asset(): Image
public function asset()
{
return $this->asset = $this->asset ?? new Image($this->root());
}
@@ -178,9 +183,9 @@ class File extends ModelWithContent
/**
* Returns the FileBlueprint object for the file
*
* @return FileBlueprint
* @return Kirby\Cms\FileBlueprint
*/
public function blueprint(): FileBlueprint
public function blueprint()
{
if (is_a($this->blueprint, 'Kirby\Cms\FileBlueprint') === true) {
return $this->blueprint;
@@ -260,6 +265,22 @@ class File extends ModelWithContent
}
}
/**
* Constructs a File object and also
* takes File models into account.
*
* @internal
* @return self
*/
public static function factory($props)
{
if (empty($props['model']) === false) {
return static::model($props['model'], $props);
}
return new static($props);
}
/**
* Returns the filename with extension
*
@@ -273,9 +294,9 @@ class File extends ModelWithContent
/**
* Returns the parent Files collection
*
* @return Files
* @return Kirby\Cms\Files
*/
public function files(): Files
public function files()
{
return $this->siblingsCollection();
}
@@ -303,7 +324,7 @@ class File extends ModelWithContent
/**
* Compares the current object with the given file object
*
* @param File $file
* @param Kirby\Cms\File $file
* @return bool
*/
public function is(File $file): bool
@@ -319,7 +340,7 @@ class File extends ModelWithContent
*/
public function mediaHash(): string
{
return crc32($this->filename()) . '-' . $this->modified();
return crc32($this->filename()) . '-' . $this->modifiedFile();
}
/**
@@ -347,24 +368,32 @@ class File extends ModelWithContent
/**
* @deprecated 3.0.0 Use `File::content()` instead
*
* @return Content
* @return Kirby\Cms\Content
*/
public function meta(): Content
public function meta()
{
return $this->content();
}
/**
* Returns the parent model.
* This is normally the parent page
* or the site object.
* Creates a file model if it has been registered
*
* @internal
* @return Site|Page
* @param string $name
* @param array $props
* @return Kirby\Cms\File
*/
public function model()
public static function model(string $name, array $props = [])
{
return $this->parent();
if ($class = (static::$models[$name] ?? null)) {
$object = new $class($props);
if (is_a($object, 'Kirby\Cms\File') === true) {
return $object;
}
}
return new static($props);
}
/**
@@ -376,8 +405,8 @@ class File extends ModelWithContent
*/
public function modified(string $format = null, string $handler = null)
{
$file = F::modified($this->root());
$content = F::modified($this->contentFile());
$file = $this->modifiedFile();
$content = $this->modifiedContent();
$modified = max($file, $content);
if (is_null($format) === true) {
@@ -389,10 +418,32 @@ class File extends ModelWithContent
return $handler($format, $modified);
}
/**
* Timestamp of the last modification
* of the content file
*
* @return integer
*/
protected function modifiedContent(): int
{
return F::modified($this->contentFile());
}
/**
* Timestamp of the last modification
* of the source file
*
* @return integer
*/
protected function modifiedFile(): int
{
return F::modified($this->root());
}
/**
* Returns the parent Page object
*
* @return Page
* @return Kirby\Cms\Page|null
*/
public function page()
{
@@ -440,53 +491,26 @@ class File extends ModelWithContent
$definition = array_merge($types[$this->type()] ?? [], $extensions[$this->extension()] ?? []);
return [
'type' => $definition['type'] ?? 'file',
'color' => $definition['color'] ?? $colorWhite,
'back' => $params['back'] ?? 'pattern',
'ratio' => $params['ratio'] ?? null,
];
$params['type'] = $definition['type'] ?? 'file';
$params['color'] = $definition['color'] ?? $colorWhite;
return parent::panelIcon($params);
}
/**
* Panel image definition
* Returns the image file object based on provided query
*
* @internal
* @param string|array|false $settings
* @param array $thumbSettings
* @return array
* @param string|null $query
* @return Kirby\Cms\File|Kirby\Cms\Asset|null
*/
public function panelImage($settings = null, array $thumbSettings = null): ?array
protected function panelImageSource(string $query = null)
{
$defaults = [
'ratio' => '3/2',
'back' => 'pattern',
'cover' => false
];
// switch the image off
if ($settings === false) {
return null;
if ($query === null && $this->isViewable()) {
return $this;
}
if (is_string($settings) === true) {
$settings = [
'query' => $settings
];
}
$image = $this->query($settings['query'] ?? null, 'Kirby\Cms\File');
if ($image === null && $this->isViewable() === true) {
$image = $this;
}
if ($image) {
$settings['url'] = $image->thumb($thumbSettings)->url(true);
unset($settings['query']);
}
return array_merge($defaults, (array)$settings);
return parent::panelImageSource($query);
}
/**
@@ -500,6 +524,38 @@ class File extends ModelWithContent
return 'files/' . $this->filename();
}
/**
* Prepares the response data for file pickers
* and file fields
*
* @param array|null $params
* @return array
*/
public function panelPickerData(array $params = []): array
{
$image = $this->panelImage($params['image'] ?? []);
$icon = $this->panelIcon($image);
$uuid = $this->id();
if (empty($params['model']) === false) {
$uuid = $this->parent() === $params['model'] ? $this->filename() : $this->id();
}
return [
'filename' => $this->filename(),
'dragText' => $this->dragText(),
'icon' => $icon,
'id' => $this->id(),
'image' => $image,
'info' => $this->toString($params['info'] ?? false),
'link' => $this->panelUrl(true),
'text' => $this->toString($params['text'] ?? '{{ file.filename }}'),
'type' => $this->type(),
'url' => $this->url(),
'uuid' => $uuid,
];
}
/**
* Returns the url to the editing view
* in the panel
@@ -516,7 +572,7 @@ class File extends ModelWithContent
/**
* Returns the parent Model object
*
* @return Model
* @return Kirby\Cms\Model
*/
public function parent()
{
@@ -541,9 +597,9 @@ class File extends ModelWithContent
/**
* Returns a collection of all parent pages
*
* @return Pages
* @return Kirby\Cms\Pages
*/
public function parents(): Pages
public function parents()
{
if (is_a($this->parent(), 'Kirby\Cms\Page') === true) {
return $this->parent()->parents()->prepend($this->parent()->id(), $this->parent());
@@ -555,40 +611,13 @@ class File extends ModelWithContent
/**
* Returns the permissions object for this file
*
* @return FilePermissions
* @return Kirby\Cms\FilePermissions
*/
public function permissions()
{
return new FilePermissions($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)
{
if ($query === null) {
return null;
}
$result = Str::query($query, [
'kirby' => $this->kirby(),
'site' => $this->site(),
'file' => $this
]);
if ($expect !== null && is_a($result, $expect) !== true) {
return null;
}
return $result;
}
/**
* Returns the absolute root to the file
*
@@ -603,7 +632,7 @@ class File extends ModelWithContent
* Returns the FileRules class to
* validate any important action.
*
* @return FileRules
* @return Kirby\Cms\FileRules
*/
protected function rules()
{
@@ -616,7 +645,7 @@ class File extends ModelWithContent
* @param array|null $blueprint
* @return self
*/
protected function setBlueprint(array $blueprint = null): self
protected function setBlueprint(array $blueprint = null)
{
if ($blueprint !== null) {
$blueprint['model'] = $this;
@@ -632,7 +661,7 @@ class File extends ModelWithContent
* @param string $filename
* @return self
*/
protected function setFilename(string $filename): self
protected function setFilename(string $filename)
{
$this->filename = $filename;
return $this;
@@ -641,10 +670,10 @@ class File extends ModelWithContent
/**
* Sets the parent model object
*
* @param Model $parent
* @param Kirby\Cms\Model $parent
* @return self
*/
protected function setParent(Model $parent = null): self
protected function setParent(Model $parent = null)
{
$this->parent = $parent;
return $this;
@@ -667,7 +696,7 @@ class File extends ModelWithContent
* @param string $template
* @return self
*/
protected function setTemplate(string $template = null): self
protected function setTemplate(string $template = null)
{
$this->template = $template;
return $this;
@@ -679,7 +708,7 @@ class File extends ModelWithContent
* @param string $url
* @return self
*/
protected function setUrl(string $url = null): self
protected function setUrl(string $url = null)
{
$this->url = $url;
return $this;
@@ -689,7 +718,7 @@ class File extends ModelWithContent
* Returns the parent Files collection
* @internal
*
* @return Files
* @return Kirby\Cms\Files
*/
protected function siblingsCollection()
{
@@ -699,9 +728,9 @@ class File extends ModelWithContent
/**
* Returns the parent Site object
*
* @return Site
* @return Kirby\Cms\Site
*/
public function site(): Site
public function site()
{
return is_a($this->parent(), 'Kirby\Cms\Site') === true ? $this->parent() : $this->kirby()->site();
}
@@ -720,7 +749,7 @@ class File extends ModelWithContent
* Returns siblings with the same template
*
* @param bool $self
* @return self
* @return Kirby\Cms\Files
*/
public function templateSiblings(bool $self = true)
{
@@ -739,25 +768,6 @@ class File extends ModelWithContent
return array_merge($this->asset()->toArray(), parent::toArray());
}
/**
* String template builder
*
* @param string|null $template
* @return string
*/
public function toString(string $template = null): string
{
if ($template === null) {
return $this->id();
}
return Str::template($template, [
'file' => $this,
'site' => $this->site(),
'kirby' => $this->kirby()
]);
}
/**
* Returns the Url
*

View File

@@ -3,13 +3,20 @@
namespace Kirby\Cms;
use Closure;
use Kirby\Data\Data;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\LogicException;
use Kirby\Image\Image;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Str;
/**
* FileActions
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
trait FileActions
{
@@ -21,7 +28,7 @@ trait FileActions
* @param bool $sanitize
* @return self
*/
public function changeName(string $name, bool $sanitize = true): self
public function changeName(string $name, bool $sanitize = true)
{
if ($sanitize === true) {
$name = F::safeName($name);
@@ -45,6 +52,9 @@ trait FileActions
throw new LogicException('The new file exists and cannot be overwritten');
}
// remove the lock of the old file
$oldFile->lock()->remove();
// remove all public versions
$oldFile->unpublish();
@@ -108,6 +118,29 @@ trait FileActions
return $result;
}
/**
* Copy the file to the given page
*
* @param Kirby\Cms\Page $page
* @return Kirby\Cms\File
*/
public function copy(Page $page)
{
F::copy($this->root(), $page->root() . '/' . $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));
}
return $page->clone()->file($this->filename());
}
/**
* Creates a new file on disk and returns the
* File object. The store is used to handle file
@@ -117,7 +150,7 @@ trait FileActions
* @param array $props
* @return self
*/
public static function create(array $props): self
public static function create(array $props)
{
if (isset($props['source'], $props['parent']) === false) {
throw new InvalidArgumentException('Please provide the "source" and "parent" props for the File');
@@ -126,8 +159,10 @@ trait FileActions
// prefer the filename from the props
$props['filename'] = F::safeName($props['filename'] ?? basename($props['source']));
$props['model'] = strtolower($props['template'] ?? 'default');
// create the basic file and a test upload object
$file = new static($props);
$file = File::factory($props);
$upload = new Image($props['source']);
// create a form for the file
@@ -176,8 +211,13 @@ trait FileActions
public function delete(): bool
{
return $this->commit('delete', [$this], function ($file) {
// remove all versions in the media folder
$file->unpublish();
// remove the lock of the old file
$file->lock()->remove();
if ($file->kirby()->multilang() === true) {
foreach ($file->translations() as $translation) {
F::remove($file->contentFile($translation->code()));
@@ -198,7 +238,7 @@ trait FileActions
*
* @return self
*/
public function publish(): self
public function publish()
{
Media::publish($this->root(), $this->mediaRoot());
return $this;
@@ -226,7 +266,7 @@ trait FileActions
* @param string $source
* @return self
*/
public function replace(string $source): self
public function replace(string $source)
{
return $this->commit('replace', [$this, new Image($source)], function ($file, $upload) {
@@ -248,7 +288,7 @@ trait FileActions
*
* @return self
*/
public function unpublish(): self
public function unpublish()
{
Media::unpublish($this->parent()->mediaRoot(), $this->filename());
return $this;

View File

@@ -5,6 +5,12 @@ namespace Kirby\Cms;
/**
* Extension of the basic blueprint class
* to handle all blueprints for files.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class FileBlueprint extends Blueprint
{
@@ -29,12 +35,18 @@ class FileBlueprint extends Blueprint
$this->props['accept'] = $this->normalizeAccept($this->props['accept'] ?? []);
}
/**
* @return array
*/
public function accept(): array
{
return $this->props['accept'];
}
protected function normalizeAccept($accept = null)
/**
* @return array
*/
protected function normalizeAccept($accept = null): array
{
if (is_string($accept) === true) {
$accept = [

View File

@@ -5,10 +5,15 @@ namespace Kirby\Cms;
use Kirby\Exception\BadMethodCallException;
use Kirby\Image\Image;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Properties;
/**
* Foundation for all file objects
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
trait FileFoundation
{
@@ -66,10 +71,10 @@ trait FileFoundation
/**
* Returns the Image object
*^
* @return Image
*
* @return Kirby\Image\Image
*/
public function asset(): Image
public function asset()
{
return $this->asset = $this->asset ?? new Image($this->root());
}
@@ -150,9 +155,9 @@ trait FileFoundation
/**
* Returns the app instance
*
* @return App
* @return Kirby\Cms\App
*/
public function kirby(): App
public function kirby()
{
return App::instance();
}
@@ -225,7 +230,7 @@ trait FileFoundation
*
* @return string|null
*/
public function type()
public function type(): ?string
{
return F::type($this->root());
}

View File

@@ -2,8 +2,16 @@
namespace Kirby\Cms;
use Kirby\Exception\InvalidArgumentException;
/**
* Resizing, blurring etc
* Resizing, blurring etc.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
trait FileModifications
{
@@ -12,7 +20,7 @@ trait FileModifications
* Blurs the image by the given amount of pixels
*
* @param boolean $pixels
* @return self
* @return Kirby\Cms\FileVersion|Kirby\Cms\File
*/
public function blur($pixels = true)
{
@@ -22,7 +30,7 @@ trait FileModifications
/**
* Converts the image to black and white
*
* @return self
* @return Kirby\Cms\FileVersion|Kirby\Cms\File
*/
public function bw()
{
@@ -35,7 +43,7 @@ trait FileModifications
* @param integer $width
* @param integer $height
* @param string|array $options
* @return self
* @return Kirby\Cms\FileVersion|Kirby\Cms\File
*/
public function crop(int $width, int $height = null, $options = null)
{
@@ -65,7 +73,7 @@ trait FileModifications
* Sets the JPEG compression quality
*
* @param integer $quality
* @return self
* @return Kirby\Cms\FileVersion|Kirby\Cms\File
*/
public function quality(int $quality)
{
@@ -79,7 +87,7 @@ trait FileModifications
* @param integer $width
* @param integer $height
* @param integer $quality
* @return self
* @return Kirby\Cms\FileVersion|Kirby\Cms\File
*/
public function resize(int $width = null, int $height = null, int $quality = null)
{
@@ -97,7 +105,7 @@ trait FileModifications
* @since 3.1.0
*
* @param array|string $sizes
* @return string
* @return string|null
*/
public function srcset($sizes = null): ?string
{
@@ -116,15 +124,22 @@ trait FileModifications
$set = [];
foreach ($sizes as $key => $value) {
if (is_string($value) === true) {
$size = $key;
$attr = $value;
if (is_array($value)) {
$options = $value;
$condition = $key;
} elseif (is_string($value) === true) {
$options = [
'width' => $key
];
$condition = $value;
} else {
$size = $value;
$attr = $value . 'w';
$options = [
'width' => $value
];
$condition = $value . 'w';
}
$set[] = $this->resize($size)->url() . ' ' . $attr;
$set[] = $this->thumb($options)->url() . ' ' . $condition;
}
return implode(', ', $set);
@@ -135,12 +150,12 @@ trait FileModifications
* The media manager takes care of generating
* those modified versions and putting them
* in the right place. This is normally the
* /media folder of your installation, but
* `/media` folder of your installation, but
* could potentially also be a CDN or any other
* place.
*
* @param array|null|string $options
* @return FileVersion|File
* @return Kirby\Cms\FileVersion|Kirby\Cms\File
*/
public function thumb($options = null)
{

View File

@@ -2,6 +2,15 @@
namespace Kirby\Cms;
/**
* FilePermissions
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class FilePermissions extends ModelPermissions
{
protected $category = 'files';

View File

@@ -12,6 +12,12 @@ use Kirby\Toolkit\V;
/**
* Validators for all file actions
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class FileRules
{

View File

@@ -4,6 +4,15 @@ namespace Kirby\Cms;
use Kirby\Toolkit\Properties;
/**
* FileVersion
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class FileVersion
{
use FileFoundation;
@@ -39,7 +48,10 @@ class FileVersion
return dirname($this->original()->id()) . '/' . $this->filename();
}
public function kirby(): App
/**
* @return Kirby\Cms\App
*/
public function kirby()
{
return $this->original()->kirby();
}

View File

@@ -22,8 +22,9 @@ use Kirby\Toolkit\Str;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Filename
{

View File

@@ -12,8 +12,9 @@ namespace Kirby\Cms;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Files extends Collection
{
@@ -31,7 +32,7 @@ class Files extends Collection
* current collection
*
* @param mixed $item
* @return Files
* @return self
*/
public function add($object)
{
@@ -75,9 +76,9 @@ class Files extends Collection
* Creates a files collection from an array of props
*
* @param array $files
* @param Model $parent
* @param Kirby\Cms\Model $parent
* @param array $inject
* @return Files
* @return self
*/
public static function factory(array $files, Model $parent)
{
@@ -89,7 +90,7 @@ class Files extends Collection
$props['kirby'] = $kirby;
$props['parent'] = $parent;
$file = new File($props);
$file = File::factory($props);
$collection->data[$file->id()] = $file;
}
@@ -101,9 +102,9 @@ class Files extends Collection
* Tries to find a file by id/filename
*
* @param string $id
* @return File|null
* @return Kirby\Cms\File|null
*/
public function findById($id)
public function findById(string $id)
{
return $this->get(ltrim($this->parent->id() . '/' . $id, '/'));
}
@@ -114,9 +115,9 @@ class Files extends Collection
* map the get method correctly.
*
* @param string $key
* @return File|null
* @return Kirby\Cms\File|null
*/
public function findByKey($key)
public function findByKey(string $key)
{
return $this->findById($key);
}
@@ -127,7 +128,7 @@ class Files extends Collection
* @param null|string|array $template
* @return self
*/
public function template($template): self
public function template($template)
{
if (empty($template) === true) {
return $this;

View File

@@ -8,6 +8,12 @@ use Kirby\Form\Form as BaseForm;
* Extension of `Kirby\Form\Form` that introduces
* a Form::for method that creates a proper form
* definition for any Cms Model.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Form extends BaseForm
{
@@ -38,6 +44,11 @@ class Form extends BaseForm
parent::__construct($props);
}
/**
* @param Kirby\Cms\Model $model
* @param array $props
* @return self
*/
public static function for(Model $model, array $props = [])
{
// get the original model data

View File

@@ -4,27 +4,36 @@ namespace Kirby\Cms;
use Kirby\Toolkit\Str;
/**
* HasChildren
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
trait HasChildren
{
/**
* The Pages collection
*
* @var Pages
* @var Kirby\Cms\Pages
*/
public $children;
/**
* The list of available drafts
*
* @var Pages
* @var Kirby\Cms\Pages
*/
public $drafts;
/**
* Returns the Pages collection
*
* @return Pages
* @return Kirby\Cms\Pages
*/
public function children()
{
@@ -38,7 +47,7 @@ trait HasChildren
/**
* Returns all children and drafts at the same time
*
* @return Pages
* @return Kirby\Cms\Pages
*/
public function childrenAndDrafts()
{
@@ -60,7 +69,7 @@ trait HasChildren
* Searches for a child draft by id
*
* @param string $path
* @return Page|null
* @return Kirby\Cms\Page|null
*/
public function draft(string $path)
{
@@ -93,7 +102,7 @@ trait HasChildren
/**
* Return all drafts of the model
*
* @return Pages
* @return Kirby\Cms\Pages
*/
public function drafts()
{
@@ -118,7 +127,7 @@ trait HasChildren
* Finds one or multiple children by id
*
* @param string ...$arguments
* @return Pages
* @return Kirby\Cms\Page|Kirby\Cms\Pages
*/
public function find(...$arguments)
{
@@ -128,7 +137,7 @@ trait HasChildren
/**
* Finds a single page or draft
*
* @return Page|null
* @return Kirby\Cms\Page|null
*/
public function findPageOrDraft(string $path)
{
@@ -138,9 +147,9 @@ trait HasChildren
/**
* Returns a collection of all children of children
*
* @return Pages
* @return Kirby\Cms\Pages
*/
public function grandChildren(): Pages
public function grandChildren()
{
return $this->children()->children();
}
@@ -171,7 +180,7 @@ trait HasChildren
*/
public function hasInvisibleChildren(): bool
{
return $this->children()->invisible()->count() > 0;
return $this->hasUnlistedChildren();
}
/**
@@ -200,16 +209,16 @@ trait HasChildren
*/
public function hasVisibleChildren(): bool
{
return $this->children()->listed()->count() > 0;
return $this->hasListedChildren();
}
/**
* Creates a flat child index
*
* @param bool $drafts
* @return Pages
* @return Kirby\Cms\Pages
*/
public function index(bool $drafts = false): Pages
public function index(bool $drafts = false)
{
if ($drafts === true) {
return $this->childrenAndDrafts()->index($drafts);

View File

@@ -2,24 +2,31 @@
namespace Kirby\Cms;
use Kirby\Toolkit\Str;
/**
* HasFiles
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
trait HasFiles
{
/**
* The Files collection
*
* @var Files
* @var Kirby\Cms\Files
*/
protected $files;
/**
* Filters the Files collection by type audio
*
* @return Files
* @return Kirby\Cms\Files
*/
public function audio(): Files
public function audio()
{
return $this->files()->filterBy('type', '==', 'audio');
}
@@ -27,9 +34,9 @@ trait HasFiles
/**
* Filters the Files collection by type code
*
* @return Files
* @return Kirby\Cms\Files
*/
public function code(): Files
public function code()
{
return $this->files()->filterBy('type', '==', 'code');
}
@@ -49,7 +56,7 @@ trait HasFiles
* Creates a new file
*
* @param array $props
* @return File
* @return Kirby\Cms\File
*/
public function createFile(array $props)
{
@@ -64,9 +71,9 @@ trait HasFiles
/**
* Filters the Files collection by type documents
*
* @return Files
* @return Kirby\Cms\Files
*/
public function documents(): Files
public function documents()
{
return $this->files()->filterBy('type', '==', 'document');
}
@@ -76,7 +83,7 @@ trait HasFiles
*
* @param string $filename
* @param string $in
* @return File
* @return Kirby\Cms\File|null
*/
public function file(string $filename = null, string $in = 'files')
{
@@ -101,9 +108,9 @@ trait HasFiles
/**
* Returns the Files collection
*
* @return Files
* @return Kirby\Cms\Files
*/
public function files(): Files
public function files()
{
if (is_a($this->files, 'Kirby\Cms\Files') === true) {
return $this->files;
@@ -176,7 +183,7 @@ trait HasFiles
* Returns a specific image by filename or the first one
*
* @param string $filename
* @return File
* @return Kirby\Cms\File|null
*/
public function image(string $filename = null)
{
@@ -186,9 +193,9 @@ trait HasFiles
/**
* Filters the Files collection by type image
*
* @return Files
* @return Kirby\Cms\Files
*/
public function images(): Files
public function images()
{
return $this->files()->filterBy('type', '==', 'image');
}
@@ -196,10 +203,10 @@ trait HasFiles
/**
* Sets the Files collection
*
* @param Files|null $files
* @param Kirby\Cms\Files|null $files
* @return self
*/
protected function setFiles(array $files = null): self
protected function setFiles(array $files = null)
{
if ($files !== null) {
$this->files = Files::factory($files, $this);
@@ -211,9 +218,9 @@ trait HasFiles
/**
* Filters the Files collection by type videos
*
* @return Files
* @return Kirby\Cms\Files
*/
public function videos(): Files
public function videos()
{
return $this->files()->filterBy('type', '==', 'video');
}

View File

@@ -2,6 +2,15 @@
namespace Kirby\Cms;
/**
* HasMethods
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
trait HasMethods
{

View File

@@ -8,8 +8,9 @@ namespace Kirby\Cms;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
trait HasSiblings
{
@@ -27,7 +28,7 @@ trait HasSiblings
/**
* Returns the next item in the collection if available
*
* @return Model|null
* @return Kirby\Cms\Model|null
*/
public function next()
{
@@ -37,7 +38,7 @@ trait HasSiblings
/**
* Returns the end of the collection starting after the current item
*
* @return Collection
* @return Kirby\Cms\Collection
*/
public function nextAll()
{
@@ -47,7 +48,7 @@ trait HasSiblings
/**
* Returns the previous item in the collection if available
*
* @return Model|null
* @return Kirby\Cms\Model|null
*/
public function prev()
{
@@ -57,7 +58,7 @@ trait HasSiblings
/**
* Returns the beginning of the collection before the current item
*
* @return Collection
* @return Kirby\Cms\Collection
*/
public function prevAll()
{
@@ -68,7 +69,7 @@ trait HasSiblings
* Returns all sibling elements
*
* @param bool $self
* @return Collection
* @return Kirby\Cms\Collection
*/
public function siblings(bool $self = true)
{

View File

@@ -9,21 +9,22 @@ namespace Kirby\Cms;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Html extends \Kirby\Toolkit\Html
{
/**
* Generates an a tag with an absolute Url
* Generates an `a` tag with an absolute Url
*
* @param string $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 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 a(string $href = null, $text = null, array $attr = []): string
public static function link(string $href = null, $text = null, array $attr = []): string
{
return parent::a(Url::to($href), $text, $attr);
return parent::link(Url::to($href), $text, $attr);
}
}

View File

@@ -2,13 +2,17 @@
namespace Kirby\Cms;
use Closure;
/**
* The Ingredients class is the foundation for
* $kirby->urls() and $kirby->roots() objects.
* `$kirby->urls()` and `$kirby->roots()` objects.
* Those are configured in `kirby/config/urls.php`
* and `kirby/config/roots.php`
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Ingredients
{
@@ -41,7 +45,7 @@ class Ingredients
}
/**
* Improved var_dump output
* Improved `var_dump` output
*
* @return array
*/
@@ -69,7 +73,7 @@ class Ingredients
* @param array $ingredients
* @return self
*/
public static function bake(array $ingredients): self
public static function bake(array $ingredients)
{
foreach ($ingredients as $name => $ingredient) {
if (is_a($ingredient, 'Closure') === true) {

View File

@@ -5,6 +5,12 @@ namespace Kirby\Cms;
/**
* Extended KirbyTag class to provide
* common helpers for tag objects
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class KirbyTag extends \Kirby\Text\KirbyTag
{
@@ -16,9 +22,9 @@ class KirbyTag extends \Kirby\Text\KirbyTag
* Afterwards it uses Kirby's global file finder.
*
* @param string $path
* @return File|null
* @return Kirby\Cms\File|null
*/
public function file(string $path): ?File
public function file(string $path)
{
$parent = $this->parent();
@@ -36,9 +42,9 @@ class KirbyTag extends \Kirby\Text\KirbyTag
/**
* Returns the current Kirby instance
*
* @return App
* @return Kirby\Cms\App
*/
public function kirby(): App
public function kirby()
{
return $this->data['kirby'] ?? App::instance();
}
@@ -46,7 +52,7 @@ class KirbyTag extends \Kirby\Text\KirbyTag
/**
* Returns the parent model
*
* @return Page|Site|File|User
* @return Kirby\Cms\Model|null
*/
public function parent()
{

View File

@@ -2,11 +2,15 @@
namespace Kirby\Cms;
use Exception;
/**
* Extension of `Kirby\Text\KirbyTags` that introduces
* `kirbytags:before` and `kirbytags:after` hooks
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class KirbyTags extends \Kirby\Text\KirbyTags
{
@@ -42,9 +46,9 @@ class KirbyTags extends \Kirby\Text\KirbyTags
* @param string $text
* @param array $data
* @param array $options
* @return string
* @return string|null
*/
protected static function hooks(array $hooks, string $text = null, array $data, array $options)
protected static function hooks(array $hooks, string $text = null, array $data, array $options): ?string
{
foreach ($hooks as $hook) {
$text = $hook->call($data['kirby'], $text, $data, $options);

View File

@@ -6,7 +6,6 @@ use Kirby\Data\Data;
use Kirby\Exception\DuplicateException;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\LogicException;
use Kirby\Exception\PermissionException;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Str;
@@ -24,8 +23,9 @@ use Throwable;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Language extends Model
{
@@ -55,6 +55,11 @@ class Language extends Model
*/
protected $name;
/**
* @var array|null
*/
protected $slugs;
/**
* @var array|null
*/
@@ -81,13 +86,14 @@ class Language extends Model
'direction',
'locale',
'name',
'slugs',
'translations',
'url',
]);
}
/**
* Improved var_dump output
* Improved `var_dump` output
*
* @return array
*/
@@ -167,12 +173,11 @@ class Language extends Model
* @param array $props
* @return self
*/
public static function create(array $props): self
public static function create(array $props)
{
$props['code'] = Str::slug($props['code'] ?? null);
$kirby = App::instance();
$languages = $kirby->languages();
$site = $kirby->site();
// make the first language the default language
if ($languages->count() === 0) {
@@ -181,9 +186,8 @@ class Language extends Model
$language = new static($props);
if ($language->exists() === true) {
throw new DuplicateException('The language already exists');
}
// validate the new language
LanguageRules::create($language);
$language->save();
@@ -209,7 +213,6 @@ class Language extends Model
$kirby = App::instance();
$languages = $kirby->languages();
$site = $kirby->site();
$code = $this->code();
if (F::remove($this->root()) !== true) {
@@ -347,13 +350,50 @@ class Language extends Model
return App::instance()->root('languages') . '/' . $this->code() . '.php';
}
/**
* Returns the LanguageRouter instance
* which is used to handle language specific
* routes.
*
* @return Kirby\Cms\LanguageRouter
*/
public function router()
{
return new LanguageRouter($this);
}
/**
* Get slug rules for language
*
* @internal
* @return array
*/
public function rules(): array
{
$code = $this->locale(LC_CTYPE);
$code = Str::contains($code, '.') ? Str::before($code, '.') : $code;
$file = $this->kirby()->root('i18n:rules') . '/' . $code . '.json';
if (F::exists($file) === false) {
$file = $this->kirby()->root('i18n:rules') . '/' . Str::before($code, '_') . '.json';
}
try {
$data = Data::read($file);
} catch (\Exception $e) {
$data = [];
}
return array_merge($data, $this->slugs());
}
/**
* Saves the language settings in the languages folder
*
* @internal
* @return self
*/
public function save(): self
public function save()
{
try {
$existingData = Data::read($this->root());
@@ -384,9 +424,9 @@ class Language extends Model
* @param string $code
* @return self
*/
protected function setCode(string $code): self
protected function setCode(string $code)
{
$this->code = $code;
$this->code = trim($code);
return $this;
}
@@ -394,7 +434,7 @@ class Language extends Model
* @param boolean $default
* @return self
*/
protected function setDefault(bool $default = false): self
protected function setDefault(bool $default = false)
{
$this->default = $default;
return $this;
@@ -404,7 +444,7 @@ class Language extends Model
* @param string $direction
* @return self
*/
protected function setDirection(string $direction = 'ltr'): self
protected function setDirection(string $direction = 'ltr')
{
$this->direction = $direction === 'rtl' ? 'rtl' : 'ltr';
return $this;
@@ -414,7 +454,7 @@ class Language extends Model
* @param string|array $locale
* @return self
*/
protected function setLocale($locale = null): self
protected function setLocale($locale = null)
{
if (is_array($locale)) {
$this->locale = $locale;
@@ -433,9 +473,19 @@ class Language extends Model
* @param string $name
* @return self
*/
protected function setName(string $name = null): self
protected function setName(string $name = null)
{
$this->name = $name ?? $this->code;
$this->name = trim($name ?? $this->code);
return $this;
}
/**
* @param array $slug
* @return self
*/
protected function setSlugs(array $slugs = null)
{
$this->slugs = $slugs ?? [];
return $this;
}
@@ -443,7 +493,7 @@ class Language extends Model
* @param array $translations
* @return self
*/
protected function setTranslations(array $translations = null): self
protected function setTranslations(array $translations = null)
{
$this->translations = $translations ?? [];
return $this;
@@ -453,12 +503,22 @@ class Language extends Model
* @param string $url
* @return self
*/
protected function setUrl(string $url = null): self
protected function setUrl(string $url = null)
{
$this->url = $url;
return $this;
}
/**
* Returns the custom slug rules for this language
*
* @return array
*/
public function slugs(): array
{
return $this->slugs;
}
/**
* Returns the most important
* properties as array
@@ -473,6 +533,7 @@ class Language extends Model
'direction' => $this->direction(),
'locale' => $this->locale(),
'name' => $this->name(),
'rules' => $this->rules(),
'url' => $this->url()
];
}
@@ -504,7 +565,7 @@ class Language extends Model
* @param array $props
* @return self
*/
public function update(array $props = null): self
public function update(array $props = null)
{
$props['slug'] = Str::slug($props['slug'] ?? null);
$kirby = App::instance();

132
kirby/src/Cms/LanguageRouter.php Executable file
View File

@@ -0,0 +1,132 @@
<?php
namespace Kirby\Cms;
use Exception;
use Kirby\Exception\NotFoundException;
use Kirby\Http\Router;
use Kirby\Toolkit\A;
use Kirby\Toolkit\Str;
/**
* The language router is used internally
* to handle language-specific (scoped) routes
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class LanguageRouter
{
/**
* The parent language
*
* @var Language
*/
protected $language;
/**
* The router instance
*
* @var Router
*/
protected $router;
/**
* Creates a new language router instance
* for the given language
*
* @param Kirby\Cms\Language $language
*/
public function __construct(Language $language)
{
$this->language = $language;
}
/**
* Fetches all scoped routes for the
* current language from the Kirby instance
*
* @return array
*/
public function routes(): array
{
$language = $this->language;
$kirby = $language->kirby();
$routes = $kirby->routes();
// only keep the scoped language routes
$routes = array_values(array_filter($routes, function ($route) use ($language) {
// no language scope
if (empty($route['language']) === true) {
return false;
}
// wildcard
if ($route['language'] === '*') {
return true;
}
// get all applicable languages
$languages = Str::split(strtolower($route['language']), '|');
// validate the language
return in_array($language->code(), $languages) === true;
}));
// add the page-scope if necessary
foreach ($routes as $index => $route) {
if ($pageId = ($route['page'] ?? null)) {
if ($page = $kirby->page($pageId)) {
// convert string patterns to arrays
$patterns = A::wrap($route['pattern']);
// prefix all patterns with the page slug
$patterns = array_map(function ($pattern) use ($page, $language) {
return $page->uri($language) . '/' . $pattern;
}, $patterns);
// reinject the pattern and the full page object
$routes[$index]['pattern'] = $patterns;
$routes[$index]['page'] = $page;
} else {
throw new NotFoundException('The page "' . $pageId . '" does not exist');
}
}
}
return $routes;
}
/**
* 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)
{
$language = $this->language;
$kirby = $language->kirby();
$router = new Router($this->routes());
try {
return $router->call($path, $kirby->request()->method(), function ($route) use ($language) {
if ($page = $route->page()) {
return $route->action()->call($route, $language, $page, ...$route->arguments());
} else {
return $route->action()->call($route, $language, ...$route->arguments());
}
});
} catch (Exception $e) {
return $kirby->resolve($path, $language->code());
}
}
}

55
kirby/src/Cms/LanguageRules.php Executable file
View File

@@ -0,0 +1,55 @@
<?php
namespace Kirby\Cms;
use Kirby\Exception\DuplicateException;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\LogicException;
use Kirby\Exception\PermissionException;
use Kirby\Toolkit\Str;
/**
* Validators for all language actions
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class LanguageRules
{
public static function create(Language $language): bool
{
if (Str::length($language->code()) < 2) {
throw new InvalidArgumentException([
'key' => 'language.code',
'data' => [
'code' => $language->code(),
'name' => $language->name()
]
]);
}
if (Str::length($language->name()) < 1) {
throw new InvalidArgumentException([
'key' => 'language.name',
'data' => [
'code' => $language->code(),
'name' => $language->name()
]
]);
}
if ($language->exists() === true) {
throw new DuplicateException([
'key' => 'language.duplicate',
'data' => [
'code' => $language->code()
]
]);
}
return true;
}
}

View File

@@ -6,6 +6,12 @@ use Kirby\Toolkit\F;
/**
* A collection of all defined site languages
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Languages extends Collection
{
@@ -25,9 +31,9 @@ class Languages extends Collection
*
* @internal
* @param array $props
* @return Language
* @return Kirby\Cms\Language
*/
public function create(array $props): Language
public function create(array $props)
{
return Language::create($props);
}
@@ -35,9 +41,9 @@ class Languages extends Collection
/**
* Returns the default language
*
* @return Language|null
* @return Kirby\Cms\Language|null
*/
public function default(): ?Language
public function default()
{
if ($language = $this->findBy('isDefault', true)) {
return $language;
@@ -48,9 +54,9 @@ class Languages extends Collection
/**
* @deprecated 3.0.0 Use `Languages::default()`instead
* @return Language|null
* @return Kirby\Cms\Language|null
*/
public function findDefault(): ?Language
public function findDefault()
{
return $this->default();
}
@@ -61,7 +67,7 @@ class Languages extends Collection
* @internal
* @return self
*/
public static function load(): self
public static function load()
{
$languages = new static;
$files = glob(App::instance()->root('languages') . '/*.php');

View File

@@ -5,71 +5,29 @@ namespace Kirby\Cms;
use Kirby\Data\Data;
use Kirby\Toolkit\Dir;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Str;
use Throwable;
/**
* Handles all tasks to get the Media API
* up and running and link files correctly
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Media
{
/**
* Tries to find a job file for the
* given filename and then calls the thumb
* component to create a thumbnail accordingly
*
* @param Model $model
* @param string $hash
* @param string $filename
* @return Response|false
*/
public static function thumb($model, string $hash, string $filename)
{
$kirby = App::instance();
if (is_string($model) === true) {
// assets
$root = $kirby->root('media') . '/assets/' . $model . '/' . $hash;
} else {
// model files
$root = $model->mediaRoot() . '/' . $hash;
}
try {
$thumb = $root . '/' . $filename;
$job = $root . '/.jobs/' . $filename . '.json';
$options = Data::read($job);
if (empty($options) === true) {
return false;
}
if (is_string($model) === true) {
$source = $kirby->root('index') . '/' . $model . '/' . $options['filename'];
} else {
$source = $model->file($options['filename'])->root();
}
$kirby->thumb($source, $thumb, $options);
F::remove($job);
return Response::file($thumb);
} catch (Throwable $e) {
return false;
}
}
/**
* Tries to find a file by model and filename
* and to copy it to the media folder.
*
* @param Model $model
* @param Kirby\Cms\Model $model
* @param string $hash
* @param string $filename
* @return Response|false
* @return Kirby\Cms\Response|false
*/
public static function link(Model $model = null, string $hash, string $filename)
{
@@ -117,6 +75,56 @@ class Media
return F::copy($src, $dest, true);
}
/**
* 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 $model
* @param string $hash
* @param string $filename
* @return Kirby\Cms\Response|false
*/
public static function thumb($model, string $hash, string $filename)
{
$kirby = App::instance();
if (is_string($model) === true) {
// assets
$root = $kirby->root('media') . '/assets/' . $model . '/' . $hash;
} else {
// model files
$root = $model->mediaRoot() . '/' . $hash;
}
try {
$thumb = $root . '/' . $filename;
$job = $root . '/.jobs/' . $filename . '.json';
$options = Data::read($job);
if (empty($options) === true) {
return false;
}
if (is_string($model) === true) {
$source = $kirby->root('index') . '/' . $model . '/' . $options['filename'];
} else {
$source = $model->file($options['filename'])->root();
}
try {
$kirby->thumb($source, $thumb, $options);
F::remove($job);
return Response::file($thumb);
} catch (Throwable $e) {
F::remove($thumb);
return Response::file($source);
}
} catch (Throwable $e) {
return false;
}
}
/**
* Deletes all versions of the given filename
* within the parent directory

View File

@@ -2,13 +2,16 @@
namespace Kirby\Cms;
use stdClass;
use ReflectionMethod;
use Kirby\Toolkit\Properties;
use Kirby\Toolkit\Str;
/**
* Foundation for Page, Site, File and User models.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
abstract class Model
{
@@ -17,14 +20,14 @@ abstract class Model
/**
* The parent Kirby instance
*
* @var App
* @var Kirby\Cms\App
*/
public static $kirby;
/**
* The parent Site instance
* The parent site instance
*
* @var Site
* @var Kirby\Cms\Site
*/
protected $site;
@@ -34,7 +37,7 @@ abstract class Model
*
* @return string
*/
public function __toString()
public function __toString(): string
{
return $this->id();
}
@@ -52,9 +55,9 @@ abstract class Model
/**
* Returns the parent Kirby instance
*
* @return App|null
* @return Kirby\Cms\App
*/
public function kirby(): App
public function kirby()
{
return static::$kirby = static::$kirby ?? App::instance();
}
@@ -62,7 +65,7 @@ abstract class Model
/**
* Returns the parent Site instance
*
* @return Site|null
* @return Kirby\Cms\Site
*/
public function site()
{
@@ -72,7 +75,7 @@ abstract class Model
/**
* Setter for the parent Kirby object
*
* @param Kirby|null $kirby
* @param Kirby\Cms\App|null $kirby
* @return self
*/
protected function setKirby(App $kirby = null)
@@ -82,10 +85,10 @@ abstract class Model
}
/**
* Setter for the parent Site object
* Setter for the parent site object
*
* @internal
* @param Site|null $site
* @param Kirby\Cms\Site|null $site
* @return self
*/
public function setSite(Site $site = null)

View File

@@ -2,6 +2,17 @@
namespace Kirby\Cms;
use Kirby\Toolkit\A;
/**
* ModelPermissions
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
abstract class ModelPermissions
{
protected $category;
@@ -10,7 +21,7 @@ abstract class ModelPermissions
protected $permissions;
protected $user;
public function __call(string $method, array $arguments = [])
public function __call(string $method, array $arguments = []): bool
{
return $this->can($method);
}
@@ -24,7 +35,7 @@ abstract class ModelPermissions
}
/**
* Improved var_dump output
* Improved `var_dump` output
*
* @return array
*/
@@ -35,16 +46,32 @@ abstract class ModelPermissions
public function can(string $action): bool
{
if ($this->user->role()->id() === 'nobody') {
$role = $this->user->role()->id();
if ($role === 'nobody') {
return false;
}
// check for a custom overall can method
if (method_exists($this, 'can' . $action) === true && $this->{'can' . $action}() === false) {
return false;
}
if (isset($this->options[$action]) === true && $this->options[$action] === false) {
return false;
// evaluate the blueprint options block
if (isset($this->options[$action]) === true) {
$options = $this->options[$action];
if ($options === false) {
return false;
}
if ($options === true) {
return true;
}
if (is_array($options) === true && A::isAssociative($options) === true) {
return $options[$role] ?? $options['*'] ?? false;
}
}
return $this->permissions->for($this->category, $action);

View File

@@ -4,33 +4,38 @@ namespace Kirby\Cms;
use Closure;
use Kirby\Data\Data;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\LogicException;
use Kirby\Toolkit\A;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Str;
use Throwable;
/**
* ModelWithContent
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
abstract class ModelWithContent extends Model
{
/**
* The content
*
* @var Content
* @var Kirby\Cms\Content
*/
public $content;
/**
* @var Translations
* @var Kirby\Cms\Translations
*/
public $translations;
/**
* Returns the blueprint of the model
*
* @return Blueprint
* @return Kirby\Cms\Blueprint
*/
abstract public function blueprint();
@@ -48,9 +53,9 @@ abstract class ModelWithContent extends Model
* Returns the content
*
* @param string $languageCode
* @return Content
* @return Kirby\Cms\Content
*/
public function content(string $languageCode = null): Content
public function content(string $languageCode = null)
{
// single language support
@@ -120,6 +125,26 @@ abstract class ModelWithContent extends Model
}
}
/**
* Returns an array with all content files
*
* @return array
*/
public function contentFiles(): array
{
if ($this->kirby()->multilang() === true) {
$files = [];
foreach ($this->kirby()->languages()->codes() as $code) {
$files[] = $this->contentFile($code);
}
return $files;
} else {
return [
$this->contentFile()
];
}
}
/**
* Prepares the content that should be written
* to the text file
@@ -232,6 +257,148 @@ abstract class ModelWithContent extends Model
return Form::for($this)->hasErrors() === false;
}
/**
* Returns the lock object for this model
*
* @return Kirby\Cms\ContentLock
*/
public function lock()
{
return new ContentLock($this);
}
/**
* Returns the panel icon definition
*
* @internal
* @param array $params
* @return array
*/
public function panelIcon(array $params = null): array
{
$defaults = [
'type' => 'page',
'ratio' => null,
'back' => 'pattern',
'color' => '#c5c9c6',
];
return array_merge($defaults, $params ?? []);
}
/**
* @internal
* @param string|array|false $settings
* @return array|null
*/
public function panelImage($settings = null): ?array
{
$defaults = [
'ratio' => '3/2',
'back' => 'pattern',
'cover' => false
];
// switch the image off
if ($settings === false) {
return null;
}
if (is_string($settings) === true) {
$settings = [
'query' => $settings
];
}
if ($image = $this->panelImageSource($settings['query'] ?? null)) {
// main url
$settings['url'] = $image->url();
// for cards
$settings['cards'] = [
'url' => 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw',
'srcset' => $image->srcset([
352,
864,
1408,
])
];
// for lists
$settings['list'] = [
'url' => 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw',
'srcset' => $image->srcset([
'1x' => [
'width' => 38,
'height' => 38,
'crop' => 'center'
],
'2x' => [
'width' => 76,
'height' => 76,
'crop' => 'center'
],
])
];
unset($settings['query']);
}
return array_merge($defaults, (array)$settings);
}
/**
* Returns the image file object based on provided query
*
* @internal
* @param string|null $query
* @return Kirby\Cms\File|Kirby\Cms\Asset|null
*/
protected function panelImageSource(string $query = null)
{
$image = $this->query($query ?? null);
// validate the query result
if (is_a($image, File::class) === false && is_a($image, Asset::class) === false) {
$image = null;
}
// fallback for files
if ($image === null && is_a($this, File::class) === true && $this->isViewable() === true) {
$image = $this;
}
return $image;
}
/**
* 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)
{
if ($query === null) {
return null;
}
$result = Str::query($query, [
'kirby' => $this->kirby(),
'site' => is_a($this, Site::class) ? $this : $this->site(),
static::CLASS_ALIAS => $this
]);
if ($expect !== null && is_a($result, $expect) !== true) {
return null;
}
return $result;
}
/**
* Read the content from the content file
*
@@ -251,7 +418,7 @@ abstract class ModelWithContent extends Model
/**
* Returns the absolute path to the model
*
* @return string
* @return string|null
*/
abstract public function root(): ?string;
@@ -330,7 +497,7 @@ abstract class ModelWithContent extends Model
/**
* Sets the Content object
*
* @param Content|null $content
* @param array|null $content
* @return self
*/
protected function setContent(array $content = null)
@@ -364,12 +531,33 @@ abstract class ModelWithContent extends Model
return $this;
}
/**
* String template builder
*
* @param string|null $template
* @return string
*/
public function toString(string $template = null): string
{
if ($template === null) {
return $this->id();
}
$result = Str::template($template, [
'kirby' => $this->kirby(),
'site' => is_a($this, Site::class) ? $this : $this->site(),
static::CLASS_ALIAS => $this
]);
return $result;
}
/**
* Returns a single translation by language code
* If no code is specified the current translation is returned
*
* @param string $languageCode
* @return Translation|null
* @param string|null $languageCode
* @return Kirby\Cms\ContentTranslation|null
*/
public function translation(string $languageCode = null)
{
@@ -379,7 +567,7 @@ abstract class ModelWithContent extends Model
/**
* Returns the translations collection
*
* @return Collection
* @return Kirby\Cms\Collection
*/
public function translations()
{

View File

@@ -2,8 +2,6 @@
namespace Kirby\Cms;
use Kirby\Toolkit\Obj;
/**
* The Nest class converts any array type
* into a Kirby style collection/object. This
@@ -11,6 +9,12 @@ use Kirby\Toolkit\Obj;
* with Kirby queries.
*
* REFACTOR: move this to the toolkit
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Nest
{

View File

@@ -5,6 +5,15 @@ namespace Kirby\Cms;
use Closure;
use Kirby\Toolkit\Collection as BaseCollection;
/**
* NestCollection
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class NestCollection extends BaseCollection
{

View File

@@ -4,6 +4,15 @@ namespace Kirby\Cms;
use Kirby\Toolkit\Obj;
/**
* NestObject
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class NestObject extends Obj
{

View File

@@ -2,15 +2,12 @@
namespace Kirby\Cms;
use Closure;
use Kirby\Data\Data;
use Kirby\Exception\Exception;
use Kirby\Exception\NotFoundException;
use Kirby\Http\Uri;
use Kirby\Toolkit\A;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Str;
use Throwable;
/**
* The `$page` object is the heart and
@@ -20,8 +17,9 @@ use Throwable;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Page extends ModelWithContent
{
@@ -51,7 +49,7 @@ class Page extends ModelWithContent
/**
* The PageBlueprint object
*
* @var PageBlueprint
* @var Kirby\Cms\PageBlueprint
*/
protected $blueprint;
@@ -94,7 +92,7 @@ class Page extends ModelWithContent
* The template, that should be loaded
* if it exists
*
* @var Template
* @var Kirby\Cms\Template
*/
protected $intendedTemplate;
@@ -113,7 +111,7 @@ class Page extends ModelWithContent
/**
* The parent page
*
* @var Page|null
* @var Kirby\Cms\Page|null
*/
protected $parent;
@@ -127,7 +125,7 @@ class Page extends ModelWithContent
/**
* The parent Site object
*
* @var Site|null
* @var Kirby\Cms\Site|null
*/
protected $site;
@@ -190,7 +188,7 @@ class Page extends ModelWithContent
}
/**
* Improved var_dump output
* Improved `var_dump` output
*
* @return array
*/
@@ -224,9 +222,9 @@ class Page extends ModelWithContent
/**
* Returns the blueprint object
*
* @return PageBlueprint
* @return Kirby\Cms\PageBlueprint
*/
public function blueprint(): PageBlueprint
public function blueprint()
{
if (is_a($this->blueprint, 'Kirby\Cms\PageBlueprint') === true) {
return $this->blueprint;
@@ -247,7 +245,7 @@ class Page extends ModelWithContent
}
$blueprints = [];
$templates = $this->blueprint()->options()['changeTemplate'] ?? [];
$templates = $this->blueprint()->changeTemplate() ?? $this->blueprint()->options()['changeTemplate'] ?? [];
$currentTemplate = $this->intendedTemplate()->name();
if (is_array($templates) === false) {
@@ -394,7 +392,7 @@ class Page extends ModelWithContent
}
if ($parent = $this->parent()) {
return $this->diruri = $this->parent()->diruri() . '/' . $dirname;
return $this->diruri = $parent->diruri() . '/' . $dirname;
} else {
return $this->diruri = $dirname;
}
@@ -436,7 +434,7 @@ class Page extends ModelWithContent
* @internal
* @return self
*/
public static function factory($props): self
public static function factory($props)
{
if (empty($props['model']) === false) {
return static::model($props['model'], $props);
@@ -479,7 +477,7 @@ class Page extends ModelWithContent
* Returns the template that should be
* loaded if it exists.
*
* @return Template
* @return Kirby\Cms\Template
*/
public function intendedTemplate()
{
@@ -516,7 +514,7 @@ class Page extends ModelWithContent
/**
* Compares the current object with the given page object
*
* @param Page|string $page
* @param Kirby\Cms\Page|string $page
* @return bool
*/
public function is($page): bool
@@ -619,7 +617,7 @@ class Page extends ModelWithContent
/**
* Checks if the page is a child of the given page
*
* @param string|Page $parent
* @param Kirby\Cms\Page|string $parent
* @return boolean
*/
public function isChildOf($parent): bool
@@ -634,7 +632,7 @@ class Page extends ModelWithContent
/**
* Checks if the page is a descendant of the given page
*
* @param string|Page $parent
* @param Kirby\Cms\Page|string $parent
* @return boolean
*/
public function isDescendantOf($parent): bool
@@ -785,6 +783,15 @@ class Page extends ModelWithContent
return $this->isListed() === false;
}
/**
* @deprecated 3.0.0 Use `Page::isListed()` intead
* @return bool
*/
public function isVisible(): bool
{
return $this->isListed();
}
/**
* Checks if the page access is verified.
* This is only used for drafts so far.
@@ -795,7 +802,10 @@ class Page extends ModelWithContent
*/
public function isVerified(string $token = null)
{
if ($this->isDraft() === false && !$draft = $this->parents()->findBy('status', 'draft')) {
if (
$this->isDraft() === false &&
$this->parents()->findBy('status', 'draft') === null
) {
return true;
}
@@ -806,15 +816,6 @@ class Page extends ModelWithContent
return $this->token() === $token;
}
/**
* @deprecated 3.0.0 Use `Page::isListed()` intead
* @return bool
*/
public function isVisible(): bool
{
return $this->isListed();
}
/**
* Returns the root to the media folder for the page
*
@@ -843,7 +844,7 @@ class Page extends ModelWithContent
* @internal
* @param string $name
* @param array $props
* @return Page
* @return self
*/
public static function model(string $name, array $props = [])
{
@@ -875,7 +876,7 @@ class Page extends ModelWithContent
*
* @return integer|null
*/
public function num()
public function num(): ?int
{
return $this->num;
}
@@ -890,22 +891,16 @@ class Page extends ModelWithContent
*/
public function panelIcon(array $params = null): array
{
$options = [
'type' => 'page',
'ratio' => $params['ratio'] ?? null,
'back' => $params['back'] ?? 'black',
];
if ($icon = $this->blueprint()->icon()) {
$options['type'] = $icon;
$params['type'] = $icon;
// check for emojis
if (strlen($icon) !== Str::length($icon)) {
$options['emoji'] = true;
$params['emoji'] = true;
}
}
return $options;
return parent::panelIcon($params);
}
/**
@@ -921,37 +916,19 @@ class Page extends ModelWithContent
}
/**
* Returns the image file object based on provided query
*
* @internal
* @param string|array|false $settings
* @param array|null $thumbSettings
* @return array|null
* @param string|null $query
* @return Kirby\Cms\File|Kirby\Cms\Asset|null
*/
public function panelImage($settings = null, array $thumbSettings = null): ?array
protected function panelImageSource(string $query = null)
{
$defaults = [
'ratio' => '3/2',
'back' => 'pattern',
'cover' => false
];
// switch the image off
if ($settings === false) {
return null;
if ($query === null) {
$query = 'page.image';
}
if (is_string($settings) === true) {
$settings = [
'query' => $settings
];
}
if ($image = $this->query($settings['query'] ?? 'page.image', 'Kirby\Cms\File')) {
$settings['url'] = $image->thumb($thumbSettings)->url(true) . '?t=' . $image->modified();
unset($settings['query']);
}
return array_merge($defaults, (array)$settings);
return parent::panelImageSource($query);
}
/**
@@ -965,6 +942,31 @@ class Page extends ModelWithContent
return 'pages/' . $this->panelId();
}
/**
* Prepares the response data for page pickers
* and page fields
*
* @param array|null $params
* @return array
*/
public function panelPickerData(array $params = []): array
{
$image = $this->panelImage($params['image'] ?? []);
$icon = $this->panelIcon($image);
return [
'dragText' => $this->dragText(),
'hasChildren' => $this->hasChildren(),
'icon' => $icon,
'id' => $this->id(),
'image' => $image,
'info' => $this->toString($params['info'] ?? false),
'link' => $this->panelUrl(true),
'text' => $this->toString($params['text'] ?? '{{ page.title }}'),
'url' => $this->url(),
];
}
/**
* Returns the url to the editing view
* in the panel
@@ -984,7 +986,7 @@ class Page extends ModelWithContent
/**
* Returns the parent Page object
*
* @return Page|null
* @return Kirby\Cms\Page|null
*/
public function parent()
{
@@ -1012,7 +1014,7 @@ class Page extends ModelWithContent
* or the Site
*
* @internal
* @return Page|Site
* @return Kirby\Cms\Page|Kirby\Cms\Site
*/
public function parentModel()
{
@@ -1022,9 +1024,9 @@ class Page extends ModelWithContent
/**
* Returns a list of all parents and their parents recursively
*
* @return Pages
* @return Kirby\Cms\Pages
*/
public function parents(): Pages
public function parents()
{
$parents = new Pages;
$page = $this->parent();
@@ -1040,7 +1042,7 @@ class Page extends ModelWithContent
/**
* Returns the permissions object for this page
*
* @return PagePermissions
* @return Kirby\Cms\PagePermissions
*/
public function permissions()
{
@@ -1077,33 +1079,6 @@ class Page extends ModelWithContent
return $url;
}
/**
* 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)
{
if ($query === null) {
return null;
}
$result = Str::query($query, [
'kirby' => $this->kirby(),
'site' => $this->site(),
'page' => $this
]);
if ($expect !== null && is_a($result, $expect) !== true) {
return null;
}
return $result;
}
/**
* Renders the page with the given data.
*
@@ -1171,12 +1146,12 @@ class Page extends ModelWithContent
/**
* @internal
* @return Template
* @return Kirby\Cms\Template
*/
public function representation($type)
{
$kirby = $this->kirby();
$template = $this->template();
$template = $this->intendedTemplate();
$representation = $kirby->template($template->name(), $type);
if ($representation->exists() === true) {
@@ -1202,7 +1177,7 @@ class Page extends ModelWithContent
* which is being used in various methods
* to check for valid actions and input.
*
* @return PageRules
* @return Kirby\Cms\PageRules
*/
protected function rules()
{
@@ -1214,7 +1189,7 @@ class Page extends ModelWithContent
*
* @param string $query
* @param array $params
* @return Pages
* @return Kirby\Cms\Pages
*/
public function search(string $query = null, $params = [])
{
@@ -1227,7 +1202,7 @@ class Page extends ModelWithContent
* @param array|null $blueprint
* @return self
*/
protected function setBlueprint(array $blueprint = null): self
protected function setBlueprint(array $blueprint = null)
{
if ($blueprint !== null) {
$blueprint['model'] = $this;
@@ -1245,7 +1220,7 @@ class Page extends ModelWithContent
* @param string $dirname
* @return self
*/
protected function setDirname(string $dirname = null): self
protected function setDirname(string $dirname = null)
{
$this->dirname = $dirname;
return $this;
@@ -1257,7 +1232,7 @@ class Page extends ModelWithContent
* @param boolean $isDraft
* @return self
*/
protected function setIsDraft(bool $isDraft = null): self
protected function setIsDraft(bool $isDraft = null)
{
$this->isDraft = $isDraft ?? false;
return $this;
@@ -1269,7 +1244,7 @@ class Page extends ModelWithContent
* @param integer $num
* @return self
*/
protected function setNum(int $num = null): self
protected function setNum(int $num = null)
{
$this->num = $num === null ? $num : intval($num);
return $this;
@@ -1278,10 +1253,10 @@ class Page extends ModelWithContent
/**
* Sets the parent page object
*
* @param Page|null $parent
* @param Kirby\Cms\Page|null $parent
* @return self
*/
protected function setParent(Page $parent = null): self
protected function setParent(Page $parent = null)
{
$this->parent = $parent;
return $this;
@@ -1293,7 +1268,7 @@ class Page extends ModelWithContent
* @param string|null $root
* @return self
*/
protected function setRoot(string $root = null): self
protected function setRoot(string $root = null)
{
$this->root = $root;
return $this;
@@ -1305,7 +1280,7 @@ class Page extends ModelWithContent
* @param string $slug
* @return self
*/
protected function setSlug(string $slug): self
protected function setSlug(string $slug)
{
$this->slug = $slug;
return $this;
@@ -1317,7 +1292,7 @@ class Page extends ModelWithContent
* @param string $template
* @return self
*/
protected function setTemplate(string $template = null): self
protected function setTemplate(string $template = null)
{
if ($template !== null) {
$this->intendedTemplate = $this->kirby()->template($template);
@@ -1332,7 +1307,7 @@ class Page extends ModelWithContent
* @param string $url
* @return self
*/
protected function setUrl(string $url = null): self
protected function setUrl(string $url = null)
{
if (is_string($url) === true) {
$url = rtrim($url, '/');
@@ -1369,7 +1344,7 @@ class Page extends ModelWithContent
*
* @return string
*/
public function status()
public function status(): string
{
if ($this->isDraft() === true) {
return 'draft';
@@ -1385,7 +1360,7 @@ class Page extends ModelWithContent
/**
* Returns the final template
*
* @return Template
* @return Kirby\Cms\Template
*/
public function template()
{
@@ -1405,9 +1380,9 @@ class Page extends ModelWithContent
/**
* Returns the title field or the slug as fallback
*
* @return Field
* @return Kirby\Cms\Field
*/
public function title(): Field
public function title()
{
return $this->content()->get('title')->or($this->slug());
}
@@ -1449,25 +1424,6 @@ class Page extends ModelWithContent
return sha1($this->id() . $this->template());
}
/**
* String template builder
*
* @param string|null $template
* @return string
*/
public function toString(string $template = null): string
{
if ($template === null) {
return $this->id();
}
return Str::template($template, [
'page' => $this,
'site' => $this->site(),
'kirby' => $this->kirby()
]);
}
/**
* Returns the UID of the page.
* The UID is basically the same as the

View File

@@ -3,7 +3,7 @@
namespace Kirby\Cms;
use Closure;
use Kirby\Data\Data;
use Kirby\Exception\DuplicateException;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\LogicException;
@@ -12,6 +12,15 @@ use Kirby\Toolkit\A;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Str;
/**
* PageActions
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
trait PageActions
{
@@ -23,7 +32,7 @@ trait PageActions
* @param int $num
* @return self
*/
public function changeNum(int $num = null): self
public function changeNum(int $num = null)
{
if ($this->isDraft() === true) {
throw new LogicException('Drafts cannot change their sorting number');
@@ -63,7 +72,7 @@ trait PageActions
* @param string $language
* @return self
*/
public function changeSlug(string $slug, string $languageCode = null): self
public function changeSlug(string $slug, string $languageCode = null)
{
// always sanitize the slug
$slug = Str::slug($slug);
@@ -90,8 +99,11 @@ trait PageActions
'root' => null
]);
// actually move stuff on disk
if ($oldPage->exists() === true) {
// remove the lock of the old page
$oldPage->lock()->remove();
// actually move stuff on disk
if (Dir::move($oldPage->root(), $newPage->root()) !== true) {
throw new LogicException('The page directory cannot be moved');
}
@@ -117,7 +129,7 @@ trait PageActions
* @param string $language
* @return self
*/
protected function changeSlugForLanguage(string $slug, string $languageCode = null): self
protected function changeSlugForLanguage(string $slug, string $languageCode = null)
{
$language = $this->kirby()->language($languageCode);
@@ -145,9 +157,9 @@ trait PageActions
*
* @param string $status "draft", "listed" or "unlisted"
* @param integer $position Optional sorting number
* @return Page
* @return self
*/
public function changeStatus(string $status, int $position = null): self
public function changeStatus(string $status, int $position = null)
{
switch ($status) {
case 'draft':
@@ -161,7 +173,7 @@ trait PageActions
}
}
protected function changeStatusToDraft(): self
protected function changeStatusToDraft()
{
$page = $this->commit('changeStatus', [$this, 'draft'], function ($page) {
return $page->unpublish();
@@ -170,7 +182,11 @@ trait PageActions
return $page;
}
protected function changeStatusToListed(int $position = null): self
/**
* @param int $position
* @return self
*/
protected function changeStatusToListed(int $position = null)
{
// create a sorting number for the page
$num = $this->createNum($position);
@@ -191,7 +207,10 @@ trait PageActions
return $page;
}
protected function changeStatusToUnlisted(): self
/**
* @return self
*/
protected function changeStatusToUnlisted()
{
if ($this->status() === 'unlisted') {
return $this;
@@ -212,7 +231,7 @@ trait PageActions
* @param string $template
* @return self
*/
public function changeTemplate(string $template): self
public function changeTemplate(string $template)
{
if ($template === $this->template()->name()) {
return $this;
@@ -259,7 +278,7 @@ trait PageActions
* @param string|null $languageCode
* @return self
*/
public function changeTitle(string $title, string $languageCode = null): self
public function changeTitle(string $title, string $languageCode = null)
{
return $this->commit('changeTitle', [$this, $title, $languageCode], function ($page, $title, $languageCode) {
return $page->save(['title' => $title], $languageCode);
@@ -291,13 +310,76 @@ trait PageActions
return $result;
}
/**
* Copies the page to a new parent
*
* @param array $options
* @return Kirby\Cms\Page
*/
public function copy(array $options = [])
{
$slug = $options['slug'] ?? $this->slug();
$isDraft = $options['isDraft'] ?? $this->isDraft();
$parent = $options['parent'] ?? null;
$parentModel = $options['parent'] ?? $this->site();
$num = $options['num'] ?? null;
$children = $options['children'] ?? false;
$files = $options['files'] ?? false;
// clean up the slug
$slug = Str::slug($slug);
if ($parentModel->findPageOrDraft($slug)) {
throw new DuplicateException([
'key' => 'page.duplicate',
'data' => [
'slug' => $slug
]
]);
}
$tmp = new static([
'isDraft' => $isDraft,
'num' => $num,
'parent' => $parent,
'slug' => $slug,
]);
$ignore = [];
// don't copy files
if ($files === false) {
foreach ($this->files() as $file) {
$ignore[] = $file->root();
// append all content files
array_push($ignore, ...$file->contentFiles());
}
}
Dir::copy($this->root(), $tmp->root(), $children, $ignore);
$copy = $parentModel->clone()->findPageOrDraft($slug);
// remove all translated slugs
if ($this->kirby()->multilang() === true) {
foreach ($this->kirby()->languages() as $language) {
if ($language->isDefault() === false) {
$copy = $copy->save(['slug' => null], $language->code());
}
}
}
return $copy;
}
/**
* Creates and stores a new page
*
* @param array $props
* @return self
*/
public static function create(array $props): self
public static function create(array $props)
{
// clean up the slug
$props['slug'] = Str::slug($props['slug'] ?? $props['content']['title'] ?? null);
@@ -352,7 +434,7 @@ trait PageActions
* @param array $props
* @return self
*/
public function createChild(array $props): self
public function createChild(array $props)
{
$props = array_merge($props, [
'url' => null,
@@ -381,10 +463,7 @@ trait PageActions
case 'date':
case 'datetime':
$format = $mode === 'date' ? 'Ymd' : 'YmdHi';
$date = $this->content()->get('date')->value();
$time = empty($date) === true ? time() : strtotime($date);
return date($format, $time);
return $this->date()->toDate($format, 'now');
break;
case 'default':
@@ -473,6 +552,31 @@ trait PageActions
});
}
/**
* Duplicates the page with the given
* slug and optionally copies all files
*
* @param string $slug
* @param array $options
* @return Kirby\Cms\Page
*/
public function duplicate(string $slug = null, array $options = [])
{
// create the slug for the duplicate
$slug = Str::slug($slug ?? $this->slug() . '-copy');
return $this->commit('duplicate', [$this, $slug, $options], function ($page, $slug, $options) {
return $this->copy([
'parent' => $this->parent(),
'slug' => $slug,
'isDraft' => true,
'files' => $options['files'] ?? false,
'children' => $options['children'] ?? false,
]);
});
}
public function publish()
{
if ($this->isDraft() === false) {
@@ -508,8 +612,9 @@ trait PageActions
/**
* Clean internal caches
* @return self
*/
public function purge(): self
public function purge()
{
$this->children = null;
$this->blueprint = null;

View File

@@ -2,6 +2,15 @@
namespace Kirby\Cms;
/**
* PageBlueprint
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class PageBlueprint extends Blueprint
{
@@ -26,6 +35,7 @@ class PageBlueprint extends Blueprint
'changeTitle' => null,
'create' => null,
'delete' => null,
'duplicate' => null,
'read' => null,
'preview' => null,
'sort' => null,

View File

@@ -2,6 +2,15 @@
namespace Kirby\Cms;
/**
* PagePermissions
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class PagePermissions extends ModelPermissions
{
protected $category = 'pages';

View File

@@ -5,12 +5,17 @@ namespace Kirby\Cms;
use Kirby\Exception\DuplicateException;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\LogicException;
use Kirby\Exception\NotFoundException;
use Kirby\Exception\PermissionException;
use Kirby\Toolkit\Str;
/**
* Validators for all page actions
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class PageRules
{
@@ -257,6 +262,20 @@ class PageRules
return true;
}
public static function duplicate(Page $page, string $slug, array $options = []): bool
{
if ($page->permissions()->duplicate() !== true) {
throw new PermissionException([
'key' => 'page.duplicate.permission',
'data' => [
'slug' => $page->slug()
]
]);
}
return true;
}
public static function update(Page $page, array $content = []): bool
{
if ($page->permissions()->update() !== true) {

View File

@@ -2,6 +2,15 @@
namespace Kirby\Cms;
/**
* PageSiblings
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
trait PageSiblings
{
@@ -25,15 +34,6 @@ trait PageSiblings
return $this->nextListed() !== null;
}
/**
* @deprecated 3.0.0 Use `Page::hasNextListed` instead
* @return boolean
*/
public function hasNextVisible(): bool
{
return $this->hasNextListed();
}
/**
* Checks if there's a next unlisted
* page in the siblings collection
@@ -45,6 +45,15 @@ trait PageSiblings
return $this->nextUnlisted() !== null;
}
/**
* @deprecated 3.0.0 Use `Page::hasNextListed` instead
* @return boolean
*/
public function hasNextVisible(): bool
{
return $this->hasNextListed();
}
/**
* @deprecated 3.0.0 Use `Page::hasPrevUnlisted` instead
* @return boolean
@@ -97,7 +106,7 @@ trait PageSiblings
/**
* Returns the next listed page if it exists
*
* @return self|null
* @return Kirby\Cms\Page|null
*/
public function nextListed()
{
@@ -107,7 +116,7 @@ trait PageSiblings
/**
* Returns the next unlisted page if it exists
*
* @return self|null
* @return Kirby\Cms\Page|null
*/
public function nextUnlisted()
{
@@ -135,7 +144,7 @@ trait PageSiblings
/**
* Returns the previous listed page
*
* @return self|null
* @return Kirby\Cms\Page|null
*/
public function prevListed()
{
@@ -145,7 +154,7 @@ trait PageSiblings
/**
* Returns the previous unlisted page
*
* @return self|null
* @return Kirby\Cms\Page|null
*/
public function prevUnlisted()
{
@@ -164,7 +173,7 @@ trait PageSiblings
/**
* Private siblings collector
*
* @return Collection
* @return Kirby\Cms\Collection
*/
protected function siblingsCollection()
{
@@ -179,7 +188,7 @@ trait PageSiblings
* Returns siblings with the same template
*
* @param bool $self
* @return self
* @return Kirby\Cms\Pages
*/
public function templateSiblings(bool $self = true)
{

View File

@@ -2,8 +2,6 @@
namespace Kirby\Cms;
use Kirby\Toolkit\F;
/**
* The `$pages` object refers to a
* collection of pages. The pages in this
@@ -16,8 +14,9 @@ use Kirby\Toolkit\F;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Pages extends Collection
{
@@ -25,7 +24,7 @@ class Pages extends Collection
/**
* Cache for the index
*
* @var null|Pages
* @var Kirby\Cms\Pages|null
*/
protected $index = null;
@@ -42,7 +41,7 @@ class Pages extends Collection
* current collection
*
* @param mixed $item
* @return Pages
* @return self
*/
public function add($object)
{
@@ -65,9 +64,9 @@ class Pages extends Collection
/**
* Returns all audio files of all children
*
* @return Files
* @return Kirby\Cms\Files
*/
public function audio(): Files
public function audio()
{
return $this->files()->filterBy("type", "audio");
}
@@ -75,13 +74,13 @@ class Pages extends Collection
/**
* Returns all children for each page in the array
*
* @return Pages
* @return Kirby\Cms\Pages
*/
public function children(): Pages
public function children()
{
$children = new Pages([], $this->parent);
foreach ($this->data as $pageKey => $page) {
foreach ($this->data as $page) {
foreach ($page->children() as $childKey => $child) {
$children->data[$childKey] = $child;
}
@@ -93,9 +92,9 @@ class Pages extends Collection
/**
* Returns all code files of all children
*
* @return Files
* @return Kirby\Cms\Files
*/
public function code(): Files
public function code()
{
return $this->files()->filterBy("type", "code");
}
@@ -103,9 +102,9 @@ class Pages extends Collection
/**
* Returns all documents of all children
*
* @return Files
* @return Kirby\Cms\Files
*/
public function documents(): Files
public function documents()
{
return $this->files()->filterBy("type", "document");
}
@@ -113,13 +112,13 @@ class Pages extends Collection
/**
* Fetch all drafts for all pages in the collection
*
* @return Pages
* @return Kirby\Cms\Pages
*/
public function drafts()
{
$drafts = new Pages([], $this->parent);
foreach ($this->data as $pageKey => $page) {
foreach ($this->data as $page) {
foreach ($page->drafts() as $draftKey => $draft) {
$drafts->data[$draftKey] = $draft;
}
@@ -132,10 +131,10 @@ class Pages extends Collection
* Creates a pages collection from an array of props
*
* @param array $pages
* @param Model $parent
* @param Kirby\Cms\Model $parent
* @param array $inject
* @param bool $draft
* @return Pages
* @return self
*/
public static function factory(array $pages, Model $model = null, bool $draft = false)
{
@@ -168,13 +167,13 @@ class Pages extends Collection
/**
* Returns all files of all children
*
* @return Files
* @return Kirby\Cms\Files
*/
public function files(): Files
public function files()
{
$files = new Files([], $this->parent);
foreach ($this->data as $pageKey => $page) {
foreach ($this->data as $page) {
foreach ($page->files() as $fileKey => $file) {
$files->data[$fileKey] = $file;
}
@@ -188,10 +187,10 @@ class Pages extends Collection
* This works recursively for children and
* children of children, etc.
*
* @param string $id
* @param string|null $id
* @return mixed
*/
public function findById($id)
public function findById(string $id = null)
{
// remove trailing or leading slashes
$id = trim($id, '/');
@@ -228,10 +227,11 @@ class Pages extends Collection
* Finds a child or child of a child recursively.
*
* @param string $id
* @param string $startAt
* @param string|null $startAt
* @param bool $multiLang
* @return mixed
*/
public function findByIdRecursive($id, $startAt = null, bool $multiLang = false)
public function findByIdRecursive(string $id, string $startAt = null, bool $multiLang = false)
{
$path = explode('/', $id);
$collection = $this;
@@ -259,10 +259,10 @@ class Pages extends Collection
/**
* Uses the specialized find by id method
*
* @param string $key
* @param string|null $key
* @return mixed
*/
public function findByKey($key)
public function findByKey(string $key = null)
{
return $this->findById($key);
}
@@ -271,7 +271,7 @@ class Pages extends Collection
* Alias for Pages::findById
*
* @param string $id
* @return Page|null
* @return Kirby\Cms\Page|null
*/
public function findByUri(string $id)
{
@@ -281,7 +281,7 @@ class Pages extends Collection
/**
* Finds the currently open page
*
* @return Page|null
* @return Kirby\Cms\Page|null
*/
public function findOpen()
{
@@ -293,7 +293,7 @@ class Pages extends Collection
* extension pages
*
* @param string $key
* @return Page|null
* @return Kirby\Cms\Page|null
*/
public function get($key, $default = null)
{
@@ -311,9 +311,9 @@ class Pages extends Collection
/**
* Returns all images of all children
*
* @return Files
* @return Kirby\Cms\Files
*/
public function images(): Files
public function images()
{
return $this->files()->filterBy("type", "image");
}
@@ -323,9 +323,9 @@ class Pages extends Collection
* pages and subpages, etc.
*
* @param bool $drafts
* @return Pages
* @return Kirby\Cms\Pages
*/
public function index(bool $drafts = false): Pages
public function index(bool $drafts = false)
{
if (is_a($this->index, 'Kirby\Cms\Pages') === true) {
return $this->index;
@@ -349,7 +349,7 @@ class Pages extends Collection
*
* @return self
*/
public function invisible(): self
public function invisible()
{
return $this->filterBy('isUnlisted', '==', true);
}
@@ -357,9 +357,9 @@ class Pages extends Collection
/**
* Returns all listed pages in the collection
*
* @return self
* @return Kirby\Cms\Pages
*/
public function listed(): self
public function listed()
{
return $this->filterBy('isListed', '==', true);
}
@@ -367,9 +367,9 @@ class Pages extends Collection
/**
* Returns all unlisted pages in the collection
*
* @return self
* @return Kirby\Cms\Pages
*/
public function unlisted(): self
public function unlisted()
{
return $this->filterBy('isUnlisted', '==', true);
}
@@ -379,7 +379,7 @@ class Pages extends Collection
*
* @return self
*/
public function merge(...$args): self
public function merge(...$args)
{
// merge multiple arguments at once
if (count($args) > 1) {
@@ -441,9 +441,9 @@ class Pages extends Collection
/*
* Returns all listed and unlisted pages in the collection
*
* @return self
* @return Kirby\Cms\Pages
*/
public function published(): self
public function published()
{
return $this->filterBy('isDraft', '==', false);
}
@@ -451,10 +451,10 @@ class Pages extends Collection
/**
* Filter all pages by the given template
*
* @param null|string|array $template
* @return self
* @param string|array $templates
* @return Kirby\Cms\Pages
*/
public function template($templates): self
public function template($templates)
{
if (empty($templates) === true) {
return $this;
@@ -472,9 +472,9 @@ class Pages extends Collection
/**
* Returns all video files of all children
*
* @return Files
* @return Kirby\Cms\Files
*/
public function videos(): Files
public function videos()
{
return $this->files()->filterBy("type", "video");
}
@@ -482,9 +482,9 @@ class Pages extends Collection
/**
* Deprecated alias for Pages::listed()
*
* @return self
* @return Kirby\Cms\Pages
*/
public function visible(): self
public function visible()
{
return $this->filterBy('isListed', '==', true);
}

View File

@@ -16,8 +16,9 @@ use Kirby\Toolkit\Pagination as BasePagination;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Pagination extends BasePagination
{
@@ -117,9 +118,9 @@ class Pagination extends BasePagination
* Returns the Url for the next page.
* Returns null if there's no next page.
*
* @return string
* @return string|null
*/
public function nextPageUrl()
public function nextPageUrl(): ?string
{
if ($page = $this->nextPage()) {
return $this->pageUrl($page);
@@ -129,13 +130,14 @@ class Pagination extends BasePagination
}
/**
* Returns the Url of the current page.
* If the $page variable is set, the Url
* 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)
public function pageUrl(int $page = null): ?string
{
if ($page === null) {
return $this->pageUrl($this->page());
@@ -163,9 +165,9 @@ class Pagination extends BasePagination
* Returns the Url for the previous page.
* Returns null if there's no previous page.
*
* @return string
* @return string|null
*/
public function prevPageUrl()
public function prevPageUrl(): ?string
{
if ($page = $this->prevPage()) {
return $this->pageUrl($page);

View File

@@ -15,6 +15,12 @@ use Throwable;
* a working panel view with all the right URLs
* and other panel options. The view template is
* located in `kirby/views/panel.php`
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Panel
{
@@ -23,7 +29,7 @@ class Panel
* Links all dist files in the media folder
* and returns the link to the requested asset
*
* @param App $kirby
* @param Kirby\Cms\App $kirby
* @return bool
*/
public static function link(App $kirby): bool
@@ -45,7 +51,7 @@ class Panel
Dir::make($mediaRoot, true);
// create a symlink to the dist folder
if (Dir::copy($kirby->root('panel') . '/dist', $versionRoot) !== true) {
if (Dir::copy($panelRoot, $versionRoot) !== true) {
throw new Exception('Panel assets could not be linked');
}
@@ -55,10 +61,10 @@ class Panel
/**
* Renders the main panel view
*
* @param App $kirby
* @return Response
* @param Kirby\Cms\App $kirby
* @return Kirby\Cms\Response
*/
public static function render(App $kirby): Response
public static function render(App $kirby)
{
try {
if (static::link($kirby) === true) {
@@ -72,15 +78,15 @@ class Panel
// get the uri object for the panel url
$uri = new Uri($url = $kirby->url('panel'));
$pluginCss = new PanelPlugins('css');
$pluginJs = new PanelPlugins('js');
// fetch all plugins
$plugins = new PanelPlugins();
$view = new View($kirby->root('kirby') . '/views/panel.php', [
'kirby' => $kirby,
'config' => $kirby->option('panel'),
'assetUrl' => $kirby->url('media') . '/panel/' . $kirby->versionHash(),
'pluginCss' => $pluginCss->url(),
'pluginJs' => $pluginJs->url(),
'pluginCss' => $plugins->url('css'),
'pluginJs' => $plugins->url('js'),
'icons' => F::read($kirby->root('panel') . '/dist/img/icons.svg'),
'panelUrl' => $uri->path()->toString(true) . '/',
'options' => [

View File

@@ -9,6 +9,12 @@ use Kirby\Toolkit\F;
* The PanelPlugins class takes care of collecting
* js and css plugin files for the panel and caches
* them in the media folder
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class PanelPlugins
{
@@ -20,30 +26,6 @@ class PanelPlugins
*/
public $files;
/**
* Cache of the unique plugin hash for the url and root
*
* @var string
*/
public $hash;
/**
* css or js
*
* @var string
*/
public $type;
/**
* Creates a new panel plugin instance by type (css or js)
*
* @param string $type
*/
public function __construct(string $type)
{
$this->type = $type;
}
/**
* Collects and returns the plugin files for all plugins
*
@@ -58,84 +40,13 @@ class PanelPlugins
$this->files = [];
foreach (App::instance()->plugins() as $plugin) {
$file = $plugin->root() . '/index.' . $this->type;
if (file_exists($file) === true) {
$this->files[] = $file;
}
$this->files[] = $plugin->root() . '/index.css';
$this->files[] = $plugin->root() . '/index.js';
}
return $this->files;
}
/**
* Checks if the cache exists
*
* @return boolean
*/
public function exist(): bool
{
return file_exists($this->root());
}
/**
* Returns the path to the cache folder
*
* @return string
*/
public function folder(): string
{
return 'panel/' . App::versionHash() . '/plugins/' . $this->type;
}
/**
* Collects and removes garbage from old plugin versions
*
* @return boolean
*/
public function gc(): bool
{
$folder = App::instance()->root('media') . '/' . $this->folder();
foreach (glob($folder . '/*') as $dir) {
$name = basename($dir);
if ($name !== $this->hash()) {
Dir::remove($dir);
}
}
return true;
}
/**
* Returns the unique hash for the cache file
* The hash is generated from all plugin filenames
* and the max modification date to make sure changes
* will always be cached properly
*
* @return string
*/
public function hash(): string
{
if ($this->hash !== null) {
return $this->hash;
}
return $this->hash = $this->id() . '-' . $this->modified();
}
/**
* Returns a unique id based on all
* plugin file roots
*
* @return string
*/
public function id(): string
{
return hash('crc32', implode(array_values($this->files())));
}
/**
* Returns the last modification
* of the collected plugin files
@@ -154,79 +65,36 @@ class PanelPlugins
return max($modified);
}
/**
* Returns the full path to the cache file
* This is used for the root and url methods
*
* @return string
*/
public function path(): string
{
return $this->folder() . '/' . $this->hash() . '/index.' . $this->type;
}
/**
* Read the files from all plugins and concatenate them
*
* @param string $type
* @return string
*/
public function read(): string
public function read(string $type): string
{
$dist = [];
foreach ($this->files() as $file) {
$dist[] = file_get_contents($file);
if (F::extension($file) === $type) {
if ($content = F::read($file)) {
$dist[] = $content;
}
}
}
return implode(PHP_EOL, $dist);
}
/**
* Checks if the cache exists and
* otherwise (re)creates it
*
* @return boolean
*/
public function publish(): bool
{
if ($this->exist() === true) {
return true;
}
$this->write();
$this->gc();
return true;
}
/**
* Absolute path to the cache file
*
* @return string
*/
public function root(): string
{
return App::instance()->root('media') . '/' . $this->path();
}
/**
* Absolute url to the cache file
* This is used by the panel to link the plugins
*
* @param string $type
* @return string
*/
public function url(): string
public function url(string $type): string
{
return App::instance()->url('media') . '/' . $this->path();
}
/**
* Creates the cache file
*
* @return boolean
*/
public function write(): bool
{
return F::write($this->root(), $this->read());
return App::instance()->url('media') . '/plugins/index.' . $type . '?' . $this->modified();
}
}

View File

@@ -8,6 +8,12 @@ use Kirby\Exception\InvalidArgumentException;
* Handles permission definition in each user
* blueprint and wraps a couple useful methods
* around it to check for available permissions.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Permissions
{
@@ -36,6 +42,7 @@ class Permissions
'changeTitle' => true,
'create' => true,
'delete' => true,
'duplicate' => true,
'preview' => true,
'read' => true,
'sort' => true,
@@ -68,16 +75,16 @@ class Permissions
public function __construct($settings = [])
{
if (is_bool($settings) === true) {
return $this->setAll($settings);
}
if (is_array($settings) === true) {
return $this->setCategories($settings);
}
if (is_bool($settings) === true) {
return $this->setAll($settings);
}
}
public function for(string $category = null, string $action = null)
public function for(string $category = null, string $action = null): bool
{
if ($action === null) {
if ($this->hasCategory($category) === false) {
@@ -94,12 +101,12 @@ class Permissions
return $this->actions[$category][$action];
}
protected function hasAction(string $category, string $action)
protected function hasAction(string $category, string $action): bool
{
return $this->hasCategory($category) === true && array_key_exists($action, $this->actions[$category]) === true;
}
protected function hasCategory(string $category)
protected function hasCategory(string $category): bool
{
return array_key_exists($category, $this->actions) === true;
}

View File

@@ -5,12 +5,17 @@ namespace Kirby\Cms;
use Exception;
use Kirby\Data\Data;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Toolkit\F;
/**
* Represents a Plugin and handles parsing of
* the composer.json. It also creates the prefix
* and media url for the plugin.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Plugin extends Model
{
@@ -89,6 +94,10 @@ class Plugin extends Model
return $this->root;
}
/**
* @param string $name
* @return self
*/
protected function setName(string $name)
{
if (preg_match('!^[a-z0-9-]+\/[a-z0-9-]+$!i', $name) == false) {

View File

@@ -10,6 +10,12 @@ use Kirby\Toolkit\F;
* Plugin assets are automatically copied/linked
* to the media folder, to make them publicly
* available. This class handles the magic around that.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class PluginAssets
{
@@ -19,7 +25,7 @@ class PluginAssets
* @param string $pluginName
* @return void
*/
public static function clean(string $pluginName)
public static function clean(string $pluginName): void
{
if ($plugin = App::instance()->plugin($pluginName)) {
$root = $plugin->root() . '/assets';
@@ -48,7 +54,7 @@ class PluginAssets
*
* @param string $pluginName
* @param string $filename
* @return string
* @return Kirby\Cms\Response|null
*/
public static function resolve(string $pluginName, string $filename)
{

View File

@@ -7,11 +7,17 @@ use Kirby\Toolkit\Facade;
/**
* Shortcut to the request object
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class R extends Facade
{
/**
* @return Request
* @return Kirby\Cms\Request
*/
public static function instance()
{

View File

@@ -7,6 +7,12 @@ use Kirby\Toolkit\Str;
/**
* Global response configuration
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Responder
{
@@ -46,7 +52,7 @@ class Responder
*/
public function __toString(): string
{
return $this->send();
return (string)$this->send();
}
/**
@@ -85,9 +91,8 @@ class Responder
* Construct response from an array
*
* @param array $response
* @return self
*/
public function fromArray(array $response)
public function fromArray(array $response): void
{
$this->body($response['body'] ?? null);
$this->code($response['code'] ?? null);
@@ -137,7 +142,7 @@ class Responder
* Shortcut to configure a json response
*
* @param array $json
* @return self
* @return string|self
*/
public function json(array $json = null)
{
@@ -169,7 +174,7 @@ class Responder
* Creates and returns the response object from the config
*
* @param string|null $body
* @return Response
* @return Kirby\Cms\Response
*/
public function send(string $body = null)
{

View File

@@ -2,15 +2,17 @@
namespace Kirby\Cms;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\NotFoundException;
use Kirby\Http\Response as BaseResponse;
/**
* Custom response object with an optimized
* redirect method to build correct Urls
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Response extends BaseResponse
class Response extends \Kirby\Http\Response
{
/**

View File

@@ -4,13 +4,18 @@ namespace Kirby\Cms;
use Exception;
use Kirby\Data\Data;
use Kirby\Exception\NotFoundException;
use Kirby\Toolkit\F;
use Kirby\Toolkit\I18n;
/**
* Represents a User role with attached
* permissions. Roles are defined by user blueprints.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Role extends Model
{
@@ -25,7 +30,7 @@ class Role extends Model
}
/**
* Improved var_dump() output
* Improved `var_dump` output
*
* @return array
*/
@@ -48,19 +53,19 @@ class Role extends Model
}
}
protected static function defaults()
protected static function defaults(): array
{
return [
'admin' => [
'description' => 'The admin has all rights',
'name' => 'admin',
'title' => 'Admin',
'description' => I18n::translate('role.admin.description'),
'title' => I18n::translate('role.admin.title'),
'permissions' => true,
],
'nobody' => [
'description' => 'This is a fallback role without any permissions',
'name' => 'nobody',
'title' => 'Nobody',
'description' => I18n::translate('role.nobody.description'),
'title' => I18n::translate('role.nobody.title'),
'permissions' => false,
]
];
@@ -71,7 +76,12 @@ class Role extends Model
return $this->description;
}
public static function factory(array $props, array $inject = []): self
/**
* @param array $props
* @param array $inject
* @return self
*/
public static function factory(array $props, array $inject = [])
{
return new static($props + $inject);
}
@@ -91,7 +101,12 @@ class Role extends Model
return $this->name() === 'nobody';
}
public static function load(string $file, array $inject = []): self
/**
* @param string $file
* @param array $inject
* @return self
*/
public static function load(string $file, array $inject = [])
{
$data = Data::read($file);
$data['name'] = F::name($file);
@@ -104,6 +119,10 @@ class Role extends Model
return $this->name;
}
/**
* @param array $inject
* @return self
*/
public static function nobody(array $inject = [])
{
try {
@@ -113,30 +132,49 @@ class Role extends Model
}
}
public function permissions(): Permissions
/**
* @return Kirby\Cms\Permissions
*/
public function permissions()
{
return $this->permissions;
}
protected function setDescription($description = null): self
/**
* @param [type] $description
* @return self
*/
protected function setDescription($description = null)
{
$this->description = I18n::translate($description, $description);
return $this;
}
protected function setName(string $name): self
/**
* @param string $name
* @return self
*/
protected function setName(string $name)
{
$this->name = $name;
return $this;
}
protected function setPermissions($permissions = null): self
/**
* @param [type] $permissions
* @return self
*/
protected function setPermissions($permissions = null)
{
$this->permissions = new Permissions($permissions);
return $this;
}
protected function setTitle($title = null): self
/**
* @param [type] $title
* @return self
*/
protected function setTitle($title = null)
{
$this->title = I18n::translate($title, $title);
return $this;

View File

@@ -3,7 +3,6 @@
namespace Kirby\Cms;
use Kirby\Toolkit\Dir;
use Kirby\Toolkit\F;
/**
* Extension of the Collection class that
@@ -12,10 +11,22 @@ use Kirby\Toolkit\F;
* collection with Role objects. It also has
* a `Roles::load()` method that handles loading
* role definitions from disk.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Roles extends Collection
{
public static function factory(array $roles, array $inject = []): self
/**
* @param array $roles
* @param array $inject
* @return self
*/
public static function factory(array $roles, array $inject = [])
{
$collection = new static;
@@ -34,7 +45,12 @@ class Roles extends Collection
return $collection->sortBy('name', 'asc');
}
public static function load(string $root = null, array $inject = []): self
/**
* @param string $root
* @param array $inject
* @return self
*/
public static function load(string $root = null, array $inject = [])
{
$roles = new static;

View File

@@ -7,11 +7,17 @@ use Kirby\Toolkit\Facade;
/**
* Shortcut to the session object
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class S extends Facade
{
/**
* @return Session
* @return Kirby\Session\Session
*/
public static function instance()
{

View File

@@ -9,9 +9,21 @@ use Kirby\Toolkit\Str;
* search logic from collections, to
* provide a more globally usable interface
* for any searches.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Search
{
/**
* @param string $query
* @param array $params
* @return Kirby\Cms\Files
*/
public static function files(string $query = null, $params = [])
{
return App::instance()->site()->index()->files()->search($query, $params);
@@ -111,11 +123,21 @@ class Search
return $results->sortBy('searchScore', 'desc');
}
/**
* @param string $query
* @param array $params
* @return Kirby\Cms\Pages
*/
public static function pages(string $query = null, $params = [])
{
return App::instance()->site()->index()->search($query, $params);
}
/**
* @param string $query
* @param array $params
* @return Kirby\Cms\Users
*/
public static function users(string $query = null, $params = [])
{
return App::instance()->users()->search($query, $params);

View File

@@ -5,6 +5,15 @@ namespace Kirby\Cms;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Toolkit\Component;
/**
* Section
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Section extends Component
{
@@ -36,11 +45,17 @@ class Section extends Component
parent::__construct($type, $attrs);
}
/**
* @return Kirby\Cms\App
*/
public function kirby()
{
return $this->model->kirby();
}
/**
* @return Kirby\Cms\Model
*/
public function model()
{
return $this->model;

View File

@@ -2,7 +2,6 @@
namespace Kirby\Cms;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\LogicException;
use Kirby\Toolkit\A;
@@ -16,8 +15,9 @@ use Kirby\Toolkit\Str;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Site extends ModelWithContent
{
@@ -128,7 +128,7 @@ class Site extends ModelWithContent
}
/**
* Improved var_dump output
* Improved `var_dump` output
*
* @return array
*/
@@ -160,9 +160,9 @@ class Site extends ModelWithContent
/**
* Returns the blueprint object
*
* @return SiteBlueprint
* @return Kirby\Cms\SiteBlueprint
*/
public function blueprint(): SiteBlueprint
public function blueprint()
{
if (is_a($this->blueprint, 'Kirby\Cms\SiteBlueprint') === true) {
return $this->blueprint;
@@ -200,7 +200,7 @@ class Site extends ModelWithContent
/**
* Builds a breadcrumb collection
*
* @return Pages
* @return Kirby\Cms\Pages
*/
public function breadcrumb()
{
@@ -243,7 +243,7 @@ class Site extends ModelWithContent
/**
* Returns the error page object
*
* @return Page
* @return Kirby\Cms\Page|null
*/
public function errorPage()
{
@@ -282,7 +282,7 @@ class Site extends ModelWithContent
/**
* Returns the home page object
*
* @return Page
* @return Kirby\Cms\Page|null
*/
public function homePage()
{
@@ -334,7 +334,7 @@ class Site extends ModelWithContent
/**
* Compares the current object with the given site object
*
* @param Site $site
* @param mixed $site
* @return bool
*/
public function is($site): bool
@@ -391,7 +391,7 @@ class Site extends ModelWithContent
* it can be found. (see `Site::homePage()`)
*
* @param string $path
* @return Page|null
* @return Kirby\Cms\Page|null
*/
public function page(string $path = null)
{
@@ -413,9 +413,9 @@ class Site extends ModelWithContent
/**
* Alias for `Site::children()`
*
* @return Pages
* @return Kirby\Cms\Pages
*/
public function pages(): Pages
public function pages()
{
return $this->children();
}
@@ -451,7 +451,7 @@ class Site extends ModelWithContent
/**
* Returns the permissions object for this site
*
* @return SitePermissions
* @return Kirby\Cms\SitePermissions
*/
public function permissions()
{
@@ -459,29 +459,26 @@ class Site extends ModelWithContent
}
/**
* Creates a string query, starting from the model
* Preview Url
*
* @internal
* @param string|null $query
* @param string|null $expect
* @return mixed
* @return string|null
*/
public function query(string $query = null, string $expect = null)
public function previewUrl(): ?string
{
if ($query === null) {
$preview = $this->blueprint()->preview();
if ($preview === false) {
return null;
}
$result = Str::query($query, [
'kirby' => $this->kirby(),
'site' => $this,
]);
if ($expect !== null && is_a($result, $expect) !== true) {
return null;
if ($preview === true) {
$url = $this->url();
} else {
$url = $preview;
}
return $result;
return $url;
}
/**
@@ -499,7 +496,7 @@ class Site extends ModelWithContent
* which is being used in various methods
* to check for valid actions and input.
*
* @return SiteRules
* @return Kirby\Cms\SiteRules
*/
protected function rules()
{
@@ -511,7 +508,7 @@ class Site extends ModelWithContent
*
* @param string $query
* @param array $params
* @return Pages
* @return Kirby\Cms\Pages
*/
public function search(string $query = null, $params = [])
{
@@ -524,7 +521,7 @@ class Site extends ModelWithContent
* @param array|null $blueprint
* @return self
*/
protected function setBlueprint(array $blueprint = null): self
protected function setBlueprint(array $blueprint = null)
{
if ($blueprint !== null) {
$blueprint['model'] = $this;
@@ -543,7 +540,7 @@ class Site extends ModelWithContent
* @param string $id
* @return self
*/
protected function setErrorPageId(string $id = 'error'): self
protected function setErrorPageId(string $id = 'error')
{
$this->errorPageId = $id;
return $this;
@@ -558,7 +555,7 @@ class Site extends ModelWithContent
* @param string $id
* @return self
*/
protected function setHomePageId(string $id = 'home'): self
protected function setHomePageId(string $id = 'home')
{
$this->homePageId = $id;
return $this;
@@ -568,10 +565,10 @@ class Site extends ModelWithContent
* Sets the current page object
*
* @internal
* @param Page|null $page
* @param Kirby\Cms\Page|null $page
* @return self
*/
public function setPage(Page $page = null): self
public function setPage(Page $page = null)
{
$this->page = $page;
return $this;
@@ -581,9 +578,9 @@ class Site extends ModelWithContent
* Sets the Url
*
* @param string $url
* @return void
* @return self
*/
protected function setUrl($url = null): self
protected function setUrl($url = null)
{
$this->url = $url;
return $this;
@@ -609,24 +606,6 @@ class Site extends ModelWithContent
];
}
/**
* String template builder
*
* @param string|null $template
* @return string
*/
public function toString(string $template = null): string
{
if ($template === null) {
return $this->url();
}
return Str::template($template, [
'site' => $this,
'kirby' => $this->kirby()
]);
}
/**
* Returns the Url
*
@@ -665,11 +644,11 @@ class Site extends ModelWithContent
* returns the current page
*
* @internal
* @param string|Page $page
* @param string|Kirby\Cms\Page $page
* @param string|null $languageCode
* @return Page
* @return Kirby\Cms\Page
*/
public function visit($page, string $languageCode = null): Page
public function visit($page, string $languageCode = null)
{
if ($languageCode !== null) {
$this->kirby()->setCurrentTranslation($languageCode);

View File

@@ -3,11 +3,16 @@
namespace Kirby\Cms;
use Closure;
use Kirby\Data\Data;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Str;
/**
* SiteActions
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
trait SiteActions
{
@@ -44,7 +49,7 @@ trait SiteActions
* @param string|null $languageCode
* @return self
*/
public function changeTitle(string $title, string $languageCode = null): self
public function changeTitle(string $title, string $languageCode = null)
{
return $this->commit('changeTitle', [$this, $title, $languageCode], function ($site, $title, $languageCode) {
return $site->save(['title' => $title], $languageCode);
@@ -55,7 +60,7 @@ trait SiteActions
* Creates a main page
*
* @param array $props
* @return Page
* @return Kirby\Cms\Page
*/
public function createChild(array $props)
{
@@ -71,8 +76,10 @@ trait SiteActions
/**
* Clean internal caches
*
* @return self
*/
public function purge(): self
public function purge()
{
$this->children = null;
$this->blueprint = null;

View File

@@ -5,9 +5,21 @@ namespace Kirby\Cms;
/**
* Extension of the basic blueprint class
* to handle the blueprint for the site.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class SiteBlueprint extends Blueprint
{
/**
* Creates a new page blueprint object
* with the given props
*
* @param array $props
*/
public function __construct(array $props)
{
parent::__construct($props);
@@ -26,4 +38,23 @@ class SiteBlueprint extends Blueprint
]
);
}
/**
* Returns the preview settings
* The preview setting controlls the "Open"
* button in the panel and redirects it to a
* different URL if necessary.
*
* @return string|boolean
*/
public function preview()
{
$preview = $this->props['options']['preview'] ?? true;
if (is_string($preview) === true) {
return $this->model->toString($preview);
}
return $preview;
}
}

View File

@@ -2,6 +2,15 @@
namespace Kirby\Cms;
/**
* SitePermissions
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class SitePermissions extends ModelPermissions
{
protected $category = 'site';

View File

@@ -2,11 +2,16 @@
namespace Kirby\Cms;
use Exception;
use Kirby\Exception\PermissionException;
/**
* Validators for all site actions
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class SiteRules
{

View File

@@ -2,8 +2,6 @@
namespace Kirby\Cms;
use Kirby\Exception\InvalidArgumentException;
/**
* The Structure class wraps
* array data into a nicely chainable
@@ -14,8 +12,9 @@ use Kirby\Exception\InvalidArgumentException;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Structure extends Collection
{

View File

@@ -14,8 +14,9 @@ namespace Kirby\Cms;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class StructureObject extends Model
{
@@ -76,9 +77,9 @@ class StructureObject extends Model
/**
* Returns the content
*
* @return Content
* @return Kirby\Cms\Content
*/
public function content(): Content
public function content()
{
if (is_a($this->content, 'Kirby\Cms\Content') === true) {
return $this->content;
@@ -119,7 +120,7 @@ class StructureObject extends Model
/**
* Returns the parent Model object
*
* @return Page|Site|File|User
* @return Kirby\Cms\Model
*/
public function parent()
{
@@ -132,7 +133,7 @@ class StructureObject extends Model
* @param array|null $content
* @return self
*/
protected function setContent(array $content = null): self
protected function setContent(array $content = null)
{
$this->content = $content;
return $this;
@@ -147,7 +148,7 @@ class StructureObject extends Model
* @param string $id
* @return self
*/
protected function setId(string $id): self
protected function setId(string $id)
{
$this->id = $id;
return $this;
@@ -157,10 +158,10 @@ class StructureObject extends Model
* Sets the parent Model. This can either be a
* Page, Site, File or User object
*
* @param Page|Site|File|User|null $parent
* @param Kirby\Cms\Model|null $parent
* @return self
*/
protected function setParent(Model $parent = null): self
protected function setParent(Model $parent = null)
{
$this->parent = $parent;
return $this;
@@ -169,10 +170,10 @@ class StructureObject extends Model
/**
* Sets the parent Structure collection
*
* @param Structure $structure
* @param Kirby\Cms\Structure $structure
* @return self
*/
protected function setStructure(Structure $structure = null): self
protected function setStructure(Structure $structure = null)
{
$this->structure = $structure;
return $this;
@@ -181,7 +182,7 @@ class StructureObject extends Model
/**
* Returns the parent Structure collection as siblings
*
* @return Structure
* @return Kirby\Cms\Structure
*/
protected function siblingsCollection()
{

View File

@@ -5,6 +5,7 @@ namespace Kirby\Cms;
use Throwable;
use Kirby\Data\Json;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\PermissionException;
use Kirby\Http\Remote;
use Kirby\Http\Uri;
@@ -12,6 +13,7 @@ use Kirby\Http\Url;
use Kirby\Toolkit\Dir;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Str;
use Kirby\Toolkit\V;
/**
* The System class gathers all information
@@ -20,6 +22,12 @@ use Kirby\Toolkit\Str;
*
* This is mostly used by the panel installer
* to check if the panel can be installed at all.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class System
{
@@ -30,7 +38,7 @@ class System
protected $app;
/**
* @param App $app
* @param Kirby\Cms\App $app
*/
public function __construct(App $app)
{
@@ -41,7 +49,7 @@ class System
}
/**
* Improved var_dump output
* Improved `var_dump` output
*
* @return array
*/
@@ -99,6 +107,29 @@ class System
return extension_loaded('curl');
}
/**
* Returns the app's human-readable
* index URL without scheme
*
* @return string
*/
public function indexUrl(): string
{
$url = $this->app->url('index');
if (Url::isAbsolute($url)) {
$uri = Url::toObject($url);
} else {
// index URL was configured without host, use the current host
$uri = Uri::current([
'path' => $url,
'query' => null
]);
}
return $uri->setScheme(null)->setSlash(false)->toString();
}
/**
* Create the most important folders
* if they don't exist yet
@@ -194,29 +225,6 @@ class System
return in_array(false, array_values($this->status()), true) === false;
}
/**
* Returns the app's index URL for
* licensing purposes without scheme
*
* @return string
*/
protected function licenseUrl(): string
{
$url = $this->app->url('index');
if (Url::isAbsolute($url)) {
$uri = Url::toObject($url);
} else {
// index URL was configured without host, use the current host
$uri = Uri::current([
'path' => $url,
'query' => null
]);
}
return $uri->setScheme(null)->setSlash(false)->toString();
}
/**
* Normalizes the app's index URL for
* licensing purposes
@@ -224,10 +232,10 @@ class System
* @param string|null $url Input URL, by default the app's index URL
* @return string Normalized URL
*/
protected function licenseUrlNormalized(string $url = null): string
protected function licenseUrl(string $url = null): string
{
if ($url === null) {
$url = $this->licenseUrl();
$url = $this->indexUrl();
}
// remove common "testing" subdomains as well as www.
@@ -300,7 +308,7 @@ class System
}
// verify the URL
if ($this->licenseUrlNormalized() !== $this->licenseUrlNormalized($license['domain'])) {
if ($this->licenseUrl() !== $this->licenseUrl($license['domain'])) {
return false;
}
@@ -346,13 +354,25 @@ class System
* @param string $email
* @return boolean
*/
public function register(string $license, string $email): bool
public function register(string $license = null, string $email = null): bool
{
if (Str::startsWith($license, 'K3-PRO-') === false) {
throw new InvalidArgumentException([
'key' => 'license.format'
]);
}
if (V::email($email) === false) {
throw new InvalidArgumentException([
'key' => 'license.email'
]);
}
$response = Remote::get('https://licenses.getkirby.com/register', [
'data' => [
'license' => $license,
'email' => $email,
'domain' => $this->licenseUrl()
'domain' => $this->indexUrl()
]
]);
@@ -373,7 +393,9 @@ class System
Json::write($file, $json);
if ($this->license() === false) {
throw new Exception('The license could not be verified');
throw new InvalidArgumentException([
'key' => 'license.verification'
]);
}
return true;

View File

@@ -5,12 +5,16 @@ namespace Kirby\Cms;
use Exception;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Tpl;
use Kirby\Toolkit\View;
use Throwable;
/**
* Represents a Kirby template and takes care
* of loading the correct file.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Template
{

View File

@@ -8,6 +8,12 @@ use Kirby\Data\Data;
/**
* Wrapper around Kirby's localization files,
* which are store in `kirby/translations`.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Translation
{
@@ -32,7 +38,7 @@ class Translation
}
/**
* Improved var_dump output
* Improved `var_dump` output
*
* @return array
*/

View File

@@ -10,19 +10,29 @@ use Kirby\Toolkit\F;
* Provides a factory method to convert an array
* to a collection of Translation objects and load
* method to load all translations from disk
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Translations extends Collection
{
public function start(string $code)
public function start(string $code): void
{
F::move($this->parent->contentFile('', true), $this->parent->contentFile($code, true));
}
public function stop(string $code)
public function stop(string $code): void
{
F::move($this->parent->contentFile($code, true), $this->parent->contentFile('', true));
}
/**
* @param array $translations
* @return self
*/
public static function factory(array $translations)
{
$collection = new static;
@@ -35,6 +45,11 @@ class Translations extends Collection
return $collection;
}
/**
* @param string $root
* @param array $inject
* @return self
*/
public static function load(string $root, array $inject = [])
{
$collection = new static;

View File

@@ -3,7 +3,6 @@
namespace Kirby\Cms;
use Kirby\Http\Url as BaseUrl;
use Kirby\Toolkit\Str;
/**
* The `Url` class extends the
@@ -16,8 +15,9 @@ use Kirby\Toolkit\Str;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Url extends BaseUrl
{

View File

@@ -7,11 +7,8 @@ use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\NotFoundException;
use Kirby\Exception\PermissionException;
use Kirby\Session\Session;
use Kirby\Toolkit\A;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Str;
use Kirby\Toolkit\V;
use Throwable;
/**
* The `$user` object represents a
@@ -19,14 +16,16 @@ use Throwable;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class User extends ModelWithContent
{
const CLASS_ALIAS = 'user';
use HasFiles;
use HasMethods;
use HasSiblings;
use UserActions;
@@ -70,6 +69,20 @@ class User extends ModelWithContent
*/
protected $language;
/**
* All registered user methods
*
* @var array
*/
public static $methods = [];
/**
* Registry with all User models
*
* @var array
*/
public static $models = [];
/**
* @var string
*/
@@ -102,6 +115,11 @@ class User extends ModelWithContent
return $this->$method;
}
// user methods
if ($this->hasMethod($method)) {
return $this->callMethod($method, $arguments);
}
// return site content otherwise
return $this->content()->get($method, $arguments);
}
@@ -118,7 +136,7 @@ class User extends ModelWithContent
}
/**
* Improved var_dump() output
* Improved `var_dump` output
*
* @return array
*/
@@ -150,7 +168,7 @@ class User extends ModelWithContent
/**
* Returns the File object for the avatar or null
*
* @return File|null
* @return Kirby\Cms\File|null
*/
public function avatar()
{
@@ -160,7 +178,7 @@ class User extends ModelWithContent
/**
* Returns the UserBlueprint object
*
* @return UserBlueprint
* @return Kirby\Cms\UserBlueprint
*/
public function blueprint()
{
@@ -237,6 +255,22 @@ class User extends ModelWithContent
return is_file($this->contentFile('default')) === true;
}
/**
* Constructs a User object and also
* takes User models into account.
*
* @internal
* @return self
*/
public static function factory($props)
{
if (empty($props['model']) === false) {
return static::model($props['model'], $props);
}
return new static($props);
}
/**
* Hashes user password
*
@@ -244,7 +278,7 @@ class User extends ModelWithContent
* @param string|null $password
* @return string|null
*/
public static function hashPassword($password)
public static function hashPassword($password): ?string
{
if ($password !== null) {
$info = password_get_info($password);
@@ -292,7 +326,7 @@ class User extends ModelWithContent
/**
* Compares the current object with the given user object
*
* @param User|null $user
* @param Kirby\Cms\User|null $user
* @return bool
*/
public function is(User $user = null): bool
@@ -370,19 +404,14 @@ class User extends ModelWithContent
* Logs the user in
*
* @param string $password
* @param Session|array $session Session options or session object to set the user in
* @param Kirby\Session\Session|array $session Session options or session object to set the user in
* @return bool
*
* @throws PermissionException If the password is not valid
*/
public function login(string $password, $session = null): bool
{
try {
$this->validatePassword($password);
} catch (Exception $e) {
throw new PermissionException(['key' => 'access.login']);
}
$this->validatePassword($password);
$this->loginPasswordless($session);
return true;
@@ -391,10 +420,10 @@ class User extends ModelWithContent
/**
* Logs the user in without checking the password
*
* @param Session|array $session Session options or session object to set the user in
* @param SKirby\Session\Session|array $session Session options or session object to set the user in
* @return void
*/
public function loginPasswordless($session = null)
public function loginPasswordless($session = null): void
{
$session = $this->sessionFromOptions($session);
@@ -405,10 +434,10 @@ class User extends ModelWithContent
/**
* Logs the user out
*
* @param Session|array $session Session options or session object to unset the user in
* @param Kirby\Session\Session|array $session Session options or session object to unset the user in
* @return void
*/
public function logout($session = null)
public function logout($session = null): void
{
$session = $this->sessionFromOptions($session);
@@ -445,6 +474,27 @@ class User extends ModelWithContent
return $this->kirby()->url('media') . '/users/' . $this->id();
}
/**
* Creates a user model if it has been registered
*
* @internal
* @param string $name
* @param array $props
* @return Kirby\Cms\User
*/
public static function model(string $name, array $props = [])
{
if ($class = (static::$models[$name] ?? null)) {
$object = new $class($props);
if (is_a($object, 'Kirby\Cms\User') === true) {
return $object;
}
}
return new static($props);
}
/**
* Returns the last modification date of the user
*
@@ -465,7 +515,7 @@ class User extends ModelWithContent
/**
* Returns the user's name
*
* @return Field
* @return Kirby\Cms\Field
*/
public function name()
{
@@ -486,7 +536,7 @@ class User extends ModelWithContent
* @internal
* @return self
*/
public static function nobody(): self
public static function nobody()
{
return new static([
'email' => 'nobody@getkirby.com',
@@ -494,6 +544,36 @@ class User extends ModelWithContent
]);
}
/**
* Panel icon definition
*
* @internal
* @param array $params
* @return array
*/
public function panelIcon(array $params = null): array
{
$params['type'] = 'user';
return parent::panelIcon($params);
}
/**
* Returns the image file object based on provided query
*
* @internal
* @param string|null $query
* @return Kirby\Cms\File|Kirby\Cms\Asset|null
*/
protected function panelImageSource(string $query = null)
{
if ($query === null) {
return $this->avatar();
}
return parent::panelImageSource($query);
}
/**
* Returns the full path without leading slash
*
@@ -505,6 +585,29 @@ class User extends ModelWithContent
return 'users/' . $this->id();
}
/**
* Returns prepared data for the panel user picker
*
* @param array|null $params
* @return array
*/
public function panelPickerData(array $params = null): array
{
$image = $this->panelImage($params['image'] ?? []);
$icon = $this->panelIcon($image);
return [
'icon' => $icon,
'id' => $this->id(),
'image' => $image,
'email' => $this->email(),
'info' => $this->toString($params['info'] ?? false),
'link' => $this->panelUrl(true),
'text' => $this->toString($params['text'] ?? '{{ user.username }}'),
'username' => $this->username(),
];
}
/**
* Returns the url to the editing view
* in the panel
@@ -537,46 +640,19 @@ class User extends ModelWithContent
}
/**
* @return UserPermissions
* @return Kirby\Cms\UserPermissions
*/
public function permissions()
{
return new UserPermissions($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)
{
if ($query === null) {
return null;
}
$result = Str::query($query, [
'kirby' => $kirby = $this->kirby(),
'site' => $kirby->site(),
'user' => $this
]);
if ($expect !== null && is_a($result, $expect) !== true) {
return null;
}
return $result;
}
/**
* Returns the user role
*
* @return string
* @return Kirby\Cms\Role
*/
public function role(): Role
public function role()
{
if (is_a($this->role, 'Kirby\Cms\Role') === true) {
return $this->role;
@@ -605,7 +681,7 @@ class User extends ModelWithContent
* Returns the UserRules class to
* validate any important action.
*
* @return UserRules
* @return Kirby\Cms\UserRules
*/
protected function rules()
{
@@ -618,7 +694,7 @@ class User extends ModelWithContent
* @param array|null $blueprint
* @return self
*/
protected function setBlueprint(array $blueprint = null): self
protected function setBlueprint(array $blueprint = null)
{
if ($blueprint !== null) {
$blueprint['model'] = $this;
@@ -634,7 +710,7 @@ class User extends ModelWithContent
* @param string $email
* @return self
*/
protected function setEmail(string $email = null): self
protected function setEmail(string $email = null)
{
if ($email !== null) {
$this->email = strtolower(trim($email));
@@ -648,7 +724,7 @@ class User extends ModelWithContent
* @param string $id
* @return self
*/
protected function setId(string $id = null): self
protected function setId(string $id = null)
{
$this->id = $id;
return $this;
@@ -660,7 +736,7 @@ class User extends ModelWithContent
* @param string $language
* @return self
*/
protected function setLanguage(string $language = null): self
protected function setLanguage(string $language = null)
{
$this->language = $language !== null ? trim($language) : null;
return $this;
@@ -672,7 +748,7 @@ class User extends ModelWithContent
* @param string $name
* @return self
*/
protected function setName(string $name = null): self
protected function setName(string $name = null)
{
$this->name = $name !== null ? trim($name) : null;
return $this;
@@ -684,7 +760,7 @@ class User extends ModelWithContent
* @param string $password
* @return self
*/
protected function setPassword(string $password = null): self
protected function setPassword(string $password = null)
{
$this->password = $password;
return $this;
@@ -696,7 +772,7 @@ class User extends ModelWithContent
* @param string $role
* @return self
*/
protected function setRole(string $role = null): self
protected function setRole(string $role = null)
{
$this->role = $role !== null ? strtolower(trim($role)) : null;
return $this;
@@ -705,10 +781,10 @@ class User extends ModelWithContent
/**
* Converts session options into a session object
*
* @param Session|array $session Session options or session object to unset the user in
* @return Session
* @param Kirby\Session\Session|array $session Session options or session object to unset the user in
* @return Kirby\Session\Session
*/
protected function sessionFromOptions($session): Session
protected function sessionFromOptions($session)
{
// use passed session options or session object if set
if (is_array($session) === true) {
@@ -723,7 +799,7 @@ class User extends ModelWithContent
/**
* Returns the parent Users collection
*
* @return Users
* @return Kirby\Cms\Users
*/
protected function siblingsCollection()
{
@@ -758,14 +834,10 @@ class User extends ModelWithContent
public function toString(string $template = null): string
{
if ($template === null) {
return $this->email();
$template = $this->email();
}
return Str::template($template, [
'user' => $this,
'site' => $this->site(),
'kirby' => $this->kirby()
]);
return parent::toString($template);
}
/**
@@ -773,7 +845,7 @@ class User extends ModelWithContent
* which is the given name or the email
* as a fallback
*
* @return string
* @return string|null
*/
public function username(): ?string
{

View File

@@ -4,16 +4,21 @@ namespace Kirby\Cms;
use Closure;
use Kirby\Data\Data;
use Kirby\Exception\DuplicateException;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentLogicException;
use Kirby\Exception\LogicException;
use Kirby\Exception\PermissionException;
use Kirby\Toolkit\Dir;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Str;
use Kirby\Toolkit\V;
/**
* UserActions
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
trait UserActions
{
@@ -23,7 +28,7 @@ trait UserActions
* @param string $email
* @return self
*/
public function changeEmail(string $email): self
public function changeEmail(string $email)
{
return $this->commit('changeEmail', [$this, $email], function ($user, $email) {
$user = $user->clone([
@@ -44,7 +49,7 @@ trait UserActions
* @param string $language
* @return self
*/
public function changeLanguage(string $language): self
public function changeLanguage(string $language)
{
return $this->commit('changeLanguage', [$this, $language], function ($user, $language) {
$user = $user->clone([
@@ -65,7 +70,7 @@ trait UserActions
* @param string $name
* @return self
*/
public function changeName(string $name): self
public function changeName(string $name)
{
return $this->commit('changeName', [$this, $name], function ($user, $name) {
$user = $user->clone([
@@ -86,7 +91,7 @@ trait UserActions
* @param string $password
* @return self
*/
public function changePassword(string $password): self
public function changePassword(string $password)
{
return $this->commit('changePassword', [$this, $password], function ($user, $password) {
$user = $user->clone([
@@ -105,7 +110,7 @@ trait UserActions
* @param string $role
* @return self
*/
public function changeRole(string $role): self
public function changeRole(string $role)
{
return $this->commit('changeRole', [$this, $role], function ($user, $role) {
$user = $user->clone([
@@ -156,7 +161,7 @@ trait UserActions
* @param array $input
* @return self
*/
public static function create(array $props = null): self
public static function create(array $props = null)
{
$data = $props;
@@ -164,7 +169,9 @@ trait UserActions
$data['password'] = static::hashPassword($props['password']);
}
$user = new static($data);
$props['role'] = $props['model'] = strtolower($props['role'] ?? 'default');
$user = User::factory($data);
// create a form for the user
$form = Form::for($user, [

View File

@@ -5,6 +5,12 @@ namespace Kirby\Cms;
/**
* Extension of the basic blueprint class
* to handle all blueprints for users.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class UserBlueprint extends Blueprint
{

View File

@@ -2,6 +2,15 @@
namespace Kirby\Cms;
/**
* UserPermissions
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class UserPermissions extends ModelPermissions
{
protected $category = 'users';

View File

@@ -11,6 +11,12 @@ use Kirby\Toolkit\V;
/**
* Validators for all user actions
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class UserRules
{
@@ -164,7 +170,7 @@ class UserRules
public static function validId(User $user, string $id): bool
{
if ($duplicate = $user->kirby()->users()->find($id)) {
if ($user->kirby()->users()->find($id)) {
throw new DuplicateException('A user with this id exists');
}

View File

@@ -13,11 +13,20 @@ use Kirby\Toolkit\Str;
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Users extends Collection
{
/**
* All registered users methods
*
* @var array
*/
public static $methods = [];
public function create(array $data)
{
return User::create($data);
@@ -29,7 +38,7 @@ class Users extends Collection
* current collection
*
* @param mixed $item
* @return Users
* @return self
*/
public function add($object)
{
@@ -56,13 +65,13 @@ class Users extends Collection
* @param array $inject
* @return self
*/
public static function factory(array $users, array $inject = []): self
public static function factory(array $users, array $inject = [])
{
$collection = new static;
// read all user blueprints
foreach ($users as $props) {
$user = new User($props + $inject);
$user = User::factory($props + $inject);
$collection->set($user->id(), $user);
}
@@ -73,9 +82,9 @@ class Users extends Collection
* Finds a user in the collection by id or email address
*
* @param string $key
* @return User|null
* @return Kirby\Cms\User|null
*/
public function findByKey($key)
public function findByKey(string $key)
{
if (Str::contains($key, '@') === true) {
return parent::findBy('email', strtolower($key));
@@ -91,7 +100,7 @@ class Users extends Collection
* @param array $inject
* @return self
*/
public static function load(string $root, array $inject = []): self
public static function load(string $root, array $inject = [])
{
$users = new static;
@@ -109,4 +118,15 @@ class Users extends Collection
return $users;
}
/**
* Shortcut for `$users->filterBy('role', 'admin')`
*
* @param string $role
* @return self
*/
public function role(string $role)
{
return $this->filterBy('role', $role);
}
}

View File

@@ -6,11 +6,17 @@ use Kirby\Toolkit\Facade;
/**
* Shortcut to the visitor object
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class Visitor extends Facade
{
/**
* @return \Kirby\Http\Visitor
* @return Kirby\Http\Visitor
*/
public static function instance()
{

View File

@@ -8,18 +8,19 @@ use Kirby\Toolkit\F;
/**
* The `Data` class provides readers and
* writers for data. The class comes with
* three handlers for `json`, `yaml` and
* `txt` encoded data, but can be extended
* and customized.
* four handlers for `json`, `php`, `txt`
* and `yaml` encoded data, but can be
* extended and customized.
*
* The read and write methods automatically
* detect, which data handler to use in order
* detect which data handler to use in order
* to correctly encode and decode passed data.
*
* @package Kirby
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://opensource.org/licenses/MIT
*/
class Data
{
@@ -50,31 +51,31 @@ class Data
/**
* Handler getter
*
* @param string $type
* @return Handler|null
* @param string $type
* @return Kirby\Data\Handler
*/
public static function handler(string $type)
{
// normalize the type
$type = strtolower($type);
$handler = static::$handlers[$type] ?? null;
$type = strtolower($type);
if ($handler === null && isset(static::$aliases[$type]) === true) {
$handler = static::$handlers[static::$aliases[$type]] ?? null;
// find a handler or alias
$handler = static::$handlers[$type] ??
static::$handlers[static::$aliases[$type] ?? null] ??
null;
if (class_exists($handler)) {
return new $handler;
}
if ($handler === null) {
throw new Exception('Missing Handler for type: "' . $type . '"');
}
return new $handler;
throw new Exception('Missing handler for type: "' . $type . '"');
}
/**
* Decode data with the specified handler
* Decodes data with the specified handler
*
* @param string $data
* @param string $type
* @param string $data
* @param string $type
* @return array
*/
public static function decode(string $data = null, string $type): array
@@ -83,10 +84,10 @@ class Data
}
/**
* Encode data with the specified handler
* Encodes data with the specified handler
*
* @param array $data
* @param string $type
* @param array $data
* @param string $type
* @return string
*/
public static function encode(array $data = null, string $type): string
@@ -95,9 +96,9 @@ class Data
}
/**
* Reads data from a file
* The data handler is automatically chosen by
* the extension if not specified.
* Reads data from a file;
* the data handler is automatically chosen by
* the extension if not specified
*
* @param string $file
* @param string $type
@@ -109,13 +110,13 @@ class Data
}
/**
* Writes data to a file.
* The data handler is automatically chosen by
* the extension if not specified.
* Writes data to a file;
* the data handler is automatically chosen by
* the extension if not specified
*
* @param string $file
* @param array $data
* @param string $type
* @param string $file
* @param array $data
* @param string $type
* @return boolean
*/
public static function write(string $file = null, array $data = [], string $type = null): bool

Some files were not shown because too many files have changed in this diff Show More