Upgrade to 3.3.0

This commit is contained in:
Bastian Allgeier
2019-11-05 09:35:58 +01:00
parent 447a9dd266
commit a431716732
186 changed files with 3068 additions and 1458 deletions

View File

@@ -37,9 +37,7 @@ class Api extends BaseApi
$this->setRequestMethod($method);
$this->setRequestData($requestData);
if ($languageCode = $this->language()) {
$this->kirby->setCurrentLanguage($languageCode);
}
$this->kirby->setCurrentLanguage($this->language());
if ($user = $this->kirby->user()) {
$this->kirby->setCurrentTranslation($user->language());

View File

@@ -4,6 +4,7 @@ namespace Kirby\Cms;
use Kirby\Data\Data;
use Kirby\Email\PHPMailer as Emailer;
use Kirby\Exception\ErrorPageException;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\NotFoundException;
use Kirby\Http\Request;
@@ -56,6 +57,7 @@ class App
protected $languages;
protected $locks;
protected $multilang;
protected $nonce;
protected $options;
protected $path;
protected $request;
@@ -120,9 +122,15 @@ class App
$this->extensionsFromOptions();
$this->extensionsFromFolders();
// trigger hook for use in plugins
$this->trigger('system.loadPlugins:after');
// handle those damn errors
$this->handleErrors();
// execute a ready callback from the config
$this->optionsFromReadyCallback();
// bake config
Config::$data = $this->options;
}
@@ -475,7 +483,7 @@ class App
*
* @param string $path
* @param mixed $parent
* @param boolean $drafts
* @param bool $drafts
* @return \Kirby\Cms\File|null
*/
public function file(string $path, $parent = null, bool $drafts = true)
@@ -586,7 +594,11 @@ class App
// Pages
if (is_a($input, 'Kirby\Cms\Page')) {
$html = $input->render();
try {
$html = $input->render();
} catch (ErrorPageException $e) {
return $this->io($e);
}
if ($input->isErrorPage() === true) {
if ($response->code() === null) {
@@ -758,7 +770,7 @@ class App
/**
* Check for a multilang setup
*
* @return boolean
* @return bool
*/
public function multilang(): bool
{
@@ -769,6 +781,17 @@ class App
return $this->multilang = $this->languages()->count() !== 0;
}
/**
* Returns the nonce, which is used
* in the panel for inline scripts
*
* @return string
*/
public function nonce(): string
{
return $this->nonce = $this->nonce ?? base64_encode(random_bytes(20));
}
/**
* Load a specific configuration option
*
@@ -791,17 +814,6 @@ class App
return $this->options;
}
/**
* Inject options from Kirby instance props
*
* @param array $options
* @return array
*/
protected function optionsFromProps(array $options = []): array
{
return $this->options = array_replace_recursive($this->options, $options);
}
/**
* Load all options from files in site/config
*
@@ -823,16 +835,49 @@ class App
return $this->options = array_replace_recursive($config, $main, $host, $addr);
}
/**
* Inject options from Kirby instance props
*
* @param array $options
* @return array
*/
protected function optionsFromProps(array $options = []): array
{
return $this->options = array_replace_recursive($this->options, $options);
}
/**
* Merge last-minute options from ready callback
*
* @return array
*/
protected function optionsFromReadyCallback(): array
{
if (isset($this->options['ready']) === true && is_callable($this->options['ready']) === true) {
// fetch last-minute options from the callback
$options = (array)$this->options['ready']($this);
// inject all last-minute options recursively
$this->options = array_replace_recursive($this->options, $options);
}
return $this->options;
}
/**
* Returns any page from the content folder
*
* @param string $id
* @param string $id|null
* @param \Kirby\Cms\Page|\Kirby\Cms\Site|null $parent
* @param bool $drafts
* @return \Kirby\Cms\Page|null
*/
public function page(string $id, $parent = null, bool $drafts = true)
public function page(?string $id = null, $parent = null, bool $drafts = true)
{
if ($id === null) {
return null;
}
$parent = $parent ?? $this->site();
if ($page = $parent->find($id)) {
@@ -1184,10 +1229,20 @@ class App
{
$options = $this->option('smartypants', []);
if ($options === true) {
if ($options === false) {
return $text;
} elseif (is_array($options) === false) {
$options = [];
}
if ($this->multilang() === true) {
$languageSmartypants = $this->language()->smartypants() ?? [];
if (empty($languageSmartypants) === false) {
$options = array_merge($options, $languageSmartypants);
}
}
return $this->component('smartypants')($this, $text, $options);
}

View File

@@ -29,18 +29,17 @@ trait AppErrors
protected function handleErrors()
{
$request = $this->request();
// TODO: implement acceptance
if ($request->ajax()) {
return $this->handleJsonErrors();
if ($this->request()->cli() === true) {
$this->handleCliErrors();
return;
}
if ($request->cli()) {
return $this->handleCliErrors();
if ($this->visitor()->prefersJson() === true) {
$this->handleJsonErrors();
return;
}
return $this->handleHtmlErrors();
$this->handleHtmlErrors();
}
protected function handleHtmlErrors()

View File

@@ -701,7 +701,6 @@ trait AppPlugins
protected function pluginsLoader(): array
{
$root = $this->root('plugins');
$kirby = $this;
$loaded = [];
foreach (Dir::read($root) as $dirname) {
@@ -709,14 +708,10 @@ trait AppPlugins
continue;
}
if (is_dir($root . '/' . $dirname) === false) {
continue;
}
$dir = $root . '/' . $dirname;
$entry = $dir . '/index.php';
if (file_exists($entry) === false) {
if (is_dir($dir) !== true || is_file($entry) !== true) {
continue;
}

View File

@@ -158,7 +158,7 @@ trait AppTranslations
$inject = $this->extensions['translations'][$locale] ?? [];
// load from disk instead
return Translation::load($locale, $this->root('translations') . '/' . $locale . '.json', $inject);
return Translation::load($locale, $this->root('i18n:translations') . '/' . $locale . '.json', $inject);
}
/**
@@ -172,6 +172,6 @@ trait AppTranslations
return $this->translations;
}
return Translations::load($this->root('translations'), $this->extensions['translations'] ?? []);
return Translations::load($this->root('i18n:translations'), $this->extensions['translations'] ?? []);
}
}

View File

@@ -77,7 +77,7 @@ trait AppUsers
* Returns a specific user by id
* or the current user if no id is given
*
* @param string $id
* @param string $id
* @return \Kirby\Cms\User|null
*/
public function user(string $id = null)

View File

@@ -165,7 +165,7 @@ class Auth
* Check if logins are blocked for the current ip or email
*
* @param string $email
* @return boolean
* @return bool
*/
public function isBlocked(string $email): bool
{
@@ -195,7 +195,7 @@ class Auth
*
* @param string $email
* @param string $password
* @param boolean $long
* @param bool $long
* @return \Kirby\Cms\User
*
* @throws PermissionException If the rate limit was exceeded or if any other error occured with debug mode off
@@ -243,7 +243,7 @@ class Auth
$message = 'Invalid email or password';
}
throw new PermissionException($message, 403);
throw new PermissionException($message);
}
// validate the user
@@ -334,7 +334,7 @@ class Auth
/**
* Logout the current user
*
* @return boolean
* @return bool
*/
public function logout(): bool
{
@@ -354,7 +354,7 @@ class Auth
* Tracks a login
*
* @param string $email
* @return boolean
* @return bool
*/
public function track(string $email): bool
{

View File

@@ -472,7 +472,7 @@ class Blueprint
return [
'label' => 'Error',
'name' => $name,
'text' => $message,
'text' => strip_tags($message),
'theme' => 'negative',
'type' => 'info',
];
@@ -595,12 +595,17 @@ class Blueprint
continue;
}
// fallback to default props when true is passed
if ($sectionProps === true) {
$sectionProps = [];
}
// inject all section extensions
$sectionProps = $this->extend($sectionProps);
$sections[$sectionName] = $sectionProps = array_merge($sectionProps, [
'name' => $sectionName,
'type' => $type = $sectionProps['type'] ?? null
'type' => $type = $sectionProps['type'] ?? $sectionName
]);
if (empty($type) === true || is_string($type) === false) {

View File

@@ -38,8 +38,8 @@ class Collection extends BaseCollection
/**
* Magic getter function
*
* @param string $key
* @param mixed $arguments
* @param string $key
* @param mixed $arguments
* @return mixed
*/
public function __call(string $key, $arguments)
@@ -101,8 +101,8 @@ class Collection extends BaseCollection
/**
* Appends an element to the data array
*
* @param mixed $key Optional collection key, will be determined from the item if not given
* @param mixed $item
* @param mixed $key Optional collection key, will be determined from the item if not given
* @param mixed $item
* @return \Kirby\Cms\Collection
*/
public function append(...$args)
@@ -165,7 +165,7 @@ class Collection extends BaseCollection
* is in the collection
*
* @param string|object $id
* @return boolean
* @return bool
*/
public function has($id): bool
{
@@ -181,7 +181,7 @@ class Collection extends BaseCollection
* The method will automatically detect objects
* or ids and then search accordingly.
*
* @param string|object $object
* @param string|object $object
* @return int
*/
public function indexOf($object): int
@@ -196,7 +196,7 @@ class Collection extends BaseCollection
/**
* Returns a Collection without the given element(s)
*
* @param mixed ...$keys any number of keys, passed as individual arguments
* @param mixed ...$keys any number of keys, passed as individual arguments
* @return \Kirby\Cms\Collection
*/
public function not(...$keys)
@@ -216,7 +216,7 @@ class Collection extends BaseCollection
/**
* Add pagination and return a sliced set of data.
*
* @param mixed ...$arguments
* @param mixed ...$arguments
* @return \Kirby\Cms\Collection
*/
public function paginate(...$arguments)
@@ -240,9 +240,9 @@ class Collection extends BaseCollection
/**
* Prepends an element to the data array
*
* @param mixed $key Optional collection key, will be determined from the item if not given
* @param mixed $item
* @return Kirby\Cms\Collection
* @param mixed $key Optional collection key, will be determined from the item if not given
* @param mixed $item
* @return \Kirby\Cms\Collection
*/
public function prepend(...$args)
{
@@ -321,7 +321,7 @@ class Collection extends BaseCollection
* to an array. This can also take a callback
* function to further modify the array result.
*
* @param Closure $map
* @param Closure $map
* @return array
*/
public function toArray(Closure $map = null): array

View File

@@ -42,8 +42,8 @@ class Collections
* Magic caller to enable something like
* `$collections->myCollection()`
*
* @param string $name
* @param array $arguments
* @param string $name
* @param array $arguments
* @return \Kirby\Cms\Collection|null
*/
public function __call(string $name, array $arguments = [])
@@ -89,7 +89,7 @@ class Collections
* Checks if a collection exists
*
* @param string $name
* @return boolean
* @return bool
*/
public function has(string $name): bool
{
@@ -109,7 +109,7 @@ class Collections
* Loads collection from php file in a
* given directory or from plugin extension.
*
* @param string $name
* @param string $name
* @return mixed
*/
public function load(string $name)

View File

@@ -69,7 +69,7 @@ class Content
* Same as `self::data()` to improve
* `var_dump` output
*
* @see self::data()
* @see self::data()
* @return array
*/
public function __debugInfo(): array
@@ -146,7 +146,7 @@ class Content
* Returns either a single field object
* or all registered fields
*
* @param string $key
* @param string $key
* @return \Kirby\Cms\Field|array
*/
public function get(string $key = null)
@@ -172,7 +172,7 @@ class Content
* Checks if a content field is set
*
* @param string $key
* @return boolean
* @return bool
*/
public function has(string $key): bool
{
@@ -197,7 +197,7 @@ class Content
* without the fields, specified by the
* passed key(s)
*
* @param string ...$keys
* @param string ...$keys
* @return self
*/
public function not(...$keys)
@@ -238,8 +238,8 @@ class Content
/**
* Returns the raw data array
*
* @see self::data()
* @return array
* @see self::data()
* @return array
*/
public function toArray(): array
{
@@ -250,8 +250,8 @@ class Content
* Updates the content and returns
* a cloned object
*
* @param array $content
* @param bool $overwrite
* @param array $content
* @param bool $overwrite
* @return self
*/
public function update(array $content = null, bool $overwrite = false)

View File

@@ -85,7 +85,7 @@ class ContentLock
'user' => $user->id(),
'email' => $user->email(),
'time' => $time,
'unlockable' => ($time + 200) <= time()
'unlockable' => ($time + 60) <= time()
];
}
@@ -144,7 +144,10 @@ class ContentLock
// 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);
throw new LogicException([
'fallback' => 'The content lock can only be removed by the user who created it. Use unlock instead.',
'httpCode' => 409
]);
}
// remove lock

View File

@@ -121,7 +121,7 @@ class ContentLocks
* Returns the file handle to a `.lock` file
*
* @param string $file
* @param boolean $create Whether to create the file if it does not exist
* @param bool $create Whether to create the file if it does not exist
* @return resource|null File handle
*/
protected function handle(string $file, bool $create = false)
@@ -167,7 +167,7 @@ class ContentLocks
*
* @param \Kirby\Cms\ModelWithContent $model
* @param array $data
* @return boolean
* @return bool
*/
public function set(ModelWithContent $model, array $data): bool
{

View File

@@ -119,7 +119,7 @@ class ContentTranslation
/**
* Checks if the translation file exists
*
* @return boolean
* @return bool
*/
public function exists(): bool
{
@@ -140,7 +140,7 @@ class ContentTranslation
* Checks if the this is the default translation
* of the model
*
* @return boolean
* @return bool
*/
public function isDefault(): bool
{

View File

@@ -27,7 +27,7 @@ class Dir extends \Kirby\Toolkit\Dir
* @param string $dir
* @param string $contentExtension
* @param array $contentIgnore
* @param boolean $multilang
* @param bool $multilang
* @return array
*/
public static function inventory(string $dir, string $contentExtension = 'txt', array $contentIgnore = null, bool $multilang = false): array

View File

@@ -95,7 +95,7 @@ class Field
*
* @param object $parent
* @param string $key
* @param mixed $value
* @param mixed $value
*/
public function __construct($parent = null, string $key, $value)
{
@@ -130,7 +130,7 @@ class Field
/**
* Checks if the field exists in the content data array
*
* @return boolean
* @return bool
*/
public function exists(): bool
{
@@ -140,7 +140,7 @@ class Field
/**
* Checks if the field content is empty
*
* @return boolean
* @return bool
*/
public function isEmpty(): bool
{
@@ -150,7 +150,7 @@ class Field
/**
* Checks if the field content is not empty
*
* @return boolean
* @return bool
*/
public function isNotEmpty(): bool
{
@@ -232,7 +232,7 @@ class Field
* the modified field will be returned. Otherwise it
* will return the field value.
*
* @param string|Closure $value
* @param string|Closure $value
* @return mixed
*/
public function value($value = null)

View File

@@ -231,12 +231,14 @@ class File extends ModelWithContent
* gets dragged onto a textarea
*
* @internal
* @param string $type (auto|kirbytext|markdown)
* @param string $type (null|auto|kirbytext|markdown)
* @param bool $absolute
* @return string
*/
public function dragText($type = 'auto', bool $absolute = false): string
public function dragText(string $type = null, bool $absolute = false): string
{
$type = $type ?? 'auto';
if ($type === 'auto') {
$type = option('panel.kirbytext', true) ? 'kirbytext' : 'markdown';
}
@@ -363,14 +365,16 @@ class File extends ModelWithContent
*/
public function meta()
{
deprecated('$file->meta() is deprecated, use $file->content() instead. $file->meta() will be removed in Kirby 3.5.0.');
return $this->content();
}
/**
* Get the file's last modification time.
*
* @param string $format
* @param string|null $handler date or strftime
* @param string $format
* @param string|null $handler date or strftime
* @return mixed
*/
public function modified(string $format = null, string $handler = null)
@@ -392,7 +396,7 @@ class File extends ModelWithContent
* Timestamp of the last modification
* of the content file
*
* @return integer
* @return int
*/
protected function modifiedContent(): int
{
@@ -403,7 +407,7 @@ class File extends ModelWithContent
* Timestamp of the last modification
* of the source file
*
* @return integer
* @return int
*/
protected function modifiedFile(): int
{

View File

@@ -82,7 +82,7 @@ trait FileActions
/**
* Changes the file's sorting number in the meta file
*
* @param integer $sort
* @param int $sort
* @return self
*/
public function changeSort(int $sort)
@@ -256,6 +256,8 @@ trait FileActions
*/
public function rename(string $name, bool $sanitize = true)
{
deprecated('$file->rename() is deprecated, use $file->changeName() instead. $file->rename() will be removed in Kirby 3.5.0.');
return $this->changeName($name, $sanitize);
}

View File

@@ -44,7 +44,7 @@ class FileBlueprint extends Blueprint
}
/**
* @param mixed $accept
* @param mixed $accept
* @return array
*/
protected function normalizeAccept($accept = null): array

View File

@@ -82,7 +82,7 @@ trait FileFoundation
/**
* Checks if the file exists on disk
*
* @return boolean
* @return bool
*/
public function exists(): bool
{
@@ -102,7 +102,7 @@ trait FileFoundation
/**
* Converts the file to html
*
* @param array $attr
* @param array $attr
* @return string
*/
public function html(array $attr = []): string
@@ -117,7 +117,7 @@ trait FileFoundation
/**
* Checks if the file is a resizable image
*
* @return boolean
* @return bool
*/
public function isResizable(): bool
{
@@ -136,7 +136,7 @@ trait FileFoundation
* Checks if a preview can be displayed for the file
* in the panel or in the frontend
*
* @return boolean
* @return bool
*/
public function isViewable(): bool
{
@@ -165,8 +165,8 @@ trait FileFoundation
/**
* Get the file's last modification time.
*
* @param string $format
* @param string|null $handler date or strftime
* @param string $format
* @param string|null $handler date or strftime
* @return mixed
*/
public function modified(string $format = null, string $handler = null)

View File

@@ -18,7 +18,7 @@ trait FileModifications
/**
* Blurs the image by the given amount of pixels
*
* @param boolean $pixels
* @param bool $pixels
* @return \Kirby\Cms\FileVersion|\Kirby\Cms\File
*/
public function blur($pixels = true)
@@ -39,8 +39,8 @@ trait FileModifications
/**
* Crops the image by the given width and height
*
* @param integer $width
* @param integer $height
* @param int $width
* @param int $height
* @param string|array $options
* @return \Kirby\Cms\FileVersion|\Kirby\Cms\File
*/
@@ -71,7 +71,7 @@ trait FileModifications
/**
* Sets the JPEG compression quality
*
* @param integer $quality
* @param int $quality
* @return \Kirby\Cms\FileVersion|\Kirby\Cms\File
*/
public function quality(int $quality)
@@ -83,9 +83,9 @@ trait FileModifications
* Resizes the file with the given width and height
* while keeping the aspect ratio.
*
* @param integer $width
* @param integer $height
* @param integer $quality
* @param int $width
* @param int $height
* @param int $quality
* @return \Kirby\Cms\FileVersion|\Kirby\Cms\File
*/
public function resize(int $width = null, int $height = null, int $quality = null)

73
kirby/src/Cms/FilePicker.php Executable file
View File

@@ -0,0 +1,73 @@
<?php
namespace Kirby\Cms;
use Kirby\Exception\InvalidArgumentException;
/**
* The FilePicker class helps to
* fetch the right files for the API calls
* for the file picker component in the panel.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class FilePicker extends Picker
{
/**
* Extends the basic defaults
*
* @return array
*/
public function defaults(): array
{
$defaults = parent::defaults();
$defaults['text'] = '{{ file.filename }}';
return $defaults;
}
/**
* Search all files for the picker
*
* @return \Kirby\Cms\Files|null
*/
public function items()
{
$model = $this->options['model'];
// find the right default query
if (empty($this->options['query']) === false) {
$query = $this->options['query'];
} elseif (is_a($model, 'Kirby\Cms\File') === true) {
$query = 'file.siblings';
} else {
$query = $model::CLASS_ALIAS . '.files';
}
// fetch all files for the picker
$files = $model->query($query);
// help mitigate some typical query usage issues
// by converting site and page objects to proper
// pages by returning their children
if (is_a($files, 'Kirby\Cms\Site') === true) {
$files = $files->files();
} elseif (is_a($files, 'Kirby\Cms\Page') === true) {
$files = $files->files();
} elseif (is_a($files, 'Kirby\Cms\User') === true) {
$files = $files->files();
} elseif (is_a($files, 'Kirby\Cms\Files') === false) {
throw new InvalidArgumentException('Your query must return a set of files');
}
// search
$files = $this->search($files);
// paginate
return $this->paginate($files);
}
}

View File

@@ -68,7 +68,7 @@ class Filename
*
* @param string $filename
* @param string $template
* @param array $attributes
* @param array $attributes
*/
public function __construct(string $filename, string $template, array $attributes = [])
{
@@ -118,7 +118,7 @@ class Filename
* to a string, that can be used in the
* new filename
*
* @param string $prefix The prefix will be used in the filename creation
* @param string $prefix The prefix will be used in the filename creation
* @return string
*/
public function attributesToString(string $prefix = null): string
@@ -265,7 +265,7 @@ class Filename
* to lowercase and `jpeg` will be
* replaced with `jpg`
*
* @param string $extension
* @param string $extension
* @return string
*/
protected function sanitizeExtension(string $extension): string
@@ -279,7 +279,7 @@ class Filename
* Sanitizes the name with Kirby's
* Str::slug function
*
* @param string $name
* @param string $name
* @return string
*/
protected function sanitizeName(string $name): string

View File

@@ -157,7 +157,7 @@ trait HasChildren
/**
* Checks if the model has any children
*
* @return boolean
* @return bool
*/
public function hasChildren(): bool
{
@@ -167,7 +167,7 @@ trait HasChildren
/**
* Checks if the model has any drafts
*
* @return boolean
* @return bool
*/
public function hasDrafts(): bool
{
@@ -175,18 +175,20 @@ trait HasChildren
}
/**
* @deprecated 3.0.0 Use `Page::hasUnlistedChildren` instead
* @return boolean
* @deprecated 3.0.0 Use `Page::hasUnlistedChildren()` instead
* @return bool
*/
public function hasInvisibleChildren(): bool
{
deprecated('$page->hasInvisibleChildren() is deprecated, use $page->hasUnlistedChildren() instead. $page->hasInvisibleChildren() will be removed in Kirby 3.5.0.');
return $this->hasUnlistedChildren();
}
/**
* Checks if the page has any listed children
*
* @return boolean
* @return bool
*/
public function hasListedChildren(): bool
{
@@ -196,7 +198,7 @@ trait HasChildren
/**
* Checks if the page has any unlisted children
*
* @return boolean
* @return bool
*/
public function hasUnlistedChildren(): bool
{
@@ -204,11 +206,13 @@ trait HasChildren
}
/**
* @deprecated 3.0.0 Use `Page::hasListedChildren` instead
* @return boolean
* @deprecated 3.0.0 Use `Page::hasListedChildren()` instead
* @return bool
*/
public function hasVisibleChildren(): bool
{
deprecated('$page->hasVisibleChildren() is deprecated, use $page->hasListedChildren() instead. $page->hasVisibleChildren() will be removed in Kirby 3.5.0.');
return $this->hasListedChildren();
}

View File

@@ -25,8 +25,8 @@ trait HasMethods
* passed arguments
*
* @internal
* @param string $method
* @param array $args
* @param string $method
* @param array $args
* @return mixed
*/
public function callMethod(string $method, array $args = [])
@@ -39,7 +39,7 @@ trait HasMethods
*
* @internal
* @param string $method
* @return boolean
* @return bool
*/
public function hasMethod(string $method): bool
{

View File

@@ -58,6 +58,11 @@ class Language extends Model
*/
protected $slugs;
/**
* @var array|null
*/
protected $smartypants;
/**
* @var array|null
*/
@@ -85,6 +90,7 @@ class Language extends Model
'locale',
'name',
'slugs',
'smartypants',
'translations',
'url',
]);
@@ -111,6 +117,28 @@ class Language extends Model
return $this->code();
}
/**
* Returns the base Url for the language
* without the path or other cruft
*
* @return string
*/
public function baseUrl(): string
{
$kirbyUrl = $this->kirby()->url();
$languageUrl = $this->url();
if (empty($this->url)) {
return $kirbyUrl;
}
if (Str::startsWith($languageUrl, $kirbyUrl) === true) {
return $kirbyUrl;
}
return Url::base($languageUrl) ?? $kirbyUrl;
}
/**
* Returns the language code/id.
* The language code is used in
@@ -129,7 +157,7 @@ class Language extends Model
*
* @param string $from
* @param string $to
* @return boolean
* @return bool
*/
protected static function converter(string $from, string $to): bool
{
@@ -201,7 +229,7 @@ class Language extends Model
* all its translation files
*
* @internal
* @return boolean
* @return bool
*/
public function delete(): bool
{
@@ -270,7 +298,7 @@ class Language extends Model
/**
* Check if the language file exists
*
* @return boolean
* @return bool
*/
public function exists(): bool
{
@@ -281,7 +309,7 @@ class Language extends Model
* Checks if this is the default language
* for the site.
*
* @return boolean
* @return bool
*/
public function isDefault(): bool
{
@@ -325,6 +353,20 @@ class Language extends Model
return $this->name;
}
/**
* Returns the URL path for the language
*
* @return string
*/
public function path(): string
{
if ($this->url === null) {
return $this->code;
}
return Url::path($this->url());
}
/**
* Returns the routing pattern for the language
*
@@ -332,11 +374,13 @@ class Language extends Model
*/
public function pattern(): string
{
if (empty($this->url) === true) {
return $this->code;
$path = $this->path();
if (empty($path) === true) {
return '(:all)';
}
return trim($this->url, '/');
return $path . '/(:all?)';
}
/**
@@ -430,7 +474,7 @@ class Language extends Model
}
/**
* @param boolean $default
* @param bool $default
* @return self
*/
protected function setDefault(bool $default = false)
@@ -488,6 +532,16 @@ class Language extends Model
return $this;
}
/**
* @param array $smartypants
* @return self
*/
protected function setSmartypants(array $smartypants = null)
{
$this->smartypants = $smartypants ?? [];
return $this;
}
/**
* @param array $translations
* @return self
@@ -518,6 +572,16 @@ class Language extends Model
return $this->slugs;
}
/**
* Returns the custom SmartyPants options for this language
*
* @return array
*/
public function smartypants(): array
{
return $this->smartypants;
}
/**
* Returns the most important
* properties as array
@@ -554,7 +618,13 @@ class Language extends Model
*/
public function url(): string
{
return Url::makeAbsolute($this->pattern(), $this->kirby()->url());
$url = $this->url;
if ($url === null) {
$url = '/' . $this->code;
}
return Url::makeAbsolute($url, $this->kirby()->url());
}
/**

143
kirby/src/Cms/LanguageRoutes.php Executable file
View File

@@ -0,0 +1,143 @@
<?php
namespace Kirby\Cms;
use Kirby\Toolkit\F;
class LanguageRoutes
{
/**
* Creates all multi-language routes
*
* @param \Kirby\Cms\App $kirby
* @return array
*/
public static function create(App $kirby): array
{
$routes = [];
// add the route for the home page
$routes[] = static::home($kirby);
// Kirby's base url
$baseurl = $kirby->url();
foreach ($kirby->languages() as $language) {
// ignore languages with a different base url
if ($language->baseurl() !== $baseurl) {
continue;
}
$routes[] = [
'pattern' => $language->pattern(),
'method' => 'ALL',
'env' => 'site',
'action' => function ($path = null) use ($language) {
if ($result = $language->router()->call($path)) {
return $result;
}
// jump through to the fallback if nothing
// can be found for this language
$this->next();
}
];
}
$routes[] = static::fallback($kirby);
return $routes;
}
/**
* Create the fallback route
* for unprefixed default language URLs.
*
* @param \Kirby\Cms\App $kirby
* @return array
*/
public static function fallback(App $kirby): array
{
return [
'pattern' => '(:all)',
'method' => 'ALL',
'env' => 'site',
'action' => function (string $path) use ($kirby) {
// check for content representations or files
$extension = F::extension($path);
// try to redirect prefixed pages
if (empty($extension) === true && $page = $kirby->page($path)) {
$url = $kirby->request()->url([
'query' => null,
'params' => null,
'fragment' => null
]);
if ($url->toString() !== $page->url()) {
return $kirby
->response()
->redirect($page->url());
}
}
return $kirby->defaultLanguage()->router()->call($path);
}
];
}
/**
* Create the multi-language home page route
*
* @param \Kirby\Cms\App $kirby
* @return array
*/
public static function home(App $kirby): array
{
// Multi-language home
return [
'pattern' => '',
'method' => 'ALL',
'env' => 'site',
'action' => function () use ($kirby) {
// find all languages with the same base url as the current installation
$languages = $kirby->languages()->filterBy('baseurl', $kirby->url());
// if there's no language with a matching base url,
// redirect to the default language
if ($languages->count() === 0) {
return $kirby
->response()
->redirect($kirby->defaultLanguage()->url());
}
// if there's just one language, we take that to render the home page
if ($languages->count() === 1) {
$currentLanguage = $languages->first();
} else {
$currentLanguage = $kirby->defaultLanguage();
}
// language detection on the home page with / as URL
if ($kirby->url() !== $currentLanguage->url()) {
if ($kirby->option('languages.detect') === true) {
return $kirby
->response()
->redirect($kirby->detectedLanguage()->url());
}
return $kirby
->response()
->redirect($currentLanguage->url());
}
// render the home page of the current language
return $currentLanguage->router()->call();
}
];
}
}

View File

@@ -72,11 +72,13 @@ class Languages extends Collection
}
/**
* @deprecated 3.0.0 Use `Languages::default()`instead
* @deprecated 3.0.0 Use `Languages::default()` instead
* @return \Kirby\Cms\Language|null
*/
public function findDefault()
{
deprecated('$languages->findDefault() is deprecated, use $languages->default() instead. $languages->findDefault() will be removed in Kirby 3.5.0.');
return $this->default();
}

View File

@@ -59,7 +59,7 @@ class Media
*
* @param string $src
* @param string $dest
* @return boolean
* @return bool
*/
public static function publish(string $src, string $dest): bool
{

View File

@@ -19,6 +19,14 @@ use Throwable;
*/
abstract class ModelWithContent extends Model
{
/**
* Each model must define a CLASS_ALIAS
* which will be used in template queries.
* The CLASS_ALIAS is a short human-readable
* version of the class name. I.e. page.
*/
const CLASS_ALIAS = null;
/**
* The content
*
@@ -194,8 +202,8 @@ abstract class ModelWithContent extends Model
* Decrement a given field value
*
* @param string $field
* @param integer $by
* @param integer $min
* @param int $by
* @param int $min
* @return self
*/
public function decrement(string $field, int $by = 1, int $min = 0)
@@ -231,8 +239,8 @@ abstract class ModelWithContent extends Model
* Increment a given field value
*
* @param string $field
* @param integer $by
* @param integer $max
* @param int $by
* @param int $max
* @return self
*/
public function increment(string $field, int $by = 1, int $max = null)
@@ -246,10 +254,21 @@ abstract class ModelWithContent extends Model
return $this->update([$field => $value]);
}
/**
* Checks if the model is locked for the current user
*
* @return bool
*/
public function isLocked(): bool
{
$lock = $this->lock();
return $lock && $lock->isLocked() === true;
}
/**
* Checks if the data has any errors
*
* @return boolean
* @return bool
*/
public function isValid(): bool
{
@@ -382,6 +401,38 @@ abstract class ModelWithContent extends Model
return $image;
}
/**
* Returns an array of all actions
* that can be performed in the Panel
* This also checks for the lock status
*
* @param array $unlock An array of options that will be force-unlocked
* @return array
*/
public function panelOptions(array $unlock = []): array
{
$options = $this->permissions()->toArray();
if ($this->isLocked()) {
foreach ($options as $key => $value) {
if (in_array($key, $unlock)) {
continue;
}
$options[$key] = false;
}
}
return $options;
}
/**
* Must return the permissions object for the model
*
* @return \Kirby\Cms\ModelPermissions
*/
abstract public function permissions();
/**
* Creates a string query, starting from the model
*
@@ -618,7 +669,7 @@ abstract class ModelWithContent extends Model
*
* @param array $input
* @param string $languageCode
* @param boolean $validate
* @param bool $validate
* @return self
*/
public function update(array $input = null, string $languageCode = null, bool $validate = false)
@@ -657,7 +708,7 @@ abstract class ModelWithContent extends Model
* @internal
* @param array $data
* @param string $languageCode
* @return boolean
* @return bool
*/
public function writeContent(array $data, string $languageCode = null): bool
{

View File

@@ -21,7 +21,7 @@ class NestCollection extends BaseCollection
* to an array. This can also take a callback
* function to further modify the array result.
*
* @param Closure $map
* @param Closure $map
* @return array
*/
public function toArray(Closure $map = null): array

View File

@@ -104,7 +104,7 @@ class Page extends ModelWithContent
/**
* The sorting number
*
* @var integer|null
* @var int|null
*/
protected $num;
@@ -253,15 +253,14 @@ class Page extends ModelWithContent
$templates = [];
}
// add the current template to the array
$templates[] = $currentTemplate;
// add the current template to the array if it's not already there
if (in_array($currentTemplate, $templates) === false) {
array_unshift($templates, $currentTemplate);
}
// make sure every template is only included once
$templates = array_unique($templates);
// sort the templates
asort($templates);
foreach ($templates as $template) {
try {
$props = Blueprint::load('pages/' . $template);
@@ -352,7 +351,7 @@ class Page extends ModelWithContent
* Returns a number indicating how deep the page
* is nested within the content folder
*
* @return integer
* @return int
*/
public function depth(): int
{
@@ -408,11 +407,13 @@ class Page extends ModelWithContent
* gets dragged onto a textarea
*
* @internal
* @param string $type (auto|kirbytext|markdown)
* @param string $type (null|auto|kirbytext|markdown)
* @return string
*/
public function dragText(string $type = 'auto'): string
public function dragText(string $type = null): string
{
$type = $type ?? 'auto';
if ($type === 'auto') {
$type = option('panel.kirbytext', true) ? 'kirbytext' : 'markdown';
}
@@ -456,7 +457,7 @@ class Page extends ModelWithContent
* Checks if the intended template
* for the page exists.
*
* @return boolean
* @return bool
*/
public function hasTemplate(): bool
{
@@ -563,7 +564,7 @@ class Page extends ModelWithContent
* Checks if the page is a direct or indirect ancestor of the given $page object
*
* @param Page $child
* @return boolean
* @return bool
*/
public function isAncestorOf(Page $child): bool
{
@@ -575,7 +576,7 @@ class Page extends ModelWithContent
* pages cache. This will also check if one
* of the ignore rules from the config kick in.
*
* @return boolean
* @return bool
*/
public function isCacheable(): bool
{
@@ -628,12 +629,12 @@ class Page extends ModelWithContent
* Checks if the page is a child of the given page
*
* @param \Kirby\Cms\Page|string $parent
* @return boolean
* @return bool
*/
public function isChildOf($parent): bool
{
if ($parent = $this->parent()) {
return $parent->is($parent);
if ($parentObj = $this->parent()) {
return $parentObj->is($parent);
}
return false;
@@ -643,7 +644,7 @@ class Page extends ModelWithContent
* Checks if the page is a descendant of the given page
*
* @param \Kirby\Cms\Page|string $parent
* @return boolean
* @return bool
*/
public function isDescendantOf($parent): bool
{
@@ -661,7 +662,7 @@ class Page extends ModelWithContent
/**
* Checks if the page is a descendant of the currently active page
*
* @return boolean
* @return bool
*/
public function isDescendantOfActive(): bool
{
@@ -675,7 +676,7 @@ class Page extends ModelWithContent
/**
* Checks if the current page is a draft
*
* @return boolean
* @return bool
*/
public function isDraft(): bool
{
@@ -695,7 +696,7 @@ class Page extends ModelWithContent
/**
* Check if the page can be read by the current user
*
* @return boolean
* @return bool
*/
public function isReadable(): bool
{
@@ -725,7 +726,7 @@ class Page extends ModelWithContent
* home and error page to stop certain
* actions. That's why there's a shortcut.
*
* @return boolean
* @return bool
*/
public function isHomeOrErrorPage(): bool
{
@@ -733,18 +734,20 @@ class Page extends ModelWithContent
}
/**
* @deprecated 3.0.0 Use `Page::isUnlisted()` intead
* @deprecated 3.0.0 Use `Page::isUnlisted()` instead
* @return bool
*/
public function isInvisible(): bool
{
deprecated('$page->isInvisible() is deprecated, use $page->isUnlisted() instead. $page->isInvisible() will be removed in Kirby 3.5.0.');
return $this->isUnlisted();
}
/**
* Checks if the page has a sorting number
*
* @return boolean
* @return bool
*/
public function isListed(): bool
{
@@ -776,7 +779,7 @@ class Page extends ModelWithContent
/**
* Checks if the page is sortable
*
* @return boolean
* @return bool
*/
public function isSortable(): bool
{
@@ -786,7 +789,7 @@ class Page extends ModelWithContent
/**
* Checks if the page has no sorting number
*
* @return boolean
* @return bool
*/
public function isUnlisted(): bool
{
@@ -794,11 +797,13 @@ class Page extends ModelWithContent
}
/**
* @deprecated 3.0.0 Use `Page::isListed()` intead
* @deprecated 3.0.0 Use `Page::isListed()` instead
* @return bool
*/
public function isVisible(): bool
{
deprecated('$page->isVisible() is deprecated, use $page->isListed() instead. $page->isVisible() will be removed in Kirby 3.5.0.');
return $this->isListed();
}
@@ -808,7 +813,7 @@ class Page extends ModelWithContent
*
* @internal
* @param string $token
* @return boolean
* @return bool
*/
public function isVerified(string $token = null)
{
@@ -884,7 +889,7 @@ class Page extends ModelWithContent
/**
* Returns the sorting number
*
* @return integer|null
* @return int|null
*/
public function num(): ?int
{
@@ -1099,7 +1104,7 @@ class Page extends ModelWithContent
*
* @param array $data
* @param string $contentType
* @param integer $code
* @param int $code
* @return string
*/
public function render(array $data = [], $contentType = 'html'): string
@@ -1241,7 +1246,7 @@ class Page extends ModelWithContent
/**
* Sets the draft flag
*
* @param boolean $isDraft
* @param bool $isDraft
* @return self
*/
protected function setIsDraft(bool $isDraft = null)
@@ -1253,7 +1258,7 @@ class Page extends ModelWithContent
/**
* Sets the sorting number
*
* @param integer $num
* @param int $num
* @return self
*/
protected function setNum(int $num = null)

View File

@@ -160,7 +160,7 @@ trait PageActions
* to either draft, listed or unlisted
*
* @param string $status "draft", "listed" or "unlisted"
* @param integer $position Optional sorting number
* @param int $position Optional sorting number
* @return self
*/
public function changeStatus(string $status, int $position = null)
@@ -462,8 +462,8 @@ trait PageActions
* Create the sorting number for the page
* depending on the blueprint settings
*
* @param integer $num
* @return integer
* @param int $num
* @return int
*/
public function createNum(int $num = null): int
{
@@ -631,12 +631,13 @@ trait PageActions
*/
public function purge()
{
$this->children = null;
$this->blueprint = null;
$this->drafts = null;
$this->files = null;
$this->content = null;
$this->inventory = null;
$this->blueprint = null;
$this->children = null;
$this->content = null;
$this->drafts = null;
$this->files = null;
$this->inventory = null;
$this->translations = null;
return $this;
}
@@ -756,7 +757,7 @@ trait PageActions
*
* @param array $input
* @param string $language
* @param boolean $validate
* @param bool $validate
* @return self
*/
public function update(array $input = null, string $language = null, bool $validate = false)

View File

@@ -75,7 +75,6 @@ class PageBlueprint extends Blueprint
protected function normalizeNum($num): string
{
$aliases = [
0 => 'zero',
'0' => 'zero',
'sort' => 'default',
];
@@ -185,7 +184,7 @@ class PageBlueprint extends Blueprint
* button in the panel and redirects it to a
* different URL if necessary.
*
* @return string|boolean
* @return string|bool
*/
public function preview()
{

264
kirby/src/Cms/PagePicker.php Executable file
View File

@@ -0,0 +1,264 @@
<?php
namespace Kirby\Cms;
use Kirby\Exception\InvalidArgumentException;
/**
* The PagePicker class helps to
* fetch the right pages and the parent
* model for the API calls for the
* page picker component in the panel.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class PagePicker extends Picker
{
/**
* @var \Kirby\Cms\Pages
*/
protected $items;
/**
* @var \Kirby\Cms\Pages
*/
protected $itemsForQuery;
/**
* @var \Kirby\Cms\Page|\Kirby\Cms\Site|null
*/
protected $parent;
/**
* Extends the basic defaults
*
* @return array
*/
public function defaults(): array
{
return array_merge(parent::defaults(), [
// Page ID of the selected parent. Used to navigate
'parent' => null,
// enable/disable subpage navigation
'subpages' => true,
]);
}
/**
* Returns the parent model object that
* is currently selected in the page picker.
* It normally starts at the site, but can
* also be any subpage. When a query is given
* and subpage navigation is deactivated,
* there will be no model available at all.
*
* @return \Kirby\Cms\Page|\Kirby\Cms\Site|null
*/
public function model()
{
// no subpages navigation = no model
if ($this->options['subpages'] === false) {
return null;
}
// the model for queries is a bit more tricky to find
if (empty($this->options['query']) === false) {
return $this->modelForQuery();
}
return $this->parent();
}
/**
* Returns a model object for the given
* query, depending on the parent and subpages
* options.
*
* @return \Kirby\Cms\Page|\Kirby\Cms\Site|null
*/
public function modelForQuery()
{
if ($this->options['subpages'] === true && empty($this->options['parent']) === false) {
return $this->parent();
}
if ($items = $this->items()) {
return $items->parent();
}
return null;
}
/**
* Returns basic information about the
* parent model that is currently selected
* in the page picker.
*
* @param \Kirby\Cms\Site|\Kirby\Cms\Page|null
* @return array|null
*/
public function modelToArray($model = null): ?array
{
if ($model === null) {
return null;
}
// the selected model is the site. there's nothing above
if (is_a($model, 'Kirby\Cms\Site') === true) {
return [
'id' => null,
'parent' => null,
'title' => $model->title()->value()
];
}
// the top-most page has been reached
// the missing id indicates that there's nothing above
if ($model->id() === $this->start()->id()) {
return [
'id' => null,
'parent' => null,
'title' => $model->title()->value()
];
}
// the model is a regular page
return [
'id' => $model->id(),
'parent' => $model->parentModel()->id(),
'title' => $model->title()->value()
];
}
/**
* Search all pages for the picker
*
* @return \Kirby\Cms\Pages|null
*/
public function items()
{
// cache
if ($this->items !== null) {
return $this->items;
}
// no query? simple parent-based search for pages
if (empty($this->options['query']) === true) {
$items = $this->itemsForParent();
// when subpage navigation is enabled, a parent
// might be passed in addition to the query.
// The parent then takes priority.
} elseif ($this->options['subpages'] === true && empty($this->options['parent']) === false) {
$items = $this->itemsForParent();
// search by query
} else {
$items = $this->itemsForQuery();
}
// filter protected pages
$items = $items->filterBy('isReadable', true);
// search
$items = $this->search($items);
// paginate the result
return $this->items = $this->paginate($items);
}
/**
* Search for pages by parent
*
* @return \Kirby\Cms\Pages
*/
public function itemsForParent()
{
return $this->parent()->children();
}
/**
* Search for pages by query string
*
* @return \Kirby\Cms\Pages
*/
public function itemsForQuery()
{
// cache
if ($this->itemsForQuery !== null) {
return $this->itemsForQuery;
}
$model = $this->options['model'];
$items = $model->query($this->options['query']);
// help mitigate some typical query usage issues
// by converting site and page objects to proper
// pages by returning their children
if (is_a($items, 'Kirby\Cms\Site') === true) {
$items = $items->children();
} elseif (is_a($items, 'Kirby\Cms\Page') === true) {
$items = $items->children();
} elseif (is_a($items, 'Kirby\Cms\Pages') === false) {
throw new InvalidArgumentException('Your query must return a set of pages');
}
return $this->itemsForQuery = $items;
}
/**
* Returns the parent model.
* The model will be used to fetch
* subpages unless there's a specific
* query to find pages instead.
*
* @return \Kirby\Cms\Page|\Kirby\Cms\Site
*/
public function parent()
{
if ($this->parent !== null) {
return $this->parent;
}
return $this->parent = $this->kirby->page($this->options['parent']) ?? $this->site;
}
/**
* Calculates the top-most model (page or site)
* that can be accessed when navigating
* through pages.
*
* @return \Kirby\Cms\Page|\Kirby\Cms\Site
*/
public function start()
{
if (empty($this->options['query']) === false) {
if ($items = $this->itemsForQuery()) {
return $items->parent();
}
return $this->site;
}
return $this->site;
}
/**
* Returns an associative array
* with all information for the picker.
* This will be passed directly to the API.
*
* @return array
*/
public function toArray(): array
{
$array = parent::toArray();
$array['model'] = $this->modelToArray($this->model());
return $array;
}
}

View File

@@ -205,6 +205,12 @@ class PageRules
public static function create(Page $page): bool
{
if (Str::length($page->slug()) < 1) {
throw new InvalidArgumentException([
'key' => 'page.slug.invalid',
]);
}
if ($page->exists() === true) {
throw new DuplicateException([
'key' => 'page.draft.duplicate',

View File

@@ -14,11 +14,13 @@ namespace Kirby\Cms;
trait PageSiblings
{
/**
* @deprecated 3.0.0 Use `Page::hasNextUnlisted` instead
* @return boolean
* @deprecated 3.0.0 Use `Page::hasNextUnlisted()` instead
* @return bool
*/
public function hasNextInvisible(): bool
{
deprecated('$page->hasNextInvisible() is deprecated, use $page->hasNextUnlisted() instead. $page->hasNextInvisible() will be removed in Kirby 3.5.0.');
return $this->hasNextUnlisted();
}
@@ -45,20 +47,24 @@ trait PageSiblings
}
/**
* @deprecated 3.0.0 Use `Page::hasNextListed` instead
* @return boolean
* @deprecated 3.0.0 Use `Page::hasNextListed()` instead
* @return bool
*/
public function hasNextVisible(): bool
{
deprecated('$page->hasNextVisible() is deprecated, use $page->hasNextListed() instead. $page->hasNextVisible() will be removed in Kirby 3.5.0.');
return $this->hasNextListed();
}
/**
* @deprecated 3.0.0 Use `Page::hasPrevUnlisted` instead
* @return boolean
* @deprecated 3.0.0 Use `Page::hasPrevUnlisted()` instead
* @return bool
*/
public function hasPrevInvisible(): bool
{
deprecated('$page->hasPrevInvisible() is deprecated, use $page->hasPrevUnlisted() instead. $page->hasPrevInvisible() will be removed in Kirby 3.5.0.');
return $this->hasPrevUnlisted();
}
@@ -85,11 +91,13 @@ trait PageSiblings
}
/**
* @deprecated 3.0.0 Use `Page::hasPrevListed instead`
* @return boolean
* @deprecated 3.0.0 Use `Page::hasPrevListed()` instead
* @return bool
*/
public function hasPrevVisible(): bool
{
deprecated('$page->hasPrevVisible() is deprecated, use $page->hasPrevListed() instead. $page->hasPrevVisible() will be removed in Kirby 3.5.0.');
return $this->hasPrevListed();
}
@@ -99,6 +107,8 @@ trait PageSiblings
*/
public function nextInvisible()
{
deprecated('$page->nextInvisible() is deprecated, use $page->nextUnlisted() instead. $page->nextInvisible() will be removed in Kirby 3.5.0.');
return $this->nextUnlisted();
}
@@ -123,11 +133,13 @@ trait PageSiblings
}
/**
* @deprecated 3.0.0 Use `Page::prevListed()` instead
* @deprecated 3.0.0 Use `Page::nextListed()` instead
* @return self|null
*/
public function nextVisible()
{
deprecated('$page->nextVisible() is deprecated, use $page->nextListed() instead. $page->nextVisible() will be removed in Kirby 3.5.0.');
return $this->nextListed();
}
@@ -137,6 +149,8 @@ trait PageSiblings
*/
public function prevInvisible()
{
deprecated('$page->prevInvisible() is deprecated, use $page->prevUnlisted() instead. $page->prevInvisible() will be removed in Kirby 3.5.0.');
return $this->prevUnlisted();
}
@@ -166,6 +180,8 @@ trait PageSiblings
*/
public function prevVisible()
{
deprecated('$page->prevVisible() is deprecated, use $page->prevListed() instead. $page->prevVisible() will be removed in Kirby 3.5.0.');
return $this->prevListed();
}

View File

@@ -2,6 +2,8 @@
namespace Kirby\Cms;
use Kirby\Exception\InvalidArgumentException;
/**
* The `$pages` object refers to a
* collection of pages. The pages in this
@@ -55,6 +57,10 @@ class Pages extends Collection
// add a page object
} elseif (is_a($object, 'Kirby\Cms\Page') === true) {
$this->__set($object->id(), $object);
// give a useful error message on invalid input
} elseif (in_array($object, [null, false, true], true) !== true) {
throw new InvalidArgumentException('You must pass a Page object to the Pages collection');
}
return $this;
@@ -344,13 +350,15 @@ class Pages extends Collection
}
/**
* Deprecated alias for Pages::unlisted()
* @deprecated 3.0.0 Use `Pages::unlisted()` instead
*
* @return self
*/
public function invisible()
{
return $this->filterBy('isUnlisted', '==', true);
deprecated('$pages->invisible() is deprecated, use $pages->unlisted() instead. $pages->invisible() will be removed in Kirby 3.5.0.');
return $this->unlisted();
}
/**
@@ -428,6 +436,27 @@ class Pages extends Collection
return $this;
}
/**
* Filter all pages by excluding the given template
*
* @param string|array $templates
* @return \Kirby\Cms\Pages
*/
public function notTemplate($templates)
{
if (empty($templates) === true) {
return $this;
}
if (is_array($templates) === false) {
$templates = [$templates];
}
return $this->filter(function ($page) use ($templates) {
return !in_array($page->intendedTemplate()->name(), $templates);
});
}
/**
* Returns an array with all page numbers
*
@@ -480,12 +509,14 @@ class Pages extends Collection
}
/**
* Deprecated alias for Pages::listed()
* @deprecated 3.0.0 Use `Pages::listed()` instead
*
* @return \Kirby\Cms\Pages
*/
public function visible()
{
return $this->filterBy('isListed', '==', true);
deprecated('$pages->visible() is deprecated, use $pages->listed() instead. $pages->visible() will be removed in Kirby 3.5.0.');
return $this->listed();
}
}

View File

@@ -81,9 +81,9 @@ class Pagination extends BasePagination
}
if ($params['method'] === 'query') {
$params['page'] = $params['page'] ?? $params['url']->query()->get($params['variable'], 1);
$params['page'] = $params['page'] ?? $params['url']->query()->get($params['variable']);
} else {
$params['page'] = $params['page'] ?? $params['url']->params()->get($params['variable'], 1);
$params['page'] = $params['page'] ?? $params['url']->params()->get($params['variable']);
}
parent::__construct($params);

View File

@@ -107,6 +107,7 @@ class Panel
'pluginCss' => $plugins->url('css'),
'pluginJs' => $plugins->url('js'),
'panelUrl' => $uri->path()->toString(true) . '/',
'nonce' => $kirby->nonce(),
'options' => [
'url' => $url,
'site' => $kirby->url('index'),

176
kirby/src/Cms/Picker.php Executable file
View File

@@ -0,0 +1,176 @@
<?php
namespace Kirby\Cms;
/**
* The Picker abstract is the foundation
* for the UserPicker, PagePicker and FilePicker
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
abstract class Picker
{
/**
* @var \Kirby\Cms\App
*/
protected $kirby;
/**
* @var array
*/
protected $options;
/**
* @var \Kirby\Cms\Site
*/
protected $site;
/**
* Creates a new Picker instance
*
* @param array $params
*/
public function __construct(array $params = [])
{
$this->options = array_merge($this->defaults(), $params);
$this->kirby = $this->options['model']->kirby();
$this->site = $this->kirby->site();
}
/**
* Return the array of default values
*
* @return array
*/
protected function defaults(): array
{
// default params
return [
// image settings (ratio, cover, etc.)
'image' => [],
// query template for the info field
'info' => false,
// number of users displayed per pagination page
'limit' => 20,
// optional mapping function for the result array
'map' => null,
// the reference model
'model' => site(),
// current page when paginating
'page' => 1,
// a query string to fetch specific items
'query' => null,
// search query
'search' => null,
// query template for the text field
'text' => null
];
}
/**
* Fetches all items for the picker
*
* @return \Kirby\Cms\Collection|null
*/
abstract public function items();
/**
* Converts all given items to an associative
* array that is already optimized for the
* panel picker component.
*
* @param \Kirby\Cms\Collection|null $items
* @return array
*/
public function itemsToArray($items = null): array
{
if ($items === null) {
return [];
}
$result = [];
foreach ($items as $index => $item) {
if (empty($this->options['map']) === false) {
$result[] = $this->options['map']($item);
} else {
$result[] = $item->panelPickerData([
'image' => $this->options['image'],
'info' => $this->options['info'],
'model' => $this->options['model'],
'text' => $this->options['text'],
]);
}
}
return $result;
}
/**
* Apply pagination to the collection
* of items according to the options.
*
* @param \Kirby\Cms\Collection $items
* @return \Kirby\Cms\Collection
*/
public function paginate($items)
{
return $items->paginate([
'limit' => $this->options['limit'],
'page' => $this->options['page']
]);
}
/**
* Return the most relevant pagination
* info as array
*
* @param \Kirby\Cms\Pagination $pagination
* @return array
*/
public function paginationToArray(Pagination $pagination): array
{
return [
'limit' => $pagination->limit(),
'page' => $pagination->page(),
'total' => $pagination->total()
];
}
/**
* Search through the collection of items
* if not deactivate in the options
*
* @param \Kirby\Cms\Collection $items
* @return \Kirby\Cms\Collection
*/
public function search($items)
{
if (empty($this->options['search']) === false) {
return $items->search($this->options['search']);
}
return $items;
}
/**
* Returns an associative array
* with all information for the picker.
* This will be passed directly to the API.
*
* @return array
*/
public function toArray(): array
{
$items = $this->items();
return [
'data' => $this->itemsToArray($items),
'pagination' => $this->paginationToArray($items->pagination()),
];
}
}

View File

@@ -19,7 +19,7 @@ class Responder
/**
* HTTP status code
*
* @var integer
* @var int
*/
protected $code = null;
@@ -73,8 +73,8 @@ class Responder
/**
* Setter and getter for the status code
*
* @param integer $code
* @return integer|self
* @param int $code
* @return int|self
*/
public function code(int $code = null)
{
@@ -156,7 +156,7 @@ class Responder
* Shortcut to create a redirect response
*
* @param string|null $location
* @param integer|null $code
* @param int|null $code
* @return self
*/
public function redirect(?string $location = null, ?int $code = null)

View File

@@ -29,8 +29,8 @@ class Roles extends Collection
*/
public function canBeChanged()
{
if ($user = App::instance()->user()) {
return $this->filter(function ($role) use ($user) {
if (App::instance()->user()) {
return $this->filter(function ($role) {
$newUser = new User([
'email' => 'test@getkirby.com',
'role' => $role->id()
@@ -52,8 +52,8 @@ class Roles extends Collection
*/
public function canBeCreated()
{
if ($user = App::instance()->user()) {
return $this->filter(function ($role) use ($user) {
if (App::instance()->user()) {
return $this->filter(function ($role) {
$newUser = new User([
'email' => 'test@getkirby.com',
'role' => $role->id()

View File

@@ -73,6 +73,7 @@ class Search
$keys[] = 'id';
if (is_a($item, 'Kirby\Cms\User') === true) {
$keys[] = 'name';
$keys[] = 'email';
$keys[] = 'role';
} elseif (is_a($item, 'Kirby\Cms\Page') === true) {

View File

@@ -273,7 +273,7 @@ class Site extends ModelWithContent
/**
* Checks if the site exists on disk
*
* @return boolean
* @return bool
*/
public function exists(): bool
{
@@ -391,7 +391,7 @@ class Site extends ModelWithContent
* prop, the home page will be returned if
* it can be found. (see `Site::homePage()`)
*
* @param string $path
* @param string $path
* @return \Kirby\Cms\Page|null
*/
public function page(string $path = null)
@@ -645,8 +645,8 @@ class Site extends ModelWithContent
* returns the current page
*
* @internal
* @param string|\Kirby\Cms\Page $page
* @param string|null $languageCode
* @param string|\Kirby\Cms\Page $page
* @param string|null $languageCode
* @return \Kirby\Cms\Page
*/
public function visit($page, string $languageCode = null)

View File

@@ -81,11 +81,12 @@ trait SiteActions
*/
public function purge()
{
$this->children = null;
$this->blueprint = null;
$this->files = null;
$this->content = null;
$this->inventory = null;
$this->blueprint = null;
$this->children = null;
$this->content = null;
$this->files = null;
$this->inventory = null;
$this->translations = null;
return $this;
}

View File

@@ -45,7 +45,7 @@ class SiteBlueprint extends Blueprint
* button in the panel and redirects it to a
* different URL if necessary.
*
* @return string|boolean
* @return string|bool
*/
public function preview()
{

View File

@@ -2,7 +2,9 @@
namespace Kirby\Cms;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\PermissionException;
use Kirby\Toolkit\Str;
/**
* Validators for all site actions
@@ -21,6 +23,10 @@ class SiteRules
throw new PermissionException(['key' => 'site.changeTitle.permission']);
}
if (Str::length($title) === 0) {
throw new InvalidArgumentException(['key' => 'site.changeTitle.empty']);
}
return true;
}

View File

@@ -79,7 +79,7 @@ class System
/**
* Check for a writable accounts folder
*
* @return boolean
* @return bool
*/
public function accounts(): bool
{
@@ -89,7 +89,7 @@ class System
/**
* Check for a writable content folder
*
* @return boolean
* @return bool
*/
public function content(): bool
{
@@ -99,7 +99,7 @@ class System
/**
* Check for an existing curl extension
*
* @return boolean
* @return bool
*/
public function curl(): bool
{
@@ -165,7 +165,7 @@ class System
* option must be explicitly set to true
* to get the installer up and running.
*
* @return boolean
* @return bool
*/
public function isInstallable(): bool
{
@@ -175,7 +175,7 @@ class System
/**
* Check if Kirby is already installed
*
* @return boolean
* @return bool
*/
public function isInstalled(): bool
{
@@ -185,7 +185,7 @@ class System
/**
* Check if this is a local installation
*
* @return boolean
* @return bool
*/
public function isLocal(): bool
{
@@ -218,7 +218,7 @@ class System
/**
* Check if all tests pass
*
* @return boolean
* @return bool
*/
public function isOk(): bool
{
@@ -318,7 +318,7 @@ class System
/**
* Check for an existing mbstring extension
*
* @return boolean
* @return bool
*/
public function mbString(): bool
{
@@ -328,7 +328,7 @@ class System
/**
* Check for a writable media folder
*
* @return boolean
* @return bool
*/
public function media(): bool
{
@@ -338,7 +338,7 @@ class System
/**
* Check for a valid PHP version
*
* @return boolean
* @return bool
*/
public function php(): bool
{
@@ -352,7 +352,7 @@ class System
*
* @param string $license
* @param string $email
* @return boolean
* @return bool
*/
public function register(string $license = null, string $email = null): bool
{
@@ -404,7 +404,7 @@ class System
/**
* Check for a valid server environment
*
* @return boolean
* @return bool
*/
public function server(): bool
{
@@ -424,7 +424,7 @@ class System
/**
* Check for a writable sessions folder
*
* @return boolean
* @return bool
*/
public function sessions(): bool
{

View File

@@ -74,7 +74,7 @@ class Template
/**
* Checks if the template exists
*
* @return boolean
* @return bool
*/
public function exists(): bool
{
@@ -190,7 +190,7 @@ class Template
/**
* Checks if the template uses the default type
*
* @return boolean
* @return bool
*/
public function hasDefaultType(): bool
{

View File

@@ -248,7 +248,7 @@ class User extends ModelWithContent
/**
* Checks if the user exists
*
* @return boolean
* @return bool
*/
public function exists(): bool
{
@@ -342,7 +342,7 @@ class User extends ModelWithContent
/**
* Checks if this user has the admin role
*
* @return boolean
* @return bool
*/
public function isAdmin(): bool
{
@@ -353,7 +353,7 @@ class User extends ModelWithContent
* Checks if the current user is the virtual
* Kirby user
*
* @return boolean
* @return bool
*/
public function isKirby(): bool
{
@@ -363,7 +363,7 @@ class User extends ModelWithContent
/**
* Checks if the current user is this user
*
* @return boolean
* @return bool
*/
public function isLoggedIn(): bool
{
@@ -374,7 +374,7 @@ class User extends ModelWithContent
* Checks if the user is the last one
* with the admin role
*
* @return boolean
* @return bool
*/
public function isLastAdmin(): bool
{
@@ -384,7 +384,7 @@ class User extends ModelWithContent
/**
* Checks if the user is the last user
*
* @return boolean
* @return bool
*/
public function isLastUser(): bool
{
@@ -426,10 +426,16 @@ class User extends ModelWithContent
*/
public function loginPasswordless($session = null): void
{
$kirby = $this->kirby();
$session = $this->sessionFromOptions($session);
$kirby->trigger('user.login:before', $this, $session);
$session->regenerateToken(); // privilege change
$session->data()->set('user.id', $this->id());
$kirby->trigger('user.login:after', $this, $session);
}
/**
@@ -440,16 +446,23 @@ class User extends ModelWithContent
*/
public function logout($session = null): void
{
$kirby = $this->kirby();
$session = $this->sessionFromOptions($session);
$kirby->trigger('user.logout:before', $this, $session);
$session->data()->remove('user.id');
if ($session->data()->get() === []) {
// session is now empty, we might as well destroy it
$session->destroy();
$kirby->trigger('user.logout:after', $this, null);
} else {
// privilege change
$session->regenerateToken();
$kirby->trigger('user.logout:after', $this, $session);
}
}
@@ -857,7 +870,7 @@ class User extends ModelWithContent
* Compares the given password with the stored one
*
* @param string $password
* @return boolean
* @return bool
*
* @throws NotFoundException If the user has no password
* @throws InvalidArgumentException If the entered password is not valid

View File

@@ -293,7 +293,7 @@ trait UserActions
* Writes the account information to disk
*
* @param array $credentials
* @return boolean
* @return bool
*/
protected function writeCredentials(array $credentials): bool
{

68
kirby/src/Cms/UserPicker.php Executable file
View File

@@ -0,0 +1,68 @@
<?php
namespace Kirby\Cms;
use Kirby\Exception\InvalidArgumentException;
/**
* The UserPicker class helps to
* fetch the right files for the API calls
* for the user picker component in the panel.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier GmbH
* @license https://getkirby.com/license
*/
class UserPicker extends Picker
{
/**
* Extends the basic defaults
*
* @return array
*/
public function defaults(): array
{
$defaults = parent::defaults();
$defaults['text'] = '{{ user.username }}';
return $defaults;
}
/**
* Search all users for the picker
*
* @return \Kirby\Cms\Users|null
*/
public function items()
{
$model = $this->options['model'];
// find the right default query
if (empty($this->options['query']) === false) {
$query = $this->options['query'];
} elseif (is_a($model, 'Kirby\Cms\User') === true) {
$query = 'user.siblings';
} else {
$query = 'kirby.users';
}
// fetch all users for the picker
$users = $model->query($query);
// catch invalid data
if (is_a($users, 'Kirby\Cms\Users') === false) {
throw new InvalidArgumentException('Your query must return a set of users');
}
// search
$users = $this->search($users);
// sort
$users = $users->sortBy('username', 'asc');
// paginate
return $this->paginate($users);
}
}