Update Kirby and dependencies

This commit is contained in:
Paul Nicoué 2022-06-17 18:02:55 +02:00
parent 750b9cc83e
commit 8c71a258b6
59 changed files with 2143 additions and 813 deletions

View file

@ -1,6 +0,0 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

View file

@ -1,4 +0,0 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
}

View file

@ -28,7 +28,7 @@ Please post all bug reports in our [issue tracker](https://github.com/getkirby/k
If you have ideas for a feature or enhancement for Kirby, please use our [feedback platform](https://feedback.getkirby.com).
**Translations, bug fixes, code contributions ...**
Read about how to contribute to the development in our [contributing guide](/.github/CONTRIBUTING.md).
Read about how to contribute to the development in our [contributing guide](/CONTRIBUTING.md).

View file

@ -1,7 +1,7 @@
##
## Bundle of CA Root Certificates
##
## Certificate data from Mozilla as of: Fri Mar 18 12:29:51 2022 GMT
## Certificate data from Mozilla as of: Tue Apr 26 03:12:05 2022 GMT
##
## This is a bundle of X.509 certificates of public Certificate Authorities
## (CA). These were automatically extracted from Mozilla's root certificates
@ -14,7 +14,7 @@
## Just configure this file as the SSLCACertificateFile.
##
## Conversion done with mk-ca-bundle.pl version 1.29.
## SHA256: 187ef9dc231135324fe78830cf4462f1ecdeab3e6c9d5e38d623391e88dc5d3c
## SHA256: 34a54d5191775c1bd37be6cfd3f09e831e072555dc3a2e51f4a2c4b0f8ada5cc
##
@ -3279,3 +3279,69 @@ PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/C
r8deVl5c1RxYIigL9zC2L7F8AjEA8GE8p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh
4rsUecrNIdSUtUlD
-----END CERTIFICATE-----
Telia Root CA v2
================
-----BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQxCzAJBgNVBAYT
AkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2
MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQK
DBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZI
hvcNAQEBBQADggIPADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ7
6zBqAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9vVYiQJ3q
9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9lRdU2HhE8Qx3FZLgmEKn
pNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTODn3WhUidhOPFZPY5Q4L15POdslv5e2QJl
tI5c0BE0312/UqeBAMN/mUWZFdUXyApT7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW
5olWK8jjfN7j/4nlNW4o6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNr
RBH0pUPCTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6WT0E
BXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63RDolUK5X6wK0dmBR4
M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZIpEYslOqodmJHixBTB0hXbOKSTbau
BcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGjYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7W
xy+G2CQ5MB0GA1UdDgQWBBRyrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ
8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi0f6X+J8wfBj5
tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMMA8iZGok1GTzTyVR8qPAs5m4H
eW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBSSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+C
y748fdHif64W1lZYudogsYMVoe+KTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygC
QMez2P2ccGrGKMOF6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15
h2Er3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMtTy3EHD70
sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pTVmBds9hCG1xLEooc6+t9
xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAWysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQ
raVplI/owd8k+BsHMYeB2F326CjYSlKArBPuUBQemMc=
-----END CERTIFICATE-----
D-TRUST BR Root CA 1 2020
=========================
-----BEGIN CERTIFICATE-----
MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE
RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0EgMSAy
MDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNV
BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAG
ByqGSM49AgEGBSuBBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7
dPYSzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0QVK5buXu
QqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/VbNafAkl1bK6CKBrqx9t
MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu
bmV0L2NybC9kLXRydXN0X2JyX3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj
dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP
PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD
AwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFWwKrY7RjEsK70Pvom
AjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHVdWNbFJWcHwHP2NVypw87
-----END CERTIFICATE-----
D-TRUST EV Root CA 1 2020
=========================
-----BEGIN CERTIFICATE-----
MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE
RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0EgMSAy
MDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNV
BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAG
ByqGSM49AgEGBSuBBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8
ZRCC/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rDwpdhQntJ
raOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3OqQo5FD4pPfsazK2/umL
MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu
bmV0L2NybC9kLXRydXN0X2V2X3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj
dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP
PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD
AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR
AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW
-----END CERTIFICATE-----

View file

@ -3,7 +3,7 @@
"description": "The Kirby 3 core",
"license": "proprietary",
"type": "kirby-cms",
"version": "3.6.3",
"version": "3.6.6",
"keywords": [
"kirby",
"cms",
@ -31,12 +31,12 @@
"claviska/simpleimage": "3.6.5",
"filp/whoops": "2.14.5",
"getkirby/composer-installer": "^1.2.1",
"laminas/laminas-escaper": "2.9.0",
"laminas/laminas-escaper": "2.10.0",
"michelf/php-smartypants": "1.8.1",
"phpmailer/phpmailer": "6.5.4",
"psr/log": "1.1.4",
"symfony/polyfill-intl-idn": "1.24.0",
"symfony/polyfill-mbstring": "1.24.0"
"symfony/polyfill-intl-idn": "1.25.0",
"symfony/polyfill-mbstring": "1.25.0"
},
"replace": {
"symfony/polyfill-php72": "*"

36
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",
"This file is @generated automatically"
],
"content-hash": "d4cf75084dae428fe0ab54124637d51f",
"content-hash": "cb6bffc372828b6d36107d104c0b2a3e",
"packages": [
{
"name": "claviska/simpleimage",
@ -175,33 +175,33 @@
},
{
"name": "laminas/laminas-escaper",
"version": "2.9.0",
"version": "2.10.0",
"source": {
"type": "git",
"url": "https://github.com/laminas/laminas-escaper.git",
"reference": "891ad70986729e20ed2e86355fcf93c9dc238a5f"
"reference": "58af67282db37d24e584a837a94ee55b9c7552be"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/891ad70986729e20ed2e86355fcf93c9dc238a5f",
"reference": "891ad70986729e20ed2e86355fcf93c9dc238a5f",
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/58af67282db37d24e584a837a94ee55b9c7552be",
"reference": "58af67282db37d24e584a837a94ee55b9c7552be",
"shasum": ""
},
"require": {
"php": "^7.3 || ~8.0.0 || ~8.1.0"
"ext-ctype": "*",
"ext-mbstring": "*",
"php": "^7.4 || ~8.0.0 || ~8.1.0"
},
"conflict": {
"zendframework/zend-escaper": "*"
},
"require-dev": {
"infection/infection": "^0.26.6",
"laminas/laminas-coding-standard": "~2.3.0",
"phpunit/phpunit": "^9.3",
"psalm/plugin-phpunit": "^0.12.2",
"vimeo/psalm": "^3.16"
},
"suggest": {
"ext-iconv": "*",
"ext-mbstring": "*"
"maglnet/composer-require-checker": "^3.8.0",
"phpunit/phpunit": "^9.5.18",
"psalm/plugin-phpunit": "^0.16.1",
"vimeo/psalm": "^4.22.0"
},
"type": "library",
"autoload": {
@ -233,7 +233,7 @@
"type": "community_bridge"
}
],
"time": "2021-09-02T17:10:53+00:00"
"time": "2022-03-08T20:15:36+00:00"
},
{
"name": "league/color-extractor",
@ -477,7 +477,7 @@
},
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.24.0",
"version": "v1.25.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
@ -544,7 +544,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.24.0"
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.25.0"
},
"funding": [
{
@ -648,7 +648,7 @@
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.24.0",
"version": "v1.25.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
@ -711,7 +711,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.24.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0"
},
"funding": [
{

View file

@ -16,8 +16,7 @@ return [
'page.changeSort' => [
'pattern' => 'pages/(:any)/changeSort',
'load' => function (string $id) {
$page = Find::page($id);
$position = null;
$page = Find::page($id);
if ($page->blueprint()->num() !== 'default') {
throw new PermissionException([

View file

@ -5,6 +5,7 @@ fields:
images:
label: field.blocks.gallery.images.label
type: files
query: model.images
multiple: true
layout: cards
size: tiny

View file

@ -13,6 +13,7 @@ fields:
image:
label: field.blocks.image.name
type: files
query: model.images
multiple: false
image:
back: black

View file

@ -168,7 +168,7 @@ return [
* @return \Kirby\Cms\Collection|bool
*/
'search' => function (App $kirby, Collection $collection, string $query = null, $params = []) {
if (empty(trim($query)) === true) {
if (empty(trim($query ?? '')) === true) {
return $collection->limit(0);
}

View file

@ -69,14 +69,13 @@ return [
return $value;
}
$value = trim($value);
$converter = $this->converters()[$this->converter()];
if (is_array($value) === true) {
return array_map($converter, $value);
}
return call_user_func($converter, $value);
return call_user_func($converter, trim($value ?? ''));
},
'converters' => function (): array {
return [

View file

@ -112,11 +112,11 @@ return function (App $app) {
* Converts the field value to a timestamp or a formatted date
*
* @param \Kirby\Cms\Field $field
* @param string|null $format PHP date formatting string
* @param string|\IntlDateFormatter|null $format PHP date formatting string
* @param string|null $fallback Fallback string for `strtotime` (since 3.2)
* @return string|int
*/
'toDate' => function (Field $field, string $format = null, string $fallback = null) use ($app) {
'toDate' => function (Field $field, $format = null, string $fallback = null) use ($app) {
if (empty($field->value) === true && $fallback === null) {
return null;
}

View file

@ -12,7 +12,8 @@ return [
'form' => function () {
$fields = $this->fields;
$disabled = $this->model->permissions()->update() === false;
$content = $this->model->content()->toArray();
$lang = $this->model->kirby()->languageCode();
$content = $this->model->content($lang)->toArray();
if ($disabled === true) {
foreach ($fields as $key => $props) {

View file

@ -6,9 +6,16 @@ return [
'props' => [
/**
* The headline for the section. This can be a simple string or a template with additional info from the parent page.
* @todo deprecate in 3.7
*/
'headline' => function ($headline = null) {
return I18n::translate($headline, $headline);
},
/**
* label is the new official replacement for headline
*/
'label' => function ($label = null) {
return I18n::translate($label, $label);
}
],
'computed' => [
@ -17,6 +24,10 @@ return [
return $this->model()->toString($this->headline);
}
if ($this->label) {
return $this->model()->toString($this->label);
}
return ucfirst($this->name);
}
]

View file

@ -396,7 +396,7 @@
"next": "Næste",
"no": "nej",
"off": "Sluk",
"on": "Tænd",
"on": "Aktiveret",
"open": "Åben",
"open.newWindow": "Åben i et nyt vindue",
"options": "Indstillinger",

View file

@ -1,7 +1,7 @@
{
"account.changeName": "Modifier votre nom",
"account.delete": "Supprimer votre compte",
"account.delete.confirm": "Voulez-vous vraiment supprimer votre compte ? Vous serez déconnecté immédiatement. Votre compte ne pourra pas être récupéré.",
"account.delete.confirm": "Voulez-vous vraiment supprimer votre compte ? Vous serez déconnecté immédiatement. Votre compte ne pourra pas être récupéré.",
"add": "Ajouter",
"author": "Auteur",
@ -61,7 +61,7 @@
"error.avatar.dimensions.invalid": "Veuillez choisir une image de profil de largeur et hauteur inférieures à 3000 pixels",
"error.avatar.mime.forbidden": "L'image du profil utilisateur doit être un fichier JPEG ou PNG",
"error.blueprint.notFound": "Le blueprint « {name} » na pu être chargé",
"error.blueprint.notFound": "Le blueprint « {name} » na pu être chargé",
"error.blocks.max.plural": "Vous ne devez pas ajouter plus de {max} blocs",
"error.blocks.max.singular": "Vous ne devez pas ajouter plus d'un bloc",
@ -69,29 +69,29 @@
"error.blocks.min.singular": "Vous devez ajouter au moins un bloc",
"error.blocks.validation": "Il y a une erreur dans le bloc {index}",
"error.email.preset.notFound": "La configuration de courriel « {name} » na pu être trouvé",
"error.email.preset.notFound": "La configuration de courriel « {name} » na pu être trouvé ",
"error.field.converter.invalid": "Convertisseur « {converter} » incorrect",
"error.field.converter.invalid": "Convertisseur « {converter} » incorrect",
"error.file.changeName.empty": "Le nom ne peut être vide",
"error.file.changeName.permission": "Vous nêtes pas autorisé à modifier le nom de « {filename} »",
"error.file.duplicate": "Un fichier nommé « {filename} » existe déjà",
"error.file.extension.forbidden": "Lextension « {extension} » nest pas autorisée",
"error.file.changeName.permission": "Vous nêtes pas autorisé à modifier le nom de « {filename} »",
"error.file.duplicate": "Un fichier nommé « {filename} » existe déjà",
"error.file.extension.forbidden": "Lextension « {extension} » nest pas autorisée",
"error.file.extension.invalid": "Extension non valide : {extension}",
"error.file.extension.missing": "Lextension pour « {filename} » est manquante",
"error.file.extension.missing": "Lextension pour « {filename} » est manquante",
"error.file.maxheight": "La hauteur de l'image ne doit pas excéder {height} pixels",
"error.file.maxsize": "Le fichier est trop volumineux",
"error.file.maxwidth": "La largeur de l'image ne doit pas excéder {width} pixels",
"error.file.mime.differs": "Le fichier transféré doit être du même type de média « {mime} »",
"error.file.mime.forbidden": "Le type de média « {mime} » nest pas autorisé",
"error.file.mime.differs": "Le fichier transféré doit être du même type de média « {mime} »",
"error.file.mime.forbidden": "Le type de média « {mime} » nest pas autorisé",
"error.file.mime.invalid": "Type de média non valide : {mime}",
"error.file.mime.missing": "Le type de média de « {filename} » na pu être détecté",
"error.file.mime.missing": "Le type de média de « {filename} » na pu être détecté",
"error.file.minheight": "La hauteur de l'image doit être au moins {height} pixels",
"error.file.minsize": "Le fichier n'est pas assez volumineux",
"error.file.minwidth": "La largeur de l'image doit être au moins {width} pixels",
"error.file.name.missing": "Veuillez entrer un titre",
"error.file.notFound": "Le fichier « {filename} » na pu être trouvé",
"error.file.orientation": "L'orientation de l'image doit être \"{orientation}\"",
"error.file.notFound": "Le fichier « {filename} » na pu être trouvé",
"error.file.orientation": "L'orientation de l'image doit être « {orientation} »",
"error.file.type.forbidden": "Vous nêtes pas autorisé à transférer des fichiers {type}",
"error.file.type.invalid": "Type de fichier non valide : {type}",
"error.file.undefined": "Le fichier na pu être trouvé",
@ -113,43 +113,43 @@
"error.offline": "Le Panel est actuellement hors ligne",
"error.page.changeSlug.permission": "Vous nêtes pas autorisé à modifier lidentifiant dURL pour « {slug} »",
"error.page.changeSlug.permission": "Vous nêtes pas autorisé à modifier lidentifiant dURL pour « {slug} »",
"error.page.changeStatus.incomplete": "La page comporte des erreurs et ne peut pas être publiée",
"error.page.changeStatus.permission": "Le statut de cette page ne peut être modifié",
"error.page.changeStatus.toDraft.invalid": "La page « {slug} » ne peut être convertie en brouillon",
"error.page.changeTemplate.invalid": "Le modèle de la page « {slug} » ne peut être changé",
"error.page.changeTemplate.permission": "Vous nêtes pas autorisé à changer le modèle de « {slug} »",
"error.page.changeStatus.toDraft.invalid": "La page « {slug} » ne peut être convertie en brouillon",
"error.page.changeTemplate.invalid": "Le modèle de la page « {slug} » ne peut être changé",
"error.page.changeTemplate.permission": "Vous nêtes pas autorisé à changer le modèle de « {slug} »",
"error.page.changeTitle.empty": "Le titre ne peut être vide",
"error.page.changeTitle.permission": "Vous nêtes pas autorisé à modifier le titre de « {slug} »",
"error.page.create.permission": "Vous nêtes pas autorisé à créer « {slug} »",
"error.page.delete": "La page « {slug} » ne peut être supprimée",
"error.page.changeTitle.permission": "Vous nêtes pas autorisé à modifier le titre de « {slug} »",
"error.page.create.permission": "Vous nêtes pas autorisé à créer « {slug} »",
"error.page.delete": "La page « {slug} » ne peut être supprimée",
"error.page.delete.confirm": "Veuillez saisir le titre de la page pour confirmer",
"error.page.delete.hasChildren": "La page comporte des sous-pages et ne peut pas être supprimée",
"error.page.delete.permission": "Vous nêtes pas autorisé à supprimer « {slug} »",
"error.page.draft.duplicate": "Un brouillon avec lidentifiant dURL « {slug} » existe déjà",
"error.page.duplicate": "Une page avec lidentifiant dURL « {slug} » existe déjà",
"error.page.duplicate.permission": "Vous n'êtes pas autorisé à dupliquer « {slug} »",
"error.page.notFound": "La page « {slug} » na pu être trouvée",
"error.page.delete.permission": "Vous nêtes pas autorisé à supprimer « {slug} »",
"error.page.draft.duplicate": "Un brouillon avec lidentifiant dURL « {slug} » existe déjà",
"error.page.duplicate": "Une page avec lidentifiant dURL « {slug} » existe déjà",
"error.page.duplicate.permission": "Vous n'êtes pas autorisé à dupliquer « {slug} »",
"error.page.notFound": "La page « {slug} » na pu être trouvée",
"error.page.num.invalid": "Veuillez saisir un numéro de position valide. Les numéros ne doivent pas être négatifs.",
"error.page.slug.invalid": "Veuillez entrer un identifiant dURL valide",
"error.page.slug.maxlength": "Lidentifiant dURL doit faire moins de \"{length}\" caractères",
"error.page.sort.permission": "La page « {slug} » ne peut être réordonnée",
"error.page.slug.maxlength": "Lidentifiant dURL doit faire moins de « {length} » caractères",
"error.page.sort.permission": "La page « {slug} » ne peut être réordonnée",
"error.page.status.invalid": "Veuillez choisir un statut de page valide",
"error.page.undefined": "La page na pu être trouvée",
"error.page.update.permission": "Vous nêtes pas autorisé à modifier « {slug} »",
"error.page.update.permission": "Vous nêtes pas autorisé à modifier « {slug} »",
"error.section.files.max.plural": "Vous ne pouvez ajouter plus de {max} fichier(s) à la section « {section} »",
"error.section.files.max.singular": "Vous ne pouvez ajouter plus dun fichier à la section « {section} »",
"error.section.files.min.plural": "La section « {section}\" » requiert au moins {min} fichiers",
"error.section.files.min.singular": "La section « {section}\" » requiert au moins un fichier",
"error.section.files.max.plural": "Vous ne pouvez ajouter plus de {max} fichier(s) à la section « {section} »",
"error.section.files.max.singular": "Vous ne pouvez ajouter plus dun fichier à la section « {section} »",
"error.section.files.min.plural": "La section « {section} » requiert au moins {min} fichiers",
"error.section.files.min.singular": "La section « {section} » requiert au moins un fichier",
"error.section.pages.max.plural": "Vous ne pouvez ajouter plus de {max} pages à la section « {section} »",
"error.section.pages.max.singular": "Vous ne pouvez ajouter plus dune page à la section « {section} »",
"error.section.pages.min.plural": "La section « {section}\" » requiert au moins {min} pages",
"error.section.pages.min.singular": "La section « {section}\" » requiert au moins une page",
"error.section.pages.max.plural": "Vous ne pouvez ajouter plus de {max} pages à la section « {section} »",
"error.section.pages.max.singular": "Vous ne pouvez ajouter plus dune page à la section « {section} »",
"error.section.pages.min.plural": "La section « {section} » requiert au moins {min} pages",
"error.section.pages.min.singular": "La section « {section} » requiert au moins une page",
"error.section.notLoaded": "La section « {name} » na pu être chargée",
"error.section.type.invalid": "Le type de section « {type} » est incorrect",
"error.section.notLoaded": "La section « {name} » na pu être chargée",
"error.section.type.invalid": "Le type de section « {type} » est incorrect",
"error.site.changeTitle.empty": "Le titre ne peut être vide",
"error.site.changeTitle.permission": "Vous nêtes pas autorisé à modifier le titre du site",
@ -157,46 +157,46 @@
"error.template.default.notFound": "Le modèle par défaut nexiste pas",
"error.unexpected": "Une erreur inattendue est survenue ! Activez le mode de débogage pour plus d'informations : https://getkirby.com/docs/reference/system/options/debug",
"error.unexpected": "Une erreur inattendue est survenue ! Activez le mode de débogage pour plus d'informations : https://getkirby.com/docs/reference/system/options/debug",
"error.user.changeEmail.permission": "Vous nêtes pas autorisé à modifier le courriel de lutilisateur «{name}»",
"error.user.changeLanguage.permission": "Vous nêtes pas autorisé à changer la langue de lutilisateur «{name}»",
"error.user.changeName.permission": "Vous nêtes pas autorisé à modifier le nom de lutilisateur «{name}»",
"error.user.changePassword.permission": "Vous nêtes pas autorisé à changer le mot de passe de lutilisateur «{name}»",
"error.user.changeEmail.permission": "Vous nêtes pas autorisé à modifier le courriel de lutilisateur « {name} »",
"error.user.changeLanguage.permission": "Vous nêtes pas autorisé à changer la langue de lutilisateur « {name} »",
"error.user.changeName.permission": "Vous nêtes pas autorisé à modifier le nom de lutilisateur « {name} »",
"error.user.changePassword.permission": "Vous nêtes pas autorisé à changer le mot de passe de lutilisateur « {name} »",
"error.user.changeRole.lastAdmin": "Le rôle du dernier administrateur ne peut être modifié",
"error.user.changeRole.permission": "Vous nêtes pas autorisé à changer le rôle de lutilisateur «{name}»",
"error.user.changeRole.permission": "Vous nêtes pas autorisé à changer le rôle de lutilisateur « {name} »",
"error.user.changeRole.toAdmin": "Vous nêtes pas autorisé à attribuer le rôle dadministrateur aux utilisateurs",
"error.user.create.permission": "Vous nêtes pas autorisé à créer cet utilisateur",
"error.user.delete": "Lutilisateur «{name}» ne peut être supprimé",
"error.user.delete": "Lutilisateur « {name} » ne peut être supprimé",
"error.user.delete.lastAdmin": "Le dernier administrateur ne peut être supprimé",
"error.user.delete.lastUser": "Le dernier utilisateur ne peut être supprimé",
"error.user.delete.permission": "Vous nêtes pas autorisé à supprimer lutilisateur «{name}»",
"error.user.duplicate": "Un utilisateur avec le courriel «{email}» existe déjà",
"error.user.delete.permission": "Vous nêtes pas autorisé à supprimer lutilisateur « {name} »",
"error.user.duplicate": "Un utilisateur avec le courriel « {email} » existe déjà",
"error.user.email.invalid": "Veuillez saisir un courriel valide",
"error.user.language.invalid": "Veuillez saisir une langue valide",
"error.user.notFound": "Lutilisateur «{name}» na pu être trouvé",
"error.user.notFound": "Lutilisateur « {name} » na pu être trouvé",
"error.user.password.invalid": "Veuillez saisir un mot de passe valide. Les mots de passe doivent comporter au moins 8 caractères.",
"error.user.password.notSame": "Les mots de passe ne sont pas identiques",
"error.user.password.undefined": "Cet utilisateur na pas de mot de passe",
"error.user.password.wrong": "Mot de passe incorrect",
"error.user.role.invalid": "Veuillez saisir un rôle valide",
"error.user.undefined": "Lutilisateur na pu être trouvé",
"error.user.update.permission": "Vous nêtes pas autorisé à modifier lutilisateur «{name}»",
"error.user.update.permission": "Vous nêtes pas autorisé à modifier lutilisateur « {name} »",
"error.validation.accepted": "Veuillez confirmer",
"error.validation.alpha": "Veuillez saisir uniquement des caractères alphabétiques minuscules",
"error.validation.alphanum": "Veuillez ne saisir que des minuscules de a à z et des chiffres de 0 à 9",
"error.validation.between": "Veuillez saisir une valeur entre « {min} » et « {max} »",
"error.validation.between": "Veuillez saisir une valeur entre « {min} » et « {max} »",
"error.validation.boolean": "Veuillez confirmer ou refuser",
"error.validation.contains": "Veuillez saisir une valeur contenant « {needle} »",
"error.validation.contains": "Veuillez saisir une valeur contenant « {needle} »",
"error.validation.date": "Veuillez saisir une date valide",
"error.validation.date.after": "Veuillez saisir une date après {date}",
"error.validation.date.before": "Veuillez saisir une date avant {date}",
"error.validation.date.between": "Veuillez saisir une date entre {min} et {max}",
"error.validation.denied": "Veuillez refuser",
"error.validation.different": "La valeur ne doit pas être « {other} »",
"error.validation.different": "La valeur ne doit pas être « {other} »",
"error.validation.email": "Veuillez saisir un courriel valide",
"error.validation.endswith": "La valeur doit se terminer par « {end} »",
"error.validation.endswith": "La valeur doit se terminer par « {end} »",
"error.validation.filename": "Veuillez saisir un nom de fichier valide",
"error.validation.in": "Veuillez saisir lun des éléments suivants: ({in})",
"error.validation.integer": "Veuillez saisir un entier valide",
@ -210,14 +210,14 @@
"error.validation.minlength": "Veuillez saisir une valeur plus longue (min. {min} caractères)",
"error.validation.minwords": "Veuillez saisir au moins {min} mot(s)",
"error.validation.more": "Veuillez saisir une valeur supérieure à {min}",
"error.validation.notcontains": "Veuillez saisir une valeur ne contenant pas « {needle} »",
"error.validation.notcontains": "Veuillez saisir une valeur ne contenant pas « {needle} »",
"error.validation.notin": "Veuillez ne saisir aucun des éléments suivants: ({notIn})",
"error.validation.option": "Veuillez sélectionner une option valide",
"error.validation.num": "Veuillez saisir un nombre valide",
"error.validation.required": "Veuillez saisir quelque chose",
"error.validation.same": "Veuillez saisir « {other} »",
"error.validation.size": "La grandeur de la valeur doit être « {size} »",
"error.validation.startswith": "La valeur doit commencer par « {start} »",
"error.validation.same": "Veuillez saisir « {other} »",
"error.validation.size": "La grandeur de la valeur doit être « {size} »",
"error.validation.startswith": "La valeur doit commencer par « {start} »",
"error.validation.time": "Veuillez saisir une heure valide",
"error.validation.time.after": "Veuillez entrer une heure après {time}",
"error.validation.time.before": "Veuillez entrer une heure avant {time}",
@ -232,9 +232,9 @@
"field.blocks.code.name": "Code",
"field.blocks.code.language": "Langue",
"field.blocks.code.placeholder": "Votre code…",
"field.blocks.delete.confirm": "Voulez-vous vraiment supprimer ce bloc ?",
"field.blocks.delete.confirm.all": "Voulez-vous vraiment supprimer tous les blocs ?",
"field.blocks.delete.confirm.selected": "Voulez-vous vraiment supprimer les blocs sélectionnés ?",
"field.blocks.delete.confirm": "Voulez-vous vraiment supprimer ce bloc ?",
"field.blocks.delete.confirm.all": "Voulez-vous vraiment supprimer tous les blocs ?",
"field.blocks.delete.confirm.selected": "Voulez-vous vraiment supprimer les blocs sélectionnés ?",
"field.blocks.empty": "Pas encore de blocs",
"field.blocks.fieldsets.label": "Veuillez sélectionner un type de bloc…",
"field.blocks.fieldsets.paste": "Presser <kbd>{{ shortcut }}</kbd> pour coller/importer des blocks depuis votre presse-papier",
@ -275,17 +275,17 @@
"field.files.empty": "Pas encore de fichier sélectionné",
"field.layout.delete": "Supprimer cette disposition",
"field.layout.delete.confirm": "Voulez-vous vraiment supprimer cette disposition ?",
"field.layout.delete.confirm": "Voulez-vous vraiment supprimer cette disposition ?",
"field.layout.empty": "Pas encore de rangées",
"field.layout.select": "Choisir une disposition",
"field.pages.empty": "Pas encore de page sélectionnée",
"field.structure.delete.confirm": "Voulez-vous vraiment supprimer cette ligne?",
"field.structure.delete.confirm": "Voulez-vous vraiment supprimer cette ligne ?",
"field.structure.empty": "Pas encore dentrée",
"field.users.empty": "Pas encore dutilisateur sélectionné",
"file.blueprint": "Ce fichier na pas encore de blueprint. Vous pouvez en définir les paramètres dans <strong>/site/blueprints/files/{blueprint}.yml</strong>",
"file.delete.confirm": "Voulez-vous vraiment supprimer <br><strong>{filename}</strong>&nbsp;?",
"file.delete.confirm": "Voulez-vous vraiment supprimer <br><strong>{filename}</strong> ?",
"file.sort": "Modifier la position",
"files": "Fichiers",
@ -315,9 +315,9 @@
"language": "Langue",
"language.code": "Code",
"language.convert": "Choisir comme langue par défaut",
"language.convert.confirm": "<p>Souhaitez-vous vraiment convertir <strong>{name}</strong> vers la langue par défaut&nbsp;? Cette action ne peut pas être annulée.</p><p>Si <strong>{name}</strong> a un contenu non traduit, il ny aura plus de solution de secours possible et certaines parties de votre site pourraient être vides.</p>",
"language.convert.confirm": "<p>Souhaitez-vous vraiment convertir <strong>{name}</strong> vers la langue par défaut ? Cette action ne peut pas être annulée.</p><p>Si <strong>{name}</strong> a un contenu non traduit, il ny aura plus de solution de secours possible et certaines parties de votre site pourraient être vides.</p>",
"language.create": "Ajouter une nouvelle langue",
"language.delete.confirm": "Voulez-vous vraiment supprimer la langue <strong>{name}</strong>, ainsi que toutes ses traductions&nbsp;? Cette action ne peut être annulée&nbsp;!",
"language.delete.confirm": "Voulez-vous vraiment supprimer la langue <strong>{name}</strong>, ainsi que toutes ses traductions ? Cette action ne peut être annulée !",
"language.deleted": "La langue a été supprimée",
"language.direction": "Sens de lecture",
"language.direction.ltr": "De gauche à droite",
@ -367,7 +367,7 @@
"login.reset": "Réinitialiser le mot de passe",
"login.toggleText.code.email": "Se connecter par courriel",
"login.toggleText.code.email-password": "Se connecter avec un mot de passe",
"login.toggleText.password-reset.email": "Mot de passe oublié ?",
"login.toggleText.password-reset.email": "Mot de passe oublié ?",
"login.toggleText.password-reset.email-password": "← Retour à la connexion",
"logout": "Se déconnecter",
@ -414,7 +414,7 @@
"page.changeStatus.position": "Veuillez sélectionner une position",
"page.changeStatus.select": "Sélectionner un nouveau statut",
"page.changeTemplate": "Changer de modèle",
"page.delete.confirm": "Voulez-vous vraiment supprimer <strong>{title}</strong>&nbsp;?",
"page.delete.confirm": "Voulez-vous vraiment supprimer <strong>{title}</strong> ?",
"page.delete.confirm.subpages": "<strong>Cette page contient des sous-pages</strong>. <br>Toutes les sous-pages seront également supprimées.",
"page.delete.confirm.title": "Veuillez saisir le titre de la page pour confirmer",
"page.draft.create": "Créer un brouillon",
@ -450,7 +450,7 @@
"replace": "Remplacer",
"retry": "Essayer à nouveau",
"revert": "Revenir",
"revert.confirm": "Voulez-vous vraiment <strong>supprimer toutes les modifications non-enregistrées</strong> ?",
"revert.confirm": "Voulez-vous vraiment <strong>supprimer toutes les modifications non-enregistrées</strong> ?",
"role": "Rôle",
"role.admin.description": "Ladministrateur dispose de tous les droits",
@ -539,7 +539,7 @@
"user.changeRole.select": "Sélectionner un nouveau rôle",
"user.create": "Ajouter un nouvel utilisateur",
"user.delete": "Supprimer cet utilisateur",
"user.delete.confirm": "Voulez-vous vraiment supprimer <br><strong>{email}</strong>?",
"user.delete.confirm": "Voulez-vous vraiment supprimer <br><strong>{email}</strong> ?",
"users": "Utilisateurs",

View file

@ -157,7 +157,7 @@
"error.template.default.notFound": "Het standaard template bestaat niet",
"error.unexpected": "An unexpected error occurred! Enable debug mode for more info: https://getkirby.com/docs/reference/system/options/debug",
"error.unexpected": "Een onverwacht fout heeft plaats gevonden! Schakel debug-modus in voor meer informatie: https://getkirby.com/docs/reference/system/options/debug",
"error.user.changeEmail.permission": "Je hebt geen rechten om het e-mailadres van gebruiker \"{name}\" te wijzigen",
"error.user.changeLanguage.permission": "Je hebt geen rechten om de taal voor gebruiker \"{name}\" te wijzigen",

View file

@ -1,22 +0,0 @@
module.exports = {
extends: [
"eslint:recommended",
"plugin:cypress/recommended",
"plugin:vue/recommended",
"prettier"
],
rules: {
"vue/attributes-order": "error",
"vue/component-definition-name-casing": "off",
"vue/html-closing-bracket-newline": [
"error",
{
singleline: "never",
multiline: "always"
}
],
"vue/multi-word-component-names": "off",
"vue/require-default-prop": "off",
"vue/require-prop-types": "error"
}
};

View file

@ -1,3 +0,0 @@
{
"trailingComma": "none"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,115 +0,0 @@
/* eslint-env node */
import fs from "fs";
import path from "path";
import { defineConfig } from "vite";
import { createVuePlugin } from "vite-plugin-vue2";
import postcssAutoprefixer from "autoprefixer";
import postcssCsso from "postcss-csso";
import postcssDirPseudoClass from "postcss-dir-pseudo-class";
import postcssLogical from "postcss-logical";
import pluginRewriteAll from "vite-plugin-rewrite-all";
let custom;
try {
custom = require("./vite.config.custom.js");
} catch (err) {
custom = {};
}
export default defineConfig(({ command }) => {
// Tell Kirby that we are in dev mode
if (command === "serve") {
// Create the flag file on start
const runningPath = __dirname + "/.vite-running";
fs.closeSync(fs.openSync(runningPath, "w"));
// Delete the flag file on any kind of exit
for (const eventType of ["exit", "SIGINT", "uncaughtException"]) {
process.on(eventType, function (err) {
if (fs.existsSync(runningPath) === true) {
fs.unlinkSync(runningPath);
}
if (eventType === "uncaughtException") {
console.error(err);
}
process.exit();
});
}
}
const proxy = {
target: process.env.VUE_APP_DEV_SERVER || "http://sandbox.test",
changeOrigin: true,
secure: false
};
return {
plugins: [createVuePlugin(), pluginRewriteAll()],
define: {
// Fix vuelidate error
"process.env.BUILD": JSON.stringify("production")
},
build: {
minify: "terser",
cssCodeSplit: false,
rollupOptions: {
input: "./src/index.js",
output: {
entryFileNames: "js/[name].js",
chunkFileNames: "js/[name].js",
assetFileNames: "[ext]/[name].[ext]"
}
}
},
optimizeDeps: {
entries: "src/**/*.{js,vue}",
exclude: [
"vitest"
]
},
css: {
postcss: {
plugins: [
postcssLogical(),
postcssDirPseudoClass(),
postcssCsso(),
postcssAutoprefixer()
]
}
},
resolve: {
alias: [
{
find: "vue",
replacement: "vue/dist/vue.esm.js"
},
{
find: "@",
replacement: path.resolve(__dirname, "src")
}
]
},
server: {
proxy: {
"/api": proxy,
"/env": proxy,
"/media": proxy
},
...custom
},
test: {
environment: "jsdom",
include: ["**/*.test.js"],
coverage: {
all: true,
exclude: ["**/*.e2e.js", "**/*.test.js"],
extension: ["js", "vue"],
src: "src",
reporter: ["text", "lcov"]
},
setupFiles: ["vitest.setup.js"]
}
};
});

View file

@ -1,4 +0,0 @@
import Vue from "vue";
Vue.config.productionTip = false;
Vue.config.devtools = false;

View file

@ -10,6 +10,7 @@ use Kirby\Exception\NotFoundException;
use Kirby\Filesystem\Dir;
use Kirby\Filesystem\F;
use Kirby\Http\Request;
use Kirby\Http\Response;
use Kirby\Http\Router;
use Kirby\Http\Server;
use Kirby\Http\Uri;
@ -709,14 +710,25 @@ class App
return $this->io(new NotFoundException());
}
// Response Configuration
// (Modified) global response configuration, e.g. in routes
if (is_a($input, 'Kirby\Cms\Responder') === true) {
// return the passed object unmodified (without injecting headers
// from the global object) to allow a complete response override
// https://github.com/getkirby/kirby/pull/4144#issuecomment-1034766726
return $input->send();
}
// Responses
if (is_a($input, 'Kirby\Http\Response') === true) {
return $input;
$data = $input->toArray();
// inject headers from the global response configuration
// lazily (only if they are not already set);
// the case-insensitive nature of headers will be
// handled by PHP's `header()` function
$data['headers'] = array_merge($response->headers(), $data['headers']);
return new Response($data);
}
// Pages

View file

@ -203,12 +203,14 @@ class Blueprint
return $props;
}
try {
$mixin = static::find($extends);
$mixin = static::extend($mixin);
$props = A::merge($mixin, $props, A::MERGE_REPLACE);
} catch (Exception $e) {
// keep the props unextended if the snippet wasn't found
foreach (A::wrap($extends) as $extend) {
try {
$mixin = static::find($extend);
$mixin = static::extend($mixin);
$props = A::merge($mixin, $props, A::MERGE_REPLACE);
} catch (Exception $e) {
// keep the props unextended if the snippet wasn't found
}
}
// remove the extends flag
@ -287,13 +289,16 @@ class Blueprint
$file = $kirby->extension('blueprints', $name);
}
// callback option can be return array or blueprint file path
if (is_callable($file) === true) {
$file = $file($kirby);
}
// now ensure that we always return the data array
if (is_string($file) === true && F::exists($file) === true) {
return static::$loaded[$name] = Data::read($file);
} elseif (is_array($file) === true) {
return static::$loaded[$name] = $file;
} elseif (is_callable($file) === true) {
return static::$loaded[$name] = $file($kirby);
}
// neither a valid file nor array data

View file

@ -54,7 +54,7 @@ class Fieldset extends Item
$this->editable = $params['editable'] ?? true;
$this->icon = $params['icon'] ?? null;
$this->model = $this->parent;
$this->name = $this->createName($params['name'] ?? Str::ucfirst($this->type));
$this->name = $this->createName($params['title'] ?? $params['name'] ?? Str::ucfirst($this->type));
$this->label = $this->createLabel($params['label'] ?? null);
$this->preview = $params['preview'] ?? null;
$this->tabs = $this->createTabs($params);

View file

@ -353,12 +353,12 @@ class File extends ModelWithContent
/**
* Get the file's last modification time.
*
* @param string|null $format
* @param string|null $handler date or strftime
* @param string|\IntlDateFormatter|null $format
* @param string|null $handler date, intl or strftime
* @param string|null $languageCode
* @return mixed
*/
public function modified(string $format = null, string $handler = null, string $languageCode = null)
public function modified($format = null, string $handler = null, string $languageCode = null)
{
$file = $this->modifiedFile();
$content = $this->modifiedContent($languageCode);

View file

@ -80,15 +80,30 @@ class FileRules
*/
public static function create(File $file, BaseFile $upload): bool
{
// We want to ensure that we are not creating duplicate files.
// If a file with the same name already exists
if ($file->exists() === true) {
if ($file->sha1() !== $upload->sha1()) {
throw new DuplicateException([
'key' => 'file.duplicate',
'data' => [
'filename' => $file->filename()
]
]);
// $file will be based on the props of the new file,
// to compare templates, we need to get the props of
// the already existing file from meta content file
$existing = $file->parent()->file($file->filename());
// if the new upload is the exact same file
// and uses the same template, we can continue
if (
$file->sha1() === $upload->sha1() &&
$file->template() === $existing->template()
) {
return true;
}
// otherwise throw an error for duplicate file
throw new DuplicateException([
'key' => 'file.duplicate',
'data' => [
'filename' => $file->filename()
]
]);
}
if ($file->permissions()->create() !== true) {

View file

@ -347,6 +347,7 @@ abstract class ModelWithContent extends Model
$result = Str::query($query, [
'kirby' => $this->kirby(),
'site' => is_a($this, 'Kirby\Cms\Site') ? $this : $this->site(),
'model' => $this,
static::CLASS_ALIAS => $this
]);
} catch (Throwable $e) {

View file

@ -25,7 +25,7 @@ class PageBlueprint extends Blueprint
// normalize all available page options
$this->props['options'] = $this->normalizeOptions(
$props['options'] ?? true,
$this->props['options'] ?? true,
// defaults
[
'changeSlug' => null,
@ -50,10 +50,10 @@ class PageBlueprint extends Blueprint
);
// normalize the ordering number
$this->props['num'] = $this->normalizeNum($props['num'] ?? 'default');
$this->props['num'] = $this->normalizeNum($this->props['num'] ?? 'default');
// normalize the available status array
$this->props['status'] = $this->normalizeStatus($props['status'] ?? null);
$this->props['status'] = $this->normalizeStatus($this->props['status'] ?? null);
}
/**

View file

@ -97,14 +97,20 @@ class Roles extends Collection
*/
public static function load(string $root = null, array $inject = [])
{
$kirby = App::instance();
$roles = new static();
// load roles from plugins
foreach (App::instance()->extensions('blueprints') as $blueprintName => $blueprint) {
foreach ($kirby->extensions('blueprints') as $blueprintName => $blueprint) {
if (substr($blueprintName, 0, 6) !== 'users/') {
continue;
}
// callback option can be return array or blueprint file path
if (is_callable($blueprint) === true) {
$blueprint = $blueprint($kirby);
}
if (is_array($blueprint) === true) {
$role = Role::factory($blueprint, $inject);
} else {

View file

@ -26,7 +26,7 @@ class SiteBlueprint extends Blueprint
// normalize all available page options
$this->props['options'] = $this->normalizeOptions(
$props['options'] ?? true,
$this->props['options'] ?? true,
// defaults
[
'changeTitle' => null,

View file

@ -445,7 +445,7 @@ class System
}
// @codeCoverageIgnoreStart
$response = Remote::get('https://licenses.getkirby.com/register', [
$response = Remote::get('https://hub.getkirby.com/register', [
'data' => [
'license' => $license,
'email' => Str::lower(trim($email)),

View file

@ -30,7 +30,7 @@ class UserBlueprint extends Blueprint
// normalize all available page options
$this->props['options'] = $this->normalizeOptions(
$props['options'] ?? true,
$this->props['options'] ?? true,
// defaults
[
'create' => null,

View file

@ -775,7 +775,11 @@ class Query
// apply it to the dataset and retrieve all rows. make sure to use Collection as the iterator to be able to attach the pagination object
$iterator = $this->iterator;
$collection = $this->offset($pagination->offset())->limit($pagination->limit())->iterator('Collection')->all();
$collection = $this
->offset($pagination->offset())
->limit($pagination->limit())
->iterator('Kirby\Toolkit\Collection')
->all();
$this->iterator($iterator);
@ -968,6 +972,11 @@ class Query
$this->bindings($sql['bindings']);
} elseif (is_callable($args[0]) === true) {
$query = clone $this;
// since the callback uses its own where condition
// it is necessary to clear/reset the cloned where condition
$query->where = null;
call_user_func($args[0], $query);
// copy over the bindings from the nested query

View file

@ -853,8 +853,14 @@ abstract class Sql
$query = [];
$bindings = [];
foreach ($values as $key => $value) {
$fields[] = $this->columnName($table, $key, $enforceQualified);
foreach ($values as $column => $value) {
$key = $this->columnName($table, $column, $enforceQualified);
if ($key === null) {
continue;
}
$fields[] = $key;
if (in_array($value, static::$literals, true) === true) {
$query[] = $value ?: 'null';
@ -896,6 +902,10 @@ abstract class Sql
foreach ($values as $column => $value) {
$key = $this->columnName($table, $column, $enforceQualified);
if ($key === null) {
continue;
}
if (in_array($value, static::$literals, true) === true) {
$query[] = $key . ' = ' . ($value ?: 'null');
continue;

View file

@ -137,7 +137,7 @@ class Sqlite extends Sql
public function tables(): array
{
return [
'query' => 'SELECT name FROM sqlite_master WHERE type = "table"',
'query' => 'SELECT name FROM sqlite_master WHERE type = "table" OR type = "view"',
'bindings' => []
];
}

View file

@ -463,11 +463,11 @@ class F
* Get the file's last modification time.
*
* @param string $file
* @param string $format
* @param string $handler date or strftime
* @param string|\IntlDateFormatter|null $format
* @param string $handler date, intl or strftime
* @return mixed
*/
public static function modified(string $file, string $format = null, string $handler = 'date')
public static function modified(string $file, $format = null, string $handler = 'date')
{
if (file_exists($file) !== true) {
return false;

View file

@ -372,11 +372,11 @@ class File
/**
* Returns the file's last modification time
*
* @param string $format
* @param string|null $handler date or strftime
* @param string|\IntlDateFormatter|null $format
* @param string|null $handler date, intl or strftime
* @return mixed
*/
public function modified(?string $format = null, ?string $handler = null)
public function modified($format = null, ?string $handler = null)
{
$kirby = $this->kirby();

View file

@ -268,7 +268,14 @@ class Server
public static function requestUri(): array
{
$uri = static::get('REQUEST_URI', '');
$uri = parse_url($uri);
if (Url::isAbsolute($uri) === true) {
$uri = parse_url($uri);
} else {
// the fake domain is needed to make sure the URL parsing is
// always correct. Even if there's a colon in the path for params
$uri = parse_url('http://getkirby.com' . $uri);
}
return [
'path' => $uri['path'] ?? null,

View file

@ -130,7 +130,7 @@ class Uri
* Creates a new URI object
*
* @param array|string $props
* @param array $inject
* @param array $inject Additional props to inject if a URL string is passed
*/
public function __construct($props = [], array $inject = [])
{
@ -144,10 +144,7 @@ class Uri
// parse the path and extract params
if (empty($props['path']) === false) {
$extract = Params::extract($props['path']);
$props['params'] ??= $extract['params'];
$props['path'] = $extract['path'];
$props['slash'] ??= $extract['slash'];
$props = static::parsePath($props);
}
$this->setProperties($this->props = $props);
@ -372,11 +369,17 @@ class Uri
}
/**
* @param \Kirby\Http\Params|string|array|null $params
* @param \Kirby\Http\Params|string|array|false|null $params
* @return $this
*/
public function setParams($params = null)
{
// ensure that the special constructor value of `false`
// is never passed through as it's not supported by `Params`
if ($params === false) {
$params = [];
}
$this->params = is_a($params, 'Kirby\Http\Params') === true ? $params : new Params($params);
return $this;
}
@ -539,4 +542,33 @@ class Uri
}
return $this;
}
/**
* Parses the path inside the props and extracts
* the params unless disabled
*
* @param array $props
* @return array Modified props array
*/
protected static function parsePath(array $props): array
{
// extract params, the rest is the path;
// only do this if not explicitly disabled (set to `false`)
if (isset($props['params']) === false || $props['params'] !== false) {
$extract = Params::extract($props['path']);
$props['params'] ??= $extract['params'];
$props['path'] = $extract['path'];
$props['slash'] ??= $extract['slash'];
return $props;
}
// use the full path;
// automatically detect the trailing slash from it if possible
if (is_string($props['path']) === true) {
$props['slash'] = substr($props['path'], -1, 1) === '/';
}
return $props;
}
}

View file

@ -16,6 +16,11 @@ use Throwable;
*/
class File extends Model
{
/**
* @var \Kirby\Cms\File
*/
protected $model;
/**
* Breadcrumb array
*
@ -423,11 +428,11 @@ class File extends Model
return [
'next' => function () use ($file, $siblings): ?array {
$next = $siblings->nth($siblings->indexOf($file) + 1);
return $next ? $next->panel()->toLink('filename') : null;
return $this->toPrevNextLink($next, 'filename');
},
'prev' => function () use ($file, $siblings): ?array {
$prev = $siblings->nth($siblings->indexOf($file) - 1);
return $prev ? $prev->panel()->toLink('filename') : null;
return $this->toPrevNextLink($prev, 'filename');
}
];
}

View file

@ -3,6 +3,7 @@
namespace Kirby\Panel;
use Kirby\Form\Form;
use Kirby\Http\Uri;
use Kirby\Toolkit\A;
/**
@ -387,6 +388,36 @@ abstract class Model
];
}
/**
* Returns link url and tooltip
* for optional sibling model and
* preserves tab selection
*
* @internal
*
* @param \Kirby\Cms\ModelWithContent|null $model
* @param string $tooltip
* @return array
*/
protected function toPrevNextLink($model = null, string $tooltip = 'title'): ?array
{
if ($model === null) {
return null;
}
$data = $model->panel()->toLink($tooltip);
if ($tab = get('tab')) {
$uri = new Uri($data['link'], [
'query' => ['tab' => $tab]
]);
$data['link'] = $uri->toString();
}
return $data;
}
/**
* Returns the url to the editing view
* in the Panel

View file

@ -14,6 +14,11 @@ namespace Kirby\Panel;
*/
class Page extends Model
{
/**
* @var \Kirby\Cms\Page
*/
protected $model;
/**
* Breadcrumb array
*
@ -309,14 +314,8 @@ class Page extends Model
};
return [
'next' => function () use ($siblings) {
$next = $siblings('next')->first();
return $next ? $next->panel()->toLink('title') : null;
},
'prev' => function () use ($siblings) {
$prev = $siblings('prev')->last();
return $prev ? $prev->panel()->toLink('title') : null;
}
'next' => fn () => $this->toPrevNextLink($siblings('next')->first()),
'prev' => fn () => $this->toPrevNextLink($siblings('prev')->last())
];
}

View file

@ -14,6 +14,11 @@ namespace Kirby\Panel;
*/
class Site extends Model
{
/**
* @var \Kirby\Cms\Site
*/
protected $model;
/**
* Returns the setup for a dropdown option
* which is used in the changes dropdown

View file

@ -14,6 +14,11 @@ namespace Kirby\Panel;
*/
class User extends Model
{
/**
* @var \Kirby\Cms\User
*/
protected $model;
/**
* Breadcrumb array
*
@ -193,14 +198,8 @@ class User extends Model
$user = $this->model;
return [
'next' => function () use ($user) {
$next = $user->next();
return $next ? $next->panel()->toLink('username') : null;
},
'prev' => function () use ($user) {
$prev = $user->prev();
return $prev ? $prev->panel()->toLink('username') : null;
}
'next' => fn () => $this->toPrevNextLink($user->next(), 'username'),
'prev' => fn () => $this->toPrevNextLink($user->prev(), 'username')
];
}

View file

@ -335,6 +335,29 @@ class A
return array_pop($array);
}
/**
* Returns a number of random elements from an array,
* either in original or shuffled order
*
* @param array $array
* @param int $count
* @param bool $shuffle
* @return array
*/
public static function random(array $array, int $count = 1, bool $shuffle = false): array
{
if ($shuffle) {
return array_slice(self::shuffle($array), 0, $count);
}
if ($count === 1) {
$key = array_rand($array);
return [$key => $array[$key]];
}
return self::get($array, array_rand($array, $count));
}
/**
* Fills an array up with additional elements to certain amount.
*
@ -726,4 +749,37 @@ class A
return $array;
}
}
/**
* Filter the array using the given callback
* using both value and key
* @since 3.6.5
*
* @param array $array
* @param callable $callback
* @return array
*/
public static function filter(array $array, callable $callback): array
{
return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
}
/**
* Remove key(s) from an array
* @since 3.6.5
*
* @param array $array
* @param int|string|array $keys
* @return array
*/
public static function without(array $array, $keys): array
{
if (is_int($keys) || is_string($keys)) {
$keys = static::wrap($keys);
}
return static::filter($array, function ($value, $key) use ($keys) {
return in_array($key, $keys, true) === false;
});
}
}

View file

@ -870,6 +870,25 @@ class Collection extends Iterator implements Countable
return $result;
}
/**
* Returns a new collection consisting of random elements,
* from the original collection, shuffled or ordered
*
* @param int $count
* @param bool $shuffle
* @return static
*/
public function random(int $count = 1, bool $shuffle = false)
{
if ($shuffle) {
return $this->shuffle()->slice(0, $count);
}
$collection = clone $this;
$collection->data = A::random($collection->data, $count);
return $collection;
}
/**
* Removes an element from the array by key
*

View file

@ -500,11 +500,7 @@ class Html extends Xml
($attr['allowfullscreen'] ?? true) === true
) {
$attr['allow'] = 'fullscreen';
}
// remove deprecated attribute
if (isset($attr['allowfullscreen']) === true) {
unset($attr['allowfullscreen']);
$attr['allowfullscreen'] = true;
}
return $attr;

View file

@ -2,6 +2,7 @@
namespace Kirby\Toolkit;
use Kirby\Exception\InvalidArgumentException;
use stdClass;
/**
@ -62,14 +63,31 @@ class Obj extends stdClass
}
/**
* Property Getter
* Gets one or multiple properties of the object
*
* @param string $property
* @param mixed $fallback
* @param string|array $property
* @param mixed $fallback If multiple properties are requested:
* Associative array of fallback values per key
* @return mixed
*/
public function get(string $property, $fallback = null)
public function get($property, $fallback = null)
{
if (is_array($property)) {
if ($fallback === null) {
$fallback = [];
}
if (!is_array($fallback)) {
throw new InvalidArgumentException('The fallback value must be an array when getting multiple properties');
}
$result = [];
foreach ($property as $key) {
$result[$key] = $this->$key ?? $fallback[$key] ?? null;
}
return $result;
}
return $this->$property ?? $fallback;
}

View file

@ -75,7 +75,12 @@ class Pagination
$params = [];
if (is_array($a) === true) {
if (is_a($a, static::class) === true) {
/**
* First argument is a pagination/self object
*/
return $a;
} elseif (is_array($a) === true) {
/**
* First argument is an option array

View file

@ -2,7 +2,9 @@
namespace Kirby\Toolkit;
use DateTime;
use Exception;
use IntlDateFormatter;
use Kirby\Exception\InvalidArgumentException;
/**
@ -264,17 +266,33 @@ class Str
* according to locale settings
*
* @param int|null $time
* @param string|null $format
* @param string $handler date or strftime
* @param string|\IntlDateFormatter|null $format
* @param string $handler date, intl or strftime
* @return string|int
*/
public static function date(?int $time = null, ?string $format = null, string $handler = 'date')
public static function date(?int $time = null, $format = null, string $handler = 'date')
{
if (is_null($format) === true) {
return $time;
}
// separately handle strftime to be able
// $format is an IntlDateFormatter instance
if (is_a($format, 'IntlDateFormatter') === true) {
return $format->format($time ?? time());
}
// `intl` handler
if ($handler === 'intl') {
$datetime = new DateTime();
if ($time !== null) {
$datetime->setTimestamp($time);
}
return IntlDateFormatter::formatObject($datetime, $format);
}
// handle `strftime` to be able
// to suppress deprecation warning
// TODO: remove strftime support for PHP 9.0
if ($handler === 'strftime') {

View file

@ -178,36 +178,36 @@
},
{
"name": "laminas/laminas-escaper",
"version": "2.9.0",
"version_normalized": "2.9.0.0",
"version": "2.10.0",
"version_normalized": "2.10.0.0",
"source": {
"type": "git",
"url": "https://github.com/laminas/laminas-escaper.git",
"reference": "891ad70986729e20ed2e86355fcf93c9dc238a5f"
"reference": "58af67282db37d24e584a837a94ee55b9c7552be"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/891ad70986729e20ed2e86355fcf93c9dc238a5f",
"reference": "891ad70986729e20ed2e86355fcf93c9dc238a5f",
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/58af67282db37d24e584a837a94ee55b9c7552be",
"reference": "58af67282db37d24e584a837a94ee55b9c7552be",
"shasum": ""
},
"require": {
"php": "^7.3 || ~8.0.0 || ~8.1.0"
"ext-ctype": "*",
"ext-mbstring": "*",
"php": "^7.4 || ~8.0.0 || ~8.1.0"
},
"conflict": {
"zendframework/zend-escaper": "*"
},
"require-dev": {
"infection/infection": "^0.26.6",
"laminas/laminas-coding-standard": "~2.3.0",
"phpunit/phpunit": "^9.3",
"psalm/plugin-phpunit": "^0.12.2",
"vimeo/psalm": "^3.16"
"maglnet/composer-require-checker": "^3.8.0",
"phpunit/phpunit": "^9.5.18",
"psalm/plugin-phpunit": "^0.16.1",
"vimeo/psalm": "^4.22.0"
},
"suggest": {
"ext-iconv": "*",
"ext-mbstring": "*"
},
"time": "2021-09-02T17:10:53+00:00",
"time": "2022-03-08T20:15:36+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -495,8 +495,8 @@
},
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.24.0",
"version_normalized": "1.24.0.0",
"version": "v1.25.0",
"version_normalized": "1.25.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
@ -529,12 +529,12 @@
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Idn\\": ""
},
"files": [
"bootstrap.php"
]
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Idn\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -565,7 +565,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.24.0"
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.25.0"
},
"funding": [
{
@ -672,8 +672,8 @@
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.24.0",
"version_normalized": "1.24.0.0",
"version": "v1.25.0",
"version_normalized": "1.25.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
@ -707,12 +707,12 @@
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [
"bootstrap.php"
]
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -738,7 +738,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.24.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0"
},
"funding": [
{

View file

@ -1,7 +1,7 @@
<?php return array(
'root' => array(
'pretty_version' => '3.6.3',
'version' => '3.6.3.0',
'pretty_version' => '3.6.6',
'version' => '3.6.6.0',
'type' => 'kirby-cms',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -29,8 +29,8 @@
'dev_requirement' => false,
),
'getkirby/cms' => array(
'pretty_version' => '3.6.3',
'version' => '3.6.3.0',
'pretty_version' => '3.6.6',
'version' => '3.6.6.0',
'type' => 'kirby-cms',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -47,12 +47,12 @@
'dev_requirement' => false,
),
'laminas/laminas-escaper' => array(
'pretty_version' => '2.9.0',
'version' => '2.9.0.0',
'pretty_version' => '2.10.0',
'version' => '2.10.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../laminas/laminas-escaper',
'aliases' => array(),
'reference' => '891ad70986729e20ed2e86355fcf93c9dc238a5f',
'reference' => '58af67282db37d24e584a837a94ee55b9c7552be',
'dev_requirement' => false,
),
'league/color-extractor' => array(
@ -98,8 +98,8 @@
'dev_requirement' => false,
),
'symfony/polyfill-intl-idn' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'pretty_version' => 'v1.25.0',
'version' => '1.25.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-idn',
'aliases' => array(),
@ -116,8 +116,8 @@
'dev_requirement' => false,
),
'symfony/polyfill-mbstring' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'pretty_version' => 'v1.25.0',
'version' => '1.25.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
'aliases' => array(),

View file

@ -16,22 +16,30 @@
"forum": "https://discourse.laminas.dev"
},
"config": {
"sort-packages": true
"sort-packages": true,
"platform": {
"php": "7.4.99"
},
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true,
"composer/package-versions-deprecated": true,
"infection/extension-installer": true
}
},
"extra": {
},
"require": {
"php": "^7.3 || ~8.0.0 || ~8.1.0"
},
"suggest": {
"ext-iconv": "*",
"php": "^7.4 || ~8.0.0 || ~8.1.0",
"ext-ctype": "*",
"ext-mbstring": "*"
},
"require-dev": {
"infection/infection": "^0.26.6",
"laminas/laminas-coding-standard": "~2.3.0",
"phpunit/phpunit": "^9.3",
"psalm/plugin-phpunit": "^0.12.2",
"vimeo/psalm": "^3.16"
"maglnet/composer-require-checker": "^3.8.0",
"phpunit/phpunit": "^9.5.18",
"psalm/plugin-phpunit": "^0.16.1",
"vimeo/psalm": "^4.22.0"
},
"autoload": {
"psr-4": {

File diff suppressed because it is too large Load diff

View file

@ -6,10 +6,8 @@ namespace Laminas\Escaper;
use function bin2hex;
use function ctype_digit;
use function function_exists;
use function hexdec;
use function htmlspecialchars;
use function iconv;
use function in_array;
use function mb_convert_encoding;
use function ord;
@ -38,7 +36,7 @@ class Escaper
* entities that XML supports. Using HTML entities would result in this error:
* XML Parsing Error: undefined entity
*
* @var array
* @var array<int, string>
*/
protected static $htmlNamedEntityMap = [
34 => 'quot', // quotation mark
@ -67,6 +65,7 @@ class Escaper
* Static Matcher which escapes characters for HTML Attribute contexts
*
* @var callable
* @psalm-var callable(array<array-key, string>):string
*/
protected $htmlAttrMatcher;
@ -74,6 +73,7 @@ class Escaper
* Static Matcher which escapes characters for Javascript contexts
*
* @var callable
* @psalm-var callable(array<array-key, string>):string
*/
protected $jsMatcher;
@ -81,6 +81,7 @@ class Escaper
* Static Matcher which escapes characters for CSS Attribute contexts
*
* @var callable
* @psalm-var callable(array<array-key, string>):string
*/
protected $cssMatcher;
@ -255,7 +256,7 @@ class Escaper
* Callback function for preg_replace_callback that applies HTML Attribute
* escaping to all matches.
*
* @param array $matches
* @param array<array-key, string> $matches
* @return string
*/
protected function htmlAttrMatcher($matches)
@ -302,7 +303,7 @@ class Escaper
* Callback function for preg_replace_callback that applies Javascript
* escaping to all matches.
*
* @param array $matches
* @param array<array-key, string> $matches
* @return string
*/
protected function jsMatcher($matches)
@ -325,7 +326,7 @@ class Escaper
* Callback function for preg_replace_callback that applies CSS
* escaping to all matches.
*
* @param array $matches
* @param array<array-key, string> $matches
* @return string
*/
protected function cssMatcher($matches)
@ -391,32 +392,21 @@ class Escaper
}
/**
* Encoding conversion helper which wraps iconv and mbstring where they exist or throws
* and exception where neither is available.
* Encoding conversion helper which wraps mb_convert_encoding
*
* @param string $string
* @param string $to
* @param array|string $from
* @throws Exception\RuntimeException
* @return string
*/
protected function convertEncoding($string, $to, $from)
{
if (function_exists('iconv')) {
$result = iconv($from, $to, $string);
} elseif (function_exists('mb_convert_encoding')) {
$result = mb_convert_encoding($string, $to, $from);
} else {
throw new Exception\RuntimeException(
static::class
. ' requires either the iconv or mbstring extension to be installed'
. ' when escaping for non UTF-8 strings.'
);
}
$result = mb_convert_encoding($string, $to, $from);
if ($result === false) {
return ''; // return non-fatal blank string on encoding errors from users
}
return $result;
}
}