Upgrade to 3.4.1
This commit is contained in:
@@ -75,14 +75,6 @@ class App
|
||||
protected $users;
|
||||
protected $visitor;
|
||||
|
||||
/**
|
||||
* List of options that shouldn't be converted
|
||||
* to a tree structure by dot syntax
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $nestIgnoreOptions = ['hooks'];
|
||||
|
||||
/**
|
||||
* Creates a new App instance
|
||||
*
|
||||
@@ -127,13 +119,8 @@ class App
|
||||
$this->extensionsFromSystem();
|
||||
$this->extensionsFromProps($props);
|
||||
$this->extensionsFromPlugins();
|
||||
$this->extensionsFromFolders();
|
||||
|
||||
// bake the options for the first time
|
||||
$this->bakeOptions();
|
||||
|
||||
// register the extensions from the normalized options
|
||||
$this->extensionsFromOptions();
|
||||
$this->extensionsFromFolders();
|
||||
|
||||
// trigger hook for use in plugins
|
||||
$this->trigger('system.loadPlugins:after');
|
||||
@@ -141,7 +128,7 @@ class App
|
||||
// execute a ready callback from the config
|
||||
$this->optionsFromReadyCallback();
|
||||
|
||||
// bake the options again with those from the ready callback
|
||||
// bake config
|
||||
$this->bakeOptions();
|
||||
}
|
||||
|
||||
@@ -238,7 +225,30 @@ class App
|
||||
*/
|
||||
protected function bakeOptions()
|
||||
{
|
||||
$this->options = A::nest($this->options, static::$nestIgnoreOptions);
|
||||
// convert the old plugin option syntax to the new one
|
||||
foreach ($this->options as $key => $value) {
|
||||
// detect option keys with the `vendor.plugin.option` format
|
||||
if (preg_match('/^([a-z0-9-]+\.[a-z0-9-]+)\.(.*)$/i', $key, $matches) === 1) {
|
||||
list(, $plugin, $option) = $matches;
|
||||
|
||||
// verify that it's really a plugin option
|
||||
if (isset(static::$plugins[str_replace('.', '/', $plugin)]) !== true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ensure that the target option array exists
|
||||
// (which it will if the plugin has any options)
|
||||
if (isset($this->options[$plugin]) !== true) {
|
||||
$this->options[$plugin] = []; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// move the option to the plugin option array
|
||||
// don't overwrite nested arrays completely but merge them
|
||||
$this->options[$plugin] = array_replace_recursive($this->options[$plugin], [$option => $value]);
|
||||
unset($this->options[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
Config::$data = $this->options;
|
||||
return $this;
|
||||
}
|
||||
|
@@ -292,19 +292,9 @@ trait AppPlugins
|
||||
protected function extendOptions(array $options, Plugin $plugin = null): array
|
||||
{
|
||||
if ($plugin !== null) {
|
||||
$prefixed = [];
|
||||
|
||||
foreach ($options as $key => $value) {
|
||||
$prefixed[$plugin->prefix() . '.' . $key] = $value;
|
||||
}
|
||||
|
||||
$options = $prefixed;
|
||||
$options = [$plugin->prefix() => $options];
|
||||
}
|
||||
|
||||
// register each option in the nesting blacklist;
|
||||
// this prevents Kirby from nesting the array keys inside each option
|
||||
static::$nestIgnoreOptions = array_merge(static::$nestIgnoreOptions, array_keys($options));
|
||||
|
||||
return $this->extensions['options'] = $this->options = A::merge($options, $this->options, A::MERGE_REPLACE);
|
||||
}
|
||||
|
||||
|
@@ -6,6 +6,7 @@ use Kirby\Data\Data;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Exception\PermissionException;
|
||||
use Kirby\Http\Idn;
|
||||
use Kirby\Http\Request\Auth\BasicAuth;
|
||||
use Kirby\Toolkit\F;
|
||||
use Throwable;
|
||||
@@ -256,6 +257,9 @@ class Auth
|
||||
*/
|
||||
public function validatePassword(string $email, string $password)
|
||||
{
|
||||
// ensure that email addresses with IDN domains are in Unicode format
|
||||
$email = Idn::decodeEmail($email);
|
||||
|
||||
// check for blocked ips
|
||||
if ($this->isBlocked($email) === true) {
|
||||
if ($this->kirby->option('debug') === true) {
|
||||
|
@@ -95,7 +95,7 @@ class LanguageRoutes
|
||||
}
|
||||
}
|
||||
|
||||
return $kirby->defaultLanguage()->router()->call($path);
|
||||
return $kirby->language()->router()->call($path);
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@@ -329,7 +329,7 @@ trait PageActions
|
||||
if ($action === 'create') {
|
||||
$argumentsAfter = ['page' => $result];
|
||||
} elseif ($action === 'duplicate') {
|
||||
$argumentsAfter = ['duplicatePage' => $result];
|
||||
$argumentsAfter = ['duplicatePage' => $result, 'originalPage' => $old];
|
||||
} elseif ($action === 'delete') {
|
||||
$argumentsAfter = ['status' => $result, 'page' => $old];
|
||||
} else {
|
||||
|
@@ -5,7 +5,6 @@ namespace Kirby\Cms;
|
||||
use Exception;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Session\Session;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
@@ -764,7 +763,7 @@ class User extends ModelWithContent
|
||||
protected function setEmail(string $email = null)
|
||||
{
|
||||
if ($email !== null) {
|
||||
$this->email = strtolower(trim($email));
|
||||
$this->email = Str::lower(trim($email));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
@@ -825,7 +824,7 @@ class User extends ModelWithContent
|
||||
*/
|
||||
protected function setRole(string $role = null)
|
||||
{
|
||||
$this->role = $role !== null ? strtolower(trim($role)) : null;
|
||||
$this->role = $role !== null ? Str::lower(trim($role)) : null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@@ -6,6 +6,7 @@ use Closure;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Exception\PermissionException;
|
||||
use Kirby\Http\Idn;
|
||||
use Kirby\Toolkit\Dir;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Toolkit\Str;
|
||||
@@ -29,7 +30,7 @@ trait UserActions
|
||||
*/
|
||||
public function changeEmail(string $email)
|
||||
{
|
||||
return $this->commit('changeEmail', ['user' => $this, 'email' => $email], function ($user, $email) {
|
||||
return $this->commit('changeEmail', ['user' => $this, 'email' => Idn::decodeEmail($email)], function ($user, $email) {
|
||||
$user = $user->clone([
|
||||
'email' => $email
|
||||
]);
|
||||
@@ -176,6 +177,10 @@ trait UserActions
|
||||
{
|
||||
$data = $props;
|
||||
|
||||
if (isset($props['email']) === true) {
|
||||
$data['email'] = Idn::decodeEmail($props['email']);
|
||||
}
|
||||
|
||||
if (isset($props['password']) === true) {
|
||||
$data['password'] = User::hashPassword($props['password']);
|
||||
}
|
||||
|
@@ -87,7 +87,7 @@ class Users extends Collection
|
||||
public function findByKey(string $key)
|
||||
{
|
||||
if (Str::contains($key, '@') === true) {
|
||||
return parent::findBy('email', strtolower($key));
|
||||
return parent::findBy('email', Str::lower($key));
|
||||
}
|
||||
|
||||
return parent::findByKey($key);
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Kirby\Email;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Kirby\Toolkit\Properties;
|
||||
use Kirby\Toolkit\V;
|
||||
@@ -23,6 +24,7 @@ class Email
|
||||
protected $attachments;
|
||||
protected $body;
|
||||
protected $bcc;
|
||||
protected $beforeSend;
|
||||
protected $cc;
|
||||
protected $from;
|
||||
protected $fromName;
|
||||
@@ -60,6 +62,11 @@ class Email
|
||||
return $this->bcc;
|
||||
}
|
||||
|
||||
public function beforeSend(): ?Closure
|
||||
{
|
||||
return $this->beforeSend;
|
||||
}
|
||||
|
||||
public function cc(): array
|
||||
{
|
||||
return $this->cc;
|
||||
@@ -159,6 +166,12 @@ class Email
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function setBeforeSend(?Closure $beforeSend = null)
|
||||
{
|
||||
$this->beforeSend = $beforeSend;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function setCc($cc = null)
|
||||
{
|
||||
$this->cc = $this->resolveEmail($cc);
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Kirby\Email;
|
||||
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use PHPMailer\PHPMailer\PHPMailer as Mailer;
|
||||
|
||||
/**
|
||||
@@ -67,6 +68,17 @@ class PHPMailer extends Email
|
||||
$mailer->Port = $this->transport()['port'] ?? null;
|
||||
}
|
||||
|
||||
// accessible phpMailer instance
|
||||
$beforeSend = $this->beforeSend();
|
||||
|
||||
if (empty($beforeSend) === false && is_a($beforeSend, 'Closure') === true) {
|
||||
$mailer = $beforeSend->call($this, $mailer) ?? $mailer;
|
||||
|
||||
if (is_a($mailer, 'PHPMailer\PHPMailer\PHPMailer') === false) {
|
||||
throw new InvalidArgumentException('"beforeSend" option return should be instance of PHPMailer\PHPMailer\PHPMailer class');
|
||||
}
|
||||
}
|
||||
|
||||
if ($debug === true) {
|
||||
return $this->isSent = true;
|
||||
}
|
||||
|
@@ -284,8 +284,9 @@ class Field extends Component
|
||||
|
||||
unset($array['model']);
|
||||
|
||||
$array['invalid'] = $this->isInvalid();
|
||||
$array['errors'] = $this->errors();
|
||||
$array['invalid'] = $this->isInvalid();
|
||||
$array['saveable'] = $this->save();
|
||||
$array['signature'] = md5(json_encode($array));
|
||||
|
||||
ksort($array);
|
||||
|
@@ -85,13 +85,22 @@ class Form
|
||||
}
|
||||
}
|
||||
|
||||
public function data($defaults = false): array
|
||||
public function content(): array
|
||||
{
|
||||
return $this->data(false, false);
|
||||
}
|
||||
|
||||
public function data($defaults = false, bool $includeNulls = true): array
|
||||
{
|
||||
$data = $this->values;
|
||||
|
||||
foreach ($this->fields as $field) {
|
||||
if ($field->save() === false || $field->unset() === true) {
|
||||
$data[$field->name()] = null;
|
||||
if ($includeNulls === true) {
|
||||
$data[$field->name()] = null;
|
||||
} else {
|
||||
unset($data[$field->name()]);
|
||||
}
|
||||
} else {
|
||||
$data[$field->name()] = $field->data($defaults);
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Kirby\Http;
|
||||
|
||||
use Kirby\Toolkit\Str;
|
||||
use TrueBV\Punycode;
|
||||
|
||||
/**
|
||||
@@ -24,4 +25,40 @@ class Idn
|
||||
{
|
||||
return (new Punycode())->encode($domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a email address to the Unicode format
|
||||
*
|
||||
* @param string $email
|
||||
* @return string
|
||||
*/
|
||||
public static function decodeEmail(string $email): string
|
||||
{
|
||||
if (Str::contains($email, 'xn--') === true) {
|
||||
$parts = Str::split($email, '@');
|
||||
$address = $parts[0];
|
||||
$domain = Idn::decode($parts[1] ?? '');
|
||||
$email = $address . '@' . $domain;
|
||||
}
|
||||
|
||||
return $email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a email address to the Punycode format
|
||||
*
|
||||
* @param string $email
|
||||
* @return string
|
||||
*/
|
||||
public static function encodeEmail(string $email): string
|
||||
{
|
||||
if (mb_detect_encoding($email, 'ASCII', true) === false) {
|
||||
$parts = Str::split($email, '@');
|
||||
$address = $parts[0];
|
||||
$domain = Idn::encode($parts[1] ?? '');
|
||||
$email = $address . '@' . $domain;
|
||||
}
|
||||
|
||||
return $email;
|
||||
}
|
||||
}
|
||||
|
@@ -177,19 +177,25 @@ class Remote
|
||||
];
|
||||
|
||||
// determine the TLS CA to use
|
||||
if (is_file($this->options['ca']) === true) {
|
||||
$this->curlopt[CURLOPT_SSL_VERIFYPEER] = true;
|
||||
$this->curlopt[CURLOPT_CAINFO] = $this->options['ca'];
|
||||
} elseif (is_dir($this->options['ca']) === true) {
|
||||
$this->curlopt[CURLOPT_SSL_VERIFYPEER] = true;
|
||||
$this->curlopt[CURLOPT_CAPATH] = $this->options['ca'];
|
||||
} elseif ($this->options['ca'] === self::CA_INTERNAL) {
|
||||
if ($this->options['ca'] === self::CA_INTERNAL) {
|
||||
$this->curlopt[CURLOPT_SSL_VERIFYPEER] = true;
|
||||
$this->curlopt[CURLOPT_CAINFO] = dirname(__DIR__, 2) . '/cacert.pem';
|
||||
} elseif ($this->options['ca'] === self::CA_SYSTEM) {
|
||||
$this->curlopt[CURLOPT_SSL_VERIFYPEER] = true;
|
||||
} elseif ($this->options['ca'] === false) {
|
||||
$this->curlopt[CURLOPT_SSL_VERIFYPEER] = false;
|
||||
} elseif (
|
||||
is_string($this->options['ca']) === true &&
|
||||
is_file($this->options['ca']) === true
|
||||
) {
|
||||
$this->curlopt[CURLOPT_SSL_VERIFYPEER] = true;
|
||||
$this->curlopt[CURLOPT_CAINFO] = $this->options['ca'];
|
||||
} elseif (
|
||||
is_string($this->options['ca']) === true &&
|
||||
is_dir($this->options['ca']) === true
|
||||
) {
|
||||
$this->curlopt[CURLOPT_SSL_VERIFYPEER] = true;
|
||||
$this->curlopt[CURLOPT_CAPATH] = $this->options['ca'];
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid "ca" option for the Remote class');
|
||||
}
|
||||
|
@@ -376,7 +376,7 @@ class Request
|
||||
/**
|
||||
* Returns the Query object
|
||||
*
|
||||
* @return \Kirby\Http\Query
|
||||
* @return \Kirby\Http\Request\Query
|
||||
*/
|
||||
public function query()
|
||||
{
|
||||
|
@@ -55,6 +55,26 @@ class Query
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the request doesn't contain query variables
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return empty($this->data) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the request contains query variables
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isNotEmpty(): bool
|
||||
{
|
||||
return empty($this->data) === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the query data array
|
||||
* back to a query string
|
||||
|
@@ -185,7 +185,7 @@ class Exif
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function isBW(): bool
|
||||
public function isBW(): ?bool
|
||||
{
|
||||
return ($this->isColor !== null) ? $this->isColor === false : null;
|
||||
}
|
||||
|
@@ -35,6 +35,30 @@ class Html extends Xml
|
||||
*/
|
||||
public static $void = '>';
|
||||
|
||||
/**
|
||||
* List of HTML tags that are considered to be self-closing
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $voidList = [
|
||||
'area',
|
||||
'base',
|
||||
'br',
|
||||
'col',
|
||||
'command',
|
||||
'embed',
|
||||
'hr',
|
||||
'img',
|
||||
'input',
|
||||
'keygen',
|
||||
'link',
|
||||
'meta',
|
||||
'param',
|
||||
'source',
|
||||
'track',
|
||||
'wbr'
|
||||
];
|
||||
|
||||
/**
|
||||
* Generic HTML tag generator
|
||||
* Can be called like `Html::p('A paragraph', ['class' => 'text'])`
|
||||
@@ -260,26 +284,7 @@ class Html extends Xml
|
||||
*/
|
||||
public static function isVoid(string $tag): bool
|
||||
{
|
||||
$void = [
|
||||
'area',
|
||||
'base',
|
||||
'br',
|
||||
'col',
|
||||
'command',
|
||||
'embed',
|
||||
'hr',
|
||||
'img',
|
||||
'input',
|
||||
'keygen',
|
||||
'link',
|
||||
'meta',
|
||||
'param',
|
||||
'source',
|
||||
'track',
|
||||
'wbr',
|
||||
];
|
||||
|
||||
return in_array(strtolower($tag), $void);
|
||||
return in_array(strtolower($tag), static::$voidList);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,8 +339,8 @@ class Html extends Xml
|
||||
* Builds an HTML tag
|
||||
*
|
||||
* @param string $name Tag name
|
||||
* @param array|string|null $content Scalar value or array with multiple lines of content or `null` to
|
||||
* generate a self-closing tag; pass an empty string to generate empty content
|
||||
* @param array|string $content Scalar value or array with multiple lines of content; self-closing
|
||||
* tags are generated automatically based on the `Html::isVoid()` list
|
||||
* @param array $attr An associative array with additional attributes for the tag
|
||||
* @param string|null $indent Indentation string, defaults to two spaces or `null` for output on one line
|
||||
* @param int $level Indentation level
|
||||
@@ -343,6 +348,12 @@ class Html extends Xml
|
||||
*/
|
||||
public static function tag(string $name, $content = '', array $attr = null, string $indent = null, int $level = 0): string
|
||||
{
|
||||
// treat an explicit `null` value as an empty tag
|
||||
// as void tags are already covered below
|
||||
if ($content === null) {
|
||||
$content = '';
|
||||
}
|
||||
|
||||
// force void elements to be self-closing
|
||||
if (static::isVoid($name) === true) {
|
||||
$content = null;
|
||||
|
@@ -306,10 +306,7 @@ V::$validators = [
|
||||
'email' => function ($value): bool {
|
||||
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;
|
||||
$email = Idn::encodeEmail($value);
|
||||
} catch (Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
|
Reference in New Issue
Block a user