](https://getkirby.com)
+[
](https://getkirby.com)
[](https://github.com/getkirby/kirby/releases/latest)
[](https://github.com/getkirby/kirby/actions?query=workflow%3ACI+branch%3Amain)
@@ -6,7 +6,7 @@
[](https://github.com/getkirby/kirby/releases/latest)
**Kirby: the CMS that adapts to any project, loved by developers and editors alike.**
-With Kirby, you build your own ideal interface. Combine forms, galleries, articles, spreadsheets and more into an amazing editing experience. You can learn more about Kirby at [getkirby.com](https://getkirby.com).
+With Kirby, you build your own ideal interface. Combine forms, galleries, articles, spreadsheets and more into an amazing editing experience. You can learn more about Kirby at [getkirby.com](https://getkirby.com).
This is Kirby's core application folder. Get started with one of the following repositories instead:
@@ -15,7 +15,8 @@ This is Kirby's core application folder. Get started with one of the following r
-### Try Kirby for free
+### Try Kirby for free
+
Kirby is not free software. However, you can try Kirby and the Starterkit on your local machine or on a test server as long as you need to make sure it is the right tool for your next project. … and when you’re convinced, [buy your license](https://getkirby.com/buy).
### Contribute
@@ -30,6 +31,7 @@ If you have ideas for a feature or enhancement for Kirby, please use our [feedba
Read about how to contribute to the development in our [contributing guide](/CONTRIBUTING.md).
## What's Kirby?
+
- **[getkirby.com](https://getkirby.com)** – Get to know the CMS.
- **[Try it](https://getkirby.com/try)** – Take a test ride with our online demo. Or download one of our kits to get started.
- **[Documentation](https://getkirby.com/docs/guide)** – Read the official guide, reference and cookbook recipes.
@@ -43,5 +45,5 @@ Read about how to contribute to the development in our [contributing guide](/CON
---
-© 2009-2023 Bastian Allgeier
+© 2009 Bastian Allgeier
[getkirby.com](https://getkirby.com) · [License agreement](https://getkirby.com/license)
diff --git a/public/kirby/cacert.pem b/public/kirby/cacert.pem
index f78a610..86d6cd8 100644
--- a/public/kirby/cacert.pem
+++ b/public/kirby/cacert.pem
@@ -1,7 +1,7 @@
##
## Bundle of CA Root Certificates
##
-## Certificate data from Mozilla as of: Mon Mar 11 15:25:27 2024 GMT
+## Certificate data from Mozilla as of: Tue Jul 2 03:12:04 2024 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: 4d96bd539f4719e9ace493757afbe4a23ee8579de1c97fbebc50bba3c12e8c1e
+## SHA256: 456ff095dde6dd73354c5c28c73d9c06f53b61a803963414cb91a1d92945cdd3
##
@@ -2600,36 +2600,6 @@ vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+
CAezNIm8BZ/3Hobui3A=
-----END CERTIFICATE-----
-GLOBALTRUST 2020
-================
------BEGIN CERTIFICATE-----
-MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx
-IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT
-VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh
-BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy
-MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi
-D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO
-VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM
-CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm
-fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA
-A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR
-JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG
-DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU
-clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ
-mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
-AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud
-IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA
-VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw
-4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9
-iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS
-8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2
-HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS
-vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918
-oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF
-YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl
-gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg==
------END CERTIFICATE-----
-
ANF Secure Server Root CA
=========================
-----BEGIN CERTIFICATE-----
@@ -3579,3 +3549,20 @@ wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+ljX273CXE2whJdV/LItM3z7gLfEdxquVeE
HVlNjM7IDiPCtyaaEBRx/pOyiriA8A4QntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0
o82bNSQ3+pCTE4FCxpgmdTdmQRCsu/WU48IxK63nI1bMNSWSs1A=
-----END CERTIFICATE-----
+
+FIRMAPROFESIONAL CA ROOT-A WEB
+==============================
+-----BEGIN CERTIFICATE-----
+MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQswCQYDVQQGEwJF
+UzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UEYQwPVkFURVMtQTYyNjM0MDY4
+MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENBIFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2
+WhcNNDcwMzMxMDkwMTM2WjBuMQswCQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25h
+bCBTQTEYMBYGA1UEYQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFM
+IENBIFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zfe9MEkVz6
+iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6CcyvHZpsKjECcfIr28jlg
+st7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FD
+Y1w8ndYn81LsF7Kpryz3dvgwHQYDVR0OBBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB
+/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgL
+cFBTApFwhVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dGXSaQ
+pYXFuXqUPoeovQA=
+-----END CERTIFICATE-----
diff --git a/public/kirby/composer.json b/public/kirby/composer.json
index 1320655..1ccb407 100644
--- a/public/kirby/composer.json
+++ b/public/kirby/composer.json
@@ -3,7 +3,7 @@
"description": "The Kirby core",
"license": "proprietary",
"type": "kirby-cms",
- "version": "4.3.0",
+ "version": "4.4.0",
"keywords": [
"kirby",
"cms",
@@ -38,15 +38,15 @@
"ext-openssl": "*",
"christian-riesen/base32": "1.6.0",
"claviska/simpleimage": "4.2.0",
- "composer/semver": "3.4.0",
+ "composer/semver": "3.4.2",
"filp/whoops": "2.15.4",
"getkirby/composer-installer": "^1.2.1",
"laminas/laminas-escaper": "2.13.0",
"michelf/php-smartypants": "1.8.1",
"phpmailer/phpmailer": "6.9.1",
- "symfony/polyfill-intl-idn": "1.29.0",
- "symfony/polyfill-mbstring": "1.29.0",
- "symfony/yaml": "6.4.8"
+ "symfony/polyfill-intl-idn": "1.30.0",
+ "symfony/polyfill-mbstring": "1.30.0",
+ "symfony/yaml": "6.4.11"
},
"replace": {
"symfony/polyfill-php72": "*"
diff --git a/public/kirby/composer.lock b/public/kirby/composer.lock
index f3c8063..ea8097d 100644
--- a/public/kirby/composer.lock
+++ b/public/kirby/composer.lock
@@ -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": "c551d482274b5418356af010c4845227",
+ "content-hash": "a49870b845c1d596a44c5af23c4dd685",
"packages": [
{
"name": "christian-riesen/base32",
@@ -120,16 +120,16 @@
},
{
"name": "composer/semver",
- "version": "3.4.0",
+ "version": "3.4.2",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
- "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32"
+ "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32",
- "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32",
+ "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6",
+ "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6",
"shasum": ""
},
"require": {
@@ -181,7 +181,7 @@
"support": {
"irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/semver/issues",
- "source": "https://github.com/composer/semver/tree/3.4.0"
+ "source": "https://github.com/composer/semver/tree/3.4.2"
},
"funding": [
{
@@ -197,7 +197,7 @@
"type": "tidelift"
}
],
- "time": "2023-08-31T09:50:34+00:00"
+ "time": "2024-07-12T11:35:52+00:00"
},
{
"name": "filp/whoops",
@@ -577,16 +577,16 @@
},
{
"name": "psr/log",
- "version": "3.0.0",
+ "version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
- "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
- "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
"shasum": ""
},
"require": {
@@ -621,9 +621,9 @@
"psr-3"
],
"support": {
- "source": "https://github.com/php-fig/log/tree/3.0.0"
+ "source": "https://github.com/php-fig/log/tree/3.0.2"
},
- "time": "2021-07-14T16:46:02+00:00"
+ "time": "2024-09-11T13:17:53+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -694,20 +694,20 @@
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.29.0",
+ "version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
- "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
@@ -753,7 +753,7 @@
"portable"
],
"support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
},
"funding": [
{
@@ -769,20 +769,20 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-intl-idn",
- "version": "v1.29.0",
+ "version": "v1.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
- "reference": "a287ed7475f85bf6f61890146edbc932c0fff919"
+ "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a287ed7475f85bf6f61890146edbc932c0fff919",
- "reference": "a287ed7475f85bf6f61890146edbc932c0fff919",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a6e83bdeb3c84391d1dfe16f42e40727ce524a5c",
+ "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c",
"shasum": ""
},
"require": {
@@ -837,7 +837,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.30.0"
},
"funding": [
{
@@ -853,24 +853,24 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-05-31T15:07:36+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
- "version": "v1.29.0",
+ "version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
- "reference": "bc45c394692b948b4d383a08d7753968bed9a83d"
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d",
- "reference": "bc45c394692b948b4d383a08d7753968bed9a83d",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.2"
},
"suggest": {
"ext-intl": "For best performance"
@@ -918,7 +918,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0"
},
"funding": [
{
@@ -934,20 +934,20 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.29.0",
+ "version": "v1.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
+ "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
- "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c",
+ "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"shasum": ""
},
"require": {
@@ -998,7 +998,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0"
},
"funding": [
{
@@ -1014,20 +1014,20 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-06-19T12:30:46+00:00"
},
{
"name": "symfony/yaml",
- "version": "v6.4.8",
+ "version": "v6.4.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "52903de178d542850f6f341ba92995d3d63e60c9"
+ "reference": "be37e7f13195e05ab84ca5269365591edd240335"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/52903de178d542850f6f341ba92995d3d63e60c9",
- "reference": "52903de178d542850f6f341ba92995d3d63e60c9",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/be37e7f13195e05ab84ca5269365591edd240335",
+ "reference": "be37e7f13195e05ab84ca5269365591edd240335",
"shasum": ""
},
"require": {
@@ -1070,7 +1070,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/yaml/tree/v6.4.8"
+ "source": "https://github.com/symfony/yaml/tree/v6.4.11"
},
"funding": [
{
@@ -1086,7 +1086,7 @@
"type": "tidelift"
}
],
- "time": "2024-05-31T14:49:08+00:00"
+ "time": "2024-08-12T09:55:28+00:00"
}
],
"packages-dev": [],
diff --git a/public/kirby/config/api/routes/files.php b/public/kirby/config/api/routes/files.php
index 7cf1073..23e1729 100644
--- a/public/kirby/config/api/routes/files.php
+++ b/public/kirby/config/api/routes/files.php
@@ -1,7 +1,7 @@
upload(function ($source, $filename) use ($path) {
- $props = [
+ // move the source file from the temp dir
+ return $this->parent($path)->createFile([
'content' => [
'sort' => $this->requestBody('sort')
],
'source' => $source,
'template' => $this->requestBody('template'),
'filename' => $filename
- ];
-
- // move the source file from the temp dir
- return $this->parent($path)->createFile($props, true);
+ ], true);
});
// @codeCoverageIgnoreEnd
}
diff --git a/public/kirby/config/areas/languages/views.php b/public/kirby/config/areas/languages/views.php
index f37ded0..ac2d6d4 100644
--- a/public/kirby/config/areas/languages/views.php
+++ b/public/kirby/config/areas/languages/views.php
@@ -12,12 +12,17 @@ return [
return App::instance()->option('languages.variables', true) !== false;
},
'action' => function (string $code) {
+ $kirby = App::instance();
$language = Find::language($code);
$link = '/languages/' . $language->code();
$strings = [];
- $foundation = App::instance()->defaultLanguage()->translations();
+ $foundation = $kirby->defaultLanguage()->translations();
$translations = $language->translations();
+ // TODO: update following line and adapt for update and delete options
+ // when new `languageVariables.*` permissions available
+ $canUpdate = $kirby->user()?->role()->permissions()->for('languages', 'update') === true;
+
ksort($foundation);
foreach ($foundation as $key => $value) {
@@ -26,13 +31,14 @@ return [
'value' => $translations[$key] ?? null,
'options' => [
[
- 'click' => 'update',
- 'icon' => 'edit',
- 'text' => I18n::translate('edit'),
+ 'click' => 'update',
+ 'disabled' => $canUpdate === false,
+ 'icon' => 'edit',
+ 'text' => I18n::translate('edit'),
],
[
'click' => 'delete',
- 'disabled' => $language->isDefault() === false,
+ 'disabled' => $canUpdate === false || $language->isDefault() === false,
'icon' => 'trash',
'text' => I18n::translate('delete'),
]
diff --git a/public/kirby/config/areas/site.php b/public/kirby/config/areas/site.php
index ab2eb78..8995d12 100644
--- a/public/kirby/config/areas/site.php
+++ b/public/kirby/config/areas/site.php
@@ -3,12 +3,14 @@
use Kirby\Toolkit\I18n;
return function ($kirby) {
+ $blueprint = $kirby->site()->blueprint();
+
return [
'breadcrumbLabel' => function () use ($kirby) {
return $kirby->site()->title()->or(I18n::translate('view.site'))->toString();
},
- 'icon' => 'home',
- 'label' => $kirby->site()->blueprint()->title() ?? I18n::translate('view.site'),
+ 'icon' => $blueprint->icon() ?? 'home',
+ 'label' => $blueprint->title() ?? I18n::translate('view.site'),
'menu' => true,
'dialogs' => require __DIR__ . '/site/dialogs.php',
'drawers' => require __DIR__ . '/site/drawers.php',
diff --git a/public/kirby/config/areas/site/dialogs.php b/public/kirby/config/areas/site/dialogs.php
index f797139..834b8ca 100644
--- a/public/kirby/config/areas/site/dialogs.php
+++ b/public/kirby/config/areas/site/dialogs.php
@@ -194,12 +194,23 @@ return [
'page.changeTitle' => [
'pattern' => 'pages/(:any)/changeTitle',
'load' => function (string $id) {
- $request = App::instance()->request();
+ $kirby = App::instance();
+ $request = $kirby->request();
$page = Find::page($id);
$permissions = $page->permissions();
$select = $request->get('select', 'title');
+ // build the path prefix
+ $path = match ($kirby->multilang()) {
+ true => Str::after($kirby->site()->url(), $kirby->url()) . '/',
+ false => '/'
+ };
+
+ if ($parent = $page->parent()) {
+ $path .= $parent->uri() . '/';
+ }
+
return [
'component' => 'k-form-dialog',
'props' => [
@@ -212,7 +223,7 @@ return [
'slug' => Field::slug([
'required' => true,
'preselect' => $select === 'slug',
- 'path' => $page->parent() ? '/' . $page->parent()->uri() . '/' : '/',
+ 'path' => $path,
'disabled' => $permissions->can('changeSlug') === false,
'wizard' => [
'text' => I18n::translate('page.changeSlug.fromTitle'),
diff --git a/public/kirby/config/areas/site/requests.php b/public/kirby/config/areas/site/requests.php
index 352334a..972b43d 100644
--- a/public/kirby/config/areas/site/requests.php
+++ b/public/kirby/config/areas/site/requests.php
@@ -5,6 +5,8 @@ use Kirby\Cms\Find;
use Kirby\Toolkit\I18n;
return [
+ // @codeCoverageIgnoreStart
+ // TODO: move to controller class and add unit tests
'tree' => [
'pattern' => 'site/tree',
'action' => function () {
@@ -62,5 +64,20 @@ return [
return $pages;
}
+ ],
+ 'tree.parents' => [
+ 'pattern' => 'site/tree/parents',
+ 'action' => function () {
+ $kirby = App::instance();
+ $request = $kirby->request();
+ $page = $kirby->page($request->get('page'));
+
+ return [
+ 'data' => $page->parents()->flip()->values(
+ fn ($parent) => $parent->uuid()?->toString() ?? $parent->id()
+ )
+ ];
+ }
]
+ // @codeCoverageIgnoreEnd
];
diff --git a/public/kirby/config/fields/mixins/picker.php b/public/kirby/config/fields/mixins/picker.php
index 5d95e45..a555c2f 100644
--- a/public/kirby/config/fields/mixins/picker.php
+++ b/public/kirby/config/fields/mixins/picker.php
@@ -2,6 +2,7 @@
use Kirby\Toolkit\I18n;
use Kirby\Toolkit\Str;
+use Kirby\Uuid\Uuids;
return [
'props' => [
@@ -75,7 +76,11 @@ return [
* @param string $store 'uuid'|'id'
*/
'store' => function (string $store = 'uuid') {
- return Str::lower($store);
+ // fall back to ID, if UUIDs globally disabled
+ return match (Uuids::enabled()) {
+ false => 'id',
+ default => Str::lower($store)
+ };
},
/**
diff --git a/public/kirby/config/fields/mixins/upload.php b/public/kirby/config/fields/mixins/upload.php
index aad3933..a5b9962 100644
--- a/public/kirby/config/fields/mixins/upload.php
+++ b/public/kirby/config/fields/mixins/upload.php
@@ -23,7 +23,11 @@ return [
$uploads = [];
}
- $uploads['accept'] = '*';
+ $uploads['accept'] = '*';
+
+ if ($preview = $this->image) {
+ $uploads['preview'] = $preview;
+ }
if ($template = $uploads['template'] ?? null) {
// get parent object for upload target
diff --git a/public/kirby/config/fields/writer.php b/public/kirby/config/fields/writer.php
index fc4a393..c9f7869 100644
--- a/public/kirby/config/fields/writer.php
+++ b/public/kirby/config/fields/writer.php
@@ -63,7 +63,14 @@ return [
'computed' => [
'value' => function () {
$value = trim($this->value ?? '');
- return Sane::sanitize($value, 'html');
+ $value = Sane::sanitize($value, 'html');
+
+ // convert non-breaking spaces to HTML entity
+ // as that's how ProseMirror handles it internally;
+ // will allow comparing saved and current content
+ $value = str_replace(' ', ' ', $value);
+
+ return $value;
}
],
'validations' => [
diff --git a/public/kirby/config/sections/files.php b/public/kirby/config/sections/files.php
index bb8532f..35ff9e3 100644
--- a/public/kirby/config/sections/files.php
+++ b/public/kirby/config/sections/files.php
@@ -194,6 +194,7 @@ return [
'multiple' => $multiple,
'max' => $max,
'api' => $this->parent->apiUrl(true) . '/files',
+ 'preview' => $this->image,
'attributes' => [
// TODO: an edge issue that needs to be solved:
// if multiple users load the same section
diff --git a/public/kirby/config/sections/mixins/layout.php b/public/kirby/config/sections/mixins/layout.php
index 150498f..75230b0 100644
--- a/public/kirby/config/sections/mixins/layout.php
+++ b/public/kirby/config/sections/mixins/layout.php
@@ -21,6 +21,14 @@ return [
$layouts = ['list', 'cardlets', 'cards', 'table'];
return in_array($layout, $layouts) ? $layout : 'list';
},
+ /**
+ * Whether the raw content file values should be used for the table column previews. Should not be used unless it eases performance issues in your setup introduced with Kirby 4.2
+ *
+ * @todo remove when Form classes have been refactored
+ */
+ 'rawvalues' => function (bool $rawvalues = false) {
+ return $rawvalues;
+ },
/**
* The size option controls the size of cards. By default cards are auto-sized and the cards grid will always fill the full width. With a size you can disable auto-sizing. Available sizes: `tiny`, `small`, `medium`, `large`, `huge`, `full`
*/
@@ -133,6 +141,24 @@ return [
$item['info'] = $model->toString($this->info);
}
+ // if forcing raw values, get those directly from content file
+ // TODO: remove once Form classes have been refactored
+ // @codeCoverageIgnoreStart
+ if ($this->rawvalues === true) {
+ foreach ($this->columns as $columnName => $column) {
+ $item[$columnName] = match (empty($column['value'])) {
+ // if column value defined, resolve the query
+ false => $model->toString($column['value']),
+ // otherwise use the form value,
+ // but don't overwrite columns
+ default => $item[$columnName] ?? $model->content()->get($column['id'] ?? $columnName)->value()
+ };
+ }
+
+ return $item;
+ }
+ // @codeCoverageIgnoreEnd
+
// Use form to get the proper values for the columns
$form = Form::for($model)->values();
@@ -142,10 +168,7 @@ return [
false => $model->toString($column['value']),
// otherwise use the form value,
// but don't overwrite columns
- default =>
- $item[$columnName] ??
- $form[$column['id'] ?? $columnName] ??
- null,
+ default => $item[$columnName] ?? $form[$column['id'] ?? $columnName] ?? null
};
}
diff --git a/public/kirby/config/tags.php b/public/kirby/config/tags.php
index fd4962d..240a365 100644
--- a/public/kirby/config/tags.php
+++ b/public/kirby/config/tags.php
@@ -112,6 +112,11 @@ return [
'width'
],
'html' => function (KirbyTag $tag): string {
+ $kirby = $tag->kirby();
+
+ $tag->width ??= $kirby->option('kirbytext.image.width');
+ $tag->height ??= $kirby->option('kirbytext.image.height');
+
if ($tag->file = $tag->file($tag->value)) {
$tag->src = $tag->file->url();
$tag->alt ??= $tag->file->alt()->or('')->value();
@@ -129,6 +134,13 @@ return [
$tag->srcset = $tag->file->srcset($srcset);
}
+
+ if ($tag->width === 'auto') {
+ $tag->width = $tag->file->width();
+ }
+ if ($tag->height === 'auto') {
+ $tag->height = $tag->file->height();
+ }
} else {
$tag->src = Url::to($tag->value);
}
@@ -157,14 +169,14 @@ return [
'alt' => $tag->alt ?? ''
]);
- if ($tag->kirby()->option('kirbytext.image.figure', true) === false) {
+ if ($kirby->option('kirbytext.image.figure', true) === false) {
return $link($image);
}
// render KirbyText in caption
if ($tag->caption) {
$options = ['markdown' => ['inline' => true]];
- $caption = $tag->kirby()->kirbytext($tag->caption, $options);
+ $caption = $kirby->kirbytext($tag->caption, $options);
$tag->caption = [$caption];
}
diff --git a/public/kirby/i18n/translations/bg.json b/public/kirby/i18n/translations/bg.json
index 32a8d04..5a3dc20 100644
--- a/public/kirby/i18n/translations/bg.json
+++ b/public/kirby/i18n/translations/bg.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Формата не може да бъде запазена",
"error.language.code": "Please enter a valid code for the language",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "The language already exists",
"error.language.name": "Please enter a valid name for the language",
"error.language.notFound": "The language could not be found",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "There's an error on the \"{field}\" field in block {blockIndex} using the \"{fieldset}\" block type in layout {layoutIndex}",
"error.layout.validation.settings": "There's an error in layout {index} settings",
diff --git a/public/kirby/i18n/translations/ca.json b/public/kirby/i18n/translations/ca.json
index 7048d1b..a7bcd3f 100644
--- a/public/kirby/i18n/translations/ca.json
+++ b/public/kirby/i18n/translations/ca.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "No s'ha pogut desar el formulari",
"error.language.code": "Introdueix un codi vàlid per a l’idioma",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "L'idioma ja existeix",
"error.language.name": "Introdueix un nom vàlid per a l'idioma",
"error.language.notFound": "The language could not be found",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "There's an error on the \"{field}\" field in block {blockIndex} using the \"{fieldset}\" block type in layout {layoutIndex}",
"error.layout.validation.settings": "There's an error in layout {index} settings",
diff --git a/public/kirby/i18n/translations/cs.json b/public/kirby/i18n/translations/cs.json
index 4576dee..326b675 100644
--- a/public/kirby/i18n/translations/cs.json
+++ b/public/kirby/i18n/translations/cs.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Formulář nemohl být uložen",
"error.language.code": "Zadejte prosím platný kód jazyka",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "Jazyk již existuje",
"error.language.name": "Zadejte prosím platné jméno jazyka",
"error.language.notFound": "Jazyk nebyl nalezen",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "V rozvržení {layoutIndex} je v poli \"{field}\" v bloku {blockIndex} při použití \"{fieldset}\" typu chyba",
"error.layout.validation.settings": "Chyba v nastavení rozvržení {index}",
diff --git a/public/kirby/i18n/translations/da.json b/public/kirby/i18n/translations/da.json
index dfd45e8..471b2c5 100644
--- a/public/kirby/i18n/translations/da.json
+++ b/public/kirby/i18n/translations/da.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Formularen kunne ikke gemmes",
"error.language.code": "Indtast venligst en gyldig kode for sproget",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "Sproget eksisterer allerede",
"error.language.name": "Indtast venligst et gyldigt navn for sproget",
"error.language.notFound": "Sproget fandtes ikke",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "There's an error on the \"{field}\" field in block {blockIndex} using the \"{fieldset}\" block type in layout {layoutIndex}",
"error.layout.validation.settings": "Der er fejl i layout {index} indstillinger",
diff --git a/public/kirby/i18n/translations/de.json b/public/kirby/i18n/translations/de.json
index 040645c..d57e2c9 100644
--- a/public/kirby/i18n/translations/de.json
+++ b/public/kirby/i18n/translations/de.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Das Formular konnte nicht gespeichert werden",
"error.language.code": "Bitte gib einen gültigen Code für die Sprache an",
+ "error.language.create.permission": "Du darfst keine Sprache anlegen",
+ "error.language.delete.permission": "Du darfst diese Sprache nicht löschen",
"error.language.duplicate": "Die Sprache besteht bereits",
"error.language.name": "Bitte gib einen gültigen Namen für die Sprache an",
"error.language.notFound": "Die Sprache konnte nicht gefunden werden",
+ "error.language.update.permission": "Du darfst diese Sprache nicht bearbeiten",
"error.layout.validation.block": "Fehler im \"{field}\" Feld in Block {blockIndex} mit dem Blocktyp \"{fieldset}\" in Layout {layoutIndex}",
"error.layout.validation.settings": "Fehler in den Einstellungen von Layout {index}",
diff --git a/public/kirby/i18n/translations/el.json b/public/kirby/i18n/translations/el.json
index 56709da..bd4c1c8 100644
--- a/public/kirby/i18n/translations/el.json
+++ b/public/kirby/i18n/translations/el.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Δεν ήταν δυνατή η αποθήκευση της φόρμας",
"error.language.code": "Please enter a valid code for the language",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "The language already exists",
"error.language.name": "Please enter a valid name for the language",
"error.language.notFound": "The language could not be found",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "There's an error on the \"{field}\" field in block {blockIndex} using the \"{fieldset}\" block type in layout {layoutIndex}",
"error.layout.validation.settings": "There's an error in layout {index} settings",
diff --git a/public/kirby/i18n/translations/en.json b/public/kirby/i18n/translations/en.json
index 1592770..6212ecf 100644
--- a/public/kirby/i18n/translations/en.json
+++ b/public/kirby/i18n/translations/en.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "The form could not be saved",
"error.language.code": "Please enter a valid code for the language",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "The language already exists",
"error.language.name": "Please enter a valid name for the language",
"error.language.notFound": "The language could not be found",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "There's an error on the \"{field}\" field in block {blockIndex} using the \"{fieldset}\" block type in layout {layoutIndex}",
"error.layout.validation.settings": "There's an error in layout {index} settings",
@@ -294,6 +297,9 @@
"field.blocks.heading.name": "Heading",
"field.blocks.heading.text": "Text",
"field.blocks.heading.placeholder": "Heading …",
+ "field.blocks.figure.back.plain": "Plain",
+ "field.blocks.figure.back.pattern.light": "Pattern (light)",
+ "field.blocks.figure.back.pattern.dark": "Pattern (dark)",
"field.blocks.image.alt": "Alternative text",
"field.blocks.image.caption": "Caption",
"field.blocks.image.crop": "Crop",
@@ -331,6 +337,7 @@
"field.blocks.video.url.placeholder": "https://youtube.com/?v=",
"field.files.empty": "No files selected yet",
+ "field.files.empty.single": "No file selected yet",
"field.layout.change": "Change layout",
"field.layout.delete": "Delete layout",
@@ -342,12 +349,14 @@
"field.object.empty": "No information yet",
"field.pages.empty": "No pages selected yet",
+ "field.pages.empty.single": "No page selected yet",
"field.structure.delete.confirm": "Do you really want to delete this row?",
"field.structure.delete.confirm.all": "Do you really want to delete all entries?",
"field.structure.empty": "No entries yet",
"field.users.empty": "No users selected yet",
+ "field.users.empty.single": "No user selected yet",
"fields.empty": "No fields yet",
@@ -595,6 +604,7 @@
"save": "Save",
"search": "Search",
+ "searching": "Searching",
"search.min": "Enter {min} characters to search",
"search.all": "Show all {count} results",
"search.results.none": "No results",
diff --git a/public/kirby/i18n/translations/eo.json b/public/kirby/i18n/translations/eo.json
index c1bf5b9..939b2cc 100644
--- a/public/kirby/i18n/translations/eo.json
+++ b/public/kirby/i18n/translations/eo.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Ne eblis konservi la formularon",
"error.language.code": "Bonvolu entajpi validan kodon por la lingvo",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "La lingvo jam ekzistas",
"error.language.name": "Bonvolu entajpi validan nomon por la lingvo",
"error.language.notFound": "La lingvo ne troveblas",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "There's an error on the \"{field}\" field in block {blockIndex} using the \"{fieldset}\" block type in layout {layoutIndex}",
"error.layout.validation.settings": "Estas eraro en la agordoj de blokaranĝo {index}",
diff --git a/public/kirby/i18n/translations/es_419.json b/public/kirby/i18n/translations/es_419.json
index 8cc34c1..dbaf216 100644
--- a/public/kirby/i18n/translations/es_419.json
+++ b/public/kirby/i18n/translations/es_419.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "No se pudo guardar el formulario",
"error.language.code": "Por favor introduce un código válido para el idioma",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "El idioma ya existe",
"error.language.name": "Por favor introduce un nombre válido para el idioma",
"error.language.notFound": "No se pudo encontrar el idioma",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "Hay un error en el campo \"{field}\" del bloque {blockIndex} que utiliza el tipo de bloque \"{fieldset}\" en el layout {layoutIndex}",
"error.layout.validation.settings": "Hay un error en los ajustes del layout {index}",
diff --git a/public/kirby/i18n/translations/es_ES.json b/public/kirby/i18n/translations/es_ES.json
index 0385355..25ea458 100644
--- a/public/kirby/i18n/translations/es_ES.json
+++ b/public/kirby/i18n/translations/es_ES.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "El formulario no pudo ser guardado",
"error.language.code": "Por favor, introduce un código válido para el idioma",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "El idioma ya existe",
"error.language.name": "Por favor, introduce un nombre válido para el idioma",
"error.language.notFound": "No se pudo encontrar el idioma",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "Hay un error en el campo \"{field}\" del bloque {blockIndex} que utiliza el tipo de bloque \"{fieldset}\" en el layout {layoutIndex}",
"error.layout.validation.settings": "Hay un error en los ajustes del layout {index}",
diff --git a/public/kirby/i18n/translations/fa.json b/public/kirby/i18n/translations/fa.json
index 6b648b8..9b92a85 100644
--- a/public/kirby/i18n/translations/fa.json
+++ b/public/kirby/i18n/translations/fa.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "امکان دخیره فرم وجود ندارد",
"error.language.code": "Please enter a valid code for the language",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "The language already exists",
"error.language.name": "Please enter a valid name for the language",
"error.language.notFound": "The language could not be found",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "There's an error on the \"{field}\" field in block {blockIndex} using the \"{fieldset}\" block type in layout {layoutIndex}",
"error.layout.validation.settings": "There's an error in layout {index} settings",
diff --git a/public/kirby/i18n/translations/fi.json b/public/kirby/i18n/translations/fi.json
index 13c1c39..68ecc72 100644
--- a/public/kirby/i18n/translations/fi.json
+++ b/public/kirby/i18n/translations/fi.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Lomaketta ei voitu tallentaa",
"error.language.code": "Anna kielen lyhenne",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "Kieli on jo olemassa",
"error.language.name": "Anna kielen nimi",
"error.language.notFound": "Kieltä ei löytynyt",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "There's an error on the \"{field}\" field in block {blockIndex} using the \"{fieldset}\" block type in layout {layoutIndex}",
"error.layout.validation.settings": "Virhe asetelman {index} asetuksissa",
diff --git a/public/kirby/i18n/translations/fr.json b/public/kirby/i18n/translations/fr.json
index 358ff06..8c75064 100644
--- a/public/kirby/i18n/translations/fr.json
+++ b/public/kirby/i18n/translations/fr.json
@@ -125,10 +125,13 @@
"error.form.incomplete": "Veuillez corriger toutes les erreurs du formulaire…",
"error.form.notSaved": "Le formulaire n’a pu être enregistré",
- "error.language.code": "Veuillez saisir un code correct pour cette langue",
+ "error.language.code": "Veuillez saisir un code correct pour la langue",
+ "error.language.create.permission": "Vous n’êtes pas autorisé à créer une langue",
+ "error.language.delete.permission": "Vous n’êtes pas autorisé à supprimer la langue",
"error.language.duplicate": "Cette langue existe déjà",
- "error.language.name": "Veuillez saisir un nom correct pour cette langue",
+ "error.language.name": "Veuillez saisir un nom correct pour la langue",
"error.language.notFound": "La langue n’a pu être trouvée",
+ "error.language.update.permission": "Vous n’êtes pas autorisé à modifier la langue",
"error.layout.validation.block": "Il y a une erreur sur le champ « {field} » du bloc {blockIndex} utilisant le type de bloc « {fieldset} » dans le layout {layoutIndex}.",
"error.layout.validation.settings": "Il y a une erreur dans les paramètres de la disposition {index}",
@@ -470,7 +473,7 @@
"login.email.password-reset.body": "Bonjour {user.nameOrEmail},\n\nVous avez récemment demandé un code de réinitialisation de mot de passe pour le Panel de {site}.\nLe code de réinitialisation de mot de passe suivant sera valable pendant {timeout} minutes :\n\n{code}\n\nSi vous n’avez pas demandé de code de réinitialisation de mot de passe, veuillez ignorer cet email ou contacter votre administrateur si vous avez des questions.\nPar sécurité, merci de ne PAS faire suivre ce courriel.",
"login.email.password-reset.subject": "Votre code de réinitialisation du mot de passe",
"login.remember": "Rester connecté",
- "login.reset": "Réinitialiser le mot de passe",
+ "login.reset": "Réinitialiser",
"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é ?",
@@ -617,8 +620,8 @@
"stats.empty": "Aucun rapport",
"status": "Statut",
- "system.info.copy": "Copy info",
- "system.info.copied": "System info copied",
+ "system.info.copy": "Copier les informations",
+ "system.info.copied": "Informations système copiées",
"system.issues.content": "Le dossier content semble exposé",
"system.issues.eol.kirby": "La version de Kirby installée a atteint la fin de son cycle de vie et ne recevra plus de mises à jour de sécurité",
"system.issues.eol.plugin": "La version du plugin { plugin } installée a atteint la fin de son cycle de vie et ne recevra plus de mises à jour de sécurité",
diff --git a/public/kirby/i18n/translations/hu.json b/public/kirby/i18n/translations/hu.json
index 27cb8d7..25c33b0 100644
--- a/public/kirby/i18n/translations/hu.json
+++ b/public/kirby/i18n/translations/hu.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Az űrlap nem menthető",
"error.language.code": "Kérlek, add meg a nyelv érvényes kódját",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "A nyelv már létezik",
"error.language.name": "Kérlek, add meg a nyelv érvényes nevét",
"error.language.notFound": "A nyelv nem található",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "There's an error on the \"{field}\" field in block {blockIndex} using the \"{fieldset}\" block type in layout {layoutIndex}",
"error.layout.validation.settings": "Hibát találtunk a(z) {index} elrendezés beállításaiban",
diff --git a/public/kirby/i18n/translations/id.json b/public/kirby/i18n/translations/id.json
index e4af95b..2dd6ba7 100644
--- a/public/kirby/i18n/translations/id.json
+++ b/public/kirby/i18n/translations/id.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Formulir tidak dapat disimpan",
"error.language.code": "Masukkan kode bahasa yang valid",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "Bahasa sudah ada",
"error.language.name": "Masukkan nama bahasa yang valid",
"error.language.notFound": "Bahasa tidak ditemukan",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "Ada kesalahan pada bidang \"{field}\" di blok {blockIndex} menggunakan tipe blok \"{fieldset}\" di tata letak {layoutIndex}",
"error.layout.validation.settings": "Ada kesalahan di pengaturan tata letak {index}",
diff --git a/public/kirby/i18n/translations/is_IS.json b/public/kirby/i18n/translations/is_IS.json
index 0608b5c..2a0def1 100644
--- a/public/kirby/i18n/translations/is_IS.json
+++ b/public/kirby/i18n/translations/is_IS.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Ekki tókst að vista upplýsingar úr forminu",
"error.language.code": "Gófúslega settu inn gildan kóða fyrir tungumál",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "Þetta tungumál er nú þegar skráð",
"error.language.name": "Gott og gyllt nafn fyrir tungumálið",
"error.language.notFound": "Tungumálið fannst ekkert",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "Það er villa í {field} sviðinu í bálkinum {blockIndex} sem notar {fieldset} bálkgerðina í rammanum {layoutIndex}",
"error.layout.validation.settings": "Hér er villa í sitllingum fyrir ramman {index}",
diff --git a/public/kirby/i18n/translations/it.json b/public/kirby/i18n/translations/it.json
index a4d9a48..6263b62 100644
--- a/public/kirby/i18n/translations/it.json
+++ b/public/kirby/i18n/translations/it.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Non è stato possibile salvare il form",
"error.language.code": "Inserisci un codice valido per la lingua",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "La lingua esiste già",
"error.language.name": "Inserisci un nome valido per la lingua",
"error.language.notFound": "La lingua non è stata trovata",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "C'è un errore sul campo \"{field}\" nel blocco {blockIndex} che utilizza il tipo di blocco \"{fieldset}\" nel layout {layoutIndex}",
"error.layout.validation.settings": "C'è un errore nelle impostazioni del layout {index}",
diff --git a/public/kirby/i18n/translations/ko.json b/public/kirby/i18n/translations/ko.json
index cfb180e..d203767 100644
--- a/public/kirby/i18n/translations/ko.json
+++ b/public/kirby/i18n/translations/ko.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "항목을 저장할 수 없습니다.",
"error.language.code": "올바른 언어 코드를 입력하세요.",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "이미 등록한 언어입니다.",
"error.language.name": "올바른 언어명을 입력하세요.",
"error.language.notFound": "언어를 찾을 수 없습니다.",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "레이아웃({layoutIndex})의 특정 블록 유형({fieldset})을 사용하는 블록({blockIndex})의 특정 필드({field})에 오류가 있습니다.",
"error.layout.validation.settings": "레이아웃({index}) 옵션을 확인하세요.",
diff --git a/public/kirby/i18n/translations/lt.json b/public/kirby/i18n/translations/lt.json
index 248c6ff..6c14373 100644
--- a/public/kirby/i18n/translations/lt.json
+++ b/public/kirby/i18n/translations/lt.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Formos nepavyko išsaugoti",
"error.language.code": "Prašome įrašyti teisingą kalbos kodą",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "Tokia kalba jau yra",
"error.language.name": "Prašome įrašyti teisingą kalbos pavadinimą",
"error.language.notFound": "Nepavyko rasti šios kalbos",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "There's an error on the \"{field}\" field in block {blockIndex} using the \"{fieldset}\" block type in layout {layoutIndex}",
"error.layout.validation.settings": "Yra klaida išdėstymo {index} nustatymuose",
diff --git a/public/kirby/i18n/translations/nb.json b/public/kirby/i18n/translations/nb.json
index 7f50aa6..f479dad 100644
--- a/public/kirby/i18n/translations/nb.json
+++ b/public/kirby/i18n/translations/nb.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Skjemaet kunne ikke lagres",
"error.language.code": "Vennligst skriv inn gyldig språkkode",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "Språket eksisterer allerede",
"error.language.name": "Vennligst skriv inn et gyldig navn for språket",
"error.language.notFound": "Finner ikke språket",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "Det er en feilmelding på \"{field}\" feltet i blokk {blockIndex} med bruk av \"{fieldset}\" blokktypen i layout {layoutIndex}",
"error.layout.validation.settings": "Det er en feil i layout {index} innstillinger",
diff --git a/public/kirby/i18n/translations/nl.json b/public/kirby/i18n/translations/nl.json
index 1677160..997697a 100644
--- a/public/kirby/i18n/translations/nl.json
+++ b/public/kirby/i18n/translations/nl.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Het formulier kon niet worden opgeslagen",
"error.language.code": "Vul een geldige code voor deze taal in",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "De taal bestaat al",
"error.language.name": "Vul een geldige naam voor deze taal in",
"error.language.notFound": "De taal kan niet worden gevonden",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "Er is een fout opgetreden bij het \"{field}\" veld in blok {blockIndex} in het \"{fieldset}\" bloktype in layout {layoutIndex}",
"error.layout.validation.settings": "Er is een fout gevonden in de instellingen van ontwerp {index} ",
@@ -617,8 +620,8 @@
"stats.empty": "Geen rapporten",
"status": "Status",
- "system.info.copy": "Copy info",
- "system.info.copied": "System info copied",
+ "system.info.copy": "Kopieer informatie",
+ "system.info.copied": "Systeem informatie gekopieerd",
"system.issues.content": "De content map lijkt zichtbaar te zijn",
"system.issues.eol.kirby": "De geïnstalleerde Kirby versie is niet meer actueel en zal geen verdere beveiligingsupdates meer ontvangen.",
"system.issues.eol.plugin": "De geïnstalleerde versie van plugin { plugin } is niet meer actueel en zal geen verdere beveiligingsupdates meer ontvangen.",
diff --git a/public/kirby/i18n/translations/pl.json b/public/kirby/i18n/translations/pl.json
index 45857ee..26269a4 100644
--- a/public/kirby/i18n/translations/pl.json
+++ b/public/kirby/i18n/translations/pl.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Nie udało się zapisać formularza",
"error.language.code": "Wprowadź poprawny kod języka.",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "Język już istnieje.",
"error.language.name": "Wprowadź poprawną nazwę języka.",
"error.language.notFound": "Język nie został odnaleziony",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "Wystąpił błąd w polu „{field}” w bloku {blockIndex} o typie bloku „{fieldset}” w układzie {layoutIndex}",
"error.layout.validation.settings": "W ustawieniach układu {index} jest błąd",
diff --git a/public/kirby/i18n/translations/pt_BR.json b/public/kirby/i18n/translations/pt_BR.json
index b851dab..6c018ca 100644
--- a/public/kirby/i18n/translations/pt_BR.json
+++ b/public/kirby/i18n/translations/pt_BR.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "O formulário não pôde ser salvo",
"error.language.code": "Por favor entre um código válido para o idioma",
+ "error.language.create.permission": "Não tem permissões para criar um idioma",
+ "error.language.delete.permission": "Não tem permissões para eliminar o idioma",
"error.language.duplicate": "O idioma já existe",
"error.language.name": "Por favor entre um nome válido para o idioma",
"error.language.notFound": "O idioma não foi encontrado",
+ "error.language.update.permission": "Não tem permissões para atualizar o idioma",
"error.layout.validation.block": "Há um erro no campo \"{field}\" no bloco {blockIndex} a usar o tipo de bloco \"{fieldset}\" no layout {layoutIndex}",
"error.layout.validation.settings": "Há um erro na configuração do layout {index}",
@@ -617,8 +620,8 @@
"stats.empty": "Nenhum relatório",
"status": "Estado",
- "system.info.copy": "Copy info",
- "system.info.copied": "System info copied",
+ "system.info.copy": "Copiar informação",
+ "system.info.copied": "Informação de sistema copiada",
"system.issues.content": "A pasta \"content\" parece não estar protegida",
"system.issues.eol.kirby": "A versão instalada do Kirby chegou ao fim da sua vida útil e não irá receber mais atualizações de segurança",
"system.issues.eol.plugin": "A versão instalada do plugin {plugin} chegou ao fim da sua vida útil e não irá receber mais atualizações de segurança",
diff --git a/public/kirby/i18n/translations/pt_PT.json b/public/kirby/i18n/translations/pt_PT.json
index 437e127..574c257 100644
--- a/public/kirby/i18n/translations/pt_PT.json
+++ b/public/kirby/i18n/translations/pt_PT.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Não foi possível guardar o formulário",
"error.language.code": "Por favor, insira um código válido para o idioma",
+ "error.language.create.permission": "Não tem permissões para criar um idioma",
+ "error.language.delete.permission": "Não tem permissões para eliminar o idioma",
"error.language.duplicate": "O idioma já existe",
"error.language.name": "Por favor, insira um nome válido para o idioma",
"error.language.notFound": "Não foi possível encontrar o idioma",
+ "error.language.update.permission": "Não tem permissões para atualizar o idioma",
"error.layout.validation.block": "Há um erro no campo \"{field}\" no bloco {blockIndex} a usar o tipo de bloco \"{fieldset}\" no layout {layoutIndex}",
"error.layout.validation.settings": "Há um erro na configuração do layout {index}",
@@ -617,8 +620,8 @@
"stats.empty": "Sem relatórios",
"status": "Estado",
- "system.info.copy": "Copy info",
- "system.info.copied": "System info copied",
+ "system.info.copy": "Copiar informação",
+ "system.info.copied": "Informação de sistema copiada",
"system.issues.content": "A pasta content parece não estar protegida",
"system.issues.eol.kirby": "A versão instalada do Kirby chegou ao fim da sua vida útil e não irá receber mais atualizações de segurança",
"system.issues.eol.plugin": "A versão instalada do plugin { plugin } chegou ao fim da sua vida útil e não irá receber mais atualizações de segurança",
diff --git a/public/kirby/i18n/translations/ro.json b/public/kirby/i18n/translations/ro.json
index 51cd677..17a022d 100644
--- a/public/kirby/i18n/translations/ro.json
+++ b/public/kirby/i18n/translations/ro.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Formularul nu a putut fi salvat",
"error.language.code": "Te rog introdu un cod valid pentru limbă",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "Limba există deja",
"error.language.name": "Te rog introdu un nume valid pentru limbă",
"error.language.notFound": "Limba nu a fost găsită",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "Există o eroare la câmpul \"{field}\" în blocul {blockIndex} care utilizează tipul de bloc \"{fieldset}\" în aranjamentul {layoutIndex}",
"error.layout.validation.settings": "Există o eroare la setările aranjamentului {index}",
diff --git a/public/kirby/i18n/translations/ru.json b/public/kirby/i18n/translations/ru.json
index 4d4e859..8ce0410 100644
--- a/public/kirby/i18n/translations/ru.json
+++ b/public/kirby/i18n/translations/ru.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Форма не может быть сохранена",
"error.language.code": "Пожалуйста, впишите правильный код языка",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "Язык уже есть",
"error.language.name": "Пожалуйста, впишите правильное название языка",
"error.language.notFound": "Не получилось найти этот язык",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "Ошибка в поле \"{field}\" в блоке {blockIndex} типа \"{fieldset}\" внутри разметки {layoutIndex}",
"error.layout.validation.settings": "Ошибка в настройках макета {index}",
diff --git a/public/kirby/i18n/translations/sk.json b/public/kirby/i18n/translations/sk.json
index f893850..70a43c9 100644
--- a/public/kirby/i18n/translations/sk.json
+++ b/public/kirby/i18n/translations/sk.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Formulár sa nepodarilo uložiť",
"error.language.code": "Please enter a valid code for the language",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "The language already exists",
"error.language.name": "Please enter a valid name for the language",
"error.language.notFound": "The language could not be found",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "There's an error on the \"{field}\" field in block {blockIndex} using the \"{fieldset}\" block type in layout {layoutIndex}",
"error.layout.validation.settings": "There's an error in layout {index} settings",
diff --git a/public/kirby/i18n/translations/sv_SE.json b/public/kirby/i18n/translations/sv_SE.json
index b390a61..feb5caf 100644
--- a/public/kirby/i18n/translations/sv_SE.json
+++ b/public/kirby/i18n/translations/sv_SE.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Formuläret kunde inte sparas",
"error.language.code": "Ange en giltig kod för språket",
+ "error.language.create.permission": "You are not allowed to create a language",
+ "error.language.delete.permission": "You are not allowed to delete the language",
"error.language.duplicate": "Språket finns redan",
"error.language.name": "Ange ett giltigt namn för språket",
"error.language.notFound": "Språket hittades inte",
+ "error.language.update.permission": "You are not allowed to update the language",
"error.layout.validation.block": "Det finns ett fel i fältet \"{field}\" i blocket {blockIndex} med blocktypen \"{fieldset}\" i layouten {layoutIndex}",
"error.layout.validation.settings": "Det finns ett fel i inställningarna för layout {index}",
diff --git a/public/kirby/i18n/translations/tr.json b/public/kirby/i18n/translations/tr.json
index 16de5e3..6d529f4 100644
--- a/public/kirby/i18n/translations/tr.json
+++ b/public/kirby/i18n/translations/tr.json
@@ -126,9 +126,12 @@
"error.form.notSaved": "Form kaydedilemedi",
"error.language.code": "Lütfen dil için geçerli bir kod girin",
+ "error.language.create.permission": "Bir dil oluşturmanıza izin verilmiyor",
+ "error.language.delete.permission": "Dili silmenize izin verilmiyor",
"error.language.duplicate": "Bu dil zaten var",
"error.language.name": "Lütfen dil için geçerli bir isim girin",
"error.language.notFound": "Dil bulunamadı",
+ "error.language.update.permission": "Dili güncellemenize izin verilmiyor",
"error.layout.validation.block": "{layoutIndex}. sıradaki düzende \"{fieldset}\" blok türünü kullanan {blockIndex}. bloktaki \"{field}\" alanında bir hata var",
"error.layout.validation.settings": "{index}. düzen ayarlarında bir hata var",
@@ -617,8 +620,8 @@
"stats.empty": "Rapor yok",
"status": "Durum",
- "system.info.copy": "Copy info",
- "system.info.copied": "System info copied",
+ "system.info.copy": "Bilgileri kopyala",
+ "system.info.copied": "Sistem bilgisi kopyalandı",
"system.issues.content": "İçerik klasörü açığa çıkmış görünüyor",
"system.issues.eol.kirby": "Yüklü Kirby sürümünüz kullanım ömrünün sonuna ulaştı ve daha fazla güvenlik güncellemesi almayacak",
"system.issues.eol.plugin": "{ plugin } eklentisinin yüklü sürümü kullanım ömrünün sonuna ulaştı ve daha fazla güvenlik güncellemesi almayacak",
diff --git a/public/kirby/src/Cms/App.php b/public/kirby/src/Cms/App.php
index a29aa14..32044c7 100644
--- a/public/kirby/src/Cms/App.php
+++ b/public/kirby/src/Cms/App.php
@@ -782,7 +782,7 @@ class App
if ($input instanceof Page) {
try {
$html = $input->render();
- } catch (ErrorPageException $e) {
+ } catch (ErrorPageException|NotFoundException $e) {
return $this->io($e);
}
diff --git a/public/kirby/src/Cms/AppErrors.php b/public/kirby/src/Cms/AppErrors.php
index 43dba9c..677a9c3 100644
--- a/public/kirby/src/Cms/AppErrors.php
+++ b/public/kirby/src/Cms/AppErrors.php
@@ -190,8 +190,19 @@ trait AppErrors
protected function getAdditionalWhoopsHandler(): CallbackHandler
{
return new CallbackHandler(function ($exception, $inspector, $run) {
- $this->trigger('system.exception', compact('exception'));
- error_log($exception);
+ $isLogged = true;
+
+ // allow hook to modify whether the exception should be logged
+ $isLogged = $this->apply(
+ 'system.exception',
+ compact('exception', 'isLogged'),
+ 'isLogged'
+ );
+
+ if ($isLogged !== false) {
+ error_log($exception);
+ }
+
return Handler::DONE;
});
}
diff --git a/public/kirby/src/Cms/AppPlugins.php b/public/kirby/src/Cms/AppPlugins.php
index 11b6c45..8cfeae3 100644
--- a/public/kirby/src/Cms/AppPlugins.php
+++ b/public/kirby/src/Cms/AppPlugins.php
@@ -717,7 +717,7 @@ trait AppPlugins
array $info = [],
string|null $root = null,
string|null $version = null
- ): PLugin|null {
+ ): Plugin|null {
if ($extends === null) {
return static::$plugins[$name] ?? null;
}
diff --git a/public/kirby/src/Cms/File.php b/public/kirby/src/Cms/File.php
index 41208db..4fac82c 100644
--- a/public/kirby/src/Cms/File.php
+++ b/public/kirby/src/Cms/File.php
@@ -624,7 +624,7 @@ class File extends ModelWithContent
* Page URL and the filename as a more stable
* alternative for the media URLs.
*/
- public function previewUrl(): string
+ public function previewUrl(): string|null
{
$parent = $this->parent();
$url = Url::to($this->id());
@@ -633,6 +633,12 @@ class File extends ModelWithContent
case 'page':
$preview = $parent->blueprint()->preview();
+ // user has no permission to preview page,
+ // also return null for file preview
+ if ($preview === false) {
+ return null;
+ }
+
// the page has a custom preview setting,
// thus the file is only accessible through
// the direct media URL
diff --git a/public/kirby/src/Cms/FileRules.php b/public/kirby/src/Cms/FileRules.php
index 33f25e3..84a93ec 100644
--- a/public/kirby/src/Cms/FileRules.php
+++ b/public/kirby/src/Cms/FileRules.php
@@ -315,7 +315,7 @@ class FileRules
public static function validMime(File $file, string $mime = null): bool
{
// make it easier to compare the mime
- $mime = strtolower($mime);
+ $mime = strtolower($mime ?? '');
if (empty($mime)) {
throw new InvalidArgumentException([
diff --git a/public/kirby/src/Cms/Language.php b/public/kirby/src/Cms/Language.php
index 5921867..d9e3eba 100644
--- a/public/kirby/src/Cms/Language.php
+++ b/public/kirby/src/Cms/Language.php
@@ -6,6 +6,7 @@ use Kirby\Data\Data;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\LogicException;
+use Kirby\Exception\PermissionException;
use Kirby\Filesystem\F;
use Kirby\Toolkit\Locale;
use Kirby\Toolkit\Str;
@@ -145,8 +146,17 @@ class Language
*/
public static function create(array $props): static
{
+ $kirby = App::instance();
+ $user = $kirby->user();
+
+ if (
+ $user === null ||
+ $user->role()->permissions()->for('languages', 'create') === false
+ ) {
+ throw new PermissionException(['key' => 'language.create.permission']);
+ }
+
$props['code'] = Str::slug($props['code'] ?? null);
- $kirby = App::instance();
$languages = $kirby->languages();
// make the first language the default language
@@ -204,8 +214,16 @@ class Language
public function delete(): bool
{
$kirby = App::instance();
+ $user = $kirby->user();
$code = $this->code();
+ if (
+ $user === null ||
+ $user->role()->permissions()->for('languages', 'delete') === false
+ ) {
+ throw new PermissionException(['key' => 'language.delete.permission']);
+ }
+
if ($this->isDeletable() === false) {
throw new Exception('The language cannot be deleted');
}
@@ -497,13 +515,22 @@ class Language
*/
public function update(array $props = null): static
{
+ $kirby = App::instance();
+ $user = $kirby->user();
+
+ if (
+ $user === null ||
+ $user->role()->permissions()->for('languages', 'update') === false
+ ) {
+ throw new PermissionException(['key' => 'language.update.permission']);
+ }
+
// don't change the language code
unset($props['code']);
// make sure the slug is nice and clean
$props['slug'] = Str::slug($props['slug'] ?? null);
- $kirby = App::instance();
$updated = $this->clone($props);
if (isset($props['translations']) === true) {
diff --git a/public/kirby/src/Cms/LanguageRouter.php b/public/kirby/src/Cms/LanguageRouter.php
index bca5daf..13ab2d1 100644
--- a/public/kirby/src/Cms/LanguageRouter.php
+++ b/public/kirby/src/Cms/LanguageRouter.php
@@ -7,6 +7,7 @@ use Kirby\Exception\NotFoundException;
use Kirby\Http\Router;
use Kirby\Toolkit\A;
use Kirby\Toolkit\Str;
+use Kirby\Uuid\Uuid;
/**
* The language router is used internally
@@ -84,6 +85,27 @@ class LanguageRouter
}
}
+ // Language-specific UUID URLs
+ $routes[] = [
+ 'pattern' => '@/(page|file)/(:all)',
+ 'method' => 'ALL',
+ 'env' => 'site',
+ 'action' => function (string $languageCode, string $type, string $id) use ($kirby, $language) {
+ // try to resolve to model, but only from UUID cache;
+ // this ensures that only existing UUIDs can be queried
+ // and attackers can't force Kirby to go through the whole
+ // site index with a non-existing UUID
+ if ($model = Uuid::for($type . '://' . $id)?->model(true)) {
+ return $kirby
+ ->response()
+ ->redirect($model->url($language->code()));
+ }
+
+ // render the error page
+ return false;
+ }
+ ];
+
return $routes;
}
diff --git a/public/kirby/src/Cms/LanguageRoutes.php b/public/kirby/src/Cms/LanguageRoutes.php
index 68bdf42..201fbd4 100644
--- a/public/kirby/src/Cms/LanguageRoutes.php
+++ b/public/kirby/src/Cms/LanguageRoutes.php
@@ -3,6 +3,7 @@
namespace Kirby\Cms;
use Kirby\Filesystem\F;
+use Kirby\Toolkit\Str;
class LanguageRoutes
{
@@ -29,9 +30,26 @@ class LanguageRoutes
'pattern' => $language->pattern(),
'method' => 'ALL',
'env' => 'site',
- 'action' => function ($path = null) use ($language) {
+ 'action' => function ($path = null) use ($kirby, $language) {
$result = $language->router()->call($path);
+ // redirect secondary-language pages that have
+ // been accessed with non-translated slugs in their path
+ // to their fully translated URL
+ if ($path !== null && $result instanceof Page) {
+ if (Str::endsWith($result->url(), $path) === false) {
+ $url = $result->url();
+ $query = $kirby->request()->query()->toString();
+
+ // preserve query across redirect
+ if (empty($query) === false) {
+ $url .= '?' . $query;
+ }
+
+ return $kirby->response()->redirect($url);
+ }
+ }
+
// explicitly test for null as $result can
// contain falsy values that should still be returned
if ($result !== null) {
diff --git a/public/kirby/src/Cms/License.php b/public/kirby/src/Cms/License.php
index 3ec3869..4aa09af 100644
--- a/public/kirby/src/Cms/License.php
+++ b/public/kirby/src/Cms/License.php
@@ -22,7 +22,7 @@ use Throwable;
*/
class License
{
- protected const HISTORY = [
+ public const HISTORY = [
'3' => '2019-02-05',
'4' => '2023-11-28'
];
diff --git a/public/kirby/src/Cms/ModelPermissions.php b/public/kirby/src/Cms/ModelPermissions.php
index 0b8cb56..a700b26 100644
--- a/public/kirby/src/Cms/ModelPermissions.php
+++ b/public/kirby/src/Cms/ModelPermissions.php
@@ -43,8 +43,16 @@ abstract class ModelPermissions
return $this->toArray();
}
- public function can(string $action): bool
- {
+ /**
+ * Returns whether the current user is allowed to do
+ * a certain action on the model
+ *
+ * @param bool $default Will be returned if $action does not exist
+ */
+ public function can(
+ string $action,
+ bool $default = false
+ ): bool {
$user = $this->user->id();
$role = $this->user->role()->id();
@@ -95,12 +103,20 @@ abstract class ModelPermissions
}
}
- return $this->permissions->for($this->category, $action);
+ return $this->permissions->for($this->category, $action, $default);
}
- public function cannot(string $action): bool
- {
- return $this->can($action) === false;
+ /**
+ * Returns whether the current user is not allowed to do
+ * a certain action on the model
+ *
+ * @param bool $default Will be returned if $action does not exist
+ */
+ public function cannot(
+ string $action,
+ bool $default = true
+ ): bool {
+ return $this->can($action, !$default) === false;
}
public function toArray(): array
diff --git a/public/kirby/src/Cms/PageBlueprint.php b/public/kirby/src/Cms/PageBlueprint.php
index 79be695..6015fbb 100644
--- a/public/kirby/src/Cms/PageBlueprint.php
+++ b/public/kirby/src/Cms/PageBlueprint.php
@@ -180,7 +180,7 @@ class PageBlueprint extends Blueprint
return $this->model->toString($preview);
}
- return $preview;
+ return $this->model->permissions()->can('preview', true);
}
/**
diff --git a/public/kirby/src/Cms/Permissions.php b/public/kirby/src/Cms/Permissions.php
index 20ba4ea..731eebc 100644
--- a/public/kirby/src/Cms/Permissions.php
+++ b/public/kirby/src/Cms/Permissions.php
@@ -41,7 +41,8 @@ class Permissions
],
'languages' => [
'create' => true,
- 'delete' => true
+ 'delete' => true,
+ 'update' => true
],
'pages' => [
'access' => true,
@@ -109,18 +110,21 @@ class Permissions
}
}
- public function for(string $category = null, string $action = null): bool
- {
+ public function for(
+ string|null $category = null,
+ string|null $action = null,
+ bool $default = false
+ ): bool {
if ($action === null) {
if ($this->hasCategory($category) === false) {
- return false;
+ return $default;
}
return $this->actions[$category];
}
if ($this->hasAction($category, $action) === false) {
- return false;
+ return $default;
}
return $this->actions[$category][$action];
diff --git a/public/kirby/src/Cms/SiteBlueprint.php b/public/kirby/src/Cms/SiteBlueprint.php
index 5f8b751..65b179c 100644
--- a/public/kirby/src/Cms/SiteBlueprint.php
+++ b/public/kirby/src/Cms/SiteBlueprint.php
@@ -51,6 +51,6 @@ class SiteBlueprint extends Blueprint
return $this->model->toString($preview);
}
- return $preview;
+ return $this->model->permissions()->can('preview', true);
}
}
diff --git a/public/kirby/src/Filesystem/Filename.php b/public/kirby/src/Filesystem/Filename.php
index a73891e..eaef058 100644
--- a/public/kirby/src/Filesystem/Filename.php
+++ b/public/kirby/src/Filesystem/Filename.php
@@ -2,6 +2,8 @@
namespace Kirby\Filesystem;
+use Kirby\Cms\App;
+use Kirby\Cms\Language;
use Kirby\Toolkit\Str;
/**
@@ -28,44 +30,32 @@ use Kirby\Toolkit\Str;
*/
class Filename
{
- /**
- * List of all applicable attributes
- */
- protected array $attributes;
-
/**
* The sanitized file extension
*/
protected string $extension;
- /**
- * The source original filename
- */
- protected string $filename;
-
/**
* The sanitized file name
*/
protected string $name;
- /**
- * The template for the final name
- */
- protected string $template;
-
/**
* Creates a new Filename object
+ *
+ * @param string $template for the final name
+ * @param array $attributes List of all applicable attributes
*/
- public function __construct(string $filename, string $template, array $attributes = [])
- {
- $this->filename = $filename;
- $this->template = $template;
- $this->attributes = $attributes;
- $this->extension = $this->sanitizeExtension(
+ public function __construct(
+ protected string $filename,
+ protected string $template,
+ protected array $attributes = []
+ ) {
+ $this->name = $this->sanitizeName($filename);
+ $this->extension = $this->sanitizeExtension(
$attributes['format'] ??
pathinfo($filename, PATHINFO_EXTENSION)
);
- $this->name = $this->sanitizeName($filename);
}
/**
@@ -242,7 +232,24 @@ class Filename
*/
protected function sanitizeName(string $name): string
{
- return F::safeBasename($name);
+ // temporarily store language rules
+ $rules = Str::$language;
+ $kirby = App::instance(null, true);
+
+ // if current user, add rules for their language to `Str` class
+ if ($user = $kirby?->user()) {
+ Str::$language = [
+ ...Str::$language,
+ ...Language::loadRules($user->language())];
+ }
+
+ // sanitize name
+ $name = F::safeBasename($this->filename);
+
+ // restore language rules
+ Str::$language = $rules;
+
+ return $name;
}
/**
diff --git a/public/kirby/src/Form/Validations.php b/public/kirby/src/Form/Validations.php
index 7f0a539..cf1235a 100644
--- a/public/kirby/src/Form/Validations.php
+++ b/public/kirby/src/Form/Validations.php
@@ -162,11 +162,18 @@ class Validations
*/
public static function pattern(Field|FieldClass $field, mixed $value): bool
{
- if ($field->isEmpty($value) === false && $field->pattern() !== null) {
- if (V::match($value, '/' . $field->pattern() . '/i') === false) {
- throw new InvalidArgumentException(
- V::message('match')
- );
+ if ($field->isEmpty($value) === false) {
+ if ($pattern = $field->pattern()) {
+ // ensure that that pattern needs to match the whole
+ // input value from start to end, not just a partial match
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern#overview
+ $pattern = '^(?:' . $pattern . ')$';
+
+ if (V::match($value, '/' . $pattern . '/i') === false) {
+ throw new InvalidArgumentException(
+ V::message('match')
+ );
+ }
}
}
diff --git a/public/kirby/src/Http/Environment.php b/public/kirby/src/Http/Environment.php
index 413f07c..bb8f4f6 100644
--- a/public/kirby/src/Http/Environment.php
+++ b/public/kirby/src/Http/Environment.php
@@ -602,13 +602,7 @@ class Environment
*/
protected function detectRequestUri(string|null $requestUri = null): Uri
{
- // make sure the URL parser works properly when there's a
- // colon in the request URI but the URI is relative
- if (Url::isAbsolute($requestUri) === false) {
- $requestUri = 'https://getkirby.com' . $requestUri;
- }
-
- $uri = new Uri($requestUri);
+ $uri = new Uri($requestUri ?? '');
// create the URI object as a combination of base uri parts
// and the parts from REQUEST_URI
diff --git a/public/kirby/src/Http/Uri.php b/public/kirby/src/Http/Uri.php
index 08c4618..9cd951c 100644
--- a/public/kirby/src/Http/Uri.php
+++ b/public/kirby/src/Http/Uri.php
@@ -88,7 +88,16 @@ class Uri
public function __construct(array|string $props = [], array $inject = [])
{
if (is_string($props) === true) {
- $props = parse_url($props);
+ // make sure the URL parser works properly when there's a
+ // colon in the string but the string is a relative URL
+ if (Url::isAbsolute($props) === false) {
+ $props = 'https://getkirby.com/' . $props;
+ $props = parse_url($props);
+ unset($props['scheme'], $props['host']);
+ } else {
+ $props = parse_url($props);
+ }
+
$props['username'] = $props['user'] ?? null;
$props['password'] = $props['pass'] ?? null;
diff --git a/public/kirby/src/Panel/File.php b/public/kirby/src/Panel/File.php
index d58e7a1..de31234 100644
--- a/public/kirby/src/Panel/File.php
+++ b/public/kirby/src/Panel/File.php
@@ -352,12 +352,15 @@ class File extends Model
$id = $this->model->id();
if (empty($params['model']) === false) {
- $parent = $this->model->parent();
+ $parent = $this->model->parent();
+ $absolute = $parent !== $params['model'];
// if the file belongs to the current parent model,
// store only name as ID to keep its path relative to the model
- $id = $parent === $params['model'] ? $name : $id;
- $absolute = $parent !== $params['model'];
+ $id = match ($absolute) {
+ true => $id,
+ false => $name
+ };
}
$params['text'] ??= '{{ file.filename }}';
@@ -399,6 +402,7 @@ class File extends Model
'template' => $file->template(),
'type' => $file->type(),
'url' => $file->url(),
+ 'uuid' => fn () => $file->uuid()?->toString(),
],
'preview' => [
'focusable' => $this->isFocusable(),
diff --git a/public/kirby/src/Panel/Model.php b/public/kirby/src/Panel/Model.php
index d0a8d88..9391780 100644
--- a/public/kirby/src/Panel/Model.php
+++ b/public/kirby/src/Panel/Model.php
@@ -335,7 +335,7 @@ abstract class Model
'link' => $this->url(true),
'sortable' => true,
'text' => $this->model->toSafeString($params['text'] ?? false),
- 'uuid' => $this->model->uuid()?->toString() ?? $this->model->id(),
+ 'uuid' => $this->model->uuid()?->toString()
];
}
diff --git a/public/kirby/src/Panel/Page.php b/public/kirby/src/Panel/Page.php
index 72c564c..40801c9 100644
--- a/public/kirby/src/Panel/Page.php
+++ b/public/kirby/src/Panel/Page.php
@@ -339,6 +339,7 @@ class Page extends Model
'previewUrl' => $page->previewUrl(),
'status' => $page->status(),
'title' => $page->title()->toString(),
+ 'uuid' => fn () => $page->uuid()?->toString(),
],
'status' => function () use ($page) {
if ($status = $page->status()) {
diff --git a/public/kirby/src/Panel/Site.php b/public/kirby/src/Panel/Site.php
index 68f7892..7286098 100644
--- a/public/kirby/src/Panel/Site.php
+++ b/public/kirby/src/Panel/Site.php
@@ -71,6 +71,7 @@ class Site extends Model
'link' => $this->url(true),
'previewUrl' => $this->model->previewUrl(),
'title' => $this->model->title()->toString(),
+ 'uuid' => fn () => $this->model->uuid()?->toString(),
]
]);
}
diff --git a/public/kirby/src/Panel/User.php b/public/kirby/src/Panel/User.php
index e0a5149..1aa8cf5 100644
--- a/public/kirby/src/Panel/User.php
+++ b/public/kirby/src/Panel/User.php
@@ -237,6 +237,7 @@ class User extends Model
'name' => $user->name()->toString(),
'role' => $user->role()->title(),
'username' => $user->username(),
+ 'uuid' => fn () => $user->uuid()?->toString()
]
]
);
diff --git a/public/kirby/src/Toolkit/A.php b/public/kirby/src/Toolkit/A.php
index b30b836..1970cc9 100644
--- a/public/kirby/src/Toolkit/A.php
+++ b/public/kirby/src/Toolkit/A.php
@@ -738,12 +738,18 @@ class A
/**
* Returns a number of random elements from an array,
* either in original or shuffled order
+ *
+ * @throws \Exception When $count is larger than array length
*/
public static function random(
array $array,
int $count = 1,
bool $shuffle = false
): array {
+ if ($count > count($array)) {
+ throw new InvalidArgumentException('$count is larger than available array items');
+ }
+
if ($shuffle === true) {
return array_slice(self::shuffle($array), 0, $count);
}
diff --git a/public/kirby/src/Uuid/Uuid.php b/public/kirby/src/Uuid/Uuid.php
index b8f310a..f0ea37a 100644
--- a/public/kirby/src/Uuid/Uuid.php
+++ b/public/kirby/src/Uuid/Uuid.php
@@ -12,6 +12,7 @@ use Kirby\Cms\Site;
use Kirby\Cms\User;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\LogicException;
+use Kirby\Exception\NotFoundException;
use Kirby\Toolkit\Str;
/**
@@ -217,7 +218,13 @@ abstract class Uuid
return (static::$generator)($length);
}
- if (App::instance()->option('content.uuid') === 'uuid-v4') {
+ $option = App::instance()->option('content.uuid');
+
+ if (is_array($option) === true) {
+ $option = $option['format'] ?? null;
+ }
+
+ if ($option === 'uuid-v4') {
return Str::uuid();
}
@@ -332,6 +339,10 @@ abstract class Uuid
}
if ($lazy === false) {
+ if (App::instance()->option('content.uuid.index') === false) {
+ throw new NotFoundException('Model for UUID ' . $this->uri->toString() . ' could not be found without searching in the site index');
+ }
+
if ($this->model = $this->findByIndex()) {
// lazily fill cache by writing to cache
// whenever looked up from index to speed
diff --git a/public/kirby/vendor/autoload.php b/public/kirby/vendor/autoload.php
new file mode 100644
index 0000000..26273e0
--- /dev/null
+++ b/public/kirby/vendor/autoload.php
@@ -0,0 +1,25 @@
+realpath = realpath($opened_path) ?: $opened_path;
+ $opened_path = $this->realpath;
+ $this->handle = fopen($this->realpath, $mode);
+ $this->position = 0;
+
+ return (bool) $this->handle;
+ }
+
+ public function stream_read($count)
+ {
+ $data = fread($this->handle, $count);
+
+ if ($this->position === 0) {
+ $data = preg_replace('{^#!.*\r?\n}', '', $data);
+ }
+
+ $this->position += strlen($data);
+
+ return $data;
+ }
+
+ public function stream_cast($castAs)
+ {
+ return $this->handle;
+ }
+
+ public function stream_close()
+ {
+ fclose($this->handle);
+ }
+
+ public function stream_lock($operation)
+ {
+ return $operation ? flock($this->handle, $operation) : true;
+ }
+
+ public function stream_seek($offset, $whence)
+ {
+ if (0 === fseek($this->handle, $offset, $whence)) {
+ $this->position = ftell($this->handle);
+ return true;
+ }
+
+ return false;
+ }
+
+ public function stream_tell()
+ {
+ return $this->position;
+ }
+
+ public function stream_eof()
+ {
+ return feof($this->handle);
+ }
+
+ public function stream_stat()
+ {
+ return array();
+ }
+
+ public function stream_set_option($option, $arg1, $arg2)
+ {
+ return true;
+ }
+
+ public function url_stat($path, $flags)
+ {
+ $path = substr($path, 17);
+ if (file_exists($path)) {
+ return stat($path);
+ }
+
+ return false;
+ }
+ }
+ }
+
+ if (
+ (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
+ || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
+ ) {
+ return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint');
+ }
+}
+
+return include __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint';
diff --git a/public/kirby/vendor/christian-riesen/base32/LICENSE b/public/kirby/vendor/christian-riesen/base32/LICENSE
new file mode 100644
index 0000000..624fceb
--- /dev/null
+++ b/public/kirby/vendor/christian-riesen/base32/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2013-2014 Christian Riesen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/public/kirby/vendor/christian-riesen/base32/src/Base32.php b/public/kirby/vendor/christian-riesen/base32/src/Base32.php
new file mode 100644
index 0000000..d71f135
--- /dev/null
+++ b/public/kirby/vendor/christian-riesen/base32/src/Base32.php
@@ -0,0 +1,168 @@
+
+ * @author Sam Williams [>=M, !=N,
getEnd()->getOperator() === '<' && $i+1 < $count) {
+ $nextInterval = $intervals['numeric'][$i+1];
+ if ($interval->getEnd()->getVersion() === $nextInterval->getStart()->getVersion() && $nextInterval->getStart()->getOperator() === '>') {
+ // only add a start if we didn't already do so, can be skipped if we're looking at second
+ // interval in [>=M, P, =M, !=N] already and we only want to add !=P right now
+ if (\count($unEqualConstraints) === 0 && (string) $interval->getStart() !== (string) Interval::fromZero()) {
+ $unEqualConstraints[] = $interval->getStart();
+ }
+ $unEqualConstraints[] = new Constraint('!=', $interval->getEnd()->getVersion());
+ continue;
+ }
+ }
+
+ if (\count($unEqualConstraints) > 0) {
+ // this is where the end of the following interval of a != constraint is added as explained above
+ if ((string) $interval->getEnd() !== (string) Interval::untilPositiveInfinity()) {
+ $unEqualConstraints[] = $interval->getEnd();
+ }
+
+ // count is 1 if entire constraint is just one != expression
+ if (\count($unEqualConstraints) > 1) {
+ $constraints[] = new MultiConstraint($unEqualConstraints, true);
+ } else {
+ $constraints[] = $unEqualConstraints[0];
+ }
+
+ $unEqualConstraints = array();
+ continue;
+ }
+
+ // convert back >= x - <= x intervals to == x
+ if ($interval->getStart()->getVersion() === $interval->getEnd()->getVersion() && $interval->getStart()->getOperator() === '>=' && $interval->getEnd()->getOperator() === '<=') {
+ $constraints[] = new Constraint('==', $interval->getStart()->getVersion());
+ continue;
+ }
+
+ if ((string) $interval->getStart() === (string) Interval::fromZero()) {
+ $constraints[] = $interval->getEnd();
+ } elseif ((string) $interval->getEnd() === (string) Interval::untilPositiveInfinity()) {
+ $constraints[] = $interval->getStart();
+ } else {
+ $constraints[] = new MultiConstraint(array($interval->getStart(), $interval->getEnd()), true);
+ }
+ }
+ }
+
+ $devConstraints = array();
+
+ if (0 === \count($intervals['branches']['names'])) {
+ if ($intervals['branches']['exclude']) {
+ if ($hasNumericMatchAll) {
+ return new MatchAllConstraint;
+ }
+ // otherwise constraint should contain a != operator and already cover this
+ }
+ } else {
+ foreach ($intervals['branches']['names'] as $branchName) {
+ if ($intervals['branches']['exclude']) {
+ $devConstraints[] = new Constraint('!=', $branchName);
+ } else {
+ $devConstraints[] = new Constraint('==', $branchName);
+ }
+ }
+
+ // excluded branches, e.g. != dev-foo are conjunctive with the interval, so
+ // > 2.0 != dev-foo must return a conjunctive constraint
+ if ($intervals['branches']['exclude']) {
+ if (\count($constraints) > 1) {
+ return new MultiConstraint(array_merge(
+ array(new MultiConstraint($constraints, false)),
+ $devConstraints
+ ), true);
+ }
+
+ if (\count($constraints) === 1 && (string)$constraints[0] === (string)Interval::fromZero()) {
+ if (\count($devConstraints) > 1) {
+ return new MultiConstraint($devConstraints, true);
+ }
+ return $devConstraints[0];
+ }
+
+ return new MultiConstraint(array_merge($constraints, $devConstraints), true);
+ }
+
+ // otherwise devConstraints contains a list of == operators for branches which are disjunctive with the
+ // rest of the constraint
+ $constraints = array_merge($constraints, $devConstraints);
+ }
+
+ if (\count($constraints) > 1) {
+ return new MultiConstraint($constraints, false);
+ }
+
+ if (\count($constraints) === 1) {
+ return $constraints[0];
+ }
+
+ return new MatchNoneConstraint;
+ }
+
+ /**
+ * Creates an array of numeric intervals and branch constraints representing a given constraint
+ *
+ * if the returned numeric array is empty it means the constraint matches nothing in the numeric range (0 - +inf)
+ * if the returned branches array is empty it means no dev-* versions are matched
+ * if a constraint matches all possible dev-* versions, branches will contain Interval::anyDev()
+ *
+ * @return array
+ * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}}
+ */
+ public static function get(ConstraintInterface $constraint)
+ {
+ $key = (string) $constraint;
+
+ if (!isset(self::$intervalsCache[$key])) {
+ self::$intervalsCache[$key] = self::generateIntervals($constraint);
+ }
+
+ return self::$intervalsCache[$key];
+ }
+
+ /**
+ * @param bool $stopOnFirstValidInterval
+ *
+ * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}}
+ */
+ private static function generateIntervals(ConstraintInterface $constraint, $stopOnFirstValidInterval = false)
+ {
+ if ($constraint instanceof MatchAllConstraint) {
+ return array('numeric' => array(new Interval(Interval::fromZero(), Interval::untilPositiveInfinity())), 'branches' => Interval::anyDev());
+ }
+
+ if ($constraint instanceof MatchNoneConstraint) {
+ return array('numeric' => array(), 'branches' => array('names' => array(), 'exclude' => false));
+ }
+
+ if ($constraint instanceof Constraint) {
+ return self::generateSingleConstraintIntervals($constraint);
+ }
+
+ if (!$constraint instanceof MultiConstraint) {
+ throw new \UnexpectedValueException('The constraint passed in should be an MatchAllConstraint, Constraint or MultiConstraint instance, got '.\get_class($constraint).'.');
+ }
+
+ $constraints = $constraint->getConstraints();
+
+ $numericGroups = array();
+ $constraintBranches = array();
+ foreach ($constraints as $c) {
+ $res = self::get($c);
+ $numericGroups[] = $res['numeric'];
+ $constraintBranches[] = $res['branches'];
+ }
+
+ if ($constraint->isDisjunctive()) {
+ $branches = Interval::noDev();
+ foreach ($constraintBranches as $b) {
+ if ($b['exclude']) {
+ if ($branches['exclude']) {
+ // disjunctive constraint, so only exclude what's excluded in all constraints
+ // !=a,!=b || !=b,!=c => !=b
+ $branches['names'] = array_intersect($branches['names'], $b['names']);
+ } else {
+ // disjunctive constraint so exclude all names which are not explicitly included in the alternative
+ // (==b || ==c) || !=a,!=b => !=a
+ $branches['exclude'] = true;
+ $branches['names'] = array_diff($b['names'], $branches['names']);
+ }
+ } else {
+ if ($branches['exclude']) {
+ // disjunctive constraint so exclude all names which are not explicitly included in the alternative
+ // !=a,!=b || (==b || ==c) => !=a
+ $branches['names'] = array_diff($branches['names'], $b['names']);
+ } else {
+ // disjunctive constraint, so just add all the other branches
+ // (==a || ==b) || ==c => ==a || ==b || ==c
+ $branches['names'] = array_merge($branches['names'], $b['names']);
+ }
+ }
+ }
+ } else {
+ $branches = Interval::anyDev();
+ foreach ($constraintBranches as $b) {
+ if ($b['exclude']) {
+ if ($branches['exclude']) {
+ // conjunctive, so just add all branch names to be excluded
+ // !=a && !=b => !=a,!=b
+ $branches['names'] = array_merge($branches['names'], $b['names']);
+ } else {
+ // conjunctive, so only keep included names which are not excluded
+ // (==a||==c) && !=a,!=b => ==c
+ $branches['names'] = array_diff($branches['names'], $b['names']);
+ }
+ } else {
+ if ($branches['exclude']) {
+ // conjunctive, so only keep included names which are not excluded
+ // !=a,!=b && (==a||==c) => ==c
+ $branches['names'] = array_diff($b['names'], $branches['names']);
+ $branches['exclude'] = false;
+ } else {
+ // conjunctive, so only keep names that are included in both
+ // (==a||==b) && (==a||==c) => ==a
+ $branches['names'] = array_intersect($branches['names'], $b['names']);
+ }
+ }
+ }
+ }
+
+ $branches['names'] = array_unique($branches['names']);
+
+ if (\count($numericGroups) === 1) {
+ return array('numeric' => $numericGroups[0], 'branches' => $branches);
+ }
+
+ $borders = array();
+ foreach ($numericGroups as $group) {
+ foreach ($group as $interval) {
+ $borders[] = array('version' => $interval->getStart()->getVersion(), 'operator' => $interval->getStart()->getOperator(), 'side' => 'start');
+ $borders[] = array('version' => $interval->getEnd()->getVersion(), 'operator' => $interval->getEnd()->getOperator(), 'side' => 'end');
+ }
+ }
+
+ $opSortOrder = self::$opSortOrder;
+ usort($borders, function ($a, $b) use ($opSortOrder) {
+ $order = version_compare($a['version'], $b['version']);
+ if ($order === 0) {
+ return $opSortOrder[$a['operator']] - $opSortOrder[$b['operator']];
+ }
+
+ return $order;
+ });
+
+ $activeIntervals = 0;
+ $intervals = array();
+ $index = 0;
+ $activationThreshold = $constraint->isConjunctive() ? \count($numericGroups) : 1;
+ $start = null;
+ foreach ($borders as $border) {
+ if ($border['side'] === 'start') {
+ $activeIntervals++;
+ } else {
+ $activeIntervals--;
+ }
+ if (!$start && $activeIntervals >= $activationThreshold) {
+ $start = new Constraint($border['operator'], $border['version']);
+ } elseif ($start && $activeIntervals < $activationThreshold) {
+ // filter out invalid intervals like > x - <= x, or >= x - < x
+ if (
+ version_compare($start->getVersion(), $border['version'], '=')
+ && (
+ ($start->getOperator() === '>' && $border['operator'] === '<=')
+ || ($start->getOperator() === '>=' && $border['operator'] === '<')
+ )
+ ) {
+ unset($intervals[$index]);
+ } else {
+ $intervals[$index] = new Interval($start, new Constraint($border['operator'], $border['version']));
+ $index++;
+
+ if ($stopOnFirstValidInterval) {
+ break;
+ }
+ }
+
+ $start = null;
+ }
+ }
+
+ return array('numeric' => $intervals, 'branches' => $branches);
+ }
+
+ /**
+ * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}}
+ */
+ private static function generateSingleConstraintIntervals(Constraint $constraint)
+ {
+ $op = $constraint->getOperator();
+
+ // handle branch constraints first
+ if (strpos($constraint->getVersion(), 'dev-') === 0) {
+ $intervals = array();
+ $branches = array('names' => array(), 'exclude' => false);
+
+ // != dev-foo means any numeric version may match, we treat >/< like != they are not really defined for branches
+ if ($op === '!=') {
+ $intervals[] = new Interval(Interval::fromZero(), Interval::untilPositiveInfinity());
+ $branches = array('names' => array($constraint->getVersion()), 'exclude' => true);
+ } elseif ($op === '==') {
+ $branches['names'][] = $constraint->getVersion();
+ }
+
+ return array(
+ 'numeric' => $intervals,
+ 'branches' => $branches,
+ );
+ }
+
+ if ($op[0] === '>') { // > & >=
+ return array('numeric' => array(new Interval($constraint, Interval::untilPositiveInfinity())), 'branches' => Interval::noDev());
+ }
+ if ($op[0] === '<') { // < & <=
+ return array('numeric' => array(new Interval(Interval::fromZero(), $constraint)), 'branches' => Interval::noDev());
+ }
+ if ($op === '!=') {
+ // convert !=x to intervals of 0 -
",document.body.appendChild(t),e=38===t.offsetHeight,document.body.removeChild(t)}return e}()?parseInt:parseFloat)(getComputedStyle(o).lineHeight),p=Prism.util.isActive(o,t),g=o.querySelector("code"),m=p?o:g||o,v=[],y=g.textContent.match(n),b=y?y.length+1:1,A=g&&m!=g?function(e,t){var i=getComputedStyle(e),n=getComputedStyle(t);function r(e){return+e.substr(0,e.length-2)}return t.offsetTop+r(n.borderTopWidth)+r(n.paddingTop)-r(i.paddingTop)}(o,g):0;h.forEach((function(e){var t=e.split("-"),i=+t[0],n=+t[1]||i;if(!((n=Math.min(b+d,n))i&&r.setAttribute("data-end",String(n)),r.style.top=(i-d-1)*f+A+"px",r.textContent=new Array(n-i+2).join(" \n")}));v.push((function(){r.style.width=o.scrollWidth+"px"})),v.push((function(){m.appendChild(r)}))}}));var P=o.id;if(p&&Prism.util.isActive(o,i)&&P){l(o,i)||v.push((function(){o.classList.add(i)}));var E=parseInt(o.getAttribute("data-start")||"1");s(".line-numbers-rows > span",o).forEach((function(e,t){var i=t+E;e.onclick=function(){var e=P+"."+i;r=!1,location.hash=e,setTimeout((function(){r=!0}),1)}}))}return function(){v.forEach(a)}}};var o=0;Prism.hooks.add("before-sanity-check",(function(e){var t=e.element.parentElement;if(u(t)){var i=0;s(".line-highlight",t).forEach((function(e){i+=e.textContent.length,e.parentNode.removeChild(e)})),i&&/^(?: \n)+$/.test(e.code.slice(-i))&&(e.code=e.code.slice(0,-i))}})),Prism.hooks.add("complete",(function e(i){var n=i.element.parentElement;if(u(n)){clearTimeout(o);var r=Prism.plugins.lineNumbers,s=i.plugins&&i.plugins.lineNumbers;l(n,t)&&r&&!s?Prism.hooks.add("line-numbers",e):(Prism.plugins.lineHighlight.highlightLines(n)(),o=setTimeout(c,1))}})),window.addEventListener("hashchange",c),window.addEventListener("resize",(function(){s("pre").filter(u).map((function(e){return Prism.plugins.lineHighlight.highlightLines(e)})).forEach(a)}))}function s(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function l(e,t){return e.classList.contains(t)}function a(e){e()}function u(e){return!!(e&&/pre/i.test(e.nodeName)&&(e.hasAttribute("data-line")||e.id&&Prism.util.isActive(e,i)))}function c(){var e=location.hash.slice(1);s(".temporary.line-highlight").forEach((function(e){e.parentNode.removeChild(e)}));var t=(e.match(/\.([\d,-]+)$/)||[,""])[1];if(t&&!document.getElementById(e)){var i=e.slice(0,e.lastIndexOf(".")),n=document.getElementById(i);n&&(n.hasAttribute("data-line")||n.setAttribute("data-line",""),Prism.plugins.lineHighlight.highlightLines(n,t,"temporary ")(),r&&document.querySelector(".temporary.line-highlight").scrollIntoView())}}}();
+!function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){var e="line-numbers",n=/\n(?!$)/g,t=Prism.plugins.lineNumbers={getLine:function(n,t){if("PRE"===n.tagName&&n.classList.contains(e)){var i=n.querySelector(".line-numbers-rows");if(i){var r=parseInt(n.getAttribute("data-start"),10)||1,s=r+(i.children.length-1);t