This commit is contained in:
Bastian Allgeier
2020-07-07 12:40:13 +02:00
parent 5f025ac2c2
commit f79d2e960c
176 changed files with 10532 additions and 5343 deletions

View File

@@ -3,15 +3,16 @@
use Kirby\Exception\PermissionException;
return function () {
$auth = $this->kirby()->auth();
$auth = $this->kirby()->auth();
$allowImpersonation = $this->kirby()->option('api.allowImpersonation') ?? false;
// csrf token check
if ($auth->type() === 'session' && $auth->csrf() === false) {
if ($auth->type($allowImpersonation) === 'session' && $auth->csrf() === false) {
throw new PermissionException('Unauthenticated');
}
// get user from session or basic auth
if ($user = $auth->user()) {
if ($user = $auth->user(null, $allowImpersonation)) {
if ($user->role()->permissions()->for('access', 'panel') === false) {
throw new PermissionException(['key' => 'access.panel']);
}

View File

@@ -71,7 +71,13 @@ return [
return $this->user();
},
'version' => function () {
return $this->kirby()->version();
$user = $this->user();
if ($user && $user->role()->permissions()->for('access', 'settings') === true) {
return $this->kirby()->version();
} else {
return null;
}
}
],
'type' => 'Kirby\Cms\System',

View File

@@ -50,6 +50,9 @@ return [
'role' => function (User $user) {
return $user->role();
},
'roles' => function (User $user) {
return $user->roles();
},
'username' => function (User $user) {
return $user->username();
}

View File

@@ -103,5 +103,21 @@ return [
return $this->file($path, $filename)->changeName($this->requestBody('name'));
}
],
[
'pattern' => 'files/search',
'method' => 'GET|POST',
'action' => function () {
$files = $this
->site()
->index(true)
->filterBy('isReadable', true)
->files();
if ($this->requestMethod() === 'GET') {
return $files->search($this->requestQuery('q'));
} else {
return $files->query($this->requestBody());
}
}
],
];

View File

@@ -119,6 +119,12 @@ return [
return $this->user($id)->changeRole($this->requestBody('role'));
}
],
[
'pattern' => 'users/(:any)/roles',
'action' => function (string $id) {
return $this->user($id)->roles();
}
],
[
'pattern' => 'users/(:any)/sections/(:any)',
'method' => 'GET',

View File

@@ -1,17 +1,21 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Collection;
use Kirby\Cms\File;
use Kirby\Cms\Filename;
use Kirby\Cms\FileVersion;
use Kirby\Cms\Template;
use Kirby\Data\Data;
use Kirby\Http\Server;
use Kirby\Http\Uri;
use Kirby\Http\Url;
use Kirby\Image\Darkroom;
use Kirby\Text\Markdown;
use Kirby\Text\SmartyPants;
use Kirby\Toolkit\A;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Str;
use Kirby\Toolkit\Tpl as Snippet;
return [
@@ -134,6 +138,106 @@ return [
return $markdown->parse($text, $inline);
},
/**
* Add your own search engine
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param \Kirby\Cms\Collection $collection Collection of searchable models
* @param string $query
* @param mixed $params
* @return \Kirby\Cms\Collection|bool
*/
'search' => function (App $kirby, Collection $collection, string $query = null, $params = []) {
if (empty(trim($query)) === true) {
return $collection->limit(0);
}
if (is_string($params) === true) {
$params = ['fields' => Str::split($params, '|')];
}
$defaults = [
'fields' => [],
'minlength' => 2,
'score' => [],
'words' => false,
];
$options = array_merge($defaults, $params);
$collection = clone $collection;
$searchwords = preg_replace('/(\s)/u', ',', $query);
$searchwords = Str::split($searchwords, ',', $options['minlength']);
$lowerQuery = mb_strtolower($query);
if (empty($options['stopwords']) === false) {
$searchwords = array_diff($searchwords, $options['stopwords']);
}
$searchwords = array_map(function ($value) use ($options) {
return $options['words'] ? '\b' . preg_quote($value) . '\b' : preg_quote($value);
}, $searchwords);
$preg = '!(' . implode('|', $searchwords) . ')!i';
$results = $collection->filter(function ($item) use ($query, $preg, $options, $lowerQuery) {
$data = $item->content()->toArray();
$keys = array_keys($data);
$keys[] = 'id';
if (is_a($item, 'Kirby\Cms\User') === true) {
$keys[] = 'name';
$keys[] = 'email';
$keys[] = 'role';
} elseif (is_a($item, 'Kirby\Cms\Page') === true) {
// apply the default score for pages
$options['score'] = array_merge([
'id' => 64,
'title' => 64,
], $options['score']);
}
if (empty($options['fields']) === false) {
$fields = array_map('strtolower', $options['fields']);
$keys = array_intersect($keys, $fields);
}
$item->searchHits = 0;
$item->searchScore = 0;
foreach ($keys as $key) {
$score = $options['score'][$key] ?? 1;
$value = $data[$key] ?? (string)$item->$key();
$lowerValue = mb_strtolower($value);
// check for exact matches
if ($lowerQuery == $lowerValue) {
$item->searchScore += 16 * $score;
$item->searchHits += 1;
// check for exact beginning matches
} elseif (Str::startsWith($lowerValue, $lowerQuery) === true) {
$item->searchScore += 8 * $score;
$item->searchHits += 1;
// check for exact query matches
} elseif ($matches = preg_match_all('!' . preg_quote($query) . '!i', $value, $r)) {
$item->searchScore += 2 * $score;
$item->searchHits += $matches;
}
// check for any match
if ($matches = preg_match_all($preg, $value, $r)) {
$item->searchHits += $matches;
$item->searchScore += $matches * $score;
}
}
return $item->searchHits > 0 ? true : false;
});
return $results->sortBy('searchScore', 'desc');
},
/**
* Add your own SmartyPants parser
*
@@ -221,12 +325,51 @@ return [
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param string $path URL path
* @param array|null $options Array of options for the Uri class
* @param Closure $originalHandler Callback function to the original URL handler with `$path` and `$options` as parameters
* @param array|string|null $options Array of options for the Uri class
* @param Closure $originalHandler Deprecated: Callback function to the original URL handler with `$path` and `$options` as parameters
* Use `$kirby->nativeComponent('url')` inside your URL component instead.
* @return string
*/
'url' => function (App $kirby, string $path = null, $options = [], Closure $originalHandler): string {
return $originalHandler($path, $options);
'url' => function (App $kirby, string $path = null, $options = null, Closure $originalHandler = null): string {
$language = null;
// get language from simple string option
if (is_string($options) === true) {
$language = $options;
$options = null;
}
// get language from array
if (is_array($options) === true && isset($options['language']) === true) {
$language = $options['language'];
unset($options['language']);
}
// get a language url for the linked page, if the page can be found
if ($kirby->multilang() === true) {
$parts = Str::split($path, '#');
if ($page = page($parts[0] ?? null)) {
$path = $page->url($language);
if (isset($parts[1]) === true) {
$path .= '#' . $parts[1];
}
}
}
// keep relative urls
if (substr($path, 0, 2) === './' || substr($path, 0, 3) === '../') {
return $path;
}
$url = Url::makeAbsolute($path, $kirby->url());
if ($options === null) {
return $url;
}
return (new Uri($url, $options))->toString();
},
];

View File

@@ -1,6 +1,6 @@
<?php
use Kirby\Data\Yaml;
use Kirby\Data\Data;
use Kirby\Toolkit\A;
return [
@@ -78,7 +78,7 @@ return [
'toFiles' => function ($value = null) {
$files = [];
foreach (Yaml::decode($value) as $id) {
foreach (Data::decode($value, 'yaml') as $id) {
if (is_array($id) === true) {
$id = $id['id'] ?? null;
}

View File

@@ -3,12 +3,10 @@
return [
'extends' => 'tags',
'props' => [
/**
* Unset inherited props
*/
'accept' => null,
/**
* Custom icon to replace the arrow down.
*/
@@ -17,8 +15,10 @@ return [
},
/**
* Enable/disable the search in the dropdown
* Also limit displayed items (display: 20)
* and set minimum number of characters to search (min: 3)
*/
'search' => function (bool $search = true) {
'search' => function ($search = true) {
return $search;
},
/**

View File

@@ -1,6 +1,6 @@
<?php
use Kirby\Data\Yaml;
use Kirby\Data\Data;
use Kirby\Toolkit\A;
return [
@@ -72,7 +72,7 @@ return [
$pages = [];
$kirby = kirby();
foreach (Yaml::decode($value) as $id) {
foreach (Data::decode($value, 'yaml') as $id) {
if (is_array($id) === true) {
$id = $id['id'] ?? null;
}

View File

@@ -1,7 +1,7 @@
<?php
use Kirby\Cms\Form;
use Kirby\Data\Yaml;
use Kirby\Data\Data;
use Kirby\Toolkit\I18n;
return [
@@ -24,6 +24,14 @@ return [
// be lowercase as well.
return array_change_key_case($columns);
},
/**
* Toggles duplicating rows for the structure
*/
'duplicate' => function (bool $duplicate = true) {
return $duplicate;
},
/**
* The placeholder text if no items have been added yet
*/
@@ -62,6 +70,12 @@ return [
'min' => function (int $min = null) {
return $min;
},
/**
* Toggles adding to the top or bottom of the list
*/
'prepend' => function (bool $prepend = null) {
return $prepend;
},
/**
* Toggles drag & drop sorting
*/
@@ -131,7 +145,7 @@ return [
],
'methods' => [
'rows' => function ($value) {
$rows = Yaml::decode($value);
$rows = Data::decode($value, 'yaml');
$value = [];
foreach ($rows as $index => $row) {

View File

@@ -1,6 +1,6 @@
<?php
use Kirby\Data\Yaml;
use Kirby\Data\Data;
use Kirby\Toolkit\A;
return [
@@ -54,7 +54,7 @@ return [
$users = [];
$kirby = kirby();
foreach (Yaml::decode($value) as $email) {
foreach (Data::decode($value, 'yaml') as $email) {
if (is_array($email) === true) {
$email = $email['email'] ?? null;
}

View File

@@ -223,11 +223,11 @@ function go(string $url = null, int $code = 302)
/**
* Shortcut for html()
*
* @param string $string unencoded text
* @param string|null $string unencoded text
* @param bool $keepTags
* @return string
*/
function h(string $string = null, bool $keepTags = false)
function h(?string $string, bool $keepTags = false)
{
return Html::encode($string, $keepTags);
}
@@ -235,11 +235,11 @@ function h(string $string = null, bool $keepTags = false)
/**
* Creates safe html by encoding special characters
*
* @param string $string unencoded text
* @param string|null $string unencoded text
* @param bool $keepTags
* @return string
*/
function html(string $string = null, bool $keepTags = false)
function html(?string $string, bool $keepTags = false)
{
return Html::encode($string, $keepTags);
}

View File

@@ -2,14 +2,11 @@
use Kirby\Cms\App;
use Kirby\Cms\Field;
use Kirby\Cms\File;
use Kirby\Cms\Files;
use Kirby\Cms\Html;
use Kirby\Cms\Page;
use Kirby\Cms\Structure;
use Kirby\Cms\Url;
use Kirby\Data\Json;
use Kirby\Data\Yaml;
use Kirby\Data\Data;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Toolkit\Str;
@@ -67,9 +64,8 @@ return function (App $app) {
'toData' => function (Field $field, string $method = ',') {
switch ($method) {
case 'yaml':
return Yaml::decode($field->value);
case 'json':
return Json::decode($field->value);
return Data::decode($field->value, $method);
default:
return $field->split($method);
}
@@ -216,7 +212,7 @@ return function (App $app) {
*/
'toStructure' => function (Field $field) {
try {
return new Structure(Yaml::decode($field->value), $field->parent());
return new Structure(Data::decode($field->value, 'yaml'), $field->parent());
} catch (Exception $e) {
if ($field->parent() === null) {
$message = 'Invalid structure data for "' . $field->key() . '" field';
@@ -287,6 +283,17 @@ return function (App $app) {
// manipulators
/**
* Applies the callback function to the field
* @since 3.4.0
*
* @param \Kirby\Cms\Field $field
* @param Closure $callback
*/
'callback' => function (Field $field, Closure $callback) {
return $callback($field);
},
/**
* Escapes the field value to be safely used in HTML
* templates without the risk of XSS attacks
@@ -309,7 +316,7 @@ return function (App $app) {
* @param string $rep
* @return \Kirby\Cms\Field
*/
'excerpt' => function (Field $field, int $chars = 0, bool $strip = true, string $rep = '…') {
'excerpt' => function (Field $field, int $chars = 0, bool $strip = true, string $rep = ' …') {
$field->value = Str::excerpt($field->kirbytext()->value(), $chars, $strip, $rep);
return $field;
},
@@ -326,14 +333,20 @@ return function (App $app) {
},
/**
* Converts all line breaks in the field content to `<br>` tags.
* Strips all block-level HTML elements from the field value,
* it can be safely placed inside of other inline elements
* without the risk of breaking the HTML structure.
* @since 3.3.0
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\Field
*/
'nl2br' => function (Field $field) {
$field->value = nl2br($field->value, false);
'inline' => function (Field $field) {
// List of valid inline elements taken from: https://developer.mozilla.org/de/docs/Web/HTML/Inline_elemente
// Obsolete elements, script tags, image maps and form elements have
// been excluded for safety reasons and as they are most likely not
// needed in most cases.
$field->value = strip_tags($field->value, '<b><i><small><abbr><cite><code><dfn><em><kbd><strong><samp><var><a><bdo><br><img><q><span><sub><sup>');
return $field;
},
@@ -384,24 +397,6 @@ return function (App $app) {
return $field;
},
/**
* Strips all block-level HTML elements from the field value,
* it can be safely placed inside of other inline elements
* without the risk of breaking the HTML structure.
* @since 3.3.0
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\Field
*/
'inline' => function (Field $field) {
// List of valid inline elements taken from: https://developer.mozilla.org/de/docs/Web/HTML/Inline_elemente
// Obsolete elements, script tags, image maps and form elements have
// been excluded for safety reasons and as they are most likely not
// needed in most cases.
$field->value = strip_tags($field->value, '<b><i><small><abbr><cite><code><dfn><em><kbd><strong><samp><var><a><bdo><br><img><q><span><sub><sup>');
return $field;
},
/**
* Converts the field content to lowercase
*
@@ -425,13 +420,55 @@ return function (App $app) {
},
/**
* Converts the field content to valid XML
* Converts all line breaks in the field content to `<br>` tags.
* @since 3.3.0
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\Field
*/
'xml' => function (Field $field) {
$field->value = Xml::encode($field->value);
'nl2br' => function (Field $field) {
$field->value = nl2br($field->value, false);
return $field;
},
/**
* Uses the field value as Kirby query
*
* @param \Kirby\Cms\Field $field
* @param string|null $expect
* @return mixed
*/
'query' => function (Field $field, string $expect = null) use ($app) {
if ($parent = $field->parent()) {
return $parent->query($field->value, $expect);
}
return Str::query($field->value, [
'kirby' => $app,
'site' => $app->site(),
'page' => $app->page()
]);
},
/**
* It parses any queries found in the field value.
*
* @param \Kirby\Cms\Field $field
* @param array $data
* @param string $fallback Fallback for tokens in the template that cannot be replaced
* @return \Kirby\Cms\Field
*/
'replace' => function (Field $field, array $data = [], string $fallback = '') use ($app) {
if ($parent = $field->parent()) {
$field->value = $field->parent()->toString($field->value, $data, $fallback);
} else {
$field->value = Str::template($field->value, array_replace([
'kirby' => $app,
'site' => $app->site(),
'page' => $app->page()
], $data), $fallback);
}
return $field;
},
@@ -475,7 +512,7 @@ return function (App $app) {
* Splits the field content into an array
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\cms\Field
* @return array
*/
'split' => function (Field $field, $separator = ',') {
return Str::split((string)$field->value, $separator);
@@ -504,6 +541,17 @@ return function (App $app) {
return $field;
},
/**
* Converts the field content to valid XML
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\Field
*/
'xml' => function (Field $field) {
$field->value = Xml::encode($field->value);
return $field;
},
// aliases
/**

View File

@@ -83,6 +83,9 @@ return [
'files' => function () {
$files = $this->parent->files()->template($this->template);
// filter out all protected files
$files = $files->filterBy('isReadable', true);
if ($this->sortBy) {
$files = $files->sortBy(...$files::sortArgs($this->sortBy));
} elseif ($this->sortable === true) {
@@ -96,8 +99,9 @@ return [
// apply the default pagination
$files = $files->paginate([
'page' => $this->page,
'limit' => $this->limit
'page' => $this->page,
'limit' => $this->limit,
'method' => 'none' // the page is manually provided
]);
return $files;
@@ -219,6 +223,7 @@ return [
'errors' => $this->errors,
'options' => [
'accept' => $this->accept,
'apiUrl' => $this->parent->apiUrl(true),
'empty' => $this->empty,
'headline' => $this->headline,
'help' => $this->help,

View File

@@ -17,10 +17,11 @@ return [
],
'props' => [
/**
* Optional array of templates that should only be allowed to add.
* Optional array of templates that should only be allowed to add
* or `false` to completely disable page creation
*/
'create' => function ($add = null) {
return $add;
'create' => function ($create = null) {
return $create;
},
/**
* Enables/disables reverse sorting
@@ -135,8 +136,9 @@ return [
// pagination
$pages = $pages->paginate([
'page' => $this->page,
'limit' => $this->limit
'page' => $this->page,
'limit' => $this->limit,
'method' => 'none' // the page is manually provided
]);
return $pages;