Upgrade to 3.2.3

This commit is contained in:
Bastian Allgeier
2019-08-06 10:24:12 +02:00
parent 40f095f5de
commit 02be32bb75
48 changed files with 356 additions and 114 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "getkirby/cms",
"description": "The Kirby 3 core",
"version": "3.2.2",
"version": "3.2.3",
"license": "proprietary",
"keywords": ["kirby", "cms", "core"],
"homepage": "https://getkirby.com",

View File

@@ -35,7 +35,7 @@ return [
'requirements' => function (System $system) {
return $system->toArray();
},
'breadcrumbTitle' => function () {
'site' => function () {
try {
return $this->site()->blueprint()->title();
} catch (Throwable $e) {
@@ -90,7 +90,6 @@ return [
],
'panel' => [
'ascii',
'breadcrumbTitle',
'isOk',
'isInstalled',
'isLocal',
@@ -99,6 +98,7 @@ return [
'license',
'multilang',
'requirements',
'site',
'slugs',
'title',
'translation',

View File

@@ -35,26 +35,14 @@ return [
$long = $this->requestBody('long');
$password = $this->requestBody('password');
try {
if ($user = $this->kirby()->auth()->login($email, $password, $long)) {
$user = $this->kirby()->auth()->login($email, $password, $long);
return [
'code' => 200,
'status' => 'ok',
'user' => $this->resolve($user)->view('auth')->toArray()
];
}
throw new NotFoundException(['key' => 'user.undefined']);
} catch (Throwable $e) {
if ($this->kirby()->option('debug') === true) {
throw $e;
} else {
// catch any kind of login error
}
}
throw new InvalidArgumentException('Invalid email or password');
}
],
[
'pattern' => 'auth/logout',

View File

@@ -1,37 +1,70 @@
<?php
use Kirby\Exception\Exception;
/**
* Content Lock Routes
*/
return [
[
'pattern' => '(:all)/lock',
'method' => 'GET',
'action' => function (string $path) {
return $this->parent($path)->lock()->get();
}
],
[
'pattern' => '(:all)/lock',
'method' => 'PATCH',
'action' => function (string $path) {
return $this->parent($path)->lock()->create();
}
],
[
'pattern' => '(:all)/lock',
'method' => 'DELETE',
'action' => function (string $path) {
return $this->parent($path)->lock()->remove();
}
],
[
'pattern' => '(:all)/unlock',
'method' => 'GET',
'action' => function (string $path) {
if ($lock = $this->parent($path)->lock()) {
return [
'isUnlocked' => $this->parent($path)->lock()->isUnlocked()
'supported' => true,
'locked' => $lock->get()
];
}
return [
'supported' => false,
'locked' => null
];
}
],
[
'pattern' => '(:all)/lock',
'method' => 'PATCH',
'action' => function (string $path) {
if ($lock = $this->parent($path)->lock()) {
return $lock->create();
}
throw new Exception([
'key' => 'lock.notImplemented',
'httpCode' => 501
]);
}
],
[
'pattern' => '(:all)/lock',
'method' => 'DELETE',
'action' => function (string $path) {
if ($lock = $this->parent($path)->lock()) {
return $lock->remove();
}
throw new Exception([
'key' => 'lock.notImplemented',
'httpCode' => 501
]);
}
],
[
'pattern' => '(:all)/unlock',
'method' => 'GET',
'action' => function (string $path) {
if ($lock = $this->parent($path)->lock()) {
return [
'supported' => true,
'unlocked' => $lock->isUnlocked()
];
}
return [
'supported' => false,
'unlocked' => null
];
}
],
@@ -39,14 +72,28 @@ return [
'pattern' => '(:all)/unlock',
'method' => 'PATCH',
'action' => function (string $path) {
return $this->parent($path)->lock()->unlock();
if ($lock = $this->parent($path)->lock()) {
return $lock->unlock();
}
throw new Exception([
'key' => 'lock.notImplemented',
'httpCode' => 501
]);
}
],
[
'pattern' => '(:all)/unlock',
'method' => 'DELETE',
'action' => function (string $path) {
return $this->parent($path)->lock()->resolve();
if ($lock = $this->parent($path)->lock()) {
return $lock->resolve();
}
throw new Exception([
'key' => 'lock.notImplemented',
'httpCode' => 501
]);
}
],
];

View File

@@ -82,7 +82,7 @@ return [
$files = $this->parent->files()->template($this->template);
if ($this->sortBy) {
$files = $files->sortBy(...Str::split($this->sortBy, ' '));
$files = $files->sortBy(...$files::sortArgs($this->sortBy));
} elseif ($this->sortable === true) {
$files = $files->sortBy('sort', 'asc');
}

View File

@@ -122,7 +122,7 @@ return [
// sort
if ($this->sortBy) {
$pages = $pages->sortBy(...Str::split($this->sortBy, ' '));
$pages = $pages->sortBy(...$pages::sortArgs($this->sortBy));
}
// pagination

View File

@@ -227,13 +227,15 @@ return [
'html' => function ($tag) {
$video = Html::video(
$tag->value,
$tag->kirby()->option('kirbytext.video.options', [])
$tag->kirby()->option('kirbytext.video.options', []),
[
'height' => $tag->height ?? $tag->kirby()->option('kirbytext.video.height'),
'width' => $tag->width ?? $tag->kirby()->option('kirbytext.video.width'),
]
);
return Html::figure([$video], $tag->caption, [
'class' => $tag->class ?? $tag->kirby()->option('kirbytext.video.class', 'video'),
'height' => $tag->height ?? $tag->kirby()->option('kirbytext.video.height'),
'width' => $tag->width ?? $tag->kirby()->option('kirbytext.video.width'),
]);
}
],

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Отляво надясно",
"language.direction.rtl": "Отдясно наляво",
"language.locale": "PHP locale string",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Име",
"language.updated": "Езикът беше обновен",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Esquerra a dreta",
"language.direction.rtl": "De dreta a esquerra",
"language.locale": "Cadena local de PHP",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nom",
"language.updated": "S'ha actualitzat l'idioma",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Zleva doprava",
"language.direction.rtl": "Zprava doleva",
"language.locale": "Řetězec lokalizace PHP",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Jméno",
"language.updated": "Jazyk byl aktualizován",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Venstre mod højre",
"language.direction.rtl": "Højre mod venstre",
"language.locale": "PHP locale string",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Navn",
"language.updated": "Sproget er blevet opdateret",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Von links nach rechts",
"language.direction.rtl": "Von rechts nach links",
"language.locale": "PHP locale string",
"language.locale.warning": "Du nutzt ein angepasstes Setup for PHP Locales. Bitte bearbeite dieses direkt in der entsprechenden Sprachdatei in /site/languages",
"language.name": "Name",
"language.updated": "Die Sprache wurde gespeichert",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Αριστερά προς τα δεξιά",
"language.direction.rtl": "Δεξιά προς τα αριστερά",
"language.locale": "Συμβολοσειρά τοπικής γλώσσας PHP",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Ονομασία",
"language.updated": "Η γλώσσα έχει ενημερωθεί",

View File

@@ -169,7 +169,6 @@
"error.user.password.notSame": "The passwords do not match",
"error.user.password.undefined": "The user does not have a password",
"error.user.role.invalid": "Please enter a valid role",
"error.user.undefined": "The user cannot be found",
"error.user.update.permission":
"You are not allowed to update the user \"{name}\"",
@@ -265,6 +264,7 @@
"language.direction.ltr": "Left to right",
"language.direction.rtl": "Right to left",
"language.locale": "PHP locale string",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Name",
"language.updated": "The language has been updated",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "De Izquierda a derecha",
"language.direction.rtl": "De derecha a izquierda",
"language.locale": "Cadena de localización PHP",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nombre",
"language.updated": "El idioma a sido actualizado",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "De izquierda a derecha",
"language.direction.rtl": "De derecha a izquierda",
"language.locale": "PHP locale string",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nombre",
"language.updated": "El idioma ha sido actualizado",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "چپ به راست",
"language.direction.rtl": "راست به چپ",
"language.locale": "PHP locale string",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "پارسی",
"language.updated": "زبان به روز شد",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Vasemmalta oikealle",
"language.direction.rtl": "Oikealta vasemmalle",
"language.locale": "PHP-lokaalin tunniste",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nimi",
"language.updated": "Kieli on päivitetty",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "De gauche à droite",
"language.direction.rtl": "De droite à gauche",
"language.locale": "Locales PHP",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nom",
"language.updated": "La langue a été mise à jour",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Balról jobbra",
"language.direction.rtl": "Jobbról balra",
"language.locale": "PHP locale sztring",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Név",
"language.updated": "A nyelv frissítve lett",

View File

@@ -183,9 +183,9 @@
"error.validation.contains":
"Masukkan nilai yang mengandung \"{needle}\"",
"error.validation.date": "Masukkan tanggal yang valid",
"error.validation.date.after": "Please enter a date after {date}",
"error.validation.date.before": "Please enter a date before {date}",
"error.validation.date.between": "Please enter a date between {min} and {max}",
"error.validation.date.after": "Masukkan tanggal setelah {date}",
"error.validation.date.before": "Masukkan tanggal sebelum {date}",
"error.validation.date.between": "Masukkan tanggal antara {min} dan {max}",
"error.validation.denied": "Mohon tolak",
"error.validation.different": "Nilai harus selain \"{other}\"",
"error.validation.email": "Masukkan surel yang valid",
@@ -265,6 +265,7 @@
"language.direction.ltr": "Kiri ke kanan",
"language.direction.rtl": "Kanan ke kiri",
"language.locale": "String \"PHP locale\"",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nama",
"language.updated": "Bahasa sudah diperbaharui",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Sinistra a destra",
"language.direction.rtl": "Destra a sinistra",
"language.locale": "Stringa \"PHP locale\"",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nome",
"language.updated": "La lingua è stata aggiornata",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "왼쪽에서 오른쪽",
"language.direction.rtl": "오른쪽에서 왼쪽",
"language.locale": "PHP 로캘 문자열",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "이름",
"language.updated": "언어를 변경했습니다.",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Venstre til høyre",
"language.direction.rtl": "Høyre til venstre",
"language.locale": "PHP locale streng",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Navn",
"language.updated": "Språk har blitt oppdatert",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Links naar rechts",
"language.direction.rtl": "Rechts naar links",
"language.locale": "PHP-locale regel",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Naam",
"language.updated": "De taal is geüpdatet",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Od lewej do prawej",
"language.direction.rtl": "Od prawej do lewej",
"language.locale": "PHP locale string",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nazwa",
"language.updated": "Język został zaktualizowany",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Esquerda para direita",
"language.direction.rtl": "Direita para esquerda",
"language.locale": "String de localização do PHP",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nome",
"language.updated": "Idioma atualizado",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Esquerda para direita",
"language.direction.rtl": "Direita para esquerda",
"language.locale": "String de localização do PHP",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Nome",
"language.updated": "Idioma atualizado",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Zľava doprava",
"language.direction.rtl": "Zprava doľava",
"language.locale": "PHP locale string",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Názov",
"language.updated": "Jazyk bol aktualizovaný",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Vänster till höger",
"language.direction.rtl": "Höger till vänster",
"language.locale": "PHP locale string",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "Namn",
"language.updated": "Språket har uppdaterats",

View File

@@ -265,6 +265,7 @@
"language.direction.ltr": "Soldan sağa",
"language.direction.rtl": "Sağdan sola",
"language.locale": "PHP yerel dizesi",
"language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages",
"language.name": "İsim",
"language.updated": "Dil güncellendi",

File diff suppressed because one or more lines are too long

View File

@@ -543,7 +543,7 @@ class Api
if (is_a($e, 'Kirby\Exception\Exception') === true) {
$result = [
'status' => 'error',
'route' => $this->route->pattern()
'route' => ($this->route)? $this->route->pattern() : null
] + $e->toArray();
} else {
// remove the document root from the file path

View File

@@ -81,7 +81,7 @@ trait AppCaches
];
}
$prefix = str_replace('/', '_', $this->system()->indexUrl()) .
$prefix = str_replace(['/', ':'], '_', $this->system()->indexUrl()) .
'/' .
str_replace('.', '/', $key);

View File

@@ -2,6 +2,8 @@
namespace Kirby\Cms;
use Throwable;
/**
* AppUsers
*
@@ -88,7 +90,11 @@ trait AppUsers
if (is_string($this->user) === true) {
return $this->auth()->impersonate($this->user);
} else {
try {
return $this->auth()->user();
} catch (Throwable $e) {
return null;
}
}
}

View File

@@ -7,6 +7,7 @@ use Kirby\Exception\PermissionException;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\NotFoundException;
use Kirby\Http\Request\Auth\BasicAuth;
use Kirby\Toolkit\F;
use Throwable;
/**
@@ -22,10 +23,12 @@ class Auth
{
protected $impersonate;
protected $kirby;
protected $user;
protected $user = false;
protected $userException;
/**
* @param Kirby\Cms\App $kirby
* @codeCoverageIgnore
*/
public function __construct(App $kirby)
{
@@ -79,13 +82,7 @@ class Auth
throw new PermissionException('Basic authentication is only allowed over HTTPS');
}
if ($user = $this->kirby->users()->find($auth->username())) {
if ($user->validatePassword($auth->password()) === true) {
return $user;
}
}
return null;
return $this->validatePassword($auth->username(), $auth->password());
}
/**
@@ -158,24 +155,33 @@ class Auth
*/
public function ipHash(): string
{
return hash('sha256', $this->kirby->visitor()->ip());
$hash = hash('sha256', $this->kirby->visitor()->ip());
// only use the first 50 chars to ensure privacy
return substr($hash, 0, 50);
}
/**
* Check if logins are blocked for the current ip
* Check if logins are blocked for the current ip or email
*
* @param string $email
* @return boolean
*/
public function isBlocked(): bool
public function isBlocked(string $email): bool
{
$ip = $this->ipHash();
$log = $this->log();
$trials = $this->kirby->option('auth.trials', 10);
$timeout = $this->kirby->option('auth.timeout', 3600);
if ($entry = ($log[$ip] ?? null)) {
if ($entry['trials'] > $trials) {
if ($entry['time'] > (time() - $timeout)) {
if ($entry = ($log['by-ip'][$ip] ?? null)) {
if ($entry['trials'] >= $trials) {
return true;
}
}
if ($this->kirby->users()->find($email)) {
if ($entry = ($log['by-email'][$email] ?? null)) {
if ($entry['trials'] >= $trials) {
return true;
}
}
@@ -190,18 +196,14 @@ class Auth
* @param string $email
* @param string $password
* @param boolean $long
* @return Kirby\Cms\User|false
* @return Kirby\Cms\User
*
* @throws PermissionException If the rate limit was exceeded or if any other error occured with debug mode off
* @throws NotFoundException If the email was invalid
* @throws InvalidArgumentException If the password is not valid (via `$user->login()`)
*/
public function login(string $email, string $password, bool $long = false)
{
// check for blocked ips
if ($this->isBlocked() === true) {
throw new PermissionException('Rate limit exceeded', 403);
}
// stop impersonating
$this->impersonate = null;
// session options
$options = [
'createMode' => 'cookie',
@@ -209,20 +211,71 @@ class Auth
];
// validate the user and log in to the session
if ($user = $this->kirby->users()->find($email)) {
if ($user->login($password, $options) === true) {
$user = $this->validatePassword($email, $password);
$user->loginPasswordless($options);
// stop impersonating
$this->impersonate = null;
return $this->user = $user;
}
/**
* Validates the user credentials and returns the user object on success;
* otherwise logs the failed attempt
*
* @param string $email
* @param string $password
* @return Kirby\Cms\User
*
* @throws PermissionException If the rate limit was exceeded or if any other error occured with debug mode off
* @throws NotFoundException If the email was invalid
* @throws InvalidArgumentException If the password is not valid (via `$user->login()`)
*/
public function validatePassword(string $email, string $password)
{
// check for blocked ips
if ($this->isBlocked($email) === true) {
if ($this->kirby->option('debug') === true) {
$message = 'Rate limit exceeded';
} else {
// avoid leaking security-relevant information
$message = 'Invalid email or password';
}
throw new PermissionException($message, 403);
}
// validate the user
try {
if ($user = $this->kirby->users()->find($email)) {
if ($user->validatePassword($password) === true) {
return $user;
}
}
throw new NotFoundException([
'key' => 'user.notFound',
'data' => [
'name' => $email
]
]);
} catch (Throwable $e) {
// log invalid login trial
$this->track();
$this->track($email);
// sleep for a random amount of milliseconds
// to make automated attacks harder
usleep(random_int(1000, 2000000));
return false;
// keep throwing the original error in debug mode,
// otherwise hide it to avoid leaking security-relevant information
if ($this->kirby->option('debug') === true) {
throw $e;
} else {
throw new PermissionException('Invalid email or password');
}
}
}
/**
@@ -243,10 +296,39 @@ class Auth
public function log(): array
{
try {
return Data::read($this->logfile(), 'json');
$log = Data::read($this->logfile(), 'json');
$read = true;
} catch (Throwable $e) {
return [];
$log = [];
$read = false;
}
// ensure that the category arrays are defined
$log['by-ip'] = $log['by-ip'] ?? [];
$log['by-email'] = $log['by-email'] ?? [];
// remove entries that are no longer needed
$originalLog = $log;
$time = time() - $this->kirby->option('auth.timeout', 3600);
foreach ($log as $category => $entries) {
$log[$category] = array_filter($entries, function ($entry) use ($time) {
return $entry['time'] > $time;
});
}
// remove all elements on the top level with different keys (old structure)
$log = array_intersect_key($log, array_flip(['by-ip', 'by-email']));
// write new log to the file system if it changed
if ($read === false || $log !== $originalLog) {
if (count($log['by-ip']) === 0 && count($log['by-email']) === 0) {
F::remove($this->logfile());
} else {
Data::write($this->logfile(), $log, 'json');
}
}
return $log;
}
/**
@@ -271,26 +353,41 @@ class Auth
/**
* Tracks a login
*
* @param string $email
* @return boolean
*/
public function track(): bool
public function track(string $email): bool
{
$ip = $this->ipHash();
$log = $this->log();
$time = time();
if (isset($log[$ip]) === true) {
$log[$ip] = [
if (isset($log['by-ip'][$ip]) === true) {
$log['by-ip'][$ip] = [
'time' => $time,
'trials' => ($log[$ip]['trials'] ?? 0) + 1
'trials' => ($log['by-ip'][$ip]['trials'] ?? 0) + 1
];
} else {
$log[$ip] = [
$log['by-ip'][$ip] = [
'time' => $time,
'trials' => 1
];
}
if ($this->kirby->users()->find($email)) {
if (isset($log['by-email'][$email]) === true) {
$log['by-email'][$email] = [
'time' => $time,
'trials' => ($log['by-email'][$email]['trials'] ?? 0) + 1
];
} else {
$log['by-email'][$email] = [
'time' => $time,
'trials' => 1
];
}
}
return Data::write($this->logfile(), $log, 'json');
}
@@ -316,8 +413,9 @@ class Auth
/**
* Validates the currently logged in user
*
* @param Kirby\Session\Sessionarray||null $session
* @return Kirby\Cms\User|null
* @param Kirby\Session\Session|array|null $session
* @return Kirby\Cms\User
* @throws
*/
public function user($session = null)
{
@@ -325,6 +423,18 @@ class Auth
return $this->impersonate;
}
// return from cache
if ($this->user === null) {
// throw the same Exception again if one was captured before
if ($this->userException !== null) {
throw $this->userException;
}
return null;
} elseif ($this->user !== false) {
return $this->user;
}
try {
if ($this->type() === 'basic') {
return $this->user = $this->currentUserFromBasicAuth();
@@ -332,7 +442,12 @@ class Auth
return $this->user = $this->currentUserFromSession($session);
}
} catch (Throwable $e) {
return $this->user = null;
$this->user = null;
// capture the Exception for future calls
$this->userException = $e;
throw $e;
}
}
}

View File

@@ -66,12 +66,12 @@ class ContentLock
}
/**
* Returns array with `locked` flag and,
* if needed, `user`, `email`, `time`, `canUnlock`
* Returns either `false` or array with `user`, `email`,
* `time` and `unlockable` keys
*
* @return array
* @return array|bool
*/
public function get(): array
public function get()
{
$data = $this->data['lock'] ?? [];
@@ -83,17 +83,14 @@ class ContentLock
$time = intval($data['time']);
return [
'locked' => true,
'user' => $user->id(),
'email' => $user->email(),
'time' => $time,
'canUnlock' => $time + $this->kirby()->option('lock.duration', 60 * 2) <= time()
'unlockable' => $time + $this->kirby()->option('lock.duration', 60 * 2) <= time()
];
}
return [
'locked' => false
];
return false;
}
/**
@@ -105,10 +102,7 @@ class ContentLock
{
$lock = $this->get();
if (
$lock['locked'] === true &&
$lock['user'] !== $this->user()->id()
) {
if ($lock !== false && $lock['user'] !== $this->user()->id()) {
return true;
}

View File

@@ -260,12 +260,19 @@ abstract class ModelWithContent extends Model
/**
* Returns the lock object for this model
*
* @return Kirby\Cms\ContentLock
* Only if a content directory exists,
* virtual pages will need to overwrite this method
*
* @return Kirby\Cms\ContentLock|null
*/
public function lock()
{
$dir = $this->contentFileDirectory();
if (is_string($dir) === true && file_exists($dir) === true) {
return new ContentLock($this);
}
}
/**
* Returns the panel icon definition

View File

@@ -420,7 +420,7 @@ class User extends ModelWithContent
/**
* Logs the user in without checking the password
*
* @param SKirby\Session\Session|array $session Session options or session object to set the user in
* @param Kirby\Session\Session|array $session Session options or session object to set the user in
* @return void
*/
public function loginPasswordless($session = null): void

View File

@@ -109,8 +109,15 @@ class Users extends Collection
continue;
}
$user = new User([
// get role information
if (file_exists($root . '/' . $userDirectory . '/index.php') === true) {
$credentials = require $root . '/' . $userDirectory . '/index.php';
}
// create user model based on role
$user = User::factory([
'id' => $userDirectory,
'model' => $credentials['role'] ?? null
] + $inject);
$users->set($user->id(), $user);

View File

@@ -247,7 +247,7 @@ class Uri
}
$uri = Server::get('REQUEST_URI');
$uri = str_replace(Server::get('HTTP_HOST'), '', $uri);
$uri = preg_replace('!^(http|https)\:\/\/' . Server::get('HTTP_HOST') . '!', '', $uri);
$uri = parse_url('http://getkirby.com' . $uri);
$url = new static(array_merge([

View File

@@ -88,7 +88,7 @@ class Image extends File
return $this->dimensions;
}
if (in_array($this->mime(), ['image/jpeg', 'image/jp2', 'image/png', 'image/gif'])) {
if (in_array($this->mime(), ['image/jpeg', 'image/jp2', 'image/png', 'image/gif', 'image/webp'])) {
return $this->dimensions = Dimensions::forImage($this->root);
}

View File

@@ -469,6 +469,9 @@ class Session
} else {
$this->needsRetransmission = true;
}
// update cache of the Sessions instance with the new token
$this->sessions->updateCache($this);
}
/**

View File

@@ -242,6 +242,18 @@ class Sessions
$this->store()->collectGarbage();
}
/**
* Updates the instance cache with a newly created
* session or a session with a regenerated token
*
* @internal
* @param Kirby\Session\Session $session Session instance to push to the cache
*/
public function updateCache(Session $session)
{
$this->cache[$session->token()] = $session;
}
/**
* Returns the auth token from the cookie
*

View File

@@ -843,6 +843,26 @@ class Collection extends Iterator implements Countable
return $collection;
}
/**
* Get sort arguments from a string
*
* @param string $sortBy
* @return array
*/
public static function sortArgs(string $sortBy): array
{
$sortArgs = Str::split($sortBy, ' ');
// fill in PHP constants
array_walk($sortArgs, function (string &$value) {
if (Str::startsWith($value, 'SORT_') === true && defined($value) === true) {
$value = constant($value);
}
});
return $sortArgs;
}
/**
* Sorts the elements by any number of fields
*

View File

@@ -76,6 +76,11 @@ class Query
*/
protected function resolve(string $query)
{
// direct key access in arrays
if (is_array($this->data) === true && array_key_exists($query, $this->data) === true) {
return $this->data[$query];
}
$parts = $this->parts($query);
$data = $this->data;
$value = null;

View File

@@ -826,7 +826,6 @@ class Str
return $string;
}
$string = trim((string)$string, $separator);
$parts = explode($separator, $string);
$out = [];

View File

@@ -3,6 +3,7 @@
namespace Kirby\Toolkit;
use Exception;
use Kirby\Http\Idn;
use Kirby\Toolkit\Str;
use ReflectionFunction;
use Throwable;
@@ -270,7 +271,20 @@ V::$validators = [
* Checks for valid email addresses
*/
'email' => function ($value): bool {
return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
if (filter_var($value, FILTER_VALIDATE_EMAIL) === false) {
try {
$parts = Str::split($value, '@');
$address = $parts[0] ?? null;
$domain = Idn::encode($parts[1] ?? '');
$email = $address . '@' . $domain;
} catch (Throwable $e) {
return false;
}
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
return true;
},
/**