Upgrade to 3.7.1

This commit is contained in:
Bastian Allgeier
2022-07-12 13:33:21 +02:00
parent 7931eb5e47
commit 1ad1eaf387
377 changed files with 63981 additions and 63824 deletions

View File

@@ -6,10 +6,19 @@
root = true root = true
[*.php] [*]
charset = utf-8 charset = utf-8
end_of_line = lf end_of_line = lf
insert_final_newline = true indent_style = tab
indent_size = 2
trim_trailing_whitespace = true trim_trailing_whitespace = true
indent_style = space
[*.php]
indent_size = 4 indent_size = 4
insert_final_newline = true
[*.yml]
indent_style = space
[*.md]
trim_trailing_whitespace = false

View File

@@ -3,7 +3,7 @@
"description": "The Kirby 3 core", "description": "The Kirby 3 core",
"license": "proprietary", "license": "proprietary",
"type": "kirby-cms", "type": "kirby-cms",
"version": "3.7.0.2", "version": "3.7.1",
"keywords": [ "keywords": [
"kirby", "kirby",
"cms", "cms",

2
kirby/composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "538cd92719a152cbecaaaec5cde21f03", "content-hash": "1d71d8053b544a0d3380bc67234ff81d",
"packages": [ "packages": [
{ {
"name": "claviska/simpleimage", "name": "claviska/simpleimage",

View File

@@ -177,7 +177,7 @@
"error.user.duplicate": "Пользователь с Email \"{email}\" уже есть", "error.user.duplicate": "Пользователь с Email \"{email}\" уже есть",
"error.user.email.invalid": "Пожалуйста, введите правильный адрес эл. почты", "error.user.email.invalid": "Пожалуйста, введите правильный адрес эл. почты",
"error.user.language.invalid": "Введите правильный язык", "error.user.language.invalid": "Введите правильный язык",
"error.user.notFound": "\u0410\u043a\u043a\u0430\u0443\u043d\u0442 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d", "error.user.notFound": "Пользователь \"{name}\" не найден",
"error.user.password.invalid": "Пожалуйста, введите правильный пароль. Он должен состоять минимум из 8 символов.", "error.user.password.invalid": "Пожалуйста, введите правильный пароль. Он должен состоять минимум из 8 символов.",
"error.user.password.notSame": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c", "error.user.password.notSame": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c",
"error.user.password.undefined": "У пользователя нет пароля", "error.user.password.undefined": "У пользователя нет пароля",
@@ -238,11 +238,11 @@
"field.blocks.delete.confirm": "Вы действительно хотите удалить этот блок?", "field.blocks.delete.confirm": "Вы действительно хотите удалить этот блок?",
"field.blocks.delete.confirm.all": "Вы действительно хотите удалить все блоки?", "field.blocks.delete.confirm.all": "Вы действительно хотите удалить все блоки?",
"field.blocks.delete.confirm.selected": "Вы действительно хотите удалить эти блоки?", "field.blocks.delete.confirm.selected": "Вы действительно хотите удалить эти блоки?",
"field.blocks.empty": "Еще нет блоков", "field.blocks.empty": "Блоков нет",
"field.blocks.fieldsets.label": "Пожалуйста, выберите тип блока…", "field.blocks.fieldsets.label": "Пожалуйста, выберите тип блока…",
"field.blocks.fieldsets.paste": "Нажмите <kbd>{{ shortcut }}</kbd> чтобы вставить/импортировать блоки из буфера памяти", "field.blocks.fieldsets.paste": "Нажмите <kbd>{{ shortcut }}</kbd> чтобы вставить/импортировать блоки из буфера памяти",
"field.blocks.gallery.name": "Галерея", "field.blocks.gallery.name": "Галерея",
"field.blocks.gallery.images.empty": "Еще нет изображений", "field.blocks.gallery.images.empty": "Изображений нет",
"field.blocks.gallery.images.label": "Изображения", "field.blocks.gallery.images.label": "Изображения",
"field.blocks.heading.level": "Уровень", "field.blocks.heading.level": "Уровень",
"field.blocks.heading.name": "Заголовок", "field.blocks.heading.name": "Заголовок",
@@ -275,17 +275,17 @@
"field.blocks.video.url.label": "Ссылка на видео", "field.blocks.video.url.label": "Ссылка на видео",
"field.blocks.video.url.placeholder": "https://youtube.com/?v=", "field.blocks.video.url.placeholder": "https://youtube.com/?v=",
"field.files.empty": "Еще не выбраны файлы", "field.files.empty": "Файлы не выбраны",
"field.layout.delete": "Удалить разметку", "field.layout.delete": "Удалить разметку",
"field.layout.delete.confirm": "Вы действительно хотите удалить эту разметку?", "field.layout.delete.confirm": "Вы действительно хотите удалить эту разметку?",
"field.layout.empty": "Еще нет строк", "field.layout.empty": "Строк нет",
"field.layout.select": "Выберите разметку", "field.layout.select": "Выберите разметку",
"field.pages.empty": "Еще не выбраны страницы", "field.pages.empty": "Страницы не выбраны",
"field.structure.delete.confirm": "Вы точно хотите удалить эту запись?", "field.structure.delete.confirm": "Вы точно хотите удалить эту запись?",
"field.structure.empty": "Еще нет записей", "field.structure.empty": "Записей нет",
"field.users.empty": "Еще нет пользователей", "field.users.empty": "Пользователей нет",
"file.blueprint": "У файла пока нет разметки. Вы можете определить новые секции и поля разметки в <strong>/site/blueprints/files/{blueprint}.yml</strong>", "file.blueprint": "У файла пока нет разметки. Вы можете определить новые секции и поля разметки в <strong>/site/blueprints/files/{blueprint}.yml</strong>",
"file.delete.confirm": "Вы точно хотите удалить файл <br><strong>{filename}</strong>?", "file.delete.confirm": "Вы точно хотите удалить файл <br><strong>{filename}</strong>?",
@@ -297,7 +297,7 @@
"hide": "Скрыть", "hide": "Скрыть",
"hour": "Час", "hour": "Час",
"import": "Импортировать", "import": "Импортировать",
"info": "Info", "info": "Информация",
"insert": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c", "insert": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c",
"insert.after": "Вставить ниже", "insert.after": "Вставить ниже",
"insert.before": "Вставить выше", "insert.before": "Вставить выше",
@@ -333,14 +333,14 @@
"languages": "Языки", "languages": "Языки",
"languages.default": "Главный язык", "languages.default": "Главный язык",
"languages.empty": "Еще нет языков", "languages.empty": "Языков нет",
"languages.secondary": "Дополнительные языки", "languages.secondary": "Дополнительные языки",
"languages.secondary.empty": "Еще нет дополнительных языков", "languages.secondary.empty": "Дополнительных языков нет",
"license": "Лицензия", "license": "Лицензия",
"license.buy": "Купить лицензию", "license.buy": "Купить лицензию",
"license.register": "Зарегистрировать", "license.register": "Зарегистрировать",
"license.manage": "Manage your licenses", "license.manage": "Управление лицензиями",
"license.register.help": "После покупки вы получили по эл. почте код лицензии. Пожалуйста скопируйте и вставьте сюда чтобы зарегистрировать.", "license.register.help": "После покупки вы получили по эл. почте код лицензии. Пожалуйста скопируйте и вставьте сюда чтобы зарегистрировать.",
"license.register.label": "Пожалуйста вставьте код лицензии", "license.register.label": "Пожалуйста вставьте код лицензии",
"license.register.success": "Спасибо за поддержку Kirby", "license.register.success": "Спасибо за поддержку Kirby",
@@ -353,7 +353,7 @@
"loading": "Загрузка", "loading": "Загрузка",
"lock.unsaved": "Несохраненные изменения", "lock.unsaved": "Несохраненные изменения",
"lock.unsaved.empty": "Больше нет несохраненных изменений", "lock.unsaved.empty": "Несохраненных изменений больше нет",
"lock.isLocked": "Несохраненные изменения пользователя <strong>{email}</strong>", "lock.isLocked": "Несохраненные изменения пользователя <strong>{email}</strong>",
"lock.file.isLocked": "В данный момент этот файл редактирует {email}, поэтому его нельзя изменить.", "lock.file.isLocked": "В данный момент этот файл редактирует {email}, поэтому его нельзя изменить.",
"lock.page.isLocked": "В данный момент эту страницу редактирует {email}, поэтому его нельзя изменить.", "lock.page.isLocked": "В данный момент эту страницу редактирует {email}, поэтому его нельзя изменить.",
@@ -406,7 +406,7 @@
"open": "Открыть", "open": "Открыть",
"open.newWindow": "Открывать в новом окне", "open.newWindow": "Открывать в новом окне",
"options": "Опции", "options": "Опции",
"options.none": "Нет параметров", "options.none": "Параметров нет",
"orientation": "Ориентация", "orientation": "Ориентация",
"orientation.landscape": "Горизонтальная", "orientation.landscape": "Горизонтальная",
@@ -424,7 +424,7 @@
"page.delete.confirm.subpages": "<strong>У этой страницы есть внутренние страницы</strong>.<br>Все внутренние страницы так же будут удалены.", "page.delete.confirm.subpages": "<strong>У этой страницы есть внутренние страницы</strong>.<br>Все внутренние страницы так же будут удалены.",
"page.delete.confirm.title": "Напишите название страницы, чтобы подтвердить", "page.delete.confirm.title": "Напишите название страницы, чтобы подтвердить",
"page.draft.create": "Создать черновик", "page.draft.create": "Создать черновик",
"page.duplicate.appendix": "Скопировать", "page.duplicate.appendix": "(копия)",
"page.duplicate.files": "Копировать файлы", "page.duplicate.files": "Копировать файлы",
"page.duplicate.pages": "Копировать страницы", "page.duplicate.pages": "Копировать страницы",
"page.sort": "Изменить позицию", "page.sort": "Изменить позицию",
@@ -437,7 +437,7 @@
"page.status.unlisted.description": "Страница доступна только по URL", "page.status.unlisted.description": "Страница доступна только по URL",
"pages": "Страницы", "pages": "Страницы",
"pages.empty": "Еще нет страниц", "pages.empty": "Страниц нет",
"pages.status.draft": "Черновики", "pages.status.draft": "Черновики",
"pages.status.listed": "Опубликовано", "pages.status.listed": "Опубликовано",
"pages.status.unlisted": "Скрытая", "pages.status.unlisted": "Скрытая",
@@ -462,7 +462,7 @@
"role.admin.description": "Администратор имеет все права", "role.admin.description": "Администратор имеет все права",
"role.admin.title": "Администратор", "role.admin.title": "Администратор",
"role.all": "Все", "role.all": "Все",
"role.empty": "Нет пользователей с такой ролью", "role.empty": "Пользователей с такой ролью нет",
"role.description.placeholder": "Без описания", "role.description.placeholder": "Без описания",
"role.nobody.description": "Эта роль применяется если у пользователя нет никаких прав", "role.nobody.description": "Эта роль применяется если у пользователя нет никаких прав",
"role.nobody.title": "Никто", "role.nobody.title": "Никто",
@@ -485,7 +485,7 @@
"slug": "Понятная ссылка", "slug": "Понятная ссылка",
"sort": "Сортировать", "sort": "Сортировать",
"stats.empty": "Нет уведомлений", "stats.empty": "Статистики нет",
"system.issues.content": "Похоже, к папке content есть несанкционированный доступ", "system.issues.content": "Похоже, к папке content есть несанкционированный доступ",
"system.issues.debug": "Включен режим отладки (debugging). Используйте его только при разработке.", "system.issues.debug": "Включен режим отладки (debugging). Используйте его только при разработке.",
"system.issues.git": "Похоже, к папке .git есть несанкционированный доступ", "system.issues.git": "Похоже, к папке .git есть несанкционированный доступ",
@@ -524,9 +524,9 @@
"translation.locale": "ru_RU", "translation.locale": "ru_RU",
"upload": "Закачать", "upload": "Закачать",
"upload.error.cantMove": "Загруженный файл не может быть перемещен", "upload.error.cantMove": "Не удается переместить загруженный файл",
"upload.error.cantWrite": "Не получилось записать файл на диск", "upload.error.cantWrite": "Не получилось записать файл на диск",
"upload.error.default": "Не получилось загрузить файл", "upload.error.default": "Не удалось загрузить файл",
"upload.error.extension": "Загрузка файла не удалась из за расширения", "upload.error.extension": "Загрузка файла не удалась из за расширения",
"upload.error.formSize": "Загруженный файл больше чем MAX_FILE_SIZE настройка в форме", "upload.error.formSize": "Загруженный файл больше чем MAX_FILE_SIZE настройка в форме",
"upload.error.iniPostSize": "Загружаемый файл больше чем post_max_size настройка в php.ini", "upload.error.iniPostSize": "Загружаемый файл больше чем post_max_size настройка в php.ini",

11
kirby/panel/cypress.config.js Executable file
View File

@@ -0,0 +1,11 @@
const { defineConfig } = require("cypress");
module.exports = defineConfig({
video: false,
e2e: {
baseUrl: "http://sandbox.test",
specPattern: "src/**/*.e2e.js",
supportFile: false
}
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1112,15 +1112,29 @@ class App
*/ */
protected function optionsFromEnvironment(array $props = []): array protected function optionsFromEnvironment(array $props = []): array
{ {
$globalUrl = $this->options['url'] ?? null;
// create the environment based on the URL setup // create the environment based on the URL setup
$this->environment = new Environment([ $this->environment = new Environment([
'allowed' => $this->options['url'] ?? null, 'allowed' => $globalUrl,
'cli' => $props['cli'] ?? null, 'cli' => $props['cli'] ?? null,
], $props['server'] ?? null); ], $props['server'] ?? null);
// merge into one clean options array // merge into one clean options array
$options = $this->environment()->options($this->root('config')); $options = $this->environment()->options($this->root('config'));
return $this->options = array_replace_recursive($this->options, $options); $this->options = array_replace_recursive($this->options, $options);
// reload the environment if the environment config has overridden
// the `url` option; this ensures that the base URL is correct
$envUrl = $this->options['url'] ?? null;
if ($envUrl !== $globalUrl) {
$this->environment->detect([
'allowed' => $envUrl,
'cli' => $props['cli'] ?? null
], $props['server'] ?? null);
}
return $this->options;
} }
/** /**

View File

@@ -886,13 +886,26 @@ trait AppPlugins
} }
$dir = $root . '/' . $dirname; $dir = $root . '/' . $dirname;
$entry = $dir . '/index.php';
if (is_dir($dir) !== true || is_file($entry) !== true) { if (is_dir($dir) !== true) {
continue; continue;
} }
$entry = $dir . '/index.php';
$script = $dir . '/index.js';
$styles = $dir . '/index.css';
if (is_file($entry) === true) {
F::loadOnce($entry); F::loadOnce($entry);
} elseif (is_file($script) === true || is_file($styles) === true) {
// if no PHP file is present but an index.js or index.css,
// register as anonymous plugin (without actual extensions)
// to be picked up by the Panel\Document class when
// rendering the Panel view
static::plugin('plugins/' . $dirname, ['root' => $dir]);
} else {
continue;
}
$loaded[] = $dir; $loaded[] = $dir;
} }

View File

@@ -255,7 +255,11 @@ class Block extends Item
$kirby = $this->parent()->kirby(); $kirby = $this->parent()->kirby();
return (string)$kirby->snippet('blocks/' . $this->type(), $this->controller(), true); return (string)$kirby->snippet('blocks/' . $this->type(), $this->controller(), true);
} catch (Throwable $e) { } catch (Throwable $e) {
if ($kirby->option('debug') === true) {
return '<p>Block error: "' . $e->getMessage() . '" in block type: "' . $this->type() . '"</p>'; return '<p>Block error: "' . $e->getMessage() . '" in block type: "' . $this->type() . '"</p>';
} }
return '';
}
} }
} }

View File

@@ -71,8 +71,9 @@ class Collection extends BaseCollection
* *
* @param string $id * @param string $id
* @param object $object * @param object $object
* @return void
*/ */
public function __set(string $id, $object) public function __set(string $id, $object): void
{ {
$this->data[$id] = $object; $this->data[$id] = $object;
} }

View File

@@ -40,9 +40,11 @@ class Structure extends Collection
* *
* @param string $id * @param string $id
* @param array|StructureObject $props * @param array|StructureObject $props
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException * @throws \Kirby\Exception\InvalidArgumentException
*/ */
public function __set(string $id, $props) public function __set(string $id, $props): void
{ {
if (is_a($props, 'Kirby\Cms\StructureObject') === true) { if (is_a($props, 'Kirby\Cms\StructureObject') === true) {
$object = $props; $object = $props;
@@ -59,6 +61,6 @@ class Structure extends Collection
]); ]);
} }
return parent::__set($object->id(), $object); parent::__set($object->id(), $object);
} }
} }

View File

@@ -146,7 +146,8 @@ abstract class Sql
'varchar' => '{{ name }} varchar(255) {{ null }} {{ default }} {{ unique }}', 'varchar' => '{{ name }} varchar(255) {{ null }} {{ default }} {{ unique }}',
'text' => '{{ name }} TEXT {{ unique }}', 'text' => '{{ name }} TEXT {{ unique }}',
'int' => '{{ name }} INT(11) UNSIGNED {{ null }} {{ default }} {{ unique }}', 'int' => '{{ name }} INT(11) UNSIGNED {{ null }} {{ default }} {{ unique }}',
'timestamp' => '{{ name }} TIMESTAMP {{ null }} {{ default }} {{ unique }}' 'timestamp' => '{{ name }} TIMESTAMP {{ null }} {{ default }} {{ unique }}',
'bool' => '{{ name }} TINYINT(1) {{ null }} {{ default }} {{ unique }}'
]; ];
} }

View File

@@ -44,7 +44,8 @@ class Sqlite extends Sql
'varchar' => '{{ name }} TEXT {{ null }} {{ default }} {{ unique }}', 'varchar' => '{{ name }} TEXT {{ null }} {{ default }} {{ unique }}',
'text' => '{{ name }} TEXT {{ null }} {{ default }} {{ unique }}', 'text' => '{{ name }} TEXT {{ null }} {{ default }} {{ unique }}',
'int' => '{{ name }} INTEGER {{ null }} {{ default }} {{ unique }}', 'int' => '{{ name }} INTEGER {{ null }} {{ default }} {{ unique }}',
'timestamp' => '{{ name }} INTEGER {{ null }} {{ default }} {{ unique }}' 'timestamp' => '{{ name }} INTEGER {{ null }} {{ default }} {{ unique }}',
'bool' => '{{ name }} INTEGER {{ null }} {{ default }} {{ unique }}'
]; ];
} }

View File

@@ -23,9 +23,9 @@ class Fields extends Collection
* *
* @param string $name * @param string $name
* @param object|array $field * @param object|array $field
* @return $this * @return void
*/ */
public function __set(string $name, $field) public function __set(string $name, $field): void
{ {
if (is_array($field) === true) { if (is_array($field) === true) {
// use the array key as name if the name is not set // use the array key as name if the name is not set
@@ -33,7 +33,7 @@ class Fields extends Collection
$field = Field::factory($field['type'], $field, $this); $field = Field::factory($field['type'], $field, $this);
} }
return parent::__set($field->name(), $field); parent::__set($field->name(), $field);
} }
/** /**

View File

@@ -326,24 +326,32 @@ class Environment
/** /**
* Sets the host name, port and protocol without configuration * Sets the host name, port and protocol without configuration
* *
* @param bool $insecure Include the `Host` and `X-Forwarded-*` headers in the search * @param bool $insecure Include the `Host`, `Forwarded` and `X-Forwarded-*` headers in the search
* @return void * @return void
*/ */
protected function detectAuto(bool $insecure = false): void protected function detectAuto(bool $insecure = false): void
{ {
// proxy server setup // proxy server setup
if ( if ($insecure === true) {
$insecure === true && $forwarded = $this->detectForwarded();
empty($this->info['HTTP_X_FORWARDED_HOST']) === false
) { $host = $forwarded['host'];
$port = $forwarded['port'];
$https = $forwarded['https'];
if ($host || $port || $https) {
$this->isBehindProxy = true; $this->isBehindProxy = true;
$this->host = $this->detectForwardedHost(); // if a port or scheme is defined but no host, assume
$this->https = $this->detectForwardedHttps(); // that the host is the same as PHP's own hostname
$this->port = $this->detectForwardedPort(); // (which is often the case with reverse proxies)
$this->host = $host ?? $this->detectHost($insecure);
$this->port = $port;
$this->https = $https;
return; return;
} }
}
// local server setup // local server setup
$this->isBehindProxy = false; $this->isBehindProxy = false;
@@ -389,6 +397,7 @@ class Environment
return true; return true;
} }
// @codeCoverageIgnoreStart
$term = getenv('TERM'); $term = getenv('TERM');
if (substr(PHP_SAPI, 0, 3) === 'cgi' && $term && $term !== 'unknown') { if (substr(PHP_SAPI, 0, 3) === 'cgi' && $term && $term !== 'unknown') {
@@ -396,10 +405,83 @@ class Environment
} }
return false; return false;
// @codeCoverageIgnoreEnd
}
/**
* Detects the host, protocol, port and client IP
* from the `Forwarded` and `X-Forwarded-*` headers
*
* @return array
*/
protected function detectForwarded(): array
{
$data = [
'for' => null,
'host' => null,
'https' => false,
'port' => null
];
// prefer the standardized `Forwarded` header if defined
$forwarded = $this->get('HTTP_FORWARDED');
if ($forwarded) {
// only use the first (outermost) proxy by using the first set of values
// before the first comma (but only a comma outside of quotes)
if (Str::contains($forwarded, ',') === true) {
$forwarded = preg_split('/"[^"]*"(*SKIP)(*F)|,/', $forwarded)[0];
}
// split into separate key=value;key=value fields by semicolon,
// but only split outside of quotes
$rawFields = preg_split('/"[^"]*"(*SKIP)(*F)|;/', $forwarded);
// split key and value into an associative array
$fields = [];
foreach ($rawFields as $field) {
$key = Str::lower(Str::before($field, '='));
$value = Str::after($field, '=');
// trim the surrounding quotes
if (Str::substr($value, 0, 1) === '"') {
$value = Str::substr($value, 1, -1);
}
$fields[$key] = $value;
}
// assemble the normalized data
if (isset($fields['host']) === true) {
$parts = $this->detectPortInHost($fields['host']);
$data['host'] = $parts['host'];
$data['port'] = $parts['port'];
}
if (isset($fields['proto']) === true) {
$data['https'] = $this->detectHttpsProtocol($fields['proto']);
}
if ($data['port'] === null && $data['https'] === true) {
$data['port'] = 443;
}
$data['for'] = $parts['for'] ?? null;
return $data;
}
// no success, try the `X-Forwarded-*` headers
$data['host'] = $this->detectForwardedHost();
$data['https'] = $this->detectForwardedHttps();
$data['port'] = $this->detectForwardedPort($data['https']);
$data['for'] = $this->get('HTTP_X_FORWARDED_FOR');
return $data;
} }
/** /**
* Detects the host name of the reverse proxy * Detects the host name of the reverse proxy
* from the `X-Forwarded-Host` header
* *
* @return string|null * @return string|null
*/ */
@@ -414,7 +496,8 @@ class Environment
} }
/** /**
* Detects the protocol of the reverse proxy * Detects the protocol of the reverse proxy from the
* `X-Forwarded-SSL` or `X-Forwarded-Proto` header
* *
* @return bool * @return bool
*/ */
@@ -432,11 +515,13 @@ class Environment
} }
/** /**
* Detects the port of the reverse proxy * Detects the port of the reverse proxy from the
* `X-Forwarded-Host` or `X-Forwarded-Port` header
* *
* @param bool $https Whether HTTPS was detected
* @return int|null * @return int|null
*/ */
protected function detectForwardedPort(): ?int protected function detectForwardedPort(bool $https): ?int
{ {
// based on forwarded port // based on forwarded port
$port = $this->get('HTTP_X_FORWARDED_PORT'); $port = $this->get('HTTP_X_FORWARDED_PORT');
@@ -451,7 +536,7 @@ class Environment
} }
// based on the detected https state // based on the detected https state
if ($this->https === true) { if ($https === true) {
return 443; return 443;
} }
@@ -789,6 +874,10 @@ class Environment
$this->get('HTTP_CLIENT_IP') $this->get('HTTP_CLIENT_IP')
]; ];
if ($this->get('HTTP_FORWARDED')) {
$ips[] = $this->detectForwarded()['for'];
}
// remove duplicates and empty ips // remove duplicates and empty ips
$ips = array_unique(array_filter($ips)); $ips = array_unique(array_filter($ips));

View File

@@ -167,8 +167,9 @@ class Uri
* *
* @param string $property * @param string $property
* @param mixed $value * @param mixed $value
* @return void
*/ */
public function __set(string $property, $value) public function __set(string $property, $value): void
{ {
if (method_exists($this, 'set' . $property) === true) { if (method_exists($this, 'set' . $property) === true) {
$this->{'set' . $property}($value); $this->{'set' . $property}($value);

View File

@@ -95,17 +95,15 @@ class Collection extends Iterator implements Countable
* *
* @param string $key string or array * @param string $key string or array
* @param mixed $value * @param mixed $value
* @return $this * @return void
*/ */
public function __set(string $key, $value) public function __set(string $key, $value): void
{ {
if ($this->caseSensitive === true) { if ($this->caseSensitive === true) {
$this->data[$key] = $value; $this->data[$key] = $value;
} else { } else {
$this->data[strtolower($key)] = $value; $this->data[strtolower($key)] = $value;
} }
return $this;
} }
/** /**

View File

@@ -141,9 +141,16 @@ class I18n
$locale ??= static::locale(); $locale ??= static::locale();
if (is_array($key) === true) { if (is_array($key) === true) {
// try to use actual locale
if (isset($key[$locale])) { if (isset($key[$locale])) {
return $key[$locale]; return $key[$locale];
} }
// try to use language code, e.g. `es` when locale is `es_ES`
$lang = Str::before($locale, '_');
if (isset($key[$lang])) {
return $key[$lang];
}
// use fallback
if (is_array($fallback)) { if (is_array($fallback)) {
return $fallback[$locale] ?? $fallback['en'] ?? reset($fallback); return $fallback[$locale] ?? $fallback['en'] ?? reset($fallback);
} }
@@ -218,6 +225,12 @@ class I18n
return static::$translations[$locale] = (static::$load)($locale); return static::$translations[$locale] = (static::$load)($locale);
} }
// try to use language code, e.g. `es` when locale is `es_ES`
$lang = Str::before($locale, '_');
if (isset(static::$translations[$lang]) === true) {
return static::$translations[$lang];
}
return static::$translations[$locale] = []; return static::$translations[$locale] = [];
} }

View File

@@ -1,8 +1,8 @@
<?php return array( <?php return array(
'root' => array( 'root' => array(
'name' => 'getkirby/cms', 'name' => 'getkirby/cms',
'pretty_version' => '3.7.0.2', 'pretty_version' => '3.7.1',
'version' => '3.7.0.2', 'version' => '3.7.1.0',
'reference' => NULL, 'reference' => NULL,
'type' => 'kirby-cms', 'type' => 'kirby-cms',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
@@ -29,8 +29,8 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'getkirby/cms' => array( 'getkirby/cms' => array(
'pretty_version' => '3.7.0.2', 'pretty_version' => '3.7.1',
'version' => '3.7.0.2', 'version' => '3.7.1.0',
'reference' => NULL, 'reference' => NULL,
'type' => 'kirby-cms', 'type' => 'kirby-cms',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',