Upgrade to rc5

This commit is contained in:
Bastian Allgeier
2020-12-10 11:24:42 +01:00
parent 3fec0d7c93
commit c378376bc9
257 changed files with 13009 additions and 1846 deletions

View File

@@ -48,7 +48,7 @@ return [
return $file->next();
},
'nextWithTemplate' => function (File $file) {
$files = $file->templateSiblings()->sortBy('sort', 'asc', 'filename', 'asc');
$files = $file->templateSiblings()->sort('sort', 'asc', 'filename', 'asc');
$index = $files->indexOf($file);
return $files->nth($index + 1);
@@ -72,7 +72,7 @@ return [
return $file->prev();
},
'prevWithTemplate' => function (File $file) {
$files = $file->templateSiblings()->sortBy('sort', 'asc', 'filename', 'asc');
$files = $file->templateSiblings()->sort('sort', 'asc', 'filename', 'asc');
$index = $files->indexOf($file);
return $files->nth($index - 1);

View File

@@ -27,7 +27,7 @@ return [
return $page->errors();
},
'files' => function (Page $page) {
return $page->files()->sortBy('sort', 'asc', 'filename', 'asc');
return $page->files()->sort('sort', 'asc', 'filename', 'asc');
},
'hasChildren' => function (Page $page) {
return $page->hasChildren();
@@ -47,9 +47,9 @@ return [
'next' => function (Page $page) {
return $page
->nextAll()
->filterBy('intendedTemplate', $page->intendedTemplate())
->filterBy('status', $page->status())
->filterBy('isReadable', true)
->filter('intendedTemplate', $page->intendedTemplate())
->filter('status', $page->status())
->filter('isReadable', true)
->first();
},
'num' => function (Page $page) {
@@ -73,9 +73,9 @@ return [
'prev' => function (Page $page) {
return $page
->prevAll()
->filterBy('intendedTemplate', $page->intendedTemplate())
->filterBy('status', $page->status())
->filterBy('isReadable', true)
->filter('intendedTemplate', $page->intendedTemplate())
->filter('status', $page->status())
->filter('isReadable', true)
->last();
},
'previewUrl' => function (Page $page) {

View File

@@ -24,7 +24,7 @@ return [
return $site->drafts();
},
'files' => function (Site $site) {
return $site->files()->sortBy('sort', 'asc', 'filename', 'asc');
return $site->files()->sort('sort', 'asc', 'filename', 'asc');
},
'options' => function (Site $site) {
return $site->permissions()->toArray();

View File

@@ -35,6 +35,21 @@ return [
'license' => function (System $system) {
return $system->license();
},
'loginMethods' => function (System $system) {
return array_keys($system->loginMethods());
},
'pendingChallenge' => function () {
if ($this->session()->get('kirby.challenge.email') === null) {
return null;
}
// fake the email challenge if no challenge was created
// to avoid leaking whether the user exists
return $this->session()->get('kirby.challenge.type', 'email');
},
'pendingEmail' => function () {
return $this->session()->get('kirby.challenge.email');
},
'requirements' => function (System $system) {
return $system->toArray();
},
@@ -86,6 +101,9 @@ return [
'isOk',
'isInstallable',
'isInstalled',
'loginMethods',
'pendingChallenge',
'pendingEmail',
'title',
'translation'
],

View File

@@ -24,7 +24,7 @@ return [
return $user->email();
},
'files' => function (User $user) {
return $user->files()->sortBy('sort', 'asc', 'filename', 'asc');
return $user->files()->sort('sort', 'asc', 'filename', 'asc');
},
'id' => function (User $user) {
return $user->id();

View File

@@ -19,7 +19,7 @@ return [
}
],
[
'pattern' => 'auth/login',
'pattern' => 'auth/code',
'method' => 'POST',
'auth' => false,
'action' => function () {
@@ -30,11 +30,7 @@ return [
throw new InvalidArgumentException('Invalid CSRF token');
}
$email = $this->requestBody('email');
$long = $this->requestBody('long');
$password = $this->requestBody('password');
$user = $this->kirby()->auth()->login($email, $password, $long);
$user = $auth->verifyChallenge($this->requestBody('code'));
return [
'code' => 200,
@@ -43,6 +39,65 @@ return [
];
}
],
[
'pattern' => 'auth/login',
'method' => 'POST',
'auth' => false,
'action' => function () {
$auth = $this->kirby()->auth();
$methods = $this->kirby()->system()->loginMethods();
// csrf token check
if ($auth->type() === 'session' && $auth->csrf() === false) {
throw new InvalidArgumentException('Invalid CSRF token');
}
$email = $this->requestBody('email');
$long = $this->requestBody('long');
$password = $this->requestBody('password');
if ($password) {
if (isset($methods['password']) !== true) {
throw new InvalidArgumentException('Login with password is not enabled');
}
if (
isset($methods['password']['2fa']) === true &&
$methods['password']['2fa'] === true
) {
$challenge = $auth->login2fa($email, $password, $long);
} else {
$user = $auth->login($email, $password, $long);
}
} else {
if (isset($methods['code']) === true) {
$mode = 'login';
} elseif (isset($methods['password-reset']) === true) {
$mode = 'password-reset';
} else {
throw new InvalidArgumentException('Login without password is not enabled');
}
$challenge = $auth->createChallenge($email, $long, $mode);
}
if (isset($user)) {
return [
'code' => 200,
'status' => 'ok',
'user' => $this->resolve($user)->view('auth')->toArray()
];
} else {
return [
'code' => 200,
'status' => 'ok',
// don't leak users that don't exist at this point
'challenge' => $challenge ?? 'email'
];
}
}
],
[
'pattern' => 'auth/logout',
'method' => 'POST',

View File

@@ -27,7 +27,7 @@ return [
'pattern' => '(:all)/files',
'method' => 'GET',
'action' => function (string $path) {
return $this->parent($path)->files()->sortBy('sort', 'asc', 'filename', 'asc');
return $this->parent($path)->files()->sort('sort', 'asc', 'filename', 'asc');
}
],
[
@@ -110,7 +110,7 @@ return [
$files = $this
->site()
->index(true)
->filterBy('isReadable', true)
->filter('isReadable', true)
->files();
if ($this->requestMethod() === 'GET') {

View File

@@ -78,7 +78,7 @@ return [
$pages = $this
->site()
->index(true)
->filterBy('isReadable', true);
->filter('isReadable', true);
if ($this->requestMethod() === 'GET') {
return $pages->search($this->requestQuery('q'));

View File

@@ -11,7 +11,7 @@ return [
'pattern' => 'users',
'method' => 'GET',
'action' => function () {
return $this->users();
return $this->users()->sort('username', 'asc', 'email', 'asc');
}
],
[

View File

@@ -0,0 +1 @@
<pre><code class="language-<?= $block->language()->or('text') ?>"><?= $block->code()->html(false) ?></code></pre>

View File

@@ -0,0 +1,59 @@
name: field.blocks.code.name
icon: code
wysiwyg: true
preview: code
fields:
code:
label: field.blocks.code.name
type: textarea
placeholder: field.blocks.code.placeholder
buttons: false
font: monospace
language:
label: field.blocks.code.language
type: select
default: text
options:
bash: Bash
basic: BASIC
c: C
clojure: Clojure
cpp: C++
csharp: C#
css: CSS
diff: Diff
elixir: Elixir
elm: Elm
erlang: Erlang
go: Go
graphql: GraphQL
haskell: Haskell
html: HTML
java: Java
js: JavaScript
json: JSON
latext: LaTeX
less: Less
lisp: Lisp
lua: Lua
makefile: Makefile
markdown: Markdown
markup: Markup
objectivec: Objective-C
pascal: Pascal
perl: Perl
php: PHP
text: Plain Text
python: Python
r: R
ruby: Ruby
rust: Rust
sass: Sass
scss: SCSS
shell: Shell
sql: SQL
swift: Swift
typescript: TypeScript
vbnet: VB.net
xml: XML
yaml: YAML

View File

@@ -0,0 +1,9 @@
<figure>
<ul>
<?php foreach ($block->images()->toFiles() as $image): ?>
<li>
<?= $image ?>
</li>
<?php endforeach ?>
</ul>
</figure>

View File

@@ -0,0 +1,15 @@
name: field.blocks.gallery.name
icon: dashboard
preview: gallery
fields:
images:
label: field.blocks.gallery.images.label
type: files
multiple: true
layout: cards
size: tiny
empty: field.blocks.gallery.images.empty
uploads:
template: blocks/image
image:
ratio: 1/1

View File

@@ -0,0 +1 @@
<<?= $level = $block->level()->or('h2') ?>><?= $block->text() ?></<?= $level ?>>

View File

@@ -0,0 +1,24 @@
name: field.blocks.heading.name
icon: title
wysiwyg: true
preview: heading
fields:
level:
label: field.blocks.heading.level
type: select
empty: false
default: "h2"
width: 1/6
options:
- h1
- h2
- h3
- h4
- h5
- h6
text:
label: field.blocks.heading.text
type: writer
inline: true
width: 5/6
placeholder: field.blocks.heading.placeholder

View File

@@ -0,0 +1,34 @@
<?php
$alt = $block->alt();
$caption = $block->caption();
$crop = $block->crop()->isTrue();
$link = $block->link();
$ratio = $block->ratio()->or('auto');
$src = null;
if ($block->location() == 'web') {
$src = $block->src();
} elseif ($image = $block->image()->toFile()) {
$alt = $alt ?? $image->alt();
$src = $image->url();
}
?>
<?php if ($src): ?>
<figure<?= attr(['data-ratio' => $ratio, 'data-crop' => $crop], ' ') ?>>
<?php if ($link->isNotEmpty()): ?>
<a href="<?= $link->toUrl() ?>">
<img src="<?= $src ?>" alt="<?= $alt ?>">
</a>
<?php else: ?>
<img src="<?= $src ?>" alt="<?= $alt ?>">
<?php endif ?>
<?php if ($caption->isNotEmpty()): ?>
<figcaption>
<?= $caption ?>
</figcaption>
<?php endif ?>
</figure>
<?php endif ?>

View File

@@ -0,0 +1,59 @@
name: field.blocks.image.name
icon: image
preview: image
fields:
location:
label: field.blocks.image.location
type: radio
columns: 2
default: "kirby"
options:
kirby: Kirby
web: Web
image:
label: field.blocks.image.name
type: files
multiple: false
image:
back: black
uploads:
template: blocks/image
when:
location: kirby
src:
label: Image URL
type: url
when:
location: web
alt:
label: field.blocks.image.alt
type: text
icon: title
caption:
label: field.blocks.image.caption
type: writer
icon: text
inline: true
link:
label: field.blocks.image.link
type: text
icon: url
ratio:
label: field.blocks.image.ratio
type: select
placeholder: Auto
width: 1/2
options:
1/1: "1:1"
16/9: "16:9"
10/8: "10:8"
21/9: "21:9"
7/5: "7:5"
4/3: "4:3"
5/3: "5:3"
3/2: "3:2"
3/1: "3:1"
crop:
label: field.blocks.image.crop
type: toggle
width: 1/2

View File

@@ -0,0 +1 @@
<?= $block->text();

View File

@@ -0,0 +1,8 @@
name: field.blocks.list.name
icon: list-bullet
wysiwyg: true
preview: list
fields:
text:
label: field.blocks.list.name
type: list

View File

@@ -0,0 +1 @@
<?= $block->text()->kt();

View File

@@ -0,0 +1,11 @@
name: field.blocks.markdown.name
icon: markdown
preview: markdown
wysiwyg: true
fields:
text:
label: field.blocks.markdown.label
placeholder: field.blocks.markdown.placeholder
type: textarea
buttons: false
font: monospace

View File

@@ -0,0 +1,8 @@
<blockquote>
<?= $block->text() ?>
<?php if ($block->citation()->isNotEmpty()): ?>
<footer>
<?= $block->citation() ?>
</footer>
<?php endif ?>
</blockquote>

View File

@@ -0,0 +1,17 @@
name: field.blocks.quote.name
icon: quote
wysiwyg: true
preview: quote
fields:
text:
label: field.blocks.quote.text.label
placeholder: field.blocks.quote.text.placeholder
type: writer
inline: true
icon: quote
citation:
label: field.blocks.quote.citation.label
placeholder: field.blocks.quote.citation.placeholder
type: writer
inline: true
icon: user

View File

@@ -0,0 +1,3 @@
name: Table
icon: menu
preview: table

View File

@@ -0,0 +1 @@
<?= $block->text();

View File

@@ -0,0 +1,9 @@
name: field.blocks.text.name
icon: text
wysiwyg: true
preview: text
fields:
text:
type: writer
nodes: false
placeholder: field.blocks.text.placeholder

View File

@@ -0,0 +1,8 @@
<?php if ($block->url()->isNotEmpty()): ?>
<figure>
<?= video($block->url()) ?>
<?php if ($block->caption()->isNotEmpty()): ?>
<figcaption><?= $block->caption() ?></figcaption>
<?php endif ?>
</figure>
<?php endif ?>

View File

@@ -0,0 +1,12 @@
name: field.blocks.video.name
icon: video
preview: video
fields:
url:
label: field.blocks.video.url.label
type: url
placeholder: field.blocks.video.url.placeholder
caption:
label: field.blocks.video.caption
type: writer
inline: true

View File

@@ -1,7 +1,26 @@
<?php
$blocksRoot = __DIR__ . '/blocks';
return [
'files/default' => __DIR__ . '/blueprints/file.yml',
'pages/default' => __DIR__ . '/blueprints/page.yml',
'site' => __DIR__ . '/blueprints/site.yml'
// blocks
'blocks/code' => $blocksRoot . '/code/code.yml',
'blocks/gallery' => $blocksRoot . '/gallery/gallery.yml',
'blocks/heading' => $blocksRoot . '/heading/heading.yml',
'blocks/image' => $blocksRoot . '/image/image.yml',
'blocks/list' => $blocksRoot . '/list/list.yml',
'blocks/markdown' => $blocksRoot . '/markdown/markdown.yml',
'blocks/quote' => $blocksRoot . '/quote/quote.yml',
'blocks/table' => $blocksRoot . '/table/table.yml',
'blocks/text' => $blocksRoot . '/text/text.yml',
'blocks/video' => $blocksRoot . '/video/video.yml',
// file blueprints
'files/default' => __DIR__ . '/blueprints/files/default.yml',
// page blueprints
'pages/default' => __DIR__ . '/blueprints/pages/default.yml',
// site blueprints
'site' => __DIR__ . '/blueprints/site.yml'
];

View File

@@ -0,0 +1,56 @@
name: Code
icon: code
fields:
code:
label: Code
type: textarea
buttons: false
font: monospace
language:
label: Language
type: select
default: text
options:
bash: Bash
basic: BASIC
c: C
clojure: Clojure
cpp: C++
csharp: C#
css: CSS
diff: Diff
elixir: Elixir
elm: Elm
erlang: Erlang
go: Go
graphql: GraphQL
haskell: Haskell
html: HTML
java: Java
js: JavaScript
json: JSON
latext: LaTeX
less: Less
lisp: Lisp
lua: Lua
makefile: Makefile
markdown: Markdown
markup: Markup
objectivec: Objective-C
pascal: Pascal
perl: Perl
php: PHP
text: Plain Text
python: Python
r: R
ruby: Ruby
rust: Rust
sass: Sass
scss: SCSS
shell: Shell
sql: SQL
swift: Swift
typescript: TypeScript
vbnet: VB.net
xml: XML
yaml: YAML

View File

@@ -0,0 +1,21 @@
icon: title
fields:
text:
type: text
level:
type: select
width: 1/2
default: 1
empty: false
default: "2"
options:
- value: "1"
text: Heading 1
- value: "2"
text: Heading 2
- value: "3"
text: Heading 3
id:
type: text
label: ID
width: 1/2

View File

@@ -0,0 +1,16 @@
name: Image
icon: image
fields:
image:
type: files
multiple: false
alt:
type: text
icon: title
caption:
type: writer
inline: true
icon: text
link:
type: text
icon: url

View File

@@ -0,0 +1,12 @@
name: Quote
icon: quote
fields:
text:
label: Quote Text
type: writer
inline: true
citation:
label: Citation
type: writer
inline: true
placeholder: by …

View File

@@ -0,0 +1,25 @@
name: Table
icon: menu
fields:
rows:
label: Menu
type: structure
columns:
dish: true
description: true
price:
before:
width: 1/4
align: right
fields:
dish:
label: Dish
type: text
description:
label: Description
type: text
price:
label: Price
type: number
before:
step: 0.01

View File

@@ -0,0 +1,5 @@
name: Text
icon: text
fields:
text:
type: writer

View File

@@ -0,0 +1,8 @@
name: Video
icon: video
label: "{{ url }}"
fields:
url:
type: url
caption:
type: writer

View File

@@ -236,7 +236,7 @@ return [
return $item->searchHits > 0 ? true : false;
});
return $results->sortBy('searchScore', 'desc');
return $results->sort('searchScore', 'desc');
},
/**

View File

@@ -1,6 +1,7 @@
<?php
return [
'blocks' => 'Kirby\Form\Field\BlocksField',
'checkboxes' => __DIR__ . '/fields/checkboxes.php',
'date' => __DIR__ . '/fields/date.php',
'email' => __DIR__ . '/fields/email.php',
@@ -9,7 +10,9 @@ return [
'headline' => __DIR__ . '/fields/headline.php',
'hidden' => __DIR__ . '/fields/hidden.php',
'info' => __DIR__ . '/fields/info.php',
'layout' => 'Kirby\Form\Field\LayoutField',
'line' => __DIR__ . '/fields/line.php',
'list' => __DIR__ . '/fields/list.php',
'multiselect' => __DIR__ . '/fields/multiselect.php',
'number' => __DIR__ . '/fields/number.php',
'pages' => __DIR__ . '/fields/pages.php',
@@ -24,5 +27,6 @@ return [
'time' => __DIR__ . '/fields/time.php',
'toggle' => __DIR__ . '/fields/toggle.php',
'url' => __DIR__ . '/fields/url.php',
'users' => __DIR__ . '/fields/users.php'
'users' => __DIR__ . '/fields/users.php',
'writer' => __DIR__ . '/fields/writer.php'
];

View File

@@ -1,21 +1,38 @@
<?php
use Kirby\Exception\Exception;
use Kirby\Toolkit\I18n;
use Kirby\Toolkit\Str;
return [
'mixins' => ['datetime'],
'props' => [
/**
* Unset inherited props
*/
'placeholder' => null,
/**
* Activate/deactivate the dropdown calendar
*/
'calendar' => function (bool $calendar = true) {
return $calendar;
},
/**
* Default date when a new page/file/user gets created
*/
'default' => function ($default = null) {
'default' => function (string $default = null) {
return $default;
},
/**
* Defines a custom format that is used when the field is saved
* Custom format (dayjs tokens: `DD`, `MM`, `YYYY`) that is
* used to display the field in the Panel
*/
'format' => function (string $format = null) {
return $format;
'display' => function ($display = 'YYYY-MM-DD') {
return I18n::translate($display, $display);
},
/**
@@ -24,22 +41,52 @@ return [
'icon' => function (string $icon = 'calendar') {
return $icon;
},
/**
* Youngest date, which can be selected/saved
* Latest date, which can be selected/saved (Y-m-d)
*/
'max' => function (string $max = null) {
return $this->toDate($max);
return $this->toDatetime($max);
},
/**
* Oldest date, which can be selected/saved
* Earliest date, which can be selected/saved (Y-m-d)
*/
'min' => function (string $min = null) {
return $this->toDate($min);
return $this->toDatetime($min);
},
/**
* The placeholder is not available
* Round to the nearest: sub-options for `unit` (day) and `size` (1)
*/
'placeholder' => null,
'step' => function ($step = null) {
if ($step === null) {
return [
'size' => 1,
'unit' => 'day'
];
}
if (is_array($step) === true) {
return $step;
}
if (is_int($step) === true) {
return [
'size' => $step,
'unit' => 'day'
];
}
if (is_string($step) === true) {
return [
'size' => 1,
'unit' => $step
];
}
throw new Exception('step option has to be defined as array');
},
/**
* Pass `true` or an array of time field options to show the time selector.
*/
@@ -55,27 +102,29 @@ return [
],
'computed' => [
'default' => function () {
return $this->toDate($this->default);
return $this->toDatetime($this->default);
},
'format' => function () {
return $this->props['format'] ?? ($this->time() === false ? 'Y-m-d' : 'Y-m-d H:i');
'display' => function () {
if ($this->display) {
return Str::upper($this->display);
}
},
'value' => function () {
return $this->toDate($this->value);
},
],
'methods' => [
'toDate' => function ($value) {
if ($timestamp = timestamp($value, $this->time['step'] ?? 5)) {
return date('Y-m-d H:i:s', $timestamp);
'step' => function () {
if ($this->time !== false) {
$timeField = require __DIR__ . '/time.php';
return $timeField['props']['step']($this->time['step'] ?? null);
}
return null;
}
return $this->step;
},
'value' => function () {
return $this->toDatetime($this->value);
},
],
'save' => function ($value) {
if ($value !== null && $date = strtotime($value)) {
return date($this->format(), $date);
if ($value !== null && $timestamp = timestamp($value)) {
$format = $this->time === false ? 'Y-m-d' : 'Y-m-d H:i:s';
return $this->toISO($timestamp, $format);
}
return '';
@@ -86,7 +135,7 @@ return [
$min = $this->min ? strtotime($this->min) : null;
$max = $this->max ? strtotime($this->max) : null;
$value = strtotime($this->value());
$format = 'd.m.Y';
$format = $this->time === false ? 'd.m.Y' : 'd.m.Y H:i';
$errors = [];
if ($value && $min && $value < $min) {

View File

@@ -11,7 +11,6 @@ return [
'before' => null,
'default' => null,
'disabled' => null,
'help' => null,
'icon' => null,
'placeholder' => null,
'required' => null,

View File

@@ -4,6 +4,19 @@ use Kirby\Toolkit\I18n;
return [
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'autofocus' => null,
'before' => null,
'default' => null,
'disabled' => null,
'icon' => null,
'placeholder' => null,
'required' => null,
'translate' => null,
/**
* Text to be displayed
*/

12
kirby/config/fields/list.php Executable file
View File

@@ -0,0 +1,12 @@
<?php
return [
'props' => [
/**
* Sets the allowed HTML formats. Available formats: `bold`, `italic`, `underline`, `strike`, `code`, `link`. Activate them all by passing `true`. Deactivate them all by passing `false`
*/
'marks' => function ($marks = true) {
return $marks;
}
]
];

View File

@@ -0,0 +1,16 @@
<?php
return [
'methods' => [
'toDatetime' => function ($value, string $format = 'Y-m-d H:i:s') {
if ($timestamp = timestamp($value, $this->step)) {
return $this->toISO($timestamp, $format);
}
return null;
},
'toISO' => function (int $time, string $format = 'Y-m-d H:i:s') {
return date($format, $time);
}
]
];

View File

@@ -30,7 +30,7 @@ return [
'template' => $template
]);
$uploads['accept'] = $file->blueprint()->accept()['mime'] ?? '*';
$uploads['accept'] = $file->blueprint()->acceptMime();
} else {
$uploads['accept'] = '*';
}

View File

@@ -99,6 +99,7 @@ return [
],
[
'pattern' => 'upload',
'method' => 'POST',
'action' => function () {
$field = $this->field();
$uploads = $field->uploads();

View File

@@ -1,24 +1,51 @@
<?php
use Kirby\Exception\Exception;
use Kirby\Toolkit\I18n;
return [
'mixins' => ['datetime'],
'props' => [
/**
* Unset inherited props
*/
'placeholder' => null,
/**
* Sets the default time when a new page/file/user is created
*/
'default' => function ($default = null) {
return $default;
},
/**
* Custom format (dayjs tokens: `HH`, `hh`, `mm`, `ss`, `a`) that is
* used to display the field in the Panel
*/
'display' => function ($display = null) {
return I18n::translate($display, $display);
},
/**
* Changes the clock icon
*/
'icon' => function (string $icon = 'clock') {
return $icon;
},
/**
* Latest time, which can be selected/saved (H:i or H:i:s)
*/
'max' => function (string $max = null) {
return $max ? $this->toDatetime(date('Y-m-d ') . $max) : null;
},
/**
* Earliest time, which can be selected/saved (H:i or H:i:s)
*/
'min' => function (string $min = null) {
return $min ? $this->toDatetime(date('Y-m-d ') . $min) : null;
},
/**
* `12` or `24` hour notation. If `12`, an AM/PM selector will be shown.
*/
@@ -26,10 +53,35 @@ return [
return $value === 24 ? 24 : 12;
},
/**
* The interval between minutes in the minutes select dropdown.
* Round to the nearest: sub-options for `unit` (minute) and `size` (5)
*/
'step' => function (int $step = 5) {
return $step;
'step' => function ($step = null) {
if ($step === null) {
return [
'size' => 5,
'unit' => 'minute'
];
}
if (is_array($step) === true) {
return $step;
}
if (is_int($step) === true) {
return [
'size' => $step,
'unit' => 'minute'
];
}
if (is_string($step) === true) {
return [
'size' => 1,
'unit' => $step
];
}
throw new Exception('step option has to be defined as array');
},
'value' => function ($value = null) {
return $value;
@@ -37,32 +89,70 @@ return [
],
'computed' => [
'default' => function () {
return $this->toTime($this->default);
return $this->toDatetime($this->default, 'H:i:s');
},
'format' => function () {
return $this->notation === 24 ? 'H:i' : 'h:i a';
},
'value' => function () {
return $this->toTime($this->value);
}
],
'methods' => [
'toTime' => function ($value) {
if ($timestamp = timestamp($value, $this->step)) {
return date('H:i', $timestamp);
'display' => function () {
if ($this->display) {
return $this->display;
}
return null;
return $this->notation === 24 ? 'HH:mm' : 'hh:mm a';
},
'value' => function () {
return $this->toDatetime($this->value, 'H:i:s');
}
],
'save' => function ($value): string {
if ($timestamp = strtotime($value)) {
return date($this->format, $timestamp);
if ($value != null && $timestamp = strtotime($value)) {
return date('H:i:s', $timestamp);
}
return '';
},
'validations' => [
'time',
'minMax' => function ($value) {
$min = $this->min ? strtotime($this->min) : null;
$max = $this->max ? strtotime($this->max) : null;
$value = strtotime($this->value());
$format = 'H:i:s';
$errors = [];
if ($value && $min && $value < $min) {
$errors['min'] = $min;
}
if ($value && $max && $value > $max) {
$errors['max'] = $max;
}
if (empty($errors) === false) {
if ($min && $max) {
throw new Exception([
'key' => 'validation.time.between',
'data' => [
'min' => date($format, $min),
'max' => date($format, $max)
]
]);
} elseif ($min) {
throw new Exception([
'key' => 'validation.time.after',
'data' => [
'time' => date($format, $min),
]
]);
} else {
throw new Exception([
'key' => 'validation.time.before',
'data' => [
'time' => date($format, $max),
]
]);
}
}
return true;
},
]
];

View File

@@ -59,9 +59,7 @@ return [
'boolean',
'required' => function ($value) {
if ($this->isRequired() && ($value === false || $this->isEmpty($value))) {
throw new InvalidArgumentException([
'key' => 'form.field.required'
]);
throw new InvalidArgumentException(I18n::translate('field.required'));
}
},
]

20
kirby/config/fields/writer.php Executable file
View File

@@ -0,0 +1,20 @@
<?php
return [
'props' => [
/**
* Enables inline mode, which will not wrap new lines in paragraphs and creates hard breaks instead.
*
* @param bool $inline
*/
'inline' => function (bool $inline = false) {
return $inline;
},
/**
* Sets the allowed HTML formats. Available formats: `bold`, `italic`, `underline`, `strike`, `code`, `link`. Activate them all by passing `true`. Deactivate them all by passing `false`
*/
'marks' => function ($marks = true) {
return $marks;
}
]
];

View File

@@ -66,16 +66,16 @@ function csrf(string $check = null)
if (func_num_args() === 0) {
// no arguments, generate/return a token
$token = $session->get('csrf');
$token = $session->get('kirby.csrf');
if (is_string($token) !== true) {
$token = bin2hex(random_bytes(32));
$session->set('csrf', $token);
$session->set('kirby.csrf', $token);
}
return $token;
} elseif (is_string($check) === true && is_string($session->get('csrf')) === true) {
} elseif (is_string($check) === true && is_string($session->get('kirby.csrf')) === true) {
// argument has been passed, check the token
return hash_equals($session->get('csrf'), $check) === true;
return hash_equals($session->get('kirby.csrf'), $check) === true;
}
return false;
@@ -747,10 +747,10 @@ function tc($key, int $count)
* by the defined step
*
* @param string $date
* @param int $step
* @param int $step array of `unit` and `size` to round to nearest
* @return string|null
*/
function timestamp(string $date = null, int $step = null): ?string
function timestamp(string $date = null, $step = null): ?string
{
if (V::date($date) === false) {
return null;
@@ -762,13 +762,46 @@ function timestamp(string $date = null, int $step = null): ?string
return $date;
}
$hours = date('H', $date);
$minutes = date('i', $date);
$minutes = floor($minutes / $step) * $step;
$minutes = str_pad($minutes, 2, 0, STR_PAD_LEFT);
$date = date('Y-m-d', $date) . ' ' . $hours . ':' . $minutes;
if (is_int($step) === true) {
$step = [
'unit' => 'minute',
'size' => 1
];
}
return strtotime($date);
if (is_array($step) === false) {
return $date;
}
$parts = [
'second' => date('s', $date),
'minute' => date('i', $date),
'hour' => date('H', $date),
'day' => date('d', $date),
'month' => date('m', $date),
'year' => date('Y', $date),
];
$current = $parts[$step['unit']];
$nearest = round($current / $step['size']) * $step['size'];
$parts[$step['unit']] = $nearest;
foreach ($parts as $part => $value) {
if ($part === $step['unit']) {
break;
}
$parts[$part] = 0;
}
return strtotime(
$parts['year'] . '-' .
str_pad($parts['month'], 2, 0, STR_PAD_LEFT) . '-' .
str_pad($parts['day'], 2, 0, STR_PAD_LEFT) . ' ' .
str_pad($parts['hour'], 2, 0, STR_PAD_LEFT) . ':' .
str_pad($parts['minute'], 2, 0, STR_PAD_LEFT) . ':' .
str_pad($parts['second'], 2, 0, STR_PAD_LEFT)
);
}
/**
@@ -829,6 +862,36 @@ function url(string $path = null, $options = null): string
return Url::to($path, $options);
}
/**
* Creates a compliant v4 UUID
* Taken from: https://github.com/symfony/polyfill
*
* @return string
*/
function uuid(): string
{
$uuid = bin2hex(random_bytes(16));
return sprintf(
'%08s-%04s-4%03s-%04x-%012s',
// 32 bits for "time_low"
substr($uuid, 0, 8),
// 16 bits for "time_mid"
substr($uuid, 8, 4),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
substr($uuid, 13, 3),
// 16 bits:
// * 8 bits for "clk_seq_hi_res",
// * 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
hexdec(substr($uuid, 16, 4)) & 0x3fff | 0x8000,
// 48 bits for "node"
substr($uuid, 20, 12)
);
}
/**
* Creates a video embed via iframe for Youtube or Vimeo
* videos. The embed Urls are automatically detected from

View File

@@ -1,9 +1,11 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Blocks;
use Kirby\Cms\Field;
use Kirby\Cms\Files;
use Kirby\Cms\Html;
use Kirby\Cms\Layouts;
use Kirby\Cms\Structure;
use Kirby\Cms\Url;
use Kirby\Data\Data;
@@ -53,6 +55,41 @@ return function (App $app) {
},
// converters
/**
* Converts a yaml or json field to a Blocks object
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\Blocks
*/
'toBlocks' => function (Field $field) {
try {
$blocks = Blocks::factory(Blocks::parse($field->value()), [
'parent' => $field->parent(),
]);
return $blocks->filter('isHidden', false);
} catch (Throwable $e) {
if ($field->parent() === null) {
$message = 'Invalid blocks data for "' . $field->key() . '" field';
} else {
$message = 'Invalid blocks data for "' . $field->key() . '" field on parent "' . $field->parent()->title() . '"';
}
throw new InvalidArgumentException($message);
}
},
/**
* Converts the field value into a proper boolean
*
* @param \Kirby\Cms\Field $field
* @param bool $default Default value if the field is empty
* @return bool
*/
'toBool' => function (Field $field, $default = false): bool {
$value = $field->isEmpty() ? $default : $field->value;
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
},
/**
* Parses the field value with the given method
@@ -71,18 +108,6 @@ return function (App $app) {
}
},
/**
* Converts the field value into a proper boolean
*
* @param \Kirby\Cms\Field $field
* @param bool $default Default value if the field is empty
* @return bool
*/
'toBool' => function (Field $field, $default = false): bool {
$value = $field->isEmpty() ? $default : $field->value;
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
},
/**
* Converts the field value to a timestamp or a formatted date
*
@@ -159,6 +184,19 @@ return function (App $app) {
return (int)$value;
},
/**
* Parse layouts and turn them into
* Layout objects
*
* @param \Kirby\Cms\Field $field
* @return \Kirby\Cms\Layouts
*/
'toLayouts' => function (Field $field) {
return Layouts::factory(Data::decode($field->value, 'json'), [
'parent' => $field->parent()
]);
},
/**
* Wraps a link tag around the field value. The field value is used as the link text
*

View File

@@ -67,6 +67,9 @@ return [
'languages' => function (array $roots) {
return $roots['site'] . '/languages';
},
'logs' => function (array $roots) {
return $roots['site'] . '/logs';
},
'models' => function (array $roots) {
return $roots['site'] . '/models';
},

View File

@@ -41,25 +41,15 @@ return [
}
return $fields;
},
}
],
'methods' => [
'errors' => function () {
return $this->form->errors();
},
'data' => function () {
$values = $this->form->values();
if (is_a($this->model, 'Kirby\Cms\Page') === true || is_a($this->model, 'Kirby\Cms\Site') === true) {
// the title should never be updated directly via
// fields section to avoid conflicts with the rename dialog
unset($values['title']);
}
return $values;
}
],
'toArray' => function () {
return [
'errors' => $this->errors,
'fields' => $this->fields,
];
}

View File

@@ -72,7 +72,7 @@ return [
'template' => $this->template
]);
return $file->blueprint()->accept()['mime'] ?? '*';
return $file->blueprint()->acceptMime();
}
return null;
@@ -84,12 +84,12 @@ return [
$files = $this->parent->files()->template($this->template);
// filter out all protected files
$files = $files->filterBy('isReadable', true);
$files = $files->filter('isReadable', true);
if ($this->sortBy) {
$files = $files->sortBy(...$files::sortArgs($this->sortBy));
} elseif ($this->sortable === true) {
$files = $files->sortBy('sort', 'asc', 'filename', 'asc');
$files = $files->sort(...$files::sortArgs($this->sortBy));
} else {
$files = $files->sort('sort', 'asc', 'filename', 'asc');
}
// flip
@@ -206,13 +206,15 @@ return [
$multiple = true;
}
$template = $this->template === 'default' ? null : $this->template;
return [
'accept' => $this->accept,
'multiple' => $multiple,
'max' => $max,
'api' => $this->parent->apiUrl(true) . '/files',
'attributes' => array_filter([
'template' => $this->template
'template' => $template
])
];
}

View File

@@ -126,7 +126,7 @@ return [
// sort
if ($this->sortBy) {
$pages = $pages->sortBy(...$pages::sortArgs($this->sortBy));
$pages = $pages->sort(...$pages::sortArgs($this->sortBy));
}
// flip
@@ -165,7 +165,9 @@ return [
'status' => $item->status(),
'permissions' => [
'sort' => $permissions->can('sort'),
'changeStatus' => $permissions->can('changeStatus')
'changeSlug' => $permissions->can('changeSlug'),
'changeStatus' => $permissions->can('changeStatus'),
'changeTitle' => $permissions->can('changeTitle')
]
];
}

17
kirby/config/snippets.php Executable file
View File

@@ -0,0 +1,17 @@
<?php
$blocksRoot = __DIR__ . '/blocks';
return [
// blocks
'blocks/code' => $blocksRoot . '/code/code.php',
'blocks/gallery' => $blocksRoot . '/gallery/gallery.php',
'blocks/heading' => $blocksRoot . '/heading/heading.php',
'blocks/image' => $blocksRoot . '/image/image.php',
'blocks/list' => $blocksRoot . '/list/list.php',
'blocks/markdown' => $blocksRoot . '/markdown/markdown.php',
'blocks/quote' => $blocksRoot . '/quote/quote.php',
'blocks/table' => $blocksRoot . '/table/table.php',
'blocks/text' => $blocksRoot . '/text/text.php',
'blocks/video' => $blocksRoot . '/video/video.php',
];

8
kirby/config/templates.php Executable file
View File

@@ -0,0 +1,8 @@
<?php
// @codeCoverageIgnoreStart
return [
'emails/auth/login' => __DIR__ . '/templates/emails/auth/login.php',
'emails/auth/password-reset' => __DIR__ . '/templates/emails/auth/password-reset.php'
];
// @codeCoverageIgnoreEnd

View File

@@ -0,0 +1,3 @@
<?php
echo I18n::template('login.email.login.body', null, compact('user', 'code', 'timeout'));

View File

@@ -0,0 +1,3 @@
<?php
echo I18n::template('login.email.password-reset.body', null, compact('user', 'code', 'timeout'));