Update to Kirby 4.7.0

This commit is contained in:
Paul Nicoué 2025-04-21 18:57:21 +02:00
parent 02a9ab387c
commit ba25a9a198
509 changed files with 26604 additions and 14872 deletions

View file

@ -12,7 +12,7 @@
],
"require": {
"php": ">=8.1.0 <8.4.0",
"getkirby/cms": "^3.10.0",
"getkirby/cms": "^4.0",
"kirbyzone/sitemapper": "^1.2.1",
"mullema/k3-image-clip": "^3.2",
"wearejust/kirby-twig": "^5.0"

313
composer.lock generated
View file

@ -4,20 +4,79 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "cbae6775c72f7f216b3cadb214a0fee3",
"content-hash": "1484ade1a41699eca25b1637f84aaccf",
"packages": [
{
"name": "claviska/simpleimage",
"version": "4.2.0",
"name": "christian-riesen/base32",
"version": "1.6.0",
"source": {
"type": "git",
"url": "https://github.com/claviska/SimpleImage.git",
"reference": "dfbe53c01dae8467468ef2b817c09b786a7839d2"
"url": "https://github.com/ChristianRiesen/base32.git",
"reference": "2e82dab3baa008e24a505649b0d583c31d31e894"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/claviska/SimpleImage/zipball/dfbe53c01dae8467468ef2b817c09b786a7839d2",
"reference": "dfbe53c01dae8467468ef2b817c09b786a7839d2",
"url": "https://api.github.com/repos/ChristianRiesen/base32/zipball/2e82dab3baa008e24a505649b0d583c31d31e894",
"reference": "2e82dab3baa008e24a505649b0d583c31d31e894",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.17",
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^8.5.13 || ^9.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Base32\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Riesen",
"email": "chris.riesen@gmail.com",
"homepage": "http://christianriesen.com",
"role": "Developer"
}
],
"description": "Base32 encoder/decoder according to RFC 4648",
"homepage": "https://github.com/ChristianRiesen/base32",
"keywords": [
"base32",
"decode",
"encode",
"rfc4648"
],
"support": {
"issues": "https://github.com/ChristianRiesen/base32/issues",
"source": "https://github.com/ChristianRiesen/base32/tree/1.6.0"
},
"time": "2021-02-26T10:19:33+00:00"
},
{
"name": "claviska/simpleimage",
"version": "4.2.1",
"source": {
"type": "git",
"url": "https://github.com/claviska/SimpleImage.git",
"reference": "ec6d5021e5a7153a2520d64c59b86b6f3c4157c5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/claviska/SimpleImage/zipball/ec6d5021e5a7153a2520d64c59b86b6f3c4157c5",
"reference": "ec6d5021e5a7153a2520d64c59b86b6f3c4157c5",
"shasum": ""
},
"require": {
@ -49,7 +108,7 @@
"description": "A PHP class that makes working with images as simple as possible.",
"support": {
"issues": "https://github.com/claviska/SimpleImage/issues",
"source": "https://github.com/claviska/SimpleImage/tree/4.2.0"
"source": "https://github.com/claviska/SimpleImage/tree/4.2.1"
},
"funding": [
{
@ -57,28 +116,28 @@
"type": "github"
}
],
"time": "2024-04-15T16:07:16+00:00"
"time": "2024-11-22T13:25:03+00:00"
},
{
"name": "composer/semver",
"version": "3.4.0",
"version": "3.4.3",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
"reference": "35e8d0af4486141bc745f23a29cc2091eb624a32"
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32",
"reference": "35e8d0af4486141bc745f23a29cc2091eb624a32",
"url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1.4",
"symfony/phpunit-bridge": "^4.2 || ^5"
"phpstan/phpstan": "^1.11",
"symfony/phpunit-bridge": "^3 || ^7"
},
"type": "library",
"extra": {
@ -122,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.3"
},
"funding": [
{
@ -138,30 +197,30 @@
"type": "tidelift"
}
],
"time": "2023-08-31T09:50:34+00:00"
"time": "2024-09-19T14:15:21+00:00"
},
{
"name": "filp/whoops",
"version": "2.15.4",
"version": "2.18.0",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546"
"reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546",
"reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546",
"url": "https://api.github.com/repos/filp/whoops/zipball/a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e",
"reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e",
"shasum": ""
},
"require": {
"php": "^5.5.9 || ^7.0 || ^8.0",
"php": "^7.1 || ^8.0",
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"require-dev": {
"mockery/mockery": "^0.9 || ^1.0",
"phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3",
"symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0"
"mockery/mockery": "^1.0",
"phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3",
"symfony/var-dumper": "^4.0 || ^5.0"
},
"suggest": {
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
@ -201,7 +260,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.15.4"
"source": "https://github.com/filp/whoops/tree/2.18.0"
},
"funding": [
{
@ -209,25 +268,26 @@
"type": "github"
}
],
"time": "2023-11-03T12:00:00+00:00"
"time": "2025-03-15T12:00:00+00:00"
},
{
"name": "getkirby/cms",
"version": "3.10.1.1",
"version": "4.7.0",
"source": {
"type": "git",
"url": "https://github.com/getkirby/kirby.git",
"reference": "1953eb4979f6d05ce7779c547bf761517024cccb"
"reference": "938fe98951cace6c77aab744779bf4e0799ad705"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/getkirby/kirby/zipball/1953eb4979f6d05ce7779c547bf761517024cccb",
"reference": "1953eb4979f6d05ce7779c547bf761517024cccb",
"url": "https://api.github.com/repos/getkirby/kirby/zipball/938fe98951cace6c77aab744779bf4e0799ad705",
"reference": "938fe98951cace6c77aab744779bf4e0799ad705",
"shasum": ""
},
"require": {
"claviska/simpleimage": "4.2.0",
"composer/semver": "3.4.0",
"christian-riesen/base32": "1.6.0",
"claviska/simpleimage": "4.2.1",
"composer/semver": "3.4.3",
"ext-ctype": "*",
"ext-curl": "*",
"ext-dom": "*",
@ -239,15 +299,15 @@
"ext-mbstring": "*",
"ext-openssl": "*",
"ext-simplexml": "*",
"filp/whoops": "2.15.4",
"filp/whoops": "2.18.0",
"getkirby/composer-installer": "^1.2.1",
"laminas/laminas-escaper": "2.13.0",
"laminas/laminas-escaper": "2.16.0",
"michelf/php-smartypants": "1.8.1",
"php": "~8.1.0 || ~8.2.0 || ~8.3.0",
"phpmailer/phpmailer": "6.9.1",
"symfony/polyfill-intl-idn": "1.30.0",
"symfony/polyfill-mbstring": "1.30.0",
"symfony/yaml": "6.4.8"
"php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
"phpmailer/phpmailer": "6.9.3",
"symfony/polyfill-intl-idn": "1.31.0",
"symfony/polyfill-mbstring": "1.31.0",
"symfony/yaml": "6.4.18"
},
"replace": {
"symfony/polyfill-php72": "*"
@ -292,7 +352,7 @@
"homepage": "https://getkirby.com"
}
],
"description": "The Kirby 3 core",
"description": "The Kirby core",
"homepage": "https://getkirby.com",
"keywords": [
"cms",
@ -311,7 +371,7 @@
"type": "custom"
}
],
"time": "2024-08-29T08:36:26+00:00"
"time": "2025-03-25T11:15:09+00:00"
},
{
"name": "getkirby/composer-installer",
@ -399,33 +459,32 @@
},
{
"name": "laminas/laminas-escaper",
"version": "2.13.0",
"version": "2.16.0",
"source": {
"type": "git",
"url": "https://github.com/laminas/laminas-escaper.git",
"reference": "af459883f4018d0f8a0c69c7a209daef3bf973ba"
"reference": "9cf1f5317ca65b4fd5c6a3c2855e24a187b288c8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/af459883f4018d0f8a0c69c7a209daef3bf973ba",
"reference": "af459883f4018d0f8a0c69c7a209daef3bf973ba",
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/9cf1f5317ca65b4fd5c6a3c2855e24a187b288c8",
"reference": "9cf1f5317ca65b4fd5c6a3c2855e24a187b288c8",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-mbstring": "*",
"php": "~8.1.0 || ~8.2.0 || ~8.3.0"
"php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
},
"conflict": {
"zendframework/zend-escaper": "*"
},
"require-dev": {
"infection/infection": "^0.27.0",
"laminas/laminas-coding-standard": "~2.5.0",
"maglnet/composer-require-checker": "^3.8.0",
"phpunit/phpunit": "^9.6.7",
"psalm/plugin-phpunit": "^0.18.4",
"vimeo/psalm": "^5.9"
"infection/infection": "^0.29.8",
"laminas/laminas-coding-standard": "~3.0.1",
"phpunit/phpunit": "^10.5.45",
"psalm/plugin-phpunit": "^0.19.2",
"vimeo/psalm": "^6.6.2"
},
"type": "library",
"autoload": {
@ -457,7 +516,7 @@
"type": "community_bridge"
}
],
"time": "2023-10-10T08:35:13+00:00"
"time": "2025-02-17T12:40:19+00:00"
},
{
"name": "league/color-extractor",
@ -618,16 +677,16 @@
},
{
"name": "phpmailer/phpmailer",
"version": "v6.9.1",
"version": "v6.9.3",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "039de174cd9c17a8389754d3b877a2ed22743e18"
"reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18",
"reference": "039de174cd9c17a8389754d3b877a2ed22743e18",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2f5c94fe7493efc213f643c23b1b1c249d40f47e",
"reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e",
"shasum": ""
},
"require": {
@ -687,7 +746,7 @@
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"support": {
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.1"
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.3"
},
"funding": [
{
@ -695,7 +754,7 @@
"type": "github"
}
],
"time": "2023-11-25T22:23:28+00:00"
"time": "2024-11-24T18:04:13+00:00"
},
{
"name": "psr/log",
@ -895,22 +954,21 @@
},
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
"reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c"
"reference": "c36586dcf89a12315939e00ec9b4474adcb1d773"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a6e83bdeb3c84391d1dfe16f42e40727ce524a5c",
"reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773",
"reference": "c36586dcf89a12315939e00ec9b4474adcb1d773",
"shasum": ""
},
"require": {
"php": ">=7.1",
"symfony/polyfill-intl-normalizer": "^1.10",
"symfony/polyfill-php72": "^1.10"
"php": ">=7.2",
"symfony/polyfill-intl-normalizer": "^1.10"
},
"suggest": {
"ext-intl": "For best performance"
@ -959,7 +1017,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0"
},
"funding": [
{
@ -975,7 +1033,7 @@
"type": "tidelift"
}
],
"time": "2024-05-31T15:07:36+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
@ -1060,20 +1118,20 @@
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c"
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
@ -1120,83 +1178,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-06-19T12:30:46+00:00"
},
{
"name": "symfony/polyfill-php81",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php81\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
},
"funding": [
{
@ -1216,16 +1198,16 @@
},
{
"name": "symfony/yaml",
"version": "v6.4.8",
"version": "v6.4.18",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "52903de178d542850f6f341ba92995d3d63e60c9"
"reference": "bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/52903de178d542850f6f341ba92995d3d63e60c9",
"reference": "52903de178d542850f6f341ba92995d3d63e60c9",
"url": "https://api.github.com/repos/symfony/yaml/zipball/bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5",
"reference": "bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5",
"shasum": ""
},
"require": {
@ -1268,7 +1250,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.18"
},
"funding": [
{
@ -1284,28 +1266,27 @@
"type": "tidelift"
}
],
"time": "2024-05-31T14:49:08+00:00"
"time": "2025-01-07T09:44:41+00:00"
},
{
"name": "twig/twig",
"version": "v3.18.0",
"version": "v3.20.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50"
"reference": "3468920399451a384bef53cf7996965f7cd40183"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50",
"reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/3468920399451a384bef53cf7996965f7cd40183",
"reference": "3468920399451a384bef53cf7996965f7cd40183",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"php": ">=8.1.0",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3",
"symfony/polyfill-php81": "^1.29"
"symfony/polyfill-mbstring": "^1.3"
},
"require-dev": {
"phpstan/phpstan": "^2.0",
@ -1352,7 +1333,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.18.0"
"source": "https://github.com/twigphp/Twig/tree/v3.20.0"
},
"funding": [
{
@ -1364,7 +1345,7 @@
"type": "tidelift"
}
],
"time": "2024-12-29T10:51:50+00:00"
"time": "2025-02-13T08:34:43+00:00"
},
{
"name": "wearejust/kirby-twig",

View file

@ -17,6 +17,10 @@ trim_trailing_whitespace = true
indent_size = 4
insert_final_newline = true
[*.vue.php]
indent_size = 2
insert_final_newline = false
[*.yml]
indent_style = space

View file

@ -6,15 +6,17 @@
To keep track of different states of our code (current release, bugfixes, features) we use branches:
| Branch | Used for | PRs allowed? |
| ----------- | ------------------------------------------------------------------------ | --------------------------- |
| `main` | Latest released version | - |
| `develop` | Working branch for next release, e.g. `3.7.x` | ✅ |
| `fix/*` | Temporary branches for single patch | - |
| `feature/*` | Temporary branches for single feature | - |
| `release/*` | Pre-releases in testing before they are merged into `main` when released | only during release testing |
| Branch | Used for | PRs allowed? |
| --------------- | ------------------------------------------------------------------------ | --------------------------- |
| `main` | Latest released version | ❌ |
| `develop-patch` | Working branch for next patch release, e.g. `4.0.x` | ✅ |
| `develop-minor` | Working branch for next minor release, e.g. `4.x.0` | ✅ |
| `v5/develop` | Working branch for next major release, e.g. `5.0.0` | ✅ |
| `fix/*` | Temporary branches for single bugfix | - |
| `feature/*` | Temporary branches for single feature | - |
| `release/*` | Pre-releases in testing before they are merged into `main` when released | only during release testing |
We will review all pull requests (PRs) to `develop` and merge them if accepted, once an appropriate version is upcoming. Please understand that this might not be the immediate next release and might take some time.
We will review all pull requests (PRs) to `develop-patch`, `develop-minor` and `v5/develop` and merge them if accepted, once an appropriate version is upcoming. Please understand that this might not be the immediate next release and might take some time.
## How you can contribute
@ -26,25 +28,25 @@ When you find a bug, the first step to fixing it is to help us understand and re
For bug fixes, please create a new branch following the name scheme: `fix/issue_number-bug-x`, e.g. `fix/234-this-nasty-bug`. Limit bug fix PRs to a single bug. **Do not mix multiple bug fixes in a single PR.** This will make it easier for us to review the fix and merge it.
- Always send bug fix PRs against the `develop` branchnot `main`.
- Always send bug fix PRs against the `develop-patch` branchnot `main`.
- Add a helpful description of what the PR does if it is not 100% self-explanatory.
- Every bug fix should include a [unit test](#tests) to avoid future regressions. Let us know if you need help with that.
- Make sure your code [style](#style) matches ours and includes [comments/in-code documentation](#documentation).
- Make sure your branch is up to date with the latest state on the `develop` branch. [Rebase](https://help.github.com/articles/about-pull-request-merges/) changes before you send the PR.
- Make sure your branch is up to date with the latest state on the `develop-patch` branch. [Rebase](https://help.github.com/articles/about-pull-request-merges/) changes before you send the PR.
- Please *don't* commit updated dist files in the `panel/dist` folder to avoid merge conflicts. We only build the dist files on release. Your branch should only contain changes to the source files.
### Features
For features create a new branch following the name scheme: `feature/issue_number-feature-x`, e.g. `feature/123-awesome-function`. Our [feedback platform](https://feedback.getkirby.com) can be a good source of highly requested features. Maybe your feature idea already exists and you can get valuable feedback from other Kirby users. Focus on a single feature per PR. Don't mix features!
- Always send feature PRs against the `develop` branchnot `main`.
- Always send feature PRs against the `develop-minor` branchnot `main`.
- Add a helpful description of what the PR does.
- New features should include [unit tests](#tests). Let us know if you need help with that.
- Make your code [style](#style) matches ours and includes [comments/in-code documentation](#documentation).
- Make sure your branch is up to date with the latest state on the `develop` branch. [Rebase](https://help.github.com/articles/about-pull-request-merges/) changes before you send the PR.
- Make sure your branch is up to date with the latest state on the `develop-minor` branch. [Rebase](https://help.github.com/articles/about-pull-request-merges/) changes before you send the PR.
- Please *don't* commit updated dist files in the `panel/dist` folder to avoid merge conflicts. We only build the dist files on release. Your branch should only contain changes to the source files.
We try to bundle features in our major releases, e.g. `3.x`. That is why we might only review and, if accepted, merge your PR once an appropriate release is upcoming. Please understand that we cannot merge all feature ideas or that it might take a while. Check out the [roadmap](https://roadmap.getkirby.com) to see upcoming releases.
We try to bundle features in our major releases, e.g. `5.0`. That is why we might only review and, if accepted, merge your PR once an appropriate release is upcoming. Please understand that we cannot merge all feature ideas or that it might take a while. Check out the [roadmap](https://roadmap.getkirby.com) to see upcoming releases.
### Translations

View file

@ -1,7 +1,7 @@
# Kirby License Agreement
Published: March 21, 2023
Source: https://getkirby.com/license/2023-03-21
Published: March 18, 2025
Source: https://getkirby.com/license/2025-03-18
## About this Agreement
@ -11,84 +11,131 @@ This End User License Agreement (the **"Agreement"**) is fundamental to the rela
If you do not agree to this Agreement, please do not download, install or use Kirby. Installation or use of Kirby signifies that you have read, understood, and agreed to be bound by this Agreement.
## Summary
This section summarizes the most important conditions of this Agreement to give you a quick overview:
- With your purchase you obtain a license. A license allows you to use Kirby according to this Agreement.
- Each project (defined by its URL) needs its own license. You need to purchase the right license for your project and/or client. You can find our license variants on <https://getkirby.com/buy>.
- In some explicitly listed cases, you can use Kirby without having to purchase a license. In these cases, this Agreement grants you the license directly. There are also cases where you can request a free or discounted license from us.
- Each license includes any Kirby version that gets released within three years from the date when you first activated your license. We also provide free security updates for older versions that may protect your project beyond three years.
- After those three years, you can continue to use Kirby for your project with any of these versions as long as you want.
- To use any newer version released after this time, you will need to upgrade your license.
- Upgrading your license extends the timeframe for an additional three years during which you can use new releases. You can perform the upgrade at any time.
- You have the right to transfer or reassign a license to another person or project if needed.
- There are some restrictions for use of Kirby that you can find below.
For the full license details, please read the Agreement in full. Only the following sections are legally binding.
## Definitions
Before we get started with the conditions of the Agreement, let's define the terms that will be used throughout it:
- When we refer to **"You"**, we mean the licensee. Before purchasing Kirby, that's the individual or company that has downloaded and/or installed Kirby for a Development Installation, Private Installation or Extension Demo. When used for a Public Site, the licensee is the individual or company that has purchased the Kirby license. If you work on a client project and have purchased the Kirby license for your client, you (and _not_ the client) are the licensee.
- When we refer to **"We"**/**"Us"**/**"Our"**, we mean the licensor, the Content Folder GmbH & Co. KG. You can find Our company and contact information on Our [contact page](https://getkirby.com/contact).
- When we refer to **"You"**, we mean the licensee. Before purchasing Kirby, that's the individual or company that has downloaded and/or installed Kirby for a Development Installation or Private Installation. When used for a Public Site, the licensee is the individual or company that has purchased the Kirby license or received a free license from Us on request. If you work on a client project and have purchased the Kirby license for your client, you (and _not_ the client) are the licensee.
- When we refer to **"We"**/**"Us"**/**"Our"**, we mean the licensor, the Content Folder GmbH & Co. KG. You can find Our company and contact information on Our [contact page](https://getkirby.com/contact).
- **"Client"** refers to the individual or company that is primarily responsible for and benefits from the Website, unless they are the licensee. You might create or work on the Website on behalf of the Client, either directly or through other intermediaries (e.g. as a freelancer for an agency that works on a client website).
- A **"Website"** is a single Kirby project that is defined by its domain name and root directory (e.g. `https://sub.example.com` or `https://example.com/example/`). Each (sub)domain and root directory is a separate Website, even if the projects are related in any way. Exception: If You use the cross-domain multi-language feature with the same `content` folder, these domains count as the same Website.
You may use Kirby as a headless backend or as a static site generator. In these cases the Website is defined by the domain and root directory of the user- or visitor-facing frontend(s).
- A **"Development Installation"** is a Website that is installed purely for the purposes of development and client preview. It must only be accessible by a restricted number of users (like on a personal computer, on a server in a network with restricted access or when protecting a staging website with a password that only a restricted number of users know).
- A **"Private Installation"** is a Website that is installed purely for personal use. It must only be accessible by You and Your family.
- An **"Extension Demo"** is a Website with the single purpose to showcase a free or commercial Kirby theme or Kirby plugin, as long as that Website only contains demo content. If the showcased extension is a Kirby theme, the demo content must be exactly as shipped with the theme. Demos for Kirby plugins may _not_ contain any additional content that is not needed to showcase the plugin in use.
- A **"Public Site"** is a Website that is _neither_ a Development Installation, a Private Installation nor an Extension Demo.
- An **"Update"** is defined as a Kirby release which adds smaller new features, minor functionality enhancements or bug fixes. This class of release is identified by the change of the revision to the right of the first decimal point, e.g. 3.1 to 3.2, 3.X.1 to 3.X.2 or 3.X.X.1 to 3.X.X.2.
- An **"Upgrade"** is a major Kirby release which incorporates major new features or enhancements that increase the core functionality of Kirby to a larger extent. This class of release is identified by the change of the revision to the left of the first decimal point, e.g. 3.X to 4.0.
- A **"Public Site"** is a Website that is _neither_ a Development Installation nor a Private Installation.
- A **"Minor Release"** is a stable Kirby release which adds smaller new features, minor functionality enhancements or bug fixes. This class of release is identified by the change of the revision to the right of the first decimal point, e.g. 4.1 to 4.2, 4.X.1 to 4.X.2.
- A **"Major Release"** is a stable Kirby release which incorporates major new features or enhancements that increase or change the core functionality of Kirby to a larger extent. It may also deprecate existing parts of the Source Code or change them in a breaking way. This class of release is identified by the change of the revision to the left of the first decimal point, e.g. 4.X to 5.0.
- A **"Major Generation"** is defined as all releases that share the revision to the left of the first decimal point, e.g. 4.0.0, 4.0.X, 4.X.0 and 4.X.X.
- The **"Source Code"** is defined as the contents of all files that are provided with Kirby and that make Kirby work. This includes (but is not limited to) all PHP, JavaScript, JSON, HTML and CSS files as well as all related image and other media files.
- The **"MIT-licensed Source Code Parts"** are defined as the parts of the Source Code for which a file header or code comment inside the same source file explicitly states the applicability of the MIT license.
- The **"Activation Date"** determines the included updates. It is defined like this:
- For a newly purchased or granted license, it is the date when the license was first activated for use with a Public Site.
- When You upgrade an already activated license, it is the date on which the upgrade was performed in Our [license hub](https://hub.getkirby.com). If the license is still within the Included Updates Period, the Activation Date of the upgrade license will be set to the end of the Included Updates Period of the existing license.
- When You upgrade a license that had _not_ been activated before, the upgrade license adopts the unactivated state of the existing license. The Activation Date is set on first activation for use with a Public Site.
- The **"Included Updates Period"** is the time span of three (3) years after the Activation Date.
- Licensees (You), Clients and Websites are **"Qualified"** if they satisfy the purchase requirements from the ["Order Process" section](#order-process) of this Agreement.
Every time you see one of these capitalized terms in the following text, it has the meaning that has been explained above.
## Usage for a Public Site
Installing Kirby on or using it for a Public Site requires a [paid license](https://getkirby.com/buy).
Installing Kirby on or using it for a Public Site requires a [paid license](https://getkirby.com/buy). Once a paid license is needed, the license must be immediately activated to the Public Sites domain name and root directory via our [license hub](https://hub.getkirby.com) or the activation feature in the Kirby Panel.
As Kirby is software and software is intangible, We don't sell it as such. Instead, this Agreement grants a license for each purchase to install and use a single instance of Kirby on a **specific Website**. Additional Kirby licenses must be purchased in order to install and use Kirby on **additional Websites**.
The license is **non-exclusive** (meaning that You are not the only one who We will issue a license) and **generally non-transferable** (meaning that the one who purchases the license is the licensee).
The license is **non-exclusive** (meaning that You are not the only one to whom We will issue a license) and **generally non-transferable** (meaning that the one who purchases the license is the licensee).
On request, We will **transfer** a license to anyone who is also allowed to buy Kirby licenses by law and this Agreement. The new licensee will take over all rights and obligations of this Agreement from You at the moment We confirm the license transfer.
On request, We will **transfer** a license to anyone who would be allowed and Qualified to purchase the license by law and this Agreement. The new licensee will take over all rights and obligations of this Agreement from You at the moment We confirm the license transfer.
We will also **reassign** a license to another Website domain and root directory of Your choice, provided that You confirm that the previous Website is no longer in operation and will not be operated with the same license in the future.
We will also **reassign** a license to another Qualified Website domain and root directory, if You confirm that the previous Website is no longer in operation and will not be operated with the same license in the future.
If you need to transfer your Kirby license to another individual or company (for example to your client or a new agency) or reassign it to a different project, please get in touch directly at <support@getkirby.com>.
If the new licensee, Website or Client in a transfer or reassignment is not Qualified for the existing license, You or the new licensee need to **upgrade the license to the qualifying terms and conditions** before the transfer or reassignment can be performed.
A license is valid for all Updates of the same major Kirby release. We reserve the right to charge an **upgrade fee for Upgrade releases**. Whether a release is an Update or Upgrade is at Our sole discretion.
> [!NOTE]
> If you need to transfer your Kirby license to another individual or company (for example to your client or a new agency) or reassign it to a different project, please get in touch directly at <support@getkirby.com>.
A license is valid for all Major Releases that We publish before the end of the Included Updates Period. It is also valid for all releases in those Major Generations independent of their release date. Whether a release is a Minor Release or Major Release is at Our sole discretion.
The use of releases in Major Generations that We publish after the Included Updates Period requires a **paid license upgrade**. An upgrade license replaces the existing license.
## Order Process
Our order process is conducted by Our online reseller [Paddle.com](https://paddle.com). Paddle.com is the Merchant of Record for all Our orders. Paddle provides all customer service inquiries and handles returns.
When purchasing a license, You are **responsible to choose the right license** based on You and the Website project. Different license variants can come with certain requirements towards You and/or the Website project. We publish all such requirements on <https://getkirby.com/buy> in a way that makes them clearly visible before the purchase. With Your purchase, You confirm that You and the Website project qualify for the selected license variant.
If the Website is created for a Client, You need to make sure that the **Client qualifies for the selected license**.
If You purchase licenses **in advance**, You need to ensure to only use the license(s) for projects that satisfy the requirements for the selected license variant(s).
We **reserve the right to verify** at any time after the purchase whether You, the Website and (if applicable) the Client are Qualified. Changes to the situation of You, the Website or the Client as well as changes to the published information on Our "Buy" page after the purchase or after the assignment to a Client do _not_ take effect on an existing license unless You upgrade the license or We transfer or reassign the license on Your request.
## Free Licenses
Kirby can be used **for free in the following cases**.
Please note that the restrictions and all other clauses of this Agreement also apply to free licenses. You may especially _not_ alter or circumvent the licensing features.
> [!NOTE]
> Please note that the restrictions and all other clauses of this Agreement also apply to free licenses. You may especially _not_ alter or circumvent the licensing features.
### Usage for a Development Installation
We believe that it should be possible to test and evaluate software before having to purchase a license. Also, We understand that a web project first needs to be built in a protected environment before it can be published.
Therefore, installing and using Kirby on a personal computer (like a desktop PC, notebook or tablet) or server for a Development Installation is **free** for as long as You need.
Therefore, installing and using Kirby on a personal computer (like a desktop PC, notebook or tablet) or server for a Development Installation is **free** for as long as You need. If You have already purchased a license, You do *not* need to activate it to the development domain(s) of the project.
The usage of Kirby in production (with the intention to handle production data or content) is _never_ considered a Development Installation, even in internal apps or systems.
> [!NOTE]
> The usage of Kirby in production (with the intention to handle production data or content) is _never_ considered a Development Installation, even in internal apps or systems.
### Usage for a Private Installation
You may also install and use Kirby for **free** in Private Installations as long as they are not accessible by anyone except You and Your family.
Our [definition](#definitions) of a Private Installation allows the following use cases:
> [!NOTE]
> Our [definition](#definitions) of a Private Installation allows the following use cases:
>
> - Private sites for personal use, for example:
> - Apps for You personally (like a personal diary)
> - Apps for You as a freelancer (like a bookkeeping, invoicing or project management app)
> - Apps for Your family (like a private photo gallery)
> - Experimental local Kirby setups for Your personal use (for example to try out Kirby features)
>
> However, the following use cases are _not_ covered and need a **[paid license](#usage-for-a-public-site)**:
>
> - Intranets for companies, authorities or organizations, no matter if on a local or public server
> - (Internal) apps for teams or entire companies, authorities or organizations
> - Websites that are accessible by the public, even for personal/non-commercial purposes
> - Use of Kirby as a local CMS for a static or headless site without a license for the frontend domain(s)
- Private sites for personal use, for example:
- Apps for You personally (like a personal diary)
- Apps for You as a freelancer (like a bookkeeping, invoicing or project management app)
- Apps for Your family (like a private photo gallery)
- Experimental local Kirby setups for Your personal use (for example to try out Kirby features)
### Free Licenses on Request
However, the following use cases are _not_ covered and need a **[paid license](#usage-for-a-public-site)**:
We provide free or discounted licenses for specific purposes:
- Intranets for companies, authorities or organizations, no matter if on a local or public server
- (Internal) apps for teams or entire companies, authorities or organizations
- Websites that are accessible by the public, even for personal/non-commercial purposes
- Use of Kirby as a local CMS for a static or headless site without a license for the frontend domain(s)
- students,
- selected educational projects, social and environmental organizations, charities and non-profits with insufficient funding and
- demo sites showcasing a Kirby extension (plugin or theme).
### Usage for an Extension Demo
Unlike licenses for Development Installations or Private Installations, these free or discounted licenses are not granted by this Agreement directly. You need to request them from Us via email to <support@getkirby.com>. Any discounts or free licenses are at Our sole discretion.
Extension Demos are not real Websites. We want to encourage you to build and showcase your themes and plugins.
Should We grant a free or discounted license under the terms of this section, You will receive a license code for use with a Public Site. The [section "Usage for a Public Site"](#usage-for-a-public-site) as well as all other terms of this Agreement apply with the following modifications:
Therefore, You may **operate Extension Demos without purchasing a license**.
Please note that this does _not_ apply to store fronts or other types of sites used to promote free or commercial themes or plugins. If such a site is built with Kirby as well, it is a Public Site and needs a **[paid license](#usage-for-a-public-site)**.
- We will only transfer or reassign free or discounted licenses if the new licensee or project qualifies for the free or discounted license at Our sole discretion.
- You may upgrade free or discounted licenses to future Major Generations like paid licenses. We will grant free or discounted upgrades under the terms of this section.
## Restrictions
@ -105,7 +152,7 @@ You may make **copies of Kirby** in any machine readable form solely for the **f
- when working on code contributions to Kirby or
- as a backup.
You may _not_ reproduce Kirby or its Source Code, in whole or in part, for **any other purpose**.
You may _not_ reproduce Kirby or its Source Code, in whole or in part, for **any other purpose**, except if granted below.
### Modification of the Source Code
@ -113,10 +160,12 @@ You may **alter, modify or extend the Source Code** for Your own use or with the
However You may _not_:
- **alter or circumvent the licensing features**, including (but not limited to) the license validation and payment prompts or
- **alter or circumvent the licensing features**, including (but not limited to) the license validation and payment prompts,
- **remove or alter any proprietary notices** on Kirby or
- **resell, redistribute or transfer** the modified or derivative version.
Please note that We **can't provide technical support** for modified or derivative versions of the Source Code.
> [!NOTE]
> Please note that We **can't provide technical support** for modified or derivative versions of the Source Code.
### Your Relationship to Third Parties
@ -129,13 +178,45 @@ The following cases are exempted from this restriction:
- You may make Kirby available to customers via a Software-as-a-Service (SaaS) offering, provided You ensure that each Website has a valid Kirby license purchased either by You or Your customer. If multiple customers share a Website, each customer needs at least one license. Your offering _must not_ appear to be provided or officially endorsed by Us.
- You may make a Kirby installation available to employees or partners of You or Your Website client. You may also disclose and distribute Kirbys Source Code to Your client together with the source code of the Website You created for them.
- You may disclose the Source Code to individuals or companies that are involved in the development or operation of Your Website (e.g. agencies, design or development freelancers, hosting providers or administrators).
- You may disclose, distribute and make available extracted parts of the Source Code according to the conditions of the following section "Extraction of Source Code parts".
E.g. the following cases are explicitly **_not_ allowed**:
> [!NOTE]
> E.g. the following cases are explicitly **_not_ allowed**:
>
> - Selling, licensing or distributing a new product based on Kirby that modifies or hides Kirbys identity as a Content Management System (CMS)
> - Forking Kirby and selling the modified version ([see above](#modification-of-the-source-code))
> - Buying licenses in bulk and reselling them in your own shop
> - Bundling or including Kirbys full Source Code in the publication and/or distribution of a Websites source code or a (free or paid) theme or plugin (please use Git submodules or Composer or provide a link to Our repository or website instead)
- Selling, licensing or distributing a new product based on Kirby that modifies or hides Kirbys identity as a Content Management System (CMS)
- Forking Kirby and selling the modified version ([see above](#restrictions__modification-of-the-source-code))
- Buying licenses in bulk and reselling them in your own shop
- Bundling or including Kirbys Source Code in the publication and/or distribution of a Websites source code or a (free or paid) theme or plugin (please use Git submodules or Composer or provide a link to Our repository or website instead)
### Extraction of Source Code parts
We provide the Source Code as a complete unit for use according to this Agreement.
Reuse of code parts in other programs or projects is _only_ permitted in the cases stated in the following sections. You may _not_ extract the Source Code or parts of the Source Code in any other way or for any other purpose.
#### Extraction of MIT-licensed Source Code Parts
Permission is hereby granted, free of charge, to any person obtaining a copy of MIT-licensed Source Code Parts, to deal in the MIT-licensed Source Code Parts without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the MIT-licensed Source Code Parts, and to permit persons to whom the MIT-licensed Source Code Parts is furnished to do so, subject to the following conditions:
The copyright notice to Bastian Allgeier and this permission notice shall be included in all copies or substantial portions of the MIT-licensed Source Code Parts.
THE MIT-LICENSED SOURCE CODE PARTS ARE 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 MIT-LICENSED SOURCE CODE PARTS OR THE USE OR OTHER DEALINGS IN THE MIT-LICENSED SOURCE CODE PARTS.
#### Extraction for Extensions
You may extract, use, disclose and distribute copies or adapted copies of individual parts of the Source Code (such as component templates), including those not licensed under the terms of the MIT license but excluding those covered by third-party licenses, if both of the following conditions are met:
- You use the code parts in or disclose/distribute them as part of an extension (plugin or theme) that You solely intend to be used with Kirby.
- Every copied or adapted code part carries a clear note that references these conditions as well as Our copyright and this Agreement. You may use the following example as a template:
```
/**
* The following code was copied or adapted from the source code of
* Kirby CMS and may not be used outside of licensed Kirby projects.
*
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
```
### Disallowed Uses
@ -157,24 +238,27 @@ The following uses of Kirby are _not_ covered by this Agreement and will result
- a safe environment or action to curtail the use of fossil fuels or to prevent climate change or
- democratic processes.
### Other Restrictions
You may also _not_:
- **extract parts of the Source Code** for use in other programs or projects (unless the code file in question is explicitly licensed under the terms of the MIT license) or
- **remove or alter any proprietary notices** on Kirby.
## Technical Support
Technical support is **provided as described on Our website** at <https://getkirby.com>. **No representations or guarantees** are made regarding the response time in which support questions are answered, however We will do Our best to respond quickly.
For each Major Generation, We aim to provide **security support for three (3) years** after the Major Release. Security support means that We will provide free security updates for the supported releases, which will include fixes for security vulnerabilities according to the following rules:
- We published a security advisory on <https://getkirby.com/security> within the respective security support period. We will publish vulnerabilities on this page as soon as they are known to Us and an official fix for any supported release is available.
- The latest release of the supported Major Generation is affected by the vulnerability.
With each vulnerability, We aim to publish the security advisory and security updates for all supported Major Generations at the same time.
> [!NOTE]
> You can find up-to-date information on our currently supported versions in our [public security policy](https://getkirby.com/security).
We reserve the right to **limit technical support for free licenses**.
## Refund Policy
We offer a **14-day**, money back refund policy if Kirby didn't work out for Your project.
If you need a refund, please get in touch directly at <support@getkirby.com>.
> [!NOTE]
> If you need a refund, please get in touch directly at <support@getkirby.com>.
## No Warranty
@ -204,6 +288,8 @@ YOU EXPRESSLY UNDERSTAND AND AGREE THAT **WE SHALL NOT BE LIABLE** FOR ANY DIREC
Bastian Allgeier **owns all rights**, title and interest to Kirby (including all intellectual property rights) and **reserves all rights to Kirby** that are not expressly granted in this Agreement.
In the event that Kirby will no longer be actively maintained, Bastian Allgeier will provide the Source Code under the terms of a free and open source software (FOSS) license as far as legally and contractually possible.
## Applicable Law & Place of Jurisdiction
1. For all disputes arising out of or in connection with this Agreement, the courts competent for Neckargemünd, Germany, shall have exclusive jurisdiction. However, We shall have the choice to file lawsuits against You before the courts competent for Your place of business.
@ -219,4 +305,4 @@ Should any provision of this Agreement be or become invalid, void or unenforceab
Due to Kirby's flexibility, you may have special use cases or requirements that don't fit this Agreement.
If that's the case or if you have any questions, feel free to get in touch: <support@getkirby.com>. We are happy to think outside the box and find custom license solutions for your creative application of Kirby.
If that's the case or if you have any questions, feel free to [get in touch](mailto:support@getkirby.com). We are happy to think outside the box and find custom license solutions for your creative application of Kirby.

View file

@ -1,12 +1,12 @@
[<img src="https://getkirby.com/assets/images/github/kirby.jpg" width="300">](https://getkirby.com)
[<img src="https://getkirby.com/assets/images/github/kirby.jpg" width="300">](https://getkirby.com)
[![Release](https://img.shields.io/github/v/release/getkirby/kirby)](https://github.com/getkirby/kirby/releases/latest)
[![CI Status](https://img.shields.io/github/actions/workflow/status/getkirby/kirby/ci.yml?branch=main&label=CI)](https://github.com/getkirby/kirby/actions?query=workflow%3ACI+branch%3Amain)
[![Coverage Status](https://img.shields.io/codecov/c/gh/getkirby/kirby?token=ROZ2RVA0OF)](https://codecov.io/gh/getkirby/kirby)
[![Downloads](https://img.shields.io/packagist/dt/getkirby/cms?color=red)](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).
**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).
This is Kirby's core application folder. Get started with one of the following repositories instead:
@ -15,21 +15,23 @@ This is Kirby's core application folder. Get started with one of the following r
<img src="https://getkirby.com/assets/images/github/kirby-screen.png" />
### 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 youre convinced, [buy your license](https://getkirby.com/buy).
### Contribute
**Found a bug?**
**Found a bug?**
Please post all bugs as individual reports in our [issue tracker](https://github.com/getkirby/kirby/issues).
**Suggest a feature**
**Suggest a feature**
If you have ideas for a feature or enhancement for Kirby, please use our [feedback platform](https://feedback.getkirby.com).
**Translations, bug fixes, code contributions ...**
**Translations, bug fixes, code contributions ...**
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.
@ -39,9 +41,9 @@ Read about how to contribute to the development in our [contributing guide](/CON
- **[Discord](https://chat.getkirby.com)** Hang out and meet the community.
- **[YouTube](https://youtube.com/kirbyCasts)** - Watch the latest video tutorials visually with Bastian.
- **[Mastodon](https://mastodon.social/@getkirby)** Spread the word.
- **[Instagram](https://www.instagram.com/getkirby/)** Share your creations: #madewithkirby.
- **[Bluesky](https://bsky.app/profile/getkirby.com)** Tell a friend.
---
© 2009-2023 Bastian Allgeier
© 2009 Bastian Allgeier
[getkirby.com](https://getkirby.com) · [License agreement](https://getkirby.com/license)

View file

@ -1,3 +1,27 @@
# Security Policy
Please see the [Security Policy on the Kirby website](https://getkirby.com/security) for a list of the currently supported Kirby versions and of past security incidents as well as for information on how to report security vulnerabilities in the Kirby core or in the Panel.
## Supported versions and past security incidents
You can find up-to-date information on the security status of each version on <https://getkirby.com/security>.
## Security of your Kirby site
We have a detailed [security guide](https://getkirby.com/docs/guide/security) with information on how to keep your Kirby installation secure.
## Reporting a vulnerability
If you have spotted a vulnerability in Kirby's core or the Panel, please make sure to let us know immediately. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
You can always contact us directly at **<security@getkirby.com>**.
If you want to encrypt your message, our GPG key is [6E6B 057A F491 FFAD 363F 6F49 9101 10FA A459 E120](https://getkirby.com/pgp.asc).
You can also use the [security advisory form on GitHub](https://github.com/getkirby/kirby/security/advisories/new) to securely and privately report a vulnerability to us.
We will send you a response as soon as possible and will keep you informed on our progress towards a fix and announcement.
> [!IMPORTANT]
> Please do not write to us publicly, e.g. in the forum, on Discord or in a GitHub issue. A public report can give attackers valuable time to exploit the issue before it is fixed.
>
> By letting us know directly and coordinating the disclosure with us, you can help to protect other Kirby users from such attacks.
>
> Also please do *not* request a CVE ID from organizations like MITRE. The responsible CVE Numbering Authority (CNA) for Kirby is GitHub. We can and will request a CVE ID for each confirmed vulnerability and will provide it to you in advance of the coordinated release.

View file

@ -6,7 +6,7 @@
*/
if (
version_compare(PHP_VERSION, '8.1.0', '>=') === false ||
version_compare(PHP_VERSION, '8.4.0', '<') === false
version_compare(PHP_VERSION, '8.5.0', '<') === false
) {
die(include __DIR__ . '/views/php.php');
}

View file

@ -1,7 +1,9 @@
##
## Bundle of CA Root Certificates
##
## Certificate data from Mozilla as of: Tue Jul 2 03:12:04 2024 GMT
## Certificate data from Mozilla as of: Tue Feb 25 04:12:03 2025 GMT
##
## Find updated versions here: https://curl.se/docs/caextract.html
##
## This is a bundle of X.509 certificates of public Certificate Authorities
## (CA). These were automatically extracted from Mozilla's root certificates
@ -14,7 +16,7 @@
## Just configure this file as the SSLCACertificateFile.
##
## Conversion done with mk-ca-bundle.pl version 1.29.
## SHA256: 456ff095dde6dd73354c5c28c73d9c06f53b61a803963414cb91a1d92945cdd3
## SHA256: 620fd89c02acb0019f1899dab7907db5d20735904f5a9a0d3a8771a5857ac482
##
@ -369,37 +371,6 @@ NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj
viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
-----END CERTIFICATE-----
SwissSign Silver CA - G2
========================
-----BEGIN CERTIFICATE-----
MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT
BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X
DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3
aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG
9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644
N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm
+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH
6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu
MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h
qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5
FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs
ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc
celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X
CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB
tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P
4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F
kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L
3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx
/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa
DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP
e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu
WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ
DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub
DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
-----END CERTIFICATE-----
SecureTrust CA
==============
-----BEGIN CERTIFICATE-----
@ -582,27 +553,6 @@ NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu
dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
-----END CERTIFICATE-----
SecureSign RootCA11
===================
-----BEGIN CERTIFICATE-----
MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi
SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS
b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw
KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1
cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL
TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO
wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq
g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP
O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA
bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX
t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh
OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r
bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ
Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01
y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061
lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I=
-----END CERTIFICATE-----
Microsec e-Szigno Root CA 2009
==============================
-----BEGIN CERTIFICATE-----
@ -2317,40 +2267,6 @@ hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB
dBb9HxEGmpv0
-----END CERTIFICATE-----
Entrust Root Certification Authority - G4
=========================================
-----BEGIN CERTIFICATE-----
MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAwgb4xCzAJBgNV
BAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3Qu
bmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1
dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eSAtIEc0MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYT
AlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0
L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhv
cml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhv
cml0eSAtIEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3D
umSXbcr3DbVZwbPLqGgZ2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV
3imz/f3ET+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j5pds
8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAMC1rlLAHGVK/XqsEQ
e9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73TDtTUXm6Hnmo9RR3RXRv06QqsYJn7
ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNXwbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5X
xNMhIWNlUpEbsZmOeX7m640A2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV
7rtNOzK+mndmnqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8
dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwlN4y6mACXi0mW
Hv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNjc0kCAwEAAaNCMEAwDwYDVR0T
AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9n
MA0GCSqGSIb3DQEBCwUAA4ICAQAS5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4Q
jbRaZIxowLByQzTSGwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht
7LGrhFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/B7NTeLUK
YvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uIAeV8KEsD+UmDfLJ/fOPt
jqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbwH5Lk6rWS02FREAutp9lfx1/cH6NcjKF+
m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKW
RGhXxNUzzxkvFMSUHHuk2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjA
JOgc47OlIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk5F6G
+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuYn/PIjhs4ViFqUZPT
kcpG2om3PVODLAgfi49T3f+sHw==
-----END CERTIFICATE-----
Microsoft ECC Root Certificate Authority 2017
=============================================
-----BEGIN CERTIFICATE-----
@ -2600,6 +2516,36 @@ 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-----
@ -3138,36 +3084,6 @@ AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8
rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR
-----END CERTIFICATE-----
Security Communication RootCA3
==============================
-----BEGIN CERTIFICATE-----
MIIFfzCCA2egAwIBAgIJAOF8N0D9G/5nMA0GCSqGSIb3DQEBDAUAMF0xCzAJBgNVBAYTAkpQMSUw
IwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQDEx5TZWN1cml0eSBD
b21tdW5pY2F0aW9uIFJvb3RDQTMwHhcNMTYwNjE2MDYxNzE2WhcNMzgwMTE4MDYxNzE2WjBdMQsw
CQYDVQQGEwJKUDElMCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UE
AxMeU2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEA48lySfcw3gl8qUCBWNO0Ot26YQ+TUG5pPDXC7ltzkBtnTCHsXzW7OT4rCmDvu20r
hvtxosis5FaU+cmvsXLUIKx00rgVrVH+hXShuRD+BYD5UpOzQD11EKzAlrenfna84xtSGc4RHwsE
NPXY9Wk8d/Nk9A2qhd7gCVAEF5aEt8iKvE1y/By7z/MGTfmfZPd+pmaGNXHIEYBMwXFAWB6+oHP2
/D5Q4eAvJj1+XCO1eXDe+uDRpdYMQXF79+qMHIjH7Iv10S9VlkZ8WjtYO/u62C21Jdp6Ts9EriGm
npjKIG58u4iFW/vAEGK78vknR+/RiTlDxN/e4UG/VHMgly1s2vPUB6PmudhvrvyMGS7TZ2crldtY
XLVqAvO4g160a75BflcJdURQVc1aEWEhCmHCqYj9E7wtiS/NYeCVvsq1e+F7NGcLH7YMx3weGVPK
p7FKFSBWFHA9K4IsD50VHUeAR/94mQ4xr28+j+2GaR57GIgUssL8gjMunEst+3A7caoreyYn8xrC
3PsXuKHqy6C0rtOUfnrQq8PsOC0RLoi/1D+tEjtCrI8Cbn3M0V9hvqG8OmpI6iZVIhZdXw3/JzOf
GAN0iltSIEdrRU0id4xVJ/CvHozJgyJUt5rQT9nO/NkuHJYosQLTA70lUhw0Zk8jq/R3gpYd0Vcw
CBEF/VfR2ccCAwEAAaNCMEAwHQYDVR0OBBYEFGQUfPxYchamCik0FW8qy7z8r6irMA4GA1UdDwEB
/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQDcAiMI4u8hOscNtybS
YpOnpSNyByCCYN8Y11StaSWSntkUz5m5UoHPrmyKO1o5yGwBQ8IibQLwYs1OY0PAFNr0Y/Dq9HHu
Tofjcan0yVflLl8cebsjqodEV+m9NU1Bu0soo5iyG9kLFwfl9+qd9XbXv8S2gVj/yP9kaWJ5rW4O
H3/uHWnlt3Jxs/6lATWUVCvAUm2PVcTJ0rjLyjQIUYWg9by0F1jqClx6vWPGOi//lkkZhOpn2ASx
YfQAW0q3nHE3GYV5v4GwxxMOdnE+OoAGrgYWp421wsTL/0ClXI2lyTrtcoHKXJg80jQDdwj98ClZ
XSEIx2C/pHF7uNkegr4Jr2VvKKu/S7XuPghHJ6APbw+LP6yVGPO5DtxnVW5inkYO0QR4ynKudtml
+LLfiAlhi+8kTtFZP1rUPcmTPCtk9YENFpb3ksP+MW/oKjJ0DvRMmEoYDjBU1cXrvMUVnuiZIesn
KwkK2/HmcBhWuwzkvvnoEKQTkrgc4NtnHVMDpCKn3F2SEDzq//wbEBrD2NCcnWXL0CsnMQMeNuE9
dnUM/0Umud1RvCPHX9jYhxBAEg09ODfnRDwYwFMJZI//1ZqmfHAuc1Uh6N//g7kdPjIe1qZ9LPFm
6Vwdp6POXiUyK+OVrCoHzrQoeIY8LaadTdJ0MN1kURXbg4NR16/9M51NZg==
-----END CERTIFICATE-----
Security Communication ECC RootCA1
==================================
-----BEGIN CERTIFICATE-----
@ -3566,3 +3482,161 @@ Y1w8ndYn81LsF7Kpryz3dvgwHQYDVR0OBBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB
cFBTApFwhVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dGXSaQ
pYXFuXqUPoeovQA=
-----END CERTIFICATE-----
TWCA CYBER Root CA
==================
-----BEGIN CERTIFICATE-----
MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQMQswCQYDVQQG
EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB
IENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQG
EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB
IENZQkVSIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1s
Ts6P40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxFavcokPFh
V8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/34bKS1PE2Y2yHer43CdT
o0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684iJkXXYJndzk834H/nY62wuFm40AZoNWDT
Nq5xQwTxaWV4fPMf88oon1oglWa0zbfuj3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK
/c/WMw+f+5eesRycnupfXtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkH
IuNZW0CP2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDAS9TM
fAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDAoS/xUgXJP+92ZuJF
2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzCkHDXShi8fgGwsOsVHkQGzaRP6AzR
wyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83
QOGt4A1WNzAdBgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB
AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0ttGlTITVX1olN
c79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn68xDiBaiA9a5F/gZbG0jAn/x
X9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNnTKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDR
IG4kqIQnoVesqlVYL9zZyvpoBJ7tRCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq
/p1hvIbZv97Tujqxf36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0R
FxbIQh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz8ppy6rBe
Pm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4NxKfKjLji7gh7MMrZQzv
It6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzXxeSDwWrruoBa3lwtcHb4yOWHh8qgnaHl
IhInD0Q9HWzq1MKLL295q39QpsQZp6F6t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X
-----END CERTIFICATE-----
SecureSign Root CA12
====================
-----BEGIN CERTIFICATE-----
MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQELBQAwUTELMAkG
A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT
ZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgwNTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJ
BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU
U2VjdXJlU2lnbiBSb290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3
emhFKxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mtp7JIKwcc
J/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zdJ1M3s6oYwlkm7Fsf0uZl
fO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gurFzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBF
EaCeVESE99g2zvVQR9wsMJvuwPWW0v4JhscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1Uef
NzFJM3IFTQy2VYzxV4+Kh9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
AQH/BAQDAgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsFAAOC
AQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6LdmmQOmFxv3Y67ilQi
LUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJmBClnW8Zt7vPemVV2zfrPIpyMpce
mik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPS
vWKErI4cqc1avTc7bgoitPQV55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhga
aaI5gdka9at/yOPiZwud9AzqVN/Ssq+xIvEg37xEHA==
-----END CERTIFICATE-----
SecureSign Root CA14
====================
-----BEGIN CERTIFICATE-----
MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEMBQAwUTELMAkG
A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT
ZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgwNzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJ
BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU
U2VjdXJlU2lnbiBSb290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh
1oq/FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOgvlIfX8xn
bacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy6pJxaeQp8E+BgQQ8sqVb
1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa
/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9JkdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOE
kJTRX45zGRBdAuVwpcAQ0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSx
jVIHvXiby8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac18iz
ju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs0Wq2XSqypWa9a4X0
dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIABSMbHdPTGrMNASRZhdCyvjG817XsY
AFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVLApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQAB
o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeq
YR3r6/wtbyPk86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E
rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ibed87hwriZLoA
ymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopTzfFP7ELyk+OZpDc8h7hi2/Ds
Hzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHSDCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPG
FrojutzdfhrGe0K22VoF3Jpf1d+42kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6q
nsb58Nn4DSEC5MUoFlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/
OfVyK4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6dB7h7sxa
OgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtlLor6CZpO2oYofaphNdgO
pygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB365jJ6UeTo3cKXhZ+PmhIIynJkBugnLN
eLLIjzwec+fBH7/PzqUqm9tEZDKgu39cJRNItX+S
-----END CERTIFICATE-----
SecureSign Root CA15
====================
-----BEGIN CERTIFICATE-----
MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMwUTELMAkGA1UE
BhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRTZWN1
cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMyNTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNV
BAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2Vj
dXJlU2lnbiBSb290IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5G
dCx4wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSRZHX+AezB
2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
AgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT9DAKBggqhkjOPQQDAwNoADBlAjEA2S6J
fl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJ
SwdLZrWeqrqgHkHZAXQ6bkU6iYAZezKYVWOr62Nuk22rGwlgMU4=
-----END CERTIFICATE-----
D-TRUST BR Root CA 2 2023
=========================
-----BEGIN CERTIFICATE-----
MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG
EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0Eg
MiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUwOTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTAT
BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCT
cfKri3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNEgXtRr90z
sWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8k12b9py0i4a6Ibn08OhZ
WiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCTRphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6
++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LUL
QyReS2tNZ9/WtT5PeB+UcSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIv
x9gvdhFP/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bSuREV
MweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+0bpwHJwh5Q8xaRfX
/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4NDfTisl01gLmB1IRpkQLLddCNxbU9
CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUZ5Dw1t61GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC
MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y
XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tIFoE9c+CeJyrr
d6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67nriv6uvw8l5VAk1/DLQOj7aRv
U9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTRVFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4
nj8+AybmTNudX0KEPUUDAxxZiMrcLmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdij
YQ6qgYF/6FKC0ULn4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff
/vtDhQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsGkoHU6XCP
pz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46ls/pdu4D58JDUjxqgejB
WoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aSEcr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/
5usWDiJFAbzdNpQ0qTUmiteXue4Icr80knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jt
n/mtd+ArY0+ew+43u3gJhJ65bvspmZDogNOfJA==
-----END CERTIFICATE-----
D-TRUST EV Root CA 2 2023
=========================
-----BEGIN CERTIFICATE-----
MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG
EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0Eg
MiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUwOTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTAT
BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1
sJkKF8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE7CUXFId/
MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFeEMbsh2aJgWi6zCudR3Mf
vc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6lHPTGGkKSv/BAQP/eX+1SH977ugpbzZM
lWGG2Pmic4ruri+W7mjNPU0oQvlFKzIbRlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3
YG14C8qKXO0elg6DpkiVjTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq910
7PncjLgcjmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZxTnXo
nMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ARZZaBhDM7DS3LAa
QzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nkhbDhezGdpn9yo7nELC7MmVcOIQxF
AZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knFNXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUqvyREBuHkV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC
MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y
XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14QvBukEdHjqOS
Mo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4pZt+UPJ26oUFKidBK7GB0aL2
QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xD
UmPBEcrCRbH0O1P1aa4846XerOhUt7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V
4U/M5d40VxDJI3IXcI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuo
dNv8ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT2vFp4LJi
TZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs7dpn1mKmS00PaaLJvOwi
S5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNPgofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/
HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAstNl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L
+KIkBI3Y4WNeApI02phhXBxvWHZks/wCuPWdCg==
-----END CERTIFICATE-----

View file

@ -1,9 +1,9 @@
{
"name": "getkirby/cms",
"description": "The Kirby 3 core",
"description": "The Kirby core",
"license": "proprietary",
"type": "kirby-cms",
"version": "3.10.1.1",
"version": "4.7.0",
"keywords": [
"kirby",
"cms",
@ -24,7 +24,7 @@
"source": "https://github.com/getkirby/kirby"
},
"require": {
"php": "~8.1.0 || ~8.2.0 || ~8.3.0",
"php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
"ext-SimpleXML": "*",
"ext-ctype": "*",
"ext-curl": "*",
@ -36,16 +36,17 @@
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
"claviska/simpleimage": "4.2.0",
"composer/semver": "3.4.0",
"filp/whoops": "2.15.4",
"christian-riesen/base32": "1.6.0",
"claviska/simpleimage": "4.2.1",
"composer/semver": "3.4.3",
"filp/whoops": "2.18.0",
"getkirby/composer-installer": "^1.2.1",
"laminas/laminas-escaper": "2.13.0",
"laminas/laminas-escaper": "2.16.0",
"michelf/php-smartypants": "1.8.1",
"phpmailer/phpmailer": "6.9.1",
"symfony/polyfill-intl-idn": "1.30.0",
"symfony/polyfill-mbstring": "1.30.0",
"symfony/yaml": "6.4.8"
"phpmailer/phpmailer": "6.9.3",
"symfony/polyfill-intl-idn": "1.31.0",
"symfony/polyfill-mbstring": "1.31.0",
"symfony/yaml": "6.4.18"
},
"replace": {
"symfony/polyfill-php72": "*"
@ -98,6 +99,8 @@
"analyze:composer": "composer validate --strict --no-check-version --no-check-all",
"analyze:phpmd": "phpmd . ansi phpmd.xml.dist --exclude 'dependencies/*,tests/*,vendor/*'",
"analyze:psalm": "psalm",
"bench": "phpbench run --report=aggregate --ref baseline",
"bench:baseline": "phpbench run --report=aggregate --tag baseline",
"build": "./scripts/build",
"ci": [
"@fix",
@ -105,8 +108,8 @@
"@test"
],
"fix": "php-cs-fixer fix",
"test": "phpunit --stderr",
"test:coverage": "phpunit --stderr --coverage-html=tests/coverage",
"test": "phpunit",
"test:coverage": "XDEBUG_MODE=coverage phpunit --coverage-html=tests/coverage",
"zip": "composer archive --format=zip --file=dist"
}
}

273
kirby/composer.lock generated
View file

@ -4,20 +4,79 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "422424ea128b83257e5cc3506bcbe984",
"content-hash": "fa52461c55dc4e276cd5853b57c95c60",
"packages": [
{
"name": "claviska/simpleimage",
"version": "4.2.0",
"name": "christian-riesen/base32",
"version": "1.6.0",
"source": {
"type": "git",
"url": "https://github.com/claviska/SimpleImage.git",
"reference": "dfbe53c01dae8467468ef2b817c09b786a7839d2"
"url": "https://github.com/ChristianRiesen/base32.git",
"reference": "2e82dab3baa008e24a505649b0d583c31d31e894"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/claviska/SimpleImage/zipball/dfbe53c01dae8467468ef2b817c09b786a7839d2",
"reference": "dfbe53c01dae8467468ef2b817c09b786a7839d2",
"url": "https://api.github.com/repos/ChristianRiesen/base32/zipball/2e82dab3baa008e24a505649b0d583c31d31e894",
"reference": "2e82dab3baa008e24a505649b0d583c31d31e894",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.17",
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^8.5.13 || ^9.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Base32\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Riesen",
"email": "chris.riesen@gmail.com",
"homepage": "http://christianriesen.com",
"role": "Developer"
}
],
"description": "Base32 encoder/decoder according to RFC 4648",
"homepage": "https://github.com/ChristianRiesen/base32",
"keywords": [
"base32",
"decode",
"encode",
"rfc4648"
],
"support": {
"issues": "https://github.com/ChristianRiesen/base32/issues",
"source": "https://github.com/ChristianRiesen/base32/tree/1.6.0"
},
"time": "2021-02-26T10:19:33+00:00"
},
{
"name": "claviska/simpleimage",
"version": "4.2.1",
"source": {
"type": "git",
"url": "https://github.com/claviska/SimpleImage.git",
"reference": "ec6d5021e5a7153a2520d64c59b86b6f3c4157c5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/claviska/SimpleImage/zipball/ec6d5021e5a7153a2520d64c59b86b6f3c4157c5",
"reference": "ec6d5021e5a7153a2520d64c59b86b6f3c4157c5",
"shasum": ""
},
"require": {
@ -49,7 +108,7 @@
"description": "A PHP class that makes working with images as simple as possible.",
"support": {
"issues": "https://github.com/claviska/SimpleImage/issues",
"source": "https://github.com/claviska/SimpleImage/tree/4.2.0"
"source": "https://github.com/claviska/SimpleImage/tree/4.2.1"
},
"funding": [
{
@ -57,28 +116,28 @@
"type": "github"
}
],
"time": "2024-04-15T16:07:16+00:00"
"time": "2024-11-22T13:25:03+00:00"
},
{
"name": "composer/semver",
"version": "3.4.0",
"version": "3.4.3",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
"reference": "35e8d0af4486141bc745f23a29cc2091eb624a32"
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32",
"reference": "35e8d0af4486141bc745f23a29cc2091eb624a32",
"url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1.4",
"symfony/phpunit-bridge": "^4.2 || ^5"
"phpstan/phpstan": "^1.11",
"symfony/phpunit-bridge": "^3 || ^7"
},
"type": "library",
"extra": {
@ -122,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.3"
},
"funding": [
{
@ -138,30 +197,30 @@
"type": "tidelift"
}
],
"time": "2023-08-31T09:50:34+00:00"
"time": "2024-09-19T14:15:21+00:00"
},
{
"name": "filp/whoops",
"version": "2.15.4",
"version": "2.18.0",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546"
"reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546",
"reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546",
"url": "https://api.github.com/repos/filp/whoops/zipball/a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e",
"reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e",
"shasum": ""
},
"require": {
"php": "^5.5.9 || ^7.0 || ^8.0",
"php": "^7.1 || ^8.0",
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"require-dev": {
"mockery/mockery": "^0.9 || ^1.0",
"phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3",
"symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0"
"mockery/mockery": "^1.0",
"phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3",
"symfony/var-dumper": "^4.0 || ^5.0"
},
"suggest": {
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
@ -201,7 +260,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.15.4"
"source": "https://github.com/filp/whoops/tree/2.18.0"
},
"funding": [
{
@ -209,7 +268,7 @@
"type": "github"
}
],
"time": "2023-11-03T12:00:00+00:00"
"time": "2025-03-15T12:00:00+00:00"
},
{
"name": "getkirby/composer-installer",
@ -260,33 +319,32 @@
},
{
"name": "laminas/laminas-escaper",
"version": "2.13.0",
"version": "2.16.0",
"source": {
"type": "git",
"url": "https://github.com/laminas/laminas-escaper.git",
"reference": "af459883f4018d0f8a0c69c7a209daef3bf973ba"
"reference": "9cf1f5317ca65b4fd5c6a3c2855e24a187b288c8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/af459883f4018d0f8a0c69c7a209daef3bf973ba",
"reference": "af459883f4018d0f8a0c69c7a209daef3bf973ba",
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/9cf1f5317ca65b4fd5c6a3c2855e24a187b288c8",
"reference": "9cf1f5317ca65b4fd5c6a3c2855e24a187b288c8",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-mbstring": "*",
"php": "~8.1.0 || ~8.2.0 || ~8.3.0"
"php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
},
"conflict": {
"zendframework/zend-escaper": "*"
},
"require-dev": {
"infection/infection": "^0.27.0",
"laminas/laminas-coding-standard": "~2.5.0",
"maglnet/composer-require-checker": "^3.8.0",
"phpunit/phpunit": "^9.6.7",
"psalm/plugin-phpunit": "^0.18.4",
"vimeo/psalm": "^5.9"
"infection/infection": "^0.29.8",
"laminas/laminas-coding-standard": "~3.0.1",
"phpunit/phpunit": "^10.5.45",
"psalm/plugin-phpunit": "^0.19.2",
"vimeo/psalm": "^6.6.2"
},
"type": "library",
"autoload": {
@ -318,7 +376,7 @@
"type": "community_bridge"
}
],
"time": "2023-10-10T08:35:13+00:00"
"time": "2025-02-17T12:40:19+00:00"
},
{
"name": "league/color-extractor",
@ -437,16 +495,16 @@
},
{
"name": "phpmailer/phpmailer",
"version": "v6.9.1",
"version": "v6.9.3",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "039de174cd9c17a8389754d3b877a2ed22743e18"
"reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18",
"reference": "039de174cd9c17a8389754d3b877a2ed22743e18",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2f5c94fe7493efc213f643c23b1b1c249d40f47e",
"reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e",
"shasum": ""
},
"require": {
@ -506,7 +564,7 @@
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"support": {
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.1"
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.3"
},
"funding": [
{
@ -514,20 +572,20 @@
"type": "github"
}
],
"time": "2023-11-25T22:23:28+00:00"
"time": "2024-11-24T18:04:13+00:00"
},
{
"name": "psr/log",
"version": "3.0.1",
"version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "79dff0b268932c640297f5208d6298f71855c03e"
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/79dff0b268932c640297f5208d6298f71855c03e",
"reference": "79dff0b268932c640297f5208d6298f71855c03e",
"url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
"shasum": ""
},
"require": {
@ -562,22 +620,22 @@
"psr-3"
],
"support": {
"source": "https://github.com/php-fig/log/tree/3.0.1"
"source": "https://github.com/php-fig/log/tree/3.0.2"
},
"time": "2024-08-21T13:31:24+00:00"
"time": "2024-09-11T13:17:53+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.5.0",
"version": "v3.5.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1"
"reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
"reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
"shasum": ""
},
"require": {
@ -585,12 +643,12 @@
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/contracts",
"name": "symfony/contracts"
},
"branch-alias": {
"dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
@ -615,7 +673,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1"
},
"funding": [
{
@ -631,24 +689,24 @@
"type": "tidelift"
}
],
"time": "2024-04-18T09:32:20+00:00"
"time": "2024-09-25T14:20:29+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "0424dff1c58f028c451efff2045f5d92410bd540"
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540",
"reference": "0424dff1c58f028c451efff2045f5d92410bd540",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
@ -659,8 +717,8 @@
"type": "library",
"extra": {
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
@ -694,7 +752,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
},
"funding": [
{
@ -710,26 +768,25 @@
"type": "tidelift"
}
],
"time": "2024-05-31T15:07:36+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
"reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c"
"reference": "c36586dcf89a12315939e00ec9b4474adcb1d773"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a6e83bdeb3c84391d1dfe16f42e40727ce524a5c",
"reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773",
"reference": "c36586dcf89a12315939e00ec9b4474adcb1d773",
"shasum": ""
},
"require": {
"php": ">=7.1",
"symfony/polyfill-intl-normalizer": "^1.10",
"symfony/polyfill-php72": "^1.10"
"php": ">=7.2",
"symfony/polyfill-intl-normalizer": "^1.10"
},
"suggest": {
"ext-intl": "For best performance"
@ -737,8 +794,8 @@
"type": "library",
"extra": {
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
@ -778,7 +835,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0"
},
"funding": [
{
@ -794,24 +851,24 @@
"type": "tidelift"
}
],
"time": "2024-05-31T15:07:36+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb"
"reference": "3833d7255cc303546435cb650316bff708a1c75c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb",
"reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb",
"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"
@ -819,8 +876,8 @@
"type": "library",
"extra": {
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
@ -859,7 +916,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0"
},
"funding": [
{
@ -875,24 +932,24 @@
"type": "tidelift"
}
],
"time": "2024-05-31T15:07:36+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c"
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
@ -903,8 +960,8 @@
"type": "library",
"extra": {
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
@ -939,7 +996,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
},
"funding": [
{
@ -955,20 +1012,20 @@
"type": "tidelift"
}
],
"time": "2024-06-19T12:30:46+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/yaml",
"version": "v6.4.8",
"version": "v6.4.18",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "52903de178d542850f6f341ba92995d3d63e60c9"
"reference": "bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/52903de178d542850f6f341ba92995d3d63e60c9",
"reference": "52903de178d542850f6f341ba92995d3d63e60c9",
"url": "https://api.github.com/repos/symfony/yaml/zipball/bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5",
"reference": "bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5",
"shasum": ""
},
"require": {
@ -1011,7 +1068,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.18"
},
"funding": [
{
@ -1027,17 +1084,17 @@
"type": "tidelift"
}
],
"time": "2024-05-31T14:49:08+00:00"
"time": "2025-01-07T09:44:41+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": "~8.1.0 || ~8.2.0 || ~8.3.0",
"php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
"ext-simplexml": "*",
"ext-ctype": "*",
"ext-curl": "*",
@ -1050,7 +1107,7 @@
"ext-mbstring": "*",
"ext-openssl": "*"
},
"platform-dev": [],
"platform-dev": {},
"platform-overrides": {
"php": "8.1.0"
},

View file

@ -3,7 +3,6 @@
return [
// cms classes
'collection' => 'Kirby\Cms\Collection',
'field' => 'Kirby\Cms\Field',
'file' => 'Kirby\Cms\File',
'files' => 'Kirby\Cms\Files',
'find' => 'Kirby\Cms\Find',
@ -24,6 +23,9 @@ return [
'users' => 'Kirby\Cms\Users',
'visitor' => 'Kirby\Cms\Visitor',
// content classes
'field' => 'Kirby\Content\Field',
// data handler
'data' => 'Kirby\Data\Data',
'json' => 'Kirby\Data\Json',
@ -69,17 +71,25 @@ return [
'v' => 'Kirby\Toolkit\V',
'xml' => 'Kirby\Toolkit\Xml',
// TODO: remove in 4.0.0
'kirby\cms\asset' => 'Kirby\Filesystem\Asset',
'kirby\cms\dir' => 'Kirby\Filesystem\Dir',
'kirby\cms\filename' => 'Kirby\Filesystem\Filename',
'kirby\cms\filefoundation' => 'Kirby\Filesystem\IsFile',
'kirby\cms\form' => 'Kirby\Form\Form',
'kirby\cms\kirbytag' => 'Kirby\Text\KirbyTag',
'kirby\cms\kirbytags' => 'Kirby\Text\KirbyTags',
'kirby\cms\template' => 'Kirby\Template\Template',
'kirby\toolkit\dir' => 'Kirby\Filesystem\Dir',
'kirby\toolkit\f' => 'Kirby\Filesystem\F',
'kirby\toolkit\file' => 'Kirby\Filesystem\File',
'kirby\toolkit\mime' => 'Kirby\Filesystem\Mime',
// Deprecated aliases:
// Any of these might be removed at any point in the future
'kirby\cms\asset' => 'Kirby\Filesystem\Asset',
'kirby\cms\content' => 'Kirby\Content\Content',
'kirby\cms\contenttranslation' => 'Kirby\Content\ContentTranslation',
'kirby\cms\dir' => 'Kirby\Filesystem\Dir',
'kirby\cms\filename' => 'Kirby\Filesystem\Filename',
'kirby\cms\filefoundation' => 'Kirby\Filesystem\IsFile',
'kirby\cms\field' => 'Kirby\Content\Field',
'kirby\cms\form' => 'Kirby\Form\Form',
'kirby\cms\kirbytag' => 'Kirby\Text\KirbyTag',
'kirby\cms\kirbytags' => 'Kirby\Text\KirbyTags',
'kirby\cms\template' => 'Kirby\Template\Template',
'kirby\form\options' => 'Kirby\Option\Options',
'kirby\form\optionsapi' => 'Kirby\Option\OptionsApi',
'kirby\form\optionsquery' => 'Kirby\Option\OptionsQuery',
'kirby\toolkit\dir' => 'Kirby\Filesystem\Dir',
'kirby\toolkit\f' => 'Kirby\Filesystem\F',
'kirby\toolkit\file' => 'Kirby\Filesystem\File',
'kirby\toolkit\mime' => 'Kirby\Filesystem\Mime',
'kirby\toolkit\query' => 'Kirby\Query\Query',
];

View file

@ -1,6 +1,6 @@
<?php
use Kirby\Exception\PermissionException;
use Kirby\Exception\AuthException;
return function () {
$auth = $this->kirby()->auth();
@ -11,17 +11,17 @@ return function () {
$auth->type($allowImpersonation) === 'session' &&
$auth->csrf() === false
) {
throw new PermissionException('Unauthenticated');
throw new AuthException('Unauthenticated');
}
// get user from session or basic auth
if ($user = $auth->user(null, $allowImpersonation)) {
if ($user->role()->permissions()->for('access', 'panel') === false) {
throw new PermissionException(['key' => 'access.panel']);
throw new AuthException(['key' => 'access.panel']);
}
return $user;
}
throw new PermissionException('Unauthenticated');
throw new AuthException('Unauthenticated');
};

View file

@ -3,6 +3,14 @@
/**
* Api Collection Definitions
*/
use Kirby\Cms\Files;
use Kirby\Cms\Languages;
use Kirby\Cms\Pages;
use Kirby\Cms\Roles;
use Kirby\Cms\Translations;
use Kirby\Cms\Users;
return [
/**
@ -10,7 +18,7 @@ return [
*/
'children' => [
'model' => 'page',
'type' => 'Kirby\Cms\Pages',
'type' => Pages::class,
'view' => 'compact'
],
@ -19,7 +27,7 @@ return [
*/
'files' => [
'model' => 'file',
'type' => 'Kirby\Cms\Files'
'type' => Files::class,
],
/**
@ -27,7 +35,7 @@ return [
*/
'languages' => [
'model' => 'language',
'type' => 'Kirby\Cms\Languages'
'type' => Languages::class,
],
/**
@ -35,7 +43,7 @@ return [
*/
'pages' => [
'model' => 'page',
'type' => 'Kirby\Cms\Pages',
'type' => Pages::class,
'view' => 'compact'
],
@ -44,7 +52,7 @@ return [
*/
'roles' => [
'model' => 'role',
'type' => 'Kirby\Cms\Roles',
'type' => Roles::class,
'view' => 'compact'
],
@ -53,7 +61,7 @@ return [
*/
'translations' => [
'model' => 'translation',
'type' => 'Kirby\Cms\Translations',
'type' => Translations::class,
'view' => 'compact'
],
@ -63,7 +71,7 @@ return [
'users' => [
'default' => fn () => $this->users(),
'model' => 'user',
'type' => 'Kirby\Cms\Users',
'type' => Users::class,
'view' => 'compact'
]

View file

@ -8,6 +8,7 @@ return [
'FileBlueprint' => include __DIR__ . '/models/FileBlueprint.php',
'FileVersion' => include __DIR__ . '/models/FileVersion.php',
'Language' => include __DIR__ . '/models/Language.php',
'License' => include __DIR__ . '/models/License.php',
'Page' => include __DIR__ . '/models/Page.php',
'PageBlueprint' => include __DIR__ . '/models/PageBlueprint.php',
'Role' => include __DIR__ . '/models/Role.php',

View file

@ -59,7 +59,7 @@ return [
'url' => fn (File $file) => $file->url(),
'uuid' => fn (File $file) => $file->uuid()?->toString()
],
'type' => 'Kirby\Cms\File',
'type' => File::class,
'views' => [
'default' => [
'content',

View file

@ -12,7 +12,6 @@ return [
'tabs' => fn (FileBlueprint $blueprint) => $blueprint->tabs(),
'title' => fn (FileBlueprint $blueprint) => $blueprint->title(),
],
'type' => 'Kirby\Cms\FileBlueprint',
'views' => [
],
'type' => FileBlueprint::class,
'views' => [],
];

View file

@ -20,7 +20,7 @@ return [
'type' => fn (FileVersion $file) => $file->type(),
'url' => fn (FileVersion $file) => $file->url(),
],
'type' => 'Kirby\Cms\FileVersion',
'type' => FileVersion::class,
'views' => [
'default' => [
'dimensions',

View file

@ -15,7 +15,7 @@ return [
'rules' => fn (Language $language) => $language->rules(),
'url' => fn (Language $language) => $language->url(),
],
'type' => 'Kirby\Cms\Language',
'type' => Language::class,
'views' => [
'default' => [
'code',

View file

@ -0,0 +1,17 @@
<?php
use Kirby\Cms\License;
/**
* Page
*/
return [
'fields' => [
'status' => fn (License $license) => $license->status()->value(),
'code' => function (License $license) {
return $this->kirby()->user()->isAdmin() ? $license->code() : $license->code(true);
},
'type' => fn (License $license) => $license->type()->label(),
],
'type' => License::class,
];

View file

@ -40,7 +40,7 @@ return [
'url' => fn (Page $page) => $page->url(),
'uuid' => fn (Page $page) => $page->uuid()?->toString()
],
'type' => 'Kirby\Cms\Page',
'type' => Page::class,
'views' => [
'compact' => [
'id',

View file

@ -15,7 +15,6 @@ return [
'tabs' => fn (PageBlueprint $blueprint) => $blueprint->tabs(),
'title' => fn (PageBlueprint $blueprint) => $blueprint->title(),
],
'type' => 'Kirby\Cms\PageBlueprint',
'views' => [
],
'type' => PageBlueprint::class,
'views' => [],
];

View file

@ -12,7 +12,7 @@ return [
'permissions' => fn (Role $role) => $role->permissions()->toArray(),
'title' => fn (Role $role) => $role->title(),
],
'type' => 'Kirby\Cms\Role',
'type' => Role::class,
'views' => [
'compact' => [
'description',

View file

@ -19,7 +19,7 @@ return [
'title' => fn (Site $site) => $site->title()->value(),
'url' => fn (Site $site) => $site->url(),
],
'type' => 'Kirby\Cms\Site',
'type' => Site::class,
'views' => [
'compact' => [
'title',

View file

@ -12,6 +12,6 @@ return [
'tabs' => fn (SiteBlueprint $blueprint) => $blueprint->tabs(),
'title' => fn (SiteBlueprint $blueprint) => $blueprint->title(),
],
'type' => 'Kirby\Cms\SiteBlueprint',
'type' => SiteBlueprint::class,
'views' => [],
];

View file

@ -49,7 +49,7 @@ return [
return null;
}
],
'type' => 'Kirby\Cms\System',
'type' => System::class,
'views' => [
'login' => [
'authStatus',

View file

@ -13,7 +13,7 @@ return [
'id' => fn (Translation $translation) => $translation->id(),
'name' => fn (Translation $translation) => $translation->name(),
],
'type' => 'Kirby\Cms\Translation',
'type' => Translation::class,
'views' => [
'compact' => [
'direction',

View file

@ -27,7 +27,7 @@ return [
'username' => fn (User $user) => $user->username(),
'uuid' => fn (User $user) => $user->uuid()?->toString()
],
'type' => 'Kirby\Cms\User',
'type' => User::class,
'views' => [
'default' => [
'avatar',

View file

@ -12,7 +12,6 @@ return [
'tabs' => fn (UserBlueprint $blueprint) => $blueprint->tabs(),
'title' => fn (UserBlueprint $blueprint) => $blueprint->title(),
],
'type' => 'Kirby\Cms\UserBlueprint',
'views' => [
],
'type' => UserBlueprint::class,
'views' => [],
];

View file

@ -19,7 +19,10 @@ return function ($kirby) {
// only add the language routes if the
// multi language setup is activated
if ($kirby->option('languages', false) !== false) {
$routes = array_merge($routes, include __DIR__ . '/routes/languages.php');
$routes = array_merge(
$routes,
include __DIR__ . '/routes/languages.php'
);
}
return $routes;

View file

@ -1,63 +1,70 @@
<?php
// routing pattern to match all models with files
$pattern = '(account|pages/[^/]+|site|users/[^/]+)';
$filePattern = '(account/|pages/[^/]+/|site/|users/[^/]+/|)files/(:any)';
$parentPattern = '(account|pages/[^/]+|site|users/[^/]+)/files';
/**
* Files Routes
*/
return [
[
'pattern' => $pattern . '/files/(:any)/sections/(:any)',
'method' => 'GET',
'action' => function (string $path, string $filename, string $sectionName) {
return $this->file($path, $filename)->blueprint()->section($sectionName)?->toResponse();
}
],
[
'pattern' => $pattern . '/files/(:any)/fields/(:any)/(:all?)',
'pattern' => $filePattern . '/fields/(:any)/(:all?)',
'method' => 'ALL',
'action' => function (string $parent, string $filename, string $fieldName, string $path = null) {
'action' => function (string $parent, string $filename, string $fieldName, string|null $path = null) {
if ($file = $this->file($parent, $filename)) {
return $this->fieldApi($file, $fieldName, $path);
}
}
],
[
'pattern' => $pattern . '/files',
'pattern' => $filePattern . '/sections/(:any)',
'method' => 'GET',
'action' => function (string $path) {
return $this->parent($path)->files()->sorted();
'action' => function (string $path, string $filename, string $sectionName) {
return $this->file($path, $filename)->blueprint()->section($sectionName)?->toResponse();
}
],
[
'pattern' => $pattern . '/files',
'pattern' => $filePattern . '/sections/(:any)/(:all?)',
'method' => 'ALL',
'action' => function (string $parent, string $filename, string $sectionName, string|null $path = null) {
if ($file = $this->file($parent, $filename)) {
return $this->sectionApi($file, $sectionName, $path);
}
}
],
[
'pattern' => $parentPattern,
'method' => 'GET',
'action' => function (string $path) {
return $this->files($path)->sorted();
}
],
[
'pattern' => $parentPattern,
'method' => 'POST',
'action' => function (string $path) {
// move_uploaded_file() not working with unit test
// @codeCoverageIgnoreStart
return $this->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
}
],
[
'pattern' => $pattern . '/files/search',
'pattern' => $parentPattern . '/search',
'method' => 'GET|POST',
'action' => function (string $path) {
$files = $this->parent($path)->files();
$files = $this->files($path);
if ($this->requestMethod() === 'GET') {
return $files->search($this->requestQuery('q'));
@ -67,24 +74,24 @@ return [
}
],
[
'pattern' => $pattern . '/files/sort',
'pattern' => $parentPattern . '/sort',
'method' => 'PATCH',
'action' => function (string $path) {
return $this->parent($path)->files()->changeSort(
return $this->files($path)->changeSort(
$this->requestBody('files'),
$this->requestBody('index')
);
}
],
[
'pattern' => $pattern . '/files/(:any)',
'pattern' => $filePattern,
'method' => 'GET',
'action' => function (string $path, string $filename) {
return $this->file($path, $filename);
}
],
[
'pattern' => $pattern . '/files/(:any)',
'pattern' => $filePattern,
'method' => 'PATCH',
'action' => function (string $path, string $filename) {
return $this->file($path, $filename)->update(
@ -95,7 +102,7 @@ return [
}
],
[
'pattern' => $pattern . '/files/(:any)',
'pattern' => $filePattern,
'method' => 'POST',
'action' => function (string $path, string $filename) {
// move the source file from the temp dir
@ -105,28 +112,29 @@ return [
}
],
[
'pattern' => $pattern . '/files/(:any)',
'pattern' => $filePattern,
'method' => 'DELETE',
'action' => function (string $path, string $filename) {
return $this->file($path, $filename)->delete();
}
],
[
'pattern' => $pattern . '/files/(:any)/name',
'pattern' => $filePattern . '/name',
'method' => 'PATCH',
'action' => function (string $path, string $filename) {
return $this->file($path, $filename)->changeName($this->requestBody('name'));
}
],
[
'pattern' => 'files/search',
'pattern' => $parentPattern . '/search',
'method' => 'GET|POST',
'action' => function () {
$files = $this
->site()
->index(true)
->filter('isReadable', true)
->files();
->filter('isListable', true)
->files()
->filter('isListable', true);
if ($this->requestMethod() === 'GET') {
return $files->search($this->requestQuery('q'));

View file

@ -0,0 +1,35 @@
<?php
// @codeCoverageIgnoreStart
return [
'routes' => function ($kirby) {
return [
[
'pattern' => 'query',
'method' => 'POST|GET',
'auth' => $kirby->option('kql.auth') !== false,
'action' => function () use ($kirby) {
$kql = '\Kirby\Kql\Kql';
if (class_exists($kql) === false) {
return [
'code' => 500,
'status' => 'error',
'message' => 'KQL plugin is not installed',
];
}
$input = $kirby->request()->get();
$result = $kql::run($input);
return [
'code' => 200,
'result' => $result,
'status' => 'ok',
];
}
]
];
}
];
// @codeCoverageIgnoreEnd

View file

@ -5,7 +5,7 @@
* Page Routes
*/
return [
// @codeCoverageIgnoreStart
[
'pattern' => 'pages/(:any)',
'method' => 'GET',
@ -100,6 +100,15 @@ return [
return $this->page($id)->changeTitle($this->requestBody('title'));
}
],
[
'pattern' => 'pages/(:any)/fields/(:any)/(:all?)',
'method' => 'ALL',
'action' => function (string $id, string $fieldName, string|null $path = null) {
if ($page = $this->page($id)) {
return $this->fieldApi($page, $fieldName, $path);
}
}
],
[
'pattern' => 'pages/(:any)/sections/(:any)',
'method' => 'GET',
@ -108,12 +117,13 @@ return [
}
],
[
'pattern' => 'pages/(:any)/fields/(:any)/(:all?)',
'pattern' => 'pages/(:any)/sections/(:any)/(:all?)',
'method' => 'ALL',
'action' => function (string $id, string $fieldName, string $path = null) {
'action' => function (string $id, string $sectionName, string|null $path = null) {
if ($page = $this->page($id)) {
return $this->fieldApi($page, $fieldName, $path);
return $this->sectionApi($page, $sectionName, $path);
}
}
],
// @codeCoverageIgnoreEnd
];

View file

@ -5,7 +5,7 @@
* Site Routes
*/
return [
// @codeCoverageIgnoreStart
[
'pattern' => 'site',
'action' => function () {
@ -75,7 +75,7 @@ return [
$pages = $this
->site()
->index(true)
->filter('isReadable', true);
->filter('isListable', true);
if ($this->requestMethod() === 'GET') {
return $pages->search($this->requestQuery('q'));
@ -84,6 +84,13 @@ return [
return $pages->query($this->requestBody());
}
],
[
'pattern' => 'site/fields/(:any)/(:all?)',
'method' => 'ALL',
'action' => function (string $fieldName, string|null $path = null) {
return $this->fieldApi($this->site(), $fieldName, $path);
}
],
[
'pattern' => 'site/sections/(:any)',
'method' => 'GET',
@ -92,11 +99,11 @@ return [
}
],
[
'pattern' => 'site/fields/(:any)/(:all?)',
'pattern' => 'site/sections/(:any)/(:all?)',
'method' => 'ALL',
'action' => function (string $fieldName, string $path = null) {
return $this->fieldApi($this->site(), $fieldName, $path);
'action' => function (string $sectionName, string|null $path = null) {
return $this->sectionApi($this->site(), $sectionName, $path);
}
]
],
// @codeCoverageIgnoreEnd
];

View file

@ -31,6 +31,18 @@ return [
];
}
],
[
'pattern' => 'system/method-test',
'method' => 'PATCH',
'action' => function () {
return [
'status' => match ($this->kirby()->request()->method()) {
'PATCH' => 'ok',
default => 'fail'
}
];
}
],
[
'pattern' => 'system/register',
'method' => 'POST',

View file

@ -8,6 +8,7 @@ use Kirby\Toolkit\Str;
* User Routes
*/
return [
// @codeCoverageIgnoreStart
[
'pattern' => 'users',
'method' => 'GET',
@ -202,7 +203,19 @@ return [
'users/(:any)/roles',
],
'action' => function (string $id) {
return $this->user($id)->roles();
$kirby = $this->kirby();
$purpose = $kirby->request()->get('purpose');
return $this->user($id)->roles($purpose);
}
],
[
'pattern' => [
'(account)/fields/(:any)/(:all?)',
'users/(:any)/fields/(:any)/(:all?)',
],
'method' => 'ALL',
'action' => function (string $id, string $fieldName, string|null $path = null) {
return $this->fieldApi($this->user($id), $fieldName, $path);
}
],
[
@ -219,12 +232,13 @@ return [
],
[
'pattern' => [
'(account)/fields/(:any)/(:all?)',
'users/(:any)/fields/(:any)/(:all?)',
'(account)/sections/(:any)/(:all?)',
'users/(:any)/sections/(:any)/(:all?)',
],
'method' => 'ALL',
'action' => function (string $id, string $fieldName, string $path = null) {
return $this->fieldApi($this->user($id), $fieldName, $path);
'action' => function (string $id, string $sectionName, string|null $path = null) {
return $this->sectionApi($this->user($id), $sectionName, $path);
}
],
// @codeCoverageIgnoreEnd
];

View file

@ -8,6 +8,7 @@ return function () {
'label' => I18n::translate('view.account'),
'search' => 'users',
'dialogs' => require __DIR__ . '/account/dialogs.php',
'drawers' => require __DIR__ . '/account/drawers.php',
'dropdowns' => require __DIR__ . '/account/dropdowns.php',
'views' => require __DIR__ . '/account/views.php'
];

View file

@ -1,5 +1,7 @@
<?php
use Kirby\Panel\UserTotpEnableDialog;
$dialogs = require __DIR__ . '/../users/dialogs.php';
return [
@ -46,6 +48,13 @@ return [
'submit' => $dialogs['user.delete']['submit'],
],
// account fields dialogs
'account.fields' => [
'pattern' => '(account)/files/(:any)/fields/(:any)/(:all?)',
'load' => $dialogs['user.fields']['load'],
'submit' => $dialogs['user.fields']['submit']
],
// change file name
'account.file.changeName' => [
'pattern' => '(account)/files/(:any)/changeName',
@ -60,6 +69,13 @@ return [
'submit' => $dialogs['user.file.changeSort']['submit'],
],
// change file template
'account.file.changeTemplate' => [
'pattern' => '(account)/files/(:any)/changeTemplate',
'load' => $dialogs['user.file.changeTemplate']['load'],
'submit' => $dialogs['user.file.changeTemplate']['submit'],
],
// delete
'account.file.delete' => [
'pattern' => '(account)/files/(:any)/delete',
@ -67,4 +83,24 @@ return [
'submit' => $dialogs['user.file.delete']['submit'],
],
// account file fields dialogs
'account.file.fields' => [
'pattern' => '(account)/files/(:any)/fields/(:any)/(:all?)',
'load' => $dialogs['user.file.fields']['load'],
'submit' => $dialogs['user.file.fields']['submit']
],
// account enable TOTP
'account.totp.enable' => [
'pattern' => '(account)/totp/enable',
'load' => fn () => (new UserTotpEnableDialog())->load(),
'submit' => fn () => (new UserTotpEnableDialog())->submit()
],
// account disable TOTP
'account.totp.disable' => [
'pattern' => '(account)/totp/disable',
'load' => $dialogs['user.totp.disable']['load'],
'submit' => $dialogs['user.totp.disable']['submit']
],
];

View file

@ -0,0 +1,19 @@
<?php
$drawers = require __DIR__ . '/../users/drawers.php';
return [
// account fields drawers
'account.fields' => [
'pattern' => '(account)/files/(:any)/fields/(:any)/(:all?)',
'load' => $drawers['user.fields']['load'],
'submit' => $drawers['user.fields']['submit']
],
// account file fields drawers
'account.file.fields' => [
'pattern' => '(account)/files/(:any)/fields/(:any)/(:all?)',
'load' => $drawers['user.file.fields']['load'],
'submit' => $drawers['user.file.fields']['submit']
],
];

View file

@ -2,6 +2,7 @@
use Kirby\Cms\App;
use Kirby\Cms\Find;
use Kirby\Toolkit\I18n;
return [
'account' => [
@ -19,6 +20,13 @@ return [
],
'account.password' => [
'pattern' => 'reset-password',
'action' => fn () => ['component' => 'k-reset-password-view']
'action' => fn () => [
'component' => 'k-reset-password-view',
'breadcrumb' => [
[
'label' => I18n::translate('view.resetPassword')
]
]
]
]
];

View file

@ -0,0 +1,61 @@
<?php
use Kirby\Cms\Find;
use Kirby\Panel\Field;
return [
'model' => [
'load' => function (
string $modelPath,
string $fieldName,
string|null $path = null
) {
return Field::dialog(
model: Find::parent($modelPath),
fieldName: $fieldName,
path: $path,
method: 'GET'
);
},
'submit' => function (
string $modelPath,
string $fieldName,
string|null $path = null
) {
return Field::dialog(
model: Find::parent($modelPath),
fieldName: $fieldName,
path: $path,
method: 'POST'
);
}
],
'file' => [
'load' => function (
string $modelPath,
string $filename,
string $fieldName,
string|null $path = null
) {
return Field::dialog(
model: Find::file($modelPath, $filename),
fieldName: $fieldName,
path: $path,
method: 'GET'
);
},
'submit' => function (
string $modelPath,
string $filename,
string $fieldName,
string|null $path = null
) {
return Field::dialog(
model: Find::file($modelPath, $filename),
fieldName: $fieldName,
path: $path,
method: 'POST'
);
}
],
];

View file

@ -0,0 +1,61 @@
<?php
use Kirby\Cms\Find;
use Kirby\Panel\Field;
return [
'model' => [
'load' => function (
string $modelPath,
string $fieldName,
string|null $path = null
) {
return Field::drawer(
model: Find::parent($modelPath),
fieldName: $fieldName,
path: $path,
method: 'GET'
);
},
'submit' => function (
string $modelPath,
string $fieldName,
string|null $path = null
) {
return Field::drawer(
model: Find::parent($modelPath),
fieldName: $fieldName,
path: $path,
method: 'POST'
);
}
],
'file' => [
'load' => function (
string $modelPath,
string $filename,
string $fieldName,
string|null $path = null
) {
return Field::drawer(
model: Find::file($modelPath, $filename),
fieldName: $fieldName,
path: $path,
method: 'GET'
);
},
'submit' => function (
string $modelPath,
string $filename,
string $fieldName,
string|null $path = null
) {
return Field::drawer(
model: Find::file($modelPath, $filename),
fieldName: $fieldName,
path: $path,
method: 'POST'
);
}
],
];

View file

@ -26,7 +26,7 @@ return [
'type' => 'slug',
'required' => true,
'icon' => 'title',
'allow' => '@._-',
'allow' => 'a-z0-9@._-',
'after' => '.' . $file->extension(),
'preselect' => true
]
@ -40,7 +40,8 @@ return [
},
'submit' => function (string $path, string $filename) {
$file = Find::file($path, $filename);
$renamed = $file->changeName($file->kirby()->request()->get('name'));
$name = $file->kirby()->request()->get('name');
$renamed = $file->changeName($name);
$oldUrl = $file->panel()->url(true);
$newUrl = $renamed->panel()->url(true);
$response = [
@ -96,6 +97,44 @@ return [
}
],
'changeTemplate' => [
'load' => function (string $path, string $filename) {
$file = Find::file($path, $filename);
$blueprints = $file->blueprints();
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'warning' => [
'type' => 'info',
'theme' => 'notice',
'text' => I18n::translate('file.changeTemplate.notice')
],
'template' => Field::template($blueprints, [
'required' => true
])
],
'theme' => 'notice',
'submitButton' => I18n::translate('change'),
'value' => [
'template' => $file->template()
]
]
];
},
'submit' => function (string $path, string $filename) {
$file = Find::file($path, $filename);
$template = $file->kirby()->request()->get('template');
$file->changeTemplate($template);
return [
'event' => 'file.changeTemplate',
];
}
],
'delete' => [
'load' => function (string $path, string $filename) {
$file = Find::file($path, $filename);
@ -129,4 +168,5 @@ return [
];
}
],
];

View file

@ -0,0 +1,11 @@
<?php
return function () {
return [
'icon' => 'lab',
'label' => 'Lab',
'menu' => false,
'drawers' => require __DIR__ . '/lab/drawers.php',
'views' => require __DIR__ . '/lab/views.php'
];
};

View file

@ -0,0 +1,30 @@
<?php
use Kirby\Panel\Lab\Docs;
return [
'lab.docs' => [
'pattern' => 'lab/docs/(:any)',
'load' => function (string $component) {
if (Docs::installed() === false) {
return [
'component' => 'k-text-drawer',
'props' => [
'text' => 'The UI docs are not installed.'
]
];
}
$docs = new Docs($component);
return [
'component' => 'k-lab-docs-drawer',
'props' => [
'icon' => 'book',
'title' => $component,
'docs' => $docs->toArray()
]
];
},
],
];

View file

@ -0,0 +1,151 @@
<?php
use Kirby\Cms\App;
use Kirby\Panel\Lab\Category;
use Kirby\Panel\Lab\Docs;
return [
'lab' => [
'pattern' => 'lab',
'action' => function () {
return [
'component' => 'k-lab-index-view',
'props' => [
'categories' => Category::all(),
'info' => Category::installed() ? null : 'The default Lab examples are not installed.',
'tab' => 'examples',
],
];
}
],
'lab.docs' => [
'pattern' => 'lab/docs',
'action' => function () {
$props = match (Docs::installed()) {
true => [
'categories' => [['examples' => Docs::all()]],
'tab' => 'docs',
],
false => [
'info' => 'The UI docs are not installed.',
'tab' => 'docs',
]
};
return [
'component' => 'k-lab-index-view',
'title' => 'Docs',
'breadcrumb' => [
[
'label' => 'Docs',
'link' => 'lab/docs'
]
],
'props' => $props,
];
}
],
'lab.doc' => [
'pattern' => 'lab/docs/(:any)',
'action' => function (string $component) {
$crumbs = [
[
'label' => 'Docs',
'link' => 'lab/docs'
],
[
'label' => $component,
'link' => 'lab/docs/' . $component
]
];
if (Docs::installed() === false) {
return [
'component' => 'k-lab-index-view',
'title' => $component,
'breadcrumb' => $crumbs,
'props' => [
'info' => 'The UI docs are not installed.',
'tab' => 'docs',
],
];
}
$docs = new Docs($component);
return [
'component' => 'k-lab-docs-view',
'title' => $component,
'breadcrumb' => $crumbs,
'props' => [
'component' => $component,
'docs' => $docs->toArray(),
'lab' => $docs->lab()
]
];
}
],
'lab.vue' => [
'pattern' => [
'lab/(:any)/(:any)/index.vue',
'lab/(:any)/(:any)/(:any)/index.vue'
],
'action' => function (
string $category,
string $id,
string|null $tab = null
) {
return Category::factory($category)->example($id, $tab)->serve();
}
],
'lab.example' => [
'pattern' => 'lab/(:any)/(:any)/(:any?)',
'action' => function (
string $category,
string $id,
string|null $tab = null
) {
$category = Category::factory($category);
$example = $category->example($id, $tab);
$props = $example->props();
$vue = $example->vue();
$compiler = App::instance()->option('panel.vue.compiler', true);
if (Docs::installed() === true && $docs = $props['docs'] ?? null) {
$docs = new Docs($docs);
}
$github = $docs?->github();
if ($source = $props['source'] ?? null) {
$github ??= 'https://github.com/getkirby/kirby/tree/main/' . $source;
}
return [
'component' => 'k-lab-playground-view',
'breadcrumb' => [
[
'label' => $category->name(),
],
[
'label' => $example->title(),
'link' => $example->url()
]
],
'props' => [
'compiler' => $compiler,
'docs' => $docs?->name(),
'examples' => $vue['examples'],
'file' => $example->module(),
'github' => $github,
'props' => $props,
'styles' => $vue['style'],
'tab' => $example->tab(),
'tabs' => array_values($example->tabs()),
'template' => $vue['template'],
'title' => $example->title(),
],
];
}
]
];

View file

@ -4,7 +4,7 @@ use Kirby\Toolkit\I18n;
return function ($kirby) {
return [
'icon' => 'globe',
'icon' => 'translate',
'label' => I18n::translate('view.languages'),
'menu' => true,
'dialogs' => require __DIR__ . '/languages/dialogs.php',

View file

@ -2,13 +2,15 @@
use Kirby\Cms\App;
use Kirby\Cms\Find;
use Kirby\Panel\Field;
use Kirby\Cms\LanguageVariable;
use Kirby\Exception\NotFoundException;
use Kirby\Toolkit\A;
use Kirby\Toolkit\Escape;
use Kirby\Toolkit\I18n;
$languageDialogFields = [
'name' => [
'counter' => false,
'label' => I18n::translate('language.name'),
'type' => 'text',
'required' => true,
@ -19,7 +21,7 @@ $languageDialogFields = [
'type' => 'text',
'required' => true,
'counter' => false,
'icon' => 'globe',
'icon' => 'translate',
'width' => '1/2'
],
'direction' => [
@ -34,11 +36,27 @@ $languageDialogFields = [
'width' => '1/2'
],
'locale' => [
'label' => I18n::translate('language.locale'),
'type' => 'text',
'counter' => false,
'label' => I18n::translate('language.locale'),
'type' => 'text',
],
];
$translationDialogFields = [
'key' => [
'counter' => false,
'icon' => null,
'label' => I18n::translate('language.variable.key'),
'type' => 'text'
],
'value' => [
'buttons' => false,
'counter' => false,
'label' => I18n::translate('language.variable.value'),
'type' => 'textarea'
]
];
return [
// create language
@ -92,8 +110,10 @@ return [
},
'submit' => function (string $id) {
Find::language($id)->delete();
return [
'event' => 'language.delete',
'event' => 'language.delete',
'redirect' => 'languages'
];
}
],
@ -152,4 +172,109 @@ return [
];
}
],
'language.translation.create' => [
'pattern' => 'languages/(:any)/translations/create',
'load' => function (string $languageCode) use ($translationDialogFields) {
// find the language to make sure it exists
Find::language($languageCode);
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => $translationDialogFields,
'size' => 'large',
],
];
},
'submit' => function (string $languageCode) {
$request = App::instance()->request();
$language = Find::language($languageCode);
$key = $request->get('key', '');
$value = $request->get('value', '');
LanguageVariable::create($key, $value);
if ($language->isDefault() === false) {
$language->variable($key)->update($value);
}
return true;
}
],
'language.translation.delete' => [
'pattern' => 'languages/(:any)/translations/(:any)/delete',
'load' => function (string $languageCode, string $translationKey) {
$variable = Find::language($languageCode)->variable($translationKey, true);
if ($variable->exists() === false) {
throw new NotFoundException([
'key' => 'language.variable.notFound'
]);
}
return [
'component' => 'k-remove-dialog',
'props' => [
'text' => I18n::template('language.variable.delete.confirm', [
'key' => Escape::html($variable->key())
])
],
];
},
'submit' => function (string $languageCode, string $translationKey) {
return Find::language($languageCode)->variable($translationKey, true)->delete();
}
],
'language.translation.update' => [
'pattern' => 'languages/(:any)/translations/(:any)/update',
'load' => function (string $languageCode, string $translationKey) use ($translationDialogFields) {
$variable = Find::language($languageCode)->variable($translationKey, true);
if ($variable->exists() === false) {
throw new NotFoundException([
'key' => 'language.variable.notFound'
]);
}
$fields = $translationDialogFields;
$fields['key']['disabled'] = true;
$fields['value']['autofocus'] = true;
// shows info text when variable is an array
// TODO: 5.0: use entries field instead showing info text
$isVariableArray = is_array($variable->value()) === true;
if ($isVariableArray === true) {
$fields['value'] = [
'label' => I18n::translate('info'),
'type' => 'info',
'text' => 'You are using an array variable for this key. Please modify it in the language file in /site/languages',
];
}
return [
'component' => 'k-form-dialog',
'props' => [
'cancelButton' => $isVariableArray === false,
'fields' => $fields,
'size' => 'large',
'submitButton' => $isVariableArray === false,
'value' => [
'key' => $variable->key(),
'value' => $variable->value()
]
],
];
},
'submit' => function (string $languageCode, string $translationKey) {
Find::language($languageCode)->variable($translationKey, true)->update(
App::instance()->request()->get('value', '')
);
return true;
}
]
];

View file

@ -1,9 +1,110 @@
<?php
use Kirby\Cms\App;
use Kirby\Cms\Find;
use Kirby\Toolkit\Escape;
use Kirby\Toolkit\I18n;
return [
'language' => [
'pattern' => 'languages/(:any)',
'when' => function (): bool {
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 = $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) {
$strings[] = [
'key' => $key,
'value' => $translations[$key] ?? null,
'options' => [
[
'click' => 'update',
'disabled' => $canUpdate === false,
'icon' => 'edit',
'text' => I18n::translate('edit'),
],
[
'click' => 'delete',
'disabled' => $canUpdate === false || $language->isDefault() === false,
'icon' => 'trash',
'text' => I18n::translate('delete'),
]
]
];
}
$next = function () use ($language) {
if ($next = $language->next()) {
return [
'link' => '/languages/' . $next->code(),
'title' => $next->name(),
];
}
};
$prev = function () use ($language) {
if ($prev = $language->prev()) {
return [
'link' => '/languages/' . $prev->code(),
'title' => $prev->name(),
];
}
};
return [
'component' => 'k-language-view',
'breadcrumb' => [
[
'label' => $name = $language->name(),
'link' => $link,
]
],
'props' => [
'deletable' => $language->isDeletable(),
'code' => Escape::html($language->code()),
'default' => $language->isDefault(),
'direction' => $language->direction(),
'id' => $language->code(),
'info' => [
[
'label' => 'Status',
'value' => I18n::translate('language.' . ($language->isDefault() ? 'default' : 'secondary')),
],
[
'label' => I18n::translate('language.code'),
'value' => $language->code(),
],
[
'label' => I18n::translate('language.locale'),
'value' => $language->locale(LC_ALL)
],
[
'label' => I18n::translate('language.direction'),
'value' => I18n::translate('language.direction.' . $language->direction()),
],
],
'name' => $name,
'next' => $next,
'prev' => $prev,
'translations' => $strings,
'url' => $language->url(),
]
];
}
],
'languages' => [
'pattern' => 'languages',
'action' => function () {
@ -13,13 +114,15 @@ return [
'component' => 'k-languages-view',
'props' => [
'languages' => $kirby->languages()->values(fn ($language) => [
'default' => $language->isDefault(),
'id' => $language->code(),
'info' => Escape::html($language->code()),
'text' => Escape::html($language->name()),
])
'deletable' => $language->isDeletable(),
'default' => $language->isDefault(),
'id' => $language->code(),
'info' => Escape::html($language->code()),
'text' => Escape::html($language->name()),
]),
'variables' => $kirby->option('languages.variables', true)
]
];
}
],
]
];

View file

@ -0,0 +1,11 @@
<?php
use Kirby\Toolkit\I18n;
return function () {
return [
'icon' => 'search',
'label' => I18n::translate('search'),
'views' => require __DIR__ . '/search/views.php'
];
};

View file

@ -0,0 +1,17 @@
<?php
use Kirby\Cms\App;
return [
'search' => [
'pattern' => 'search',
'action' => function () {
return [
'component' => 'k-search-view',
'props' => [
'type' => App::instance()->request()->get('type'),
]
];
}
],
];

View file

@ -3,15 +3,19 @@
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',
'dropdowns' => require __DIR__ . '/site/dropdowns.php',
'requests' => require __DIR__ . '/site/requests.php',
'searches' => require __DIR__ . '/site/searches.php',
'views' => require __DIR__ . '/site/views.php',
];

View file

@ -2,14 +2,21 @@
use Kirby\Cms\App;
use Kirby\Cms\Find;
use Kirby\Cms\PageRules;
use Kirby\Cms\Url;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\PermissionException;
use Kirby\Panel\ChangesDialog;
use Kirby\Panel\Field;
use Kirby\Panel\PageCreateDialog;
use Kirby\Panel\Panel;
use Kirby\Toolkit\Escape;
use Kirby\Toolkit\I18n;
use Kirby\Toolkit\Str;
use Kirby\Uuid\Uuids;
$fields = require __DIR__ . '/../fields/dialogs.php';
$files = require __DIR__ . '/../files/dialogs.php';
return [
@ -155,10 +162,16 @@ return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'notice' => [
'type' => 'info',
'theme' => 'notice',
'text' => I18n::translate('page.changeTemplate.notice')
],
'template' => Field::template($blueprints, [
'required' => true
])
],
'theme' => 'notice',
'submitButton' => I18n::translate('change'),
'value' => [
'template' => $page->intendedTemplate()->name()
@ -167,9 +180,10 @@ return [
];
},
'submit' => function (string $id) {
$request = App::instance()->request();
$page = Find::page($id);
$template = App::instance()->request()->get('template');
Find::page($id)->changeTemplate($request->get('template'));
$page->changeTemplate($template);
return [
'event' => 'page.changeTemplate',
@ -181,12 +195,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' => [
@ -199,7 +224,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'),
@ -224,17 +249,8 @@ return [
$slug = trim($request->get('slug', ''));
// basic input validation before we move on
if (Str::length($title) === 0) {
throw new InvalidArgumentException([
'key' => 'page.changeTitle.empty'
]);
}
if (Str::length($slug) === 0) {
throw new InvalidArgumentException([
'key' => 'page.slug.invalid'
]);
}
PageRules::validateTitleLength($title);
PageRules::validateSlugLength($slug);
// nothing changed
if ($page->title()->value() === $title && $page->slug() === $slug) {
@ -277,93 +293,30 @@ return [
'page.create' => [
'pattern' => 'pages/create',
'load' => function () {
$kirby = App::instance();
$request = $kirby->request();
$request = App::instance()->request();
$dialog = new PageCreateDialog(
parentId: $request->get('parent'),
sectionId: $request->get('section'),
slug: $request->get('slug'),
template: $request->get('template'),
title: $request->get('title'),
viewId: $request->get('view'),
);
// the parent model for the new page
$parent = $request->get('parent', 'site');
// the view on which the add button is located
// this is important to find the right section
// and provide the correct templates for the new page
$view = $request->get('view', $parent);
// templates will be fetched depending on the
// section settings in the blueprint
$section = $request->get('section');
// this is the parent model
$model = Find::parent($parent);
// this is the view model
// i.e. site if the add button is on
// the dashboard
$view = Find::parent($view);
// available blueprints/templates for the new page
// are always loaded depending on the matching section
// in the view model blueprint
$blueprints = $view->blueprints($section);
// the pre-selected template
$template = $blueprints[0]['name'] ?? $blueprints[0]['value'] ?? null;
$fields = [
'parent' => Field::hidden(),
'title' => Field::title([
'required' => true,
'preselect' => true
]),
'slug' => Field::slug([
'required' => true,
'sync' => 'title',
'path' => empty($model->id()) === false ? '/' . $model->id() . '/' : '/'
]),
'template' => Field::hidden()
];
// only show template field if > 1 templates available
// or when in debug mode
if (count($blueprints) > 1 || $kirby->option('debug') === true) {
$fields['template'] = Field::template($blueprints, [
'required' => true
]);
}
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => $fields,
'submitButton' => I18n::translate('page.draft.create'),
'value' => [
'parent' => $parent,
'slug' => '',
'template' => $template,
'title' => '',
]
]
];
return $dialog->load();
},
'submit' => function () {
$request = App::instance()->request();
$title = trim($request->get('title', ''));
$dialog = new PageCreateDialog(
parentId: $request->get('parent'),
sectionId: $request->get('section'),
slug: $request->get('slug'),
template: $request->get('template'),
title: $request->get('title'),
viewId: $request->get('view'),
);
if (Str::length($title) === 0) {
throw new InvalidArgumentException([
'key' => 'page.changeTitle.empty'
]);
}
$page = Find::parent($request->get('parent', 'site'))->createChild([
'content' => ['title' => $title],
'slug' => $request->get('slug'),
'template' => $request->get('template'),
]);
return [
'event' => 'page.create',
'redirect' => $page->panel()->url(true)
];
return $dialog->submit($request->get());
}
],
@ -479,7 +432,7 @@ return [
];
}
$slugAppendix = Str::slug(I18n::translate('page.duplicate.appendix'));
$slugAppendix = Url::slug(I18n::translate('page.duplicate.appendix'));
$titleAppendix = I18n::translate('page.duplicate.appendix');
// if the item to be duplicated already exists
@ -529,6 +482,13 @@ return [
}
],
// page field dialogs
'page.fields' => [
'pattern' => '(pages/.*?)/fields/(:any)/(:all?)',
'load' => $fields['model']['load'],
'submit' => $fields['model']['submit']
],
// change filename
'page.file.changeName' => [
'pattern' => '(pages/.*?)/files/(:any)/changeName',
@ -543,6 +503,13 @@ return [
'submit' => $files['changeSort']['submit'],
],
// change template
'page.file.changeTemplate' => [
'pattern' => '(pages/.*?)/files/(:any)/changeTemplate',
'load' => $files['changeTemplate']['load'],
'submit' => $files['changeTemplate']['submit'],
],
// delete
'page.file.delete' => [
'pattern' => '(pages/.*?)/files/(:any)/delete',
@ -550,6 +517,56 @@ return [
'submit' => $files['delete']['submit'],
],
// page file field dialogs
'page.file.fields' => [
'pattern' => '(pages/.*?)/files/(:any)/fields/(:any)/(:all?)',
'load' => $fields['file']['load'],
'submit' => $fields['file']['submit'],
],
// move page
'page.move' => [
'pattern' => 'pages/(:any)/move',
'load' => function (string $id) {
$page = Find::page($id);
$parent = $page->parentModel();
if (Uuids::enabled() === false) {
$parentId = $parent?->id() ?? '/';
} else {
$parentId = $parent?->uuid()->toString() ?? 'site://';
}
return [
'component' => 'k-page-move-dialog',
'props' => [
'value' => [
'move' => $page->panel()->url(true),
'parent' => $parentId
]
]
];
},
'submit' => function (string $id) {
$kirby = App::instance();
$parentId = $kirby->request()->get('parent');
$parent = (empty($parentId) === true || $parentId === '/' || $parentId === 'site://') ? $kirby->site() : Find::page($parentId);
$oldPage = Find::page($id);
$newPage = $oldPage->move($parent);
return [
'event' => 'page.move',
'redirect' => $newPage->panel()->url(true),
'dispatch' => [
'content/move' => [
$oldPage->panel()->url(true),
$newPage->panel()->url(true)
]
],
];
}
],
// change site title
'site.changeTitle' => [
'pattern' => 'site/changeTitle',
@ -572,14 +589,21 @@ return [
},
'submit' => function () {
$kirby = App::instance();
$kirby->site()->changeTitle($kirby->request()->get('title'));
return [
'event' => 'site.changeTitle',
];
}
],
// site field dialogs
'site.fields' => [
'pattern' => '(site)/fields/(:any)/(:all?)',
'load' => $fields['model']['load'],
'submit' => $fields['model']['submit'],
],
// change filename
'site.file.changeName' => [
'pattern' => '(site)/files/(:any)/changeName',
@ -594,6 +618,13 @@ return [
'submit' => $files['changeSort']['submit'],
],
// change template
'site.file.changeTemplate' => [
'pattern' => '(site)/files/(:any)/changeTemplate',
'load' => $files['changeTemplate']['load'],
'submit' => $files['changeTemplate']['submit'],
],
// delete
'site.file.delete' => [
'pattern' => '(site)/files/(:any)/delete',
@ -601,4 +632,24 @@ return [
'submit' => $files['delete']['submit'],
],
// site file field dialogs
'site.file.fields' => [
'pattern' => '(site)/files/(:any)/fields/(:any)/(:all?)',
'load' => $fields['file']['load'],
'submit' => $fields['file']['submit'],
],
// content changes
'changes' => [
'pattern' => 'changes',
'load' => function () {
$dialog = new ChangesDialog();
return $dialog->load();
},
'submit' => function () {
$dialog = new ChangesDialog();
$ids = App::instance()->request()->get('ids');
return $dialog->submit($ids);
}
],
];

View file

@ -0,0 +1,33 @@
<?php
$fields = require __DIR__ . '/../fields/drawers.php';
return [
// page field drawers
'page.fields' => [
'pattern' => '(pages/.*?)/fields/(:any)/(:all?)',
'load' => $fields['model']['load'],
'submit' => $fields['model']['submit']
],
// page file field drawers
'page.file.fields' => [
'pattern' => '(pages/.*?)/files/(:any)/fields/(:any)/(:all?)',
'load' => $fields['file']['load'],
'submit' => $fields['file']['submit'],
],
// site field drawers
'site.fields' => [
'pattern' => '(site)/fields/(:any)/(:all?)',
'load' => $fields['model']['load'],
'submit' => $fields['model']['submit'],
],
// site file field drawers
'site.file.fields' => [
'pattern' => '(site)/files/(:any)/fields/(:any)/(:all?)',
'load' => $fields['file']['load'],
'submit' => $fields['file']['submit'],
],
];

View file

@ -1,14 +1,9 @@
<?php
use Kirby\Panel\Dropdown;
$files = require __DIR__ . '/../files/dropdowns.php';
return [
'changes' => [
'pattern' => 'changes',
'options' => fn () => Dropdown::changes()
],
'page' => [
'pattern' => 'pages/(:any)',
'options' => function (string $path) {

View file

@ -0,0 +1,90 @@
<?php
use Kirby\Cms\App;
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 () {
$kirby = App::instance();
$request = $kirby->request();
$move = $request->get('move');
$move = $move ? Find::parent($move) : null;
$parent = $request->get('parent');
if ($parent === null) {
$site = $kirby->site();
$panel = $site->panel();
$uuid = $site->uuid()?->toString();
$url = $site->url();
$value = $uuid ?? '/';
return [
[
'children' => $panel->url(true),
'disabled' => $move?->isMovableTo($site) === false,
'hasChildren' => true,
'icon' => 'home',
'id' => '/',
'label' => I18n::translate('view.site'),
'open' => false,
'url' => $url,
'uuid' => $uuid,
'value' => $value
]
];
}
$parent = Find::parent($parent);
$pages = [];
foreach ($parent->childrenAndDrafts()->filterBy('isListable', true) as $child) {
$panel = $child->panel();
$uuid = $child->uuid()?->toString();
$url = $child->url();
$value = $uuid ?? $child->id();
$pages[] = [
'children' => $panel->url(true),
'disabled' => $move?->isMovableTo($child) === false,
'hasChildren' => $child->hasChildren() === true || $child->hasDrafts() === true,
'icon' => $panel->image()['icon'] ?? null,
'id' => $child->id(),
'open' => false,
'label' => $child->title()->value(),
'url' => $url,
'uuid' => $uuid,
'value' => $value
];
}
return $pages;
}
],
'tree.parents' => [
'pattern' => 'site/tree/parents',
'action' => function () {
$kirby = App::instance();
$request = $kirby->request();
$root = $request->get('root');
$page = $kirby->page($request->get('page'));
$parents = $page?->parents()->flip()->values(
fn ($parent) => $parent->uuid()?->toString() ?? $parent->id()
) ?? [];
// if root is included, add the site as top-level parent
if ($root === 'true') {
array_unshift($parents, $kirby->site()->uuid()?->toString() ?? '/');
}
return [
'data' => $parents
];
}
]
// @codeCoverageIgnoreEnd
];

View file

@ -8,50 +8,49 @@ return [
'pages' => [
'label' => I18n::translate('pages'),
'icon' => 'page',
'query' => function (string $query = null) {
$pages = App::instance()->site()
'query' => function (string|null $query, int $limit, int $page) {
$kirby = App::instance();
$pages = $kirby->site()
->index(true)
->search($query)
->filter('isReadable', true)
->limit(10);
->filter('isListable', true)
->paginate($limit, $page);
$results = [];
foreach ($pages as $page) {
$results[] = [
return [
'results' => $pages->values(fn ($page) => [
'image' => $page->panel()->image(),
'text' => Escape::html($page->title()->value()),
'link' => $page->panel()->url(true),
'info' => Escape::html($page->id())
];
}
return $results;
'info' => Escape::html($page->id()),
'uuid' => $page->uuid()?->toString(),
]),
'pagination' => $pages->pagination()->toArray()
];
}
],
'files' => [
'label' => I18n::translate('files'),
'icon' => 'image',
'query' => function (string $query = null) {
$files = App::instance()->site()
'query' => function (string|null $query, int $limit, int $page) {
$kirby = App::instance();
$files = $kirby->site()
->index(true)
->filter('isReadable', true)
->filter('isListable', true)
->files()
->filter('isListable', true)
->search($query)
->limit(10);
->paginate($limit, $page);
$results = [];
foreach ($files as $file) {
$results[] = [
return [
'results' => $files->values(fn ($file) => [
'image' => $file->panel()->image(),
'text' => Escape::html($file->filename()),
'link' => $file->panel()->url(true),
'info' => Escape::html($file->id())
];
}
return $results;
'info' => Escape::html($file->id()),
'uuid' => $file->uuid()->toString(),
]),
'pagination' => $files->pagination()->toArray()
];
}
]
];

View file

@ -1,6 +1,7 @@
<?php
use Kirby\Cms\App;
use Kirby\Exception\LogicException;
use Kirby\Panel\Field;
use Kirby\Toolkit\I18n;
@ -8,64 +9,85 @@ return [
// license key
'license' => [
'load' => function () {
$license = App::instance()->system()->license();
// @codeCoverageIgnoreStart
// the system is registered but the license
// key is only visible for admins
if ($license === true) {
$license = 'Kirby 3';
}
// @codeCoverageIgnoreEnd
$kirby = App::instance();
$license = $kirby->system()->license();
$obfuscated = $kirby->user()->isAdmin() === false;
$status = $license->status();
$renewable = $status->renewable();
return [
'component' => 'k-form-dialog',
'component' => 'k-license-dialog',
'props' => [
'size' => 'medium',
'fields' => [
'license' => [
'type' => 'info',
'label' => I18n::translate('license'),
'text' => $license ? $license : I18n::translate('license.unregistered.label'),
'theme' => $license ? 'code' : 'negative',
'help' => $license ?
// @codeCoverageIgnoreStart
'<a href="https://hub.getkirby.com">' . I18n::translate('license.manage') . ' &rarr;</a>' :
// @codeCoverageIgnoreEnd
'<a href="https://getkirby.com/buy">' . I18n::translate('license.buy') . ' &rarr;</a>'
]
'license' => [
'code' => $license->code($obfuscated),
'icon' => $status->icon(),
'info' => $status->info($license->renewal('Y-m-d', 'date')),
'theme' => $status->theme(),
'type' => $license->label(),
],
'submitButton' => false,
'cancelButton' => false,
'cancelButton' => $renewable,
'submitButton' => $renewable ? [
'icon' => 'refresh',
'text' => I18n::translate('renew'),
'theme' => 'love',
] : false,
]
];
},
'submit' => function () {
// @codeCoverageIgnoreStart
$response = App::instance()->system()->license()->upgrade();
// the upgrade is still needed
if ($response['status'] === 'upgrade') {
return [
'redirect' => $response['url']
];
}
// the upgrade has already been completed
if ($response['status'] === 'complete') {
return [
'event' => 'system.renew',
'message' => I18n::translate('license.success')
];
}
throw new LogicException('The upgrade failed');
// @codeCoverageIgnoreEnd
}
],
// license registration
'registration' => [
'load' => function () {
$system = App::instance()->system();
$local = $system->isLocal();
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'domain' => [
'label' => I18n::translate('license.activate.label'),
'type' => 'info',
'theme' => $system->isLocal() ? 'notice' : 'info',
'text' => I18n::template('license.register.' . ($system->isLocal() ? 'local' : 'domain'), ['host' => $system->indexUrl()])
'theme' => $local ? 'warning' : 'info',
'text' => I18n::template('license.activate.' . ($local ? 'local' : 'domain'), ['host' => $system->indexUrl()])
],
'license' => [
'label' => I18n::translate('license.register.label'),
'label' => I18n::translate('license.code.label'),
'type' => 'text',
'required' => true,
'counter' => false,
'placeholder' => 'K3-',
'help' => I18n::translate('license.register.help')
'placeholder' => 'K-',
'help' => I18n::translate('license.code.help') . ' ' . '<a href="https://getkirby.com/buy" target="_blank">' . I18n::translate('license.buy') . ' &rarr;</a>'
],
'email' => Field::email(['required' => true])
],
'submitButton' => I18n::translate('license.register'),
'submitButton' => [
'icon' => 'key',
'text' => I18n::translate('activate'),
'theme' => 'love',
],
'value' => [
'license' => null,
'email' => null
@ -83,7 +105,7 @@ return [
return [
'event' => 'system.register',
'message' => I18n::translate('license.register.success')
'message' => I18n::translate('license.success')
];
// @codeCoverageIgnoreEnd
}

View file

@ -11,31 +11,34 @@ return [
$system = $kirby->system();
$updateStatus = $system->updateStatus();
$license = $system->license();
$debugMode = $kirby->option('debug', false) === true;
$isLocal = $system->isLocal();
$environment = [
[
'label' => $license ? I18n::translate('license') : I18n::translate('license.register.label'),
'value' => $license ? 'Kirby 3' : I18n::translate('license.unregistered.label'),
'theme' => $license ? null : 'negative',
'dialog' => $license ? 'license' : 'registration'
'label' => $license->status()->label(),
'value' => $license->label(),
'theme' => $license->status()->theme(),
'icon' => $license->status()->icon(),
'dialog' => $license->status()->dialog()
],
[
'label' => $updateStatus?->label() ?? I18n::translate('version'),
'value' => $kirby->version(),
'link' => (
$updateStatus ?
$updateStatus->url() :
'https://github.com/getkirby/kirby/releases/tag/' . $kirby->version()
),
'theme' => $updateStatus?->theme()
'link' => $updateStatus?->url() ??
'https://github.com/getkirby/kirby/releases/tag/' . $kirby->version(),
'theme' => $updateStatus?->theme(),
'icon' => $updateStatus?->icon() ?? 'info'
],
[
'label' => 'PHP',
'value' => phpversion()
'value' => phpversion(),
'icon' => 'code'
],
[
'label' => I18n::translate('server'),
'value' => $system->serverSoftware() ?? '?'
'value' => $system->serverSoftwareShort() ?? '?',
'icon' => 'server'
]
];
@ -44,10 +47,14 @@ return [
$plugins = $system->plugins()->values(function ($plugin) use (&$exceptions) {
$authors = $plugin->authorsNames();
$updateStatus = $plugin->updateStatus();
$version = $updateStatus?->toArray() ?? $plugin->version() ?? '';
$version = $updateStatus?->toArray();
$version ??= $plugin->version() ?? '';
if ($updateStatus !== null) {
$exceptions = array_merge($exceptions, $updateStatus->exceptionMessages());
$exceptions = [
...$exceptions,
...$updateStatus->exceptionMessages()
];
}
return [
@ -63,15 +70,29 @@ return [
$security = $updateStatus?->messages() ?? [];
if ($kirby->option('debug', false) === true) {
if ($isLocal === true) {
$security[] = [
'id' => 'debug',
'text' => I18n::translate('system.issues.debug'),
'link' => 'https://getkirby.com/security/debug'
'id' => 'local',
'icon' => 'info',
'theme' => 'info',
'text' => I18n::translate('system.issues.local')
];
}
if ($kirby->environment()->https() !== true) {
if ($debugMode === true) {
$security[] = [
'id' => 'debug',
'icon' => $isLocal ? 'info' : 'alert',
'theme' => $isLocal ? 'info' : 'negative',
'text' => I18n::translate('system.issues.debug'),
'link' => 'https://getkirby.com/security/debug'
];
}
if (
$isLocal === false &&
$kirby->environment()->https() !== true
) {
$security[] = [
'id' => 'https',
'text' => I18n::translate('system.issues.https'),
@ -79,19 +100,34 @@ return [
];
}
if ($kirby->option('panel.vue.compiler', null) === null) {
$security[] = [
'id' => 'vue-compiler',
'link' => 'https://getkirby.com/security/vue-compiler',
'text' => I18n::translate('system.issues.vue.compiler'),
'theme' => 'notice'
];
}
// sensitive URLs
if ($isLocal === false) {
$sensitive = [
'content' => $system->exposedFileUrl('content'),
'git' => $system->exposedFileUrl('git'),
'kirby' => $system->exposedFileUrl('kirby'),
'site' => $system->exposedFileUrl('site')
];
}
return [
'component' => 'k-system-view',
'props' => [
'environment' => $environment,
'exceptions' => $kirby->option('debug') === true ? $exceptions : [],
'exceptions' => $debugMode ? $exceptions : [],
'info' => $system->info(),
'plugins' => $plugins,
'security' => $security,
'urls' => [
'content' => $system->exposedFileUrl('content'),
'git' => $system->exposedFileUrl('git'),
'kirby' => $system->exposedFileUrl('kirby'),
'site' => $system->exposedFileUrl('site')
]
'urls' => $sensitive ?? null
]
];
}

View file

@ -9,6 +9,7 @@ return function ($kirby) {
'search' => 'users',
'menu' => true,
'dialogs' => require __DIR__ . '/users/dialogs.php',
'drawers' => require __DIR__ . '/users/drawers.php',
'dropdowns' => require __DIR__ . '/users/dropdowns.php',
'searches' => require __DIR__ . '/users/searches.php',
'views' => require __DIR__ . '/users/views.php'

View file

@ -3,12 +3,15 @@
use Kirby\Cms\App;
use Kirby\Cms\Find;
use Kirby\Cms\UserRules;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Panel\Field;
use Kirby\Panel\Panel;
use Kirby\Panel\UserTotpDisableDialog;
use Kirby\Toolkit\Escape;
use Kirby\Toolkit\I18n;
$fields = require __DIR__ . '/../fields/dialogs.php';
$files = require __DIR__ . '/../files/dialogs.php';
return [
@ -18,6 +21,19 @@ return [
'pattern' => 'users/create',
'load' => function () {
$kirby = App::instance();
$roles = $kirby->roles()->canBeCreated();
// get default value for role
if ($role = $kirby->request()->get('role')) {
$role = $roles->find($role)?->id();
}
// get role field definition, incl. available role options
$roles = Field::role(
roles: $roles,
props: ['required' => true]
);
return [
'component' => 'k-form-dialog',
'props' => [
@ -27,13 +43,13 @@ return [
'link' => false,
'required' => true
]),
'password' => Field::password(),
'password' => Field::password([
'autocomplete' => 'new-password'
]),
'translation' => Field::translation([
'required' => true
]),
'role' => Field::role([
'required' => true
])
'role' => $roles
],
'submitButton' => I18n::translate('create'),
'value' => [
@ -41,7 +57,7 @@ return [
'email' => '',
'password' => '',
'translation' => $kirby->panelLanguage(),
'role' => $kirby->user()->role()->name()
'role' => $role ?? $roles['options'][0]['value'] ?? null
]
]
];
@ -167,17 +183,23 @@ return [
'user.changePassword' => [
'pattern' => 'users/(:any)/changePassword',
'load' => function (string $id) {
$user = Find::user($id);
Find::user($id);
return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'fields' => [
'currentPassword' => Field::password([
'label' => I18n::translate('user.changePassword.current'),
'autocomplete' => 'current-password'
]),
'password' => Field::password([
'label' => I18n::translate('user.changePassword.new'),
'label' => I18n::translate('user.changePassword.new'),
'autocomplete' => 'new-password'
]),
'passwordConfirmation' => Field::password([
'label' => I18n::translate('user.changePassword.new.confirm'),
'label' => I18n::translate('user.changePassword.new.confirm'),
'autocomplete' => 'new-password'
])
],
'submitButton' => I18n::translate('change'),
@ -185,13 +207,26 @@ return [
];
},
'submit' => function (string $id) {
$request = App::instance()->request();
$kirby = App::instance();
$request = $kirby->request();
$user = Find::user($id);
$currentPassword = $request->get('currentPassword');
$password = $request->get('password');
$passwordConfirmation = $request->get('passwordConfirmation');
// validate the password
// validate the current password of the acting user
try {
$kirby->user()->validatePassword($currentPassword);
} catch (Exception) {
// catching and re-throwing exception to avoid automatic
// sign-out of current user from the Panel
throw new InvalidArgumentException([
'key' => 'user.password.wrong'
]);
}
// validate the new password
UserRules::validPassword($user, $password ?? '');
// compare passwords
@ -220,10 +255,13 @@ return [
'component' => 'k-form-dialog',
'props' => [
'fields' => [
'role' => Field::role([
'label' => I18n::translate('user.changeRole.select'),
'required' => true,
])
'role' => Field::role(
roles: $user->roles(),
props: [
'label' => I18n::translate('user.changeRole.select'),
'required' => true,
]
)
],
'submitButton' => I18n::translate('user.changeRole'),
'value' => [
@ -287,6 +325,13 @@ return [
}
],
// user field dialogs
'user.fields' => [
'pattern' => '(users/.*?)/fields/(:any)/(:all?)',
'load' => $fields['model']['load'],
'submit' => $fields['model']['submit']
],
// change file name
'user.file.changeName' => [
'pattern' => '(users/.*?)/files/(:any)/changeName',
@ -301,11 +346,31 @@ return [
'submit' => $files['changeSort']['submit'],
],
// change file template
'user.file.changeTemplate' => [
'pattern' => '(users/.*?)/files/(:any)/changeTemplate',
'load' => $files['changeTemplate']['load'],
'submit' => $files['changeTemplate']['submit'],
],
// delete file
'user.file.delete' => [
'pattern' => '(users/.*?)/files/(:any)/delete',
'load' => $files['delete']['load'],
'submit' => $files['delete']['submit'],
]
],
// user file fields dialogs
'user.file.fields' => [
'pattern' => '(users/.*?)/files/(:any)/fields/(:any)/(:all?)',
'load' => $fields['file']['load'],
'submit' => $fields['file']['submit']
],
// user disable TOTP
'user.totp.disable' => [
'pattern' => 'users/(:any)/totp/disable',
'load' => fn (string $id) => (new UserTotpDisableDialog($id))->load(),
'submit' => fn (string $id) => (new UserTotpDisableDialog($id))->submit()
],
];

View file

@ -0,0 +1,18 @@
<?php
$fields = require __DIR__ . '/../fields/drawers.php';
return [
// user field drawers
'user.fields' => [
'pattern' => '(users/.*?)/fields/(:any)/(:all?)',
'load' => $fields['model']['load'],
'submit' => $fields['model']['submit']
],
// user file fields drawers
'user.file.fields' => [
'pattern' => '(users/.*?)/files/(:any)/fields/(:any)/(:all?)',
'load' => $fields['file']['load'],
'submit' => $fields['file']['submit']
],
];

View file

@ -8,20 +8,22 @@ return [
'users' => [
'label' => I18n::translate('users'),
'icon' => 'users',
'query' => function (string $query = null) {
$users = App::instance()->users()->search($query)->limit(10);
$results = [];
'query' => function (string|null $query, int $limit, int $page) {
$kirby = App::instance();
$users = $kirby->users()
->search($query)
->paginate($limit, $page);
foreach ($users as $user) {
$results[] = [
return [
'results' => $users->values(fn ($user) => [
'image' => $user->panel()->image(),
'text' => Escape::html($user->username()),
'link' => $user->panel()->url(true),
'info' => Escape::html($user->role()->title())
];
}
return $results;
'info' => Escape::html($user->role()->title()),
'uuid' => $user->uuid()->toString(),
]),
'pagination' => $users->pagination()->toArray()
];
}
]
];

View file

@ -18,7 +18,8 @@ return [
return [
'component' => 'k-users-view',
'props' => [
'role' => function () use ($kirby, $roles, $role) {
'canCreate' => $kirby->roles()->canBeCreated()->count() > 0,
'role' => function () use ($roles, $role) {
if ($role) {
return $roles[$role] ?? null;
}
@ -31,6 +32,10 @@ return [
$users = $users->role($role);
}
// sort users alphabetically
$users = $users->sortBy('username', 'asc');
// paginate
$users = $users->paginate([
'limit' => 20,
'page' => $kirby->request()->get('page')

View file

@ -8,7 +8,7 @@ fields:
query: model.images
multiple: true
layout: cards
size: tiny
size: small
empty: field.blocks.gallery.images.empty
uploads:
template: blocks/image

View file

@ -5,20 +5,31 @@ preview: heading
fields:
level:
label: field.blocks.heading.level
type: select
type: toggles
empty: false
default: "h2"
width: 1/6
labels: false
options:
- h1
- h2
- h3
- h4
- h5
- h6
- value: h1
icon: h1
text: H1
- value: h2
icon: h2
text: H2
- value: h3
icon: h3
text: H3
- value: h4
icon: h4
text: H4
- value: h5
icon: h5
text: H5
- value: h6
icon: h6
text: H6
text:
label: field.blocks.heading.text
type: writer
inline: true
width: 5/6
placeholder: field.blocks.heading.placeholder

View file

@ -7,9 +7,10 @@ fields:
type: radio
columns: 2
default: "kirby"
required: true
options:
kirby: Kirby
web: Web
kirby: "{{ t('field.blocks.image.location.internal') }}"
web: "{{ t('field.blocks.image.location.external') }}"
image:
label: field.blocks.image.name
type: files

View file

@ -2,12 +2,31 @@
use Kirby\Cms\Html;
/** @var \Kirby\Cms\Block $block */
$caption = $block->caption();
if (
$block->location() == 'kirby' &&
$video = $block->video()->toFile()
) {
$url = $video->url();
$attrs = array_filter([
'autoplay' => $block->autoplay()->toBool(),
'controls' => $block->controls()->toBool(),
'loop' => $block->loop()->toBool(),
'muted' => $block->muted()->toBool() || $block->autoplay()->toBool(),
'playsinline' => $block->autoplay()->toBool(),
'poster' => $block->poster()->toFile()?->url(),
'preload' => $block->preload()->value(),
]);
} else {
$url = $block->url();
}
?>
<?php if ($video = Html::video($block->url())): ?>
<?php if ($video = Html::video($url, [], $attrs ?? [])): ?>
<figure>
<?= $video ?>
<?php if ($block->caption()->isNotEmpty()): ?>
<figcaption><?= $block->caption() ?></figcaption>
<?php if ($caption->isNotEmpty()): ?>
<figcaption><?= $caption ?></figcaption>
<?php endif ?>
</figure>
<?php endif ?>

View file

@ -2,11 +2,77 @@ name: field.blocks.video.name
icon: video
preview: video
fields:
location:
label: field.blocks.video.location
type: radio
columns: 2
default: "web"
options:
kirby: "{{ t('field.blocks.image.location.internal') }}"
web: "{{ t('field.blocks.image.location.external') }}"
url:
label: field.blocks.video.url.label
type: url
placeholder: field.blocks.video.url.placeholder
when:
location: web
video:
label: field.blocks.video.name
type: files
query: model.videos
multiple: false
# you might want to add a template for videos here
when:
location: kirby
poster:
label: field.blocks.video.poster
type: files
query: model.images
multiple: false
image:
back: black
uploads:
template: blocks/image
when:
location: kirby
caption:
label: field.blocks.video.caption
type: writer
inline: true
autoplay:
label: field.blocks.video.autoplay
type: toggle
width: 1/3
when:
location: kirby
muted:
label: field.blocks.video.muted
type: toggle
width: 1/3
default: true
when:
location: kirby
loop:
label: field.blocks.video.loop
type: toggle
width: 1/3
when:
location: kirby
controls:
label: field.blocks.video.controls
type: toggle
width: 1/3
default: true
when:
location: kirby
preload:
label: field.blocks.video.preload
type: select
width: 2/3
default: auto
options:
- auto
- metadata
- none
when:
location: kirby

View file

@ -1,56 +0,0 @@
name: Code
icon: code
fields:
code:
label: Code
type: textarea
buttons: false
font: monospace
language:
label: Language
type: select
default: text
options:
bash: Bash
basic: BASIC
c: C
clojure: Clojure
cpp: C++
csharp: C#
css: CSS
diff: Diff
elixir: Elixir
elm: Elm
erlang: Erlang
go: Go
graphql: GraphQL
haskell: Haskell
html: HTML
java: Java
js: JavaScript
json: JSON
latext: LaTeX
less: Less
lisp: Lisp
lua: Lua
makefile: Makefile
markdown: Markdown
markup: Markup
objectivec: Objective-C
pascal: Pascal
perl: Perl
php: PHP
text: Plain Text
python: Python
r: R
ruby: Ruby
rust: Rust
sass: Sass
scss: SCSS
shell: Shell
sql: SQL
swift: Swift
typescript: TypeScript
vbnet: VB.net
xml: XML
yaml: YAML

View file

@ -1,20 +0,0 @@
icon: title
fields:
text:
type: text
level:
type: select
width: 1/2
empty: false
default: "2"
options:
- value: "1"
text: Heading 1
- value: "2"
text: Heading 2
- value: "3"
text: Heading 3
id:
type: text
label: ID
width: 1/2

View file

@ -1,16 +0,0 @@
name: Image
icon: image
fields:
image:
type: files
multiple: false
alt:
type: text
icon: title
caption:
type: writer
inline: true
icon: text
link:
type: text
icon: url

View file

@ -1,12 +0,0 @@
name: Quote
icon: quote
fields:
text:
label: Quote Text
type: writer
inline: true
citation:
label: Citation
type: writer
inline: true
placeholder: by …

View file

@ -1,25 +0,0 @@
name: Table
icon: menu
fields:
rows:
label: Menu
type: structure
columns:
dish: true
description: true
price:
before:
width: 1/4
align: right
fields:
dish:
label: Dish
type: text
description:
label: Description
type: text
price:
label: Price
type: number
before:
step: 0.01

View file

@ -1,5 +0,0 @@
name: Text
icon: text
fields:
text:
type: writer

View file

@ -1,8 +0,0 @@
name: Video
icon: video
label: "{{ url }}"
fields:
url:
type: url
caption:
type: writer

View file

@ -1,2 +0,0 @@
name: File
title: file

View file

@ -1,3 +0,0 @@
name: Page
title: Page

View file

@ -1,7 +0,0 @@
name: Site
title: Site
sections:
pages:
headline: Pages
type: pages

View file

@ -8,24 +8,26 @@ use Kirby\Cms\Page;
use Kirby\Cms\User;
use Kirby\Data\Data;
use Kirby\Email\PHPMailer as Emailer;
use Kirby\Exception\NotFoundException;
use Kirby\Filesystem\F;
use Kirby\Filesystem\Filename;
use Kirby\Http\Uri;
use Kirby\Http\Url;
use Kirby\Image\Darkroom;
use Kirby\Session\SessionStore;
use Kirby\Template\Snippet;
use Kirby\Template\Template;
use Kirby\Text\Markdown;
use Kirby\Text\SmartyPants;
use Kirby\Toolkit\A;
use Kirby\Toolkit\Str;
use Kirby\Uuid\Uuid;
return [
/**
* Used by the `css()` helper
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param string $url Relative or absolute URL
* @param string|array $options An array of attributes for the link tag or a media attribute string
*/
@ -33,35 +35,39 @@ return [
/**
* Add your own email provider
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param array $props
* @param bool $debug
*/
'email' => function (App $kirby, array $props = [], bool $debug = false) {
'email' => function (
App $kirby,
array $props = [],
bool $debug = false
) {
return new Emailer($props, $debug);
},
/**
* Modify URLs for file objects
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param \Kirby\Cms\File $file The original file object
* @return string
*/
'file::url' => function (App $kirby, File $file): string {
'file::url' => function (
App $kirby,
File $file
): string {
return $file->mediaUrl();
},
/**
* Adapt file characteristics
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param \Kirby\Cms\File|\Kirby\Filesystem\Asset $file The file object
* @param array $options All thumb options (width, height, crop, blur, grayscale)
* @return \Kirby\Cms\File|\Kirby\Cms\FileVersion|\Kirby\Filesystem\Asset
*/
'file::version' => function (App $kirby, $file, array $options = []) {
'file::version' => function (
App $kirby,
$file,
array $options = []
) {
// if file is not resizable, return
if ($file->isResizable() === false) {
return $file;
@ -100,7 +106,6 @@ return [
/**
* Used by the `js()` helper
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param string $url Relative or absolute URL
* @param string|array $options An array of attributes for the link tag or a media attribute string
*/
@ -109,14 +114,12 @@ return [
/**
* Add your own Markdown parser
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param string $text Text to parse
* @param array $options Markdown options
* @return string
*/
'markdown' => function (
App $kirby,
string $text = null,
string|null $text = null,
array $options = []
): string {
static $markdown;
@ -140,9 +143,9 @@ return [
'search' => function (
App $kirby,
Collection $collection,
string|null $query = '',
array|string $params = []
): Collection|bool {
string|null $query = null,
string|array $params = []
): Collection {
if (is_string($params) === true) {
$params = ['fields' => Str::split($params, '|')];
}
@ -154,8 +157,9 @@ return [
'words' => false,
];
$options = array_merge($defaults, $params);
$query = trim($query ?? '');
$collection = clone $collection;
$options = array_merge($defaults, $params);
$query = trim($query ?? '');
// empty or too short search query
if (Str::length($query) < $options['minlength']) {
@ -227,7 +231,7 @@ return [
$scoring['score'] += 16 * $score;
$scoring['hits'] += 1;
// check for exact beginning matches
// check for exact beginning matches
} elseif (
$options['words'] === false &&
Str::startsWith($lowerValue, $query) === true
@ -235,7 +239,7 @@ return [
$scoring['score'] += 8 * $score;
$scoring['hits'] += 1;
// check for exact query matches
// check for exact query matches
} elseif ($matches = preg_match_all('!' . $exact . '!ui', $value, $r)) {
$scoring['score'] += 2 * $score;
$scoring['hits'] += $matches;
@ -259,15 +263,24 @@ return [
);
},
/**
* Add your own session store
*/
'session::store' => function (App $kirby): string|SessionStore {
return $kirby->root('sessions');
},
/**
* Add your own SmartyPants parser
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param string $text Text to parse
* @param array $options SmartyPants options
* @return string
*/
'smartypants' => function (App $kirby, string $text = null, array $options = []): string {
'smartypants' => function (
App $kirby,
string|null $text = null,
array $options = []
): string {
static $smartypants;
static $config;
@ -284,43 +297,55 @@ return [
/**
* Add your own snippet loader
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param string|array $name Snippet name
* @param array $data Data array for the snippet
*/
'snippet' => function (App $kirby, string|array|null $name, array $data = [], bool $slots = false): Snippet|string {
'snippet' => function (
App $kirby,
string|array|null $name,
array $data = [],
bool $slots = false
): Snippet|string {
return Snippet::factory($name, $data, $slots);
},
/**
* Add your own template engine
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param string $name Template name
* @param string $type Extension type
* @param string $defaultType Default extension type
* @return \Kirby\Template\Template
*/
'template' => function (App $kirby, string $name, string $type = 'html', string $defaultType = 'html') {
'template' => function (
App $kirby,
string $name,
string $type = 'html',
string $defaultType = 'html'
) {
return new Template($name, $type, $defaultType);
},
/**
* Add your own thumb generator
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param string $src Root of the original file
* @param string $dst Template string for the root to the desired destination
* @param array $options All thumb options that should be applied: `width`, `height`, `crop`, `blur`, `grayscale`
* @return string
*/
'thumb' => function (App $kirby, string $src, string $dst, array $options): string {
'thumb' => function (
App $kirby,
string $src,
string $dst,
array $options
): string {
$darkroom = Darkroom::factory(
$kirby->option('thumbs.driver', 'gd'),
$kirby->option('thumbs', [])
);
$options = $darkroom->preprocess($src, $options);
$root = (new Filename($src, $dst, $options))->toString();
$options = $darkroom->preprocess($src, $options);
$root = (new Filename($src, $dst, $options))->toString();
F::copy($src, $root, true);
$darkroom->process($root, $options);
@ -331,12 +356,15 @@ return [
/**
* Modify all URLs
*
* @param \Kirby\Cms\App $kirby Kirby instance
* @param string|null $path URL path
* @param array|string|null $options Array of options for the Uri class
* @return string
* @throws \Kirby\Exception\NotFoundException If an invalid UUID was passed
*/
'url' => function (App $kirby, string $path = null, $options = null): string {
'url' => function (
App $kirby,
string|null $path = null,
$options = null
): string {
$language = null;
// get language from simple string option
@ -378,6 +406,23 @@ return [
return $path;
}
// support UUIDs
if (
$path !== null &&
(
Uuid::is($path, 'page') === true ||
Uuid::is($path, 'file') === true
)
) {
$model = Uuid::for($path)->model();
if ($model === null) {
throw new NotFoundException('The model could not be found for "' . $path . '" uuid');
}
$path = $model->url();
}
$url = Url::makeAbsolute($path, $kirby->url());
if ($options === null) {

View file

@ -29,13 +29,13 @@ return [
/**
* Maximum number of checked boxes
*/
'max' => function (int $max = null) {
'max' => function (int|null $max = null) {
return $max;
},
/**
* Minimum number of checked boxes
*/
'min' => function (int $min = null) {
'min' => function (int|null $min = null) {
return $min;
},
'value' => function ($value = null) {

View file

@ -0,0 +1,145 @@
<?php
use Kirby\Exception\InvalidArgumentException;
use Kirby\Field\FieldOptions;
use Kirby\Toolkit\A;
use Kirby\Toolkit\Escape;
use Kirby\Toolkit\Str;
return [
'props' => [
/**
* Unset inherited props
*/
'after' => null,
'before' => null,
/**
* Whether to allow alpha transparency in the color
*/
'alpha' => function (bool $alpha = false) {
return $alpha;
},
/**
* The CSS format (hex, rgb, hsl) to display and store the value
*/
'format' => function (string $format = 'hex'): string {
if (in_array($format, ['hex', 'hsl', 'rgb']) === false) {
throw new InvalidArgumentException('Unsupported format for color field (supported: hex, rgb, hsl)');
}
return $format;
},
/**
* Change mode to disable the color picker (`input`) or to only
* show the `options` as toggles
*/
'mode' => function (string $mode = 'picker'): string {
if (in_array($mode, ['picker', 'input', 'options']) === false) {
throw new InvalidArgumentException('Unsupported mode for color field (supported: picker, input, options)');
}
return $mode;
},
/**
* List of colors that will be shown as buttons
* to directly select them
*/
'options' => function (array $options = []): array {
return $options;
}
],
'computed' => [
'default' => function (): string {
return Str::lower($this->default);
},
'options' => function (): array {
// resolve options to support manual arrays
// alongside api and query options
$props = FieldOptions::polyfill($this->props);
$options = FieldOptions::factory([
'text' => '{{ item.value }}',
'value' => '{{ item.key }}',
...$props['options']
]);
$options = $options->render($this->model());
if (empty($options) === true) {
return [];
}
$options = match (true) {
// simple array of values
// or value=text (from Options class)
is_numeric($options[0]['value']) ||
$options[0]['value'] === $options[0]['text']
=> A::map($options, fn ($option) => [
'value' => $option['text']
]),
// deprecated: name => value, flipping
// TODO: start throwing in warning in v5
$this->isColor($options[0]['text'])
=> A::map($options, fn ($option) => [
'value' => $option['text'],
// ensure that any HTML in the new text is escaped
'text' => Escape::html($option['value'])
]),
default
=> A::map($options, fn ($option) => [
'value' => $option['value'],
'text' => $option['text']
]),
};
return $options;
}
],
'methods' => [
'isColor' => function (string $value): bool {
return
$this->isHex($value) ||
$this->isRgb($value) ||
$this->isHsl($value);
},
'isHex' => function (string $value): bool {
return preg_match('/^#([\da-f]{3,4}){1,2}$/i', $value) === 1;
},
'isHsl' => function (string $value): bool {
return preg_match('/^hsla?\(\s*(\d{1,3}\.?\d*)(deg|rad|grad|turn)?(?:,|\s)+(\d{1,3})%(?:,|\s)+(\d{1,3})%(?:,|\s|\/)*(\d*(?:\.\d+)?)(%?)\s*\)?$/i', $value) === 1;
},
'isRgb' => function (string $value): bool {
return preg_match('/^rgba?\(\s*(\d{1,3})(%?)(?:,|\s)+(\d{1,3})(%?)(?:,|\s)+(\d{1,3})(%?)(?:,|\s|\/)*(\d*(?:\.\d+)?)(%?)\s*\)?$/i', $value) === 1;
},
],
'validations' => [
'color' => function ($value) {
if (empty($value) === true) {
return true;
}
if ($this->format === 'hex' && $this->isHex($value) === false) {
throw new InvalidArgumentException([
'key' => 'validation.color',
'data' => ['format' => 'hex']
]);
}
if ($this->format === 'rgb' && $this->isRgb($value) === false) {
throw new InvalidArgumentException([
'key' => 'validation.color',
'data' => ['format' => 'rgb']
]);
}
if ($this->format === 'hsl' && $this->isHsl($value) === false) {
throw new InvalidArgumentException([
'key' => 'validation.color',
'data' => ['format' => 'hsl']
]);
}
}
]
];

View file

@ -24,7 +24,7 @@ return [
/**
* Default date when a new page/file/user gets created
*/
'default' => function (string $default = null): string {
'default' => function (string|null $default = null): string {
return $this->toDatetime($default) ?? '';
},
@ -46,13 +46,13 @@ return [
/**
* Latest date, which can be selected/saved (Y-m-d)
*/
'max' => function (string $max = null): string|null {
'max' => function (string|null $max = null): string|null {
return Date::optional($max);
},
/**
* Earliest date, which can be selected/saved (Y-m-d)
*/
'min' => function (string $min = null): string|null {
'min' => function (string|null $min = null): string|null {
return Date::optional($min);
},

View file

@ -1,5 +1,6 @@
<?php
use Kirby\Cms\ModelWithContent;
use Kirby\Data\Data;
use Kirby\Toolkit\A;
@ -36,7 +37,10 @@ return [
'parentModel' => function () {
if (
is_string($this->parent) === true &&
$model = $this->model()->query($this->parent, 'Kirby\Cms\Model')
$model = $this->model()->query(
$this->parent,
ModelWithContent::class
)
) {
return $model;
}

View file

@ -14,13 +14,6 @@ return [
'icon' => null,
'placeholder' => null,
'required' => null,
'translate' => null,
/**
* If `false`, the prepended number will be hidden
*/
'numbered' => function (bool $numbered = true) {
return $numbered;
}
'translate' => null
]
];

View file

@ -1,3 +1,5 @@
<?php
return [];
return [
'hidden' => true
];

View file

@ -12,7 +12,6 @@ return [
'before' => null,
'default' => null,
'disabled' => null,
'icon' => null,
'placeholder' => null,
'required' => null,
'translate' => null,
@ -27,7 +26,7 @@ return [
/**
* Change the design of the info box
*/
'theme' => function (string $theme = null) {
'theme' => function (string|null $theme = null) {
return $theme;
}
],

View file

@ -0,0 +1,172 @@
<?php
use Kirby\Exception\InvalidArgumentException;
use Kirby\Http\Url;
use Kirby\Toolkit\Str;
use Kirby\Toolkit\V;
return [
'props' => [
'after' => null,
'before' => null,
'icon' => null,
'placeholder' => null,
/**
* @values 'anchor', 'url, 'page, 'file', 'email', 'tel', 'custom'
*/
'options' => function (array|null $options = null): array {
// default options
if ($options === null) {
return [
'url',
'page',
'file',
'email',
'tel',
'anchor'
];
}
// validate options
$available = array_keys($this->availableTypes());
if ($unavailable = array_diff($options, $available)) {
throw new InvalidArgumentException([
'key' => 'field.link.options',
'data' => ['options' => implode(', ', $unavailable)]
]);
}
return $options;
},
'value' => function (string|null $value = null) {
return $value ?? '';
}
],
'methods' => [
'activeTypes' => function () {
return array_filter(
$this->availableTypes(),
fn (string $type) => in_array($type, $this->props['options']),
ARRAY_FILTER_USE_KEY
);
},
'availableTypes' => function () {
return [
'anchor' => [
'detect' => function (string $value): bool {
return Str::startsWith($value, '#') === true;
},
'link' => function (string $value): string {
return $value;
},
'validate' => function (string $value): bool {
return Str::startsWith($value, '#') === true;
},
],
'email' => [
'detect' => function (string $value): bool {
return Str::startsWith($value, 'mailto:') === true;
},
'link' => function (string $value): string {
return str_replace('mailto:', '', $value);
},
'validate' => function (string $value): bool {
return V::email($value);
},
],
'file' => [
'detect' => function (string $value): bool {
return Str::startsWith($value, 'file://') === true;
},
'link' => function (string $value): string {
return $value;
},
'validate' => function (string $value): bool {
return V::uuid($value, 'file');
},
],
'page' => [
'detect' => function (string $value): bool {
return Str::startsWith($value, 'page://') === true;
},
'link' => function (string $value): string {
return $value;
},
'validate' => function (string $value): bool {
return V::uuid($value, 'page');
},
],
'tel' => [
'detect' => function (string $value): bool {
return Str::startsWith($value, 'tel:') === true;
},
'link' => function (string $value): string {
return str_replace('tel:', '', $value);
},
'validate' => function (string $value): bool {
return V::tel($value);
},
],
'url' => [
'detect' => function (string $value): bool {
return Str::startsWith($value, 'http://') === true || Str::startsWith($value, 'https://') === true;
},
'link' => function (string $value): string {
return $value;
},
'validate' => function (string $value): bool {
return V::url($value);
},
],
// needs to come last
'custom' => [
'detect' => function (string $value): bool {
return true;
},
'link' => function (string $value): string {
return $value;
},
'validate' => function (): bool {
return true;
},
]
];
},
],
'validations' => [
'value' => function (string|null $value) {
if (empty($value) === true) {
return true;
}
$detected = false;
foreach ($this->activeTypes() as $type => $options) {
if ($options['detect']($value) !== true) {
continue;
}
$link = $options['link']($value);
$detected = true;
if ($options['validate']($link) === false) {
throw new InvalidArgumentException([
'key' => 'validation.' . $type
]);
}
}
// none of the configured types has been detected
if ($detected === false) {
throw new InvalidArgumentException([
'key' => 'validation.linkType'
]);
}
return true;
},
]
];

View file

@ -7,6 +7,12 @@ return [
*/
'marks' => function ($marks = true) {
return $marks;
},
/**
* Sets the allowed nodes. Available nodes: `bulletList`, `orderedList`
*/
'nodes' => function ($nodes = null) {
return $nodes;
}
],
'computed' => [

View file

@ -7,7 +7,7 @@ return [
/**
* Defines a custom format that is used when the field is saved
*/
'format' => function (string $format = null) {
'format' => function (string|null $format = null) {
return $format;
}
],

View file

@ -12,7 +12,7 @@ return [
},
/**
* Layout size for cards: `tiny`, `small`, `medium`, `large` or `huge`
* Layout size for cards: `tiny`, `small`, `medium`, `large`, `huge`, `full`
*/
'size' => function (string $size = 'auto') {
return $size;

View file

@ -36,7 +36,7 @@ return [
},
'sanitizeOption' => function ($value) {
$options = array_column($this->options(), 'value');
return in_array($value, $options, true) === true ? $value : null;
return in_array($value, $options) === true ? $value : null;
},
'sanitizeOptions' => function ($values) {
$options = array_column($this->options(), 'value');

View file

@ -2,6 +2,7 @@
use Kirby\Toolkit\I18n;
use Kirby\Toolkit\Str;
use Kirby\Uuid\Uuids;
return [
'props' => [
@ -22,7 +23,7 @@ return [
/**
* Info text for each item
*/
'info' => function (string $info = null) {
'info' => function (string|null $info = null) {
return $info;
},
@ -36,14 +37,14 @@ return [
/**
* The minimum number of required selected
*/
'min' => function (int $min = null) {
'min' => function (int|null $min = null) {
return $min;
},
/**
* The maximum number of allowed selected
*/
'max' => function (int $max = null) {
'max' => function (int|null $max = null) {
return $max;
},
@ -57,7 +58,7 @@ return [
/**
* Query for the items to be included in the picker
*/
'query' => function (string $query = null) {
'query' => function (string|null $query = null) {
return $query;
},
@ -75,13 +76,17 @@ 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)
};
},
/**
* Main text for each item
*/
'text' => function (string $text = null) {
'text' => function (string|null $text = null) {
return $text;
},
],

View file

@ -3,6 +3,7 @@
use Kirby\Cms\Api;
use Kirby\Cms\File;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
return [
'props' => [
@ -22,18 +23,27 @@ return [
$uploads = [];
}
$template = $uploads['template'] ?? null;
$uploads['accept'] = '*';
if ($preview = $this->image) {
$uploads['preview'] = $preview;
}
if ($template = $uploads['template'] ?? null) {
// get parent object for upload target
$parent = $this->uploadParent($uploads['parent'] ?? null);
if ($parent === null) {
throw new InvalidArgumentException('"' . $uploads['parent'] . '" could not be resolved as a valid parent for the upload');
}
if ($template) {
$file = new File([
'filename' => 'tmp',
'parent' => $this->model(),
'parent' => $parent,
'template' => $template
]);
$uploads['accept'] = $file->blueprint()->acceptMime();
} else {
$uploads['accept'] = '*';
$uploads['accept'] = $file->blueprint()->acceptAttribute();
}
return $uploads;
@ -45,15 +55,7 @@ return [
throw new Exception('Uploads are disabled for this field');
}
if ($parentQuery = ($params['parent'] ?? null)) {
$parent = $this->model()->query($parentQuery);
} else {
$parent = $this->model();
}
if ($parent instanceof File) {
$parent = $parent->parent();
}
$parent = $this->uploadParent($params['parent'] ?? null);
return $api->upload(function ($source, $filename) use ($parent, $params, $map) {
$props = [
@ -71,6 +73,19 @@ return [
return $map($file, $parent);
});
},
'uploadParent' => function (string|null $parentQuery = null) {
$parent = $this->model();
if ($parentQuery) {
$parent = $parent->query($parentQuery);
}
if ($parent instanceof File) {
$parent = $parent->parent();
}
return $parent;
}
]
];

View file

@ -1,35 +1,23 @@
<?php
use Kirby\Toolkit\Str;
use Kirby\Toolkit\V;
return [
'extends' => 'tags',
'props' => [
/**
* Unset inherited props
* If set to `all`, any type of input is accepted. If set to `options` only the predefined options are accepted as input.
*/
'accept' => null,
'accept' => function ($value = 'options') {
return V::in($value, ['all', 'options']) ? $value : 'all';
},
/**
* Custom icon to replace the arrow down.
*/
'icon' => function (string $icon = null) {
'icon' => function (string $icon = 'checklist') {
return $icon;
},
/**
* Enable/disable the search in the dropdown
* Also limit displayed items (display: 20)
* and set minimum number of characters to search (min: 3)
*/
'search' => function ($search = true) {
return $search;
},
/**
* If `true`, selected entries will be sorted
* according to their position in the dropdown
*/
'sort' => function (bool $sort = false) {
return $sort;
},
],
'methods' => [
'toValues' => function ($value) {

View file

@ -13,13 +13,13 @@ return [
/**
* The lowest allowed number
*/
'min' => function (float $min = null) {
'min' => function (float|null $min = null) {
return $min;
},
/**
* The highest allowed number
*/
'max' => function (float $max = null) {
'max' => function (float|null $max = null) {
return $max;
},
/**

View file

@ -47,7 +47,7 @@ return [
},
'fields' => function () {
if (empty($this->fields) === true) {
throw new Exception('Please provide some fields for the object');
return [];
}
return $this->form()->fields()->toArray();

View file

@ -31,7 +31,7 @@ return [
/**
* Optional query to select a specific set of pages
*/
'query' => function (string $query = null) {
'query' => function (string|null $query = null) {
return $query;
},

View file

@ -1,5 +1,7 @@
<?php
use Kirby\Toolkit\I18n;
return [
'extends' => 'number',
'props' => [
@ -18,6 +20,13 @@ return [
* Enables/disables the tooltip and set the before and after values
*/
'tooltip' => function ($tooltip = true) {
if (is_array($tooltip) === true) {
$after = $tooltip['after'] ?? null;
$before = $tooltip['before'] ?? null;
$tooltip['after'] = I18n::translate($after, $after);
$tooltip['before'] = I18n::translate($before, $before);
}
return $tooltip;
},
]

View file

@ -13,7 +13,7 @@ return [
/**
* Custom icon to replace the arrow down.
*/
'icon' => function (string $icon = null) {
'icon' => function (string|null $icon = null) {
return $icon;
},
/**

Some files were not shown because too many files have changed in this diff Show more