composer update
This commit is contained in:
parent
0b3c362c5e
commit
a1f0701630
142 changed files with 4530 additions and 1195 deletions
626
package-lock.json
generated
626
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -24,6 +24,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.1.3",
|
||||
"vite": "^5.4.3"
|
||||
"vite": "^7.1.6"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
63
public/composer.lock
generated
63
public/composer.lock
generated
|
|
@ -120,16 +120,16 @@
|
|||
},
|
||||
{
|
||||
"name": "composer/semver",
|
||||
"version": "3.4.3",
|
||||
"version": "3.4.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/semver.git",
|
||||
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12"
|
||||
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
|
||||
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95",
|
||||
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -181,7 +181,7 @@
|
|||
"support": {
|
||||
"irc": "ircs://irc.libera.chat:6697/composer",
|
||||
"issues": "https://github.com/composer/semver/issues",
|
||||
"source": "https://github.com/composer/semver/tree/3.4.3"
|
||||
"source": "https://github.com/composer/semver/tree/3.4.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -191,13 +191,9 @@
|
|||
{
|
||||
"url": "https://github.com/composer",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-19T14:15:21+00:00"
|
||||
"time": "2025-08-20T19:15:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filp/whoops",
|
||||
|
|
@ -272,22 +268,22 @@
|
|||
},
|
||||
{
|
||||
"name": "getkirby/cms",
|
||||
"version": "5.0.4",
|
||||
"version": "5.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/getkirby/kirby.git",
|
||||
"reference": "c9708e5c648fbc3ee381114ceae8876d4ca4f9a1"
|
||||
"reference": "fb11f5e3ec422e948fb1a52f16988335bb3489b4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/getkirby/kirby/zipball/c9708e5c648fbc3ee381114ceae8876d4ca4f9a1",
|
||||
"reference": "c9708e5c648fbc3ee381114ceae8876d4ca4f9a1",
|
||||
"url": "https://api.github.com/repos/getkirby/kirby/zipball/fb11f5e3ec422e948fb1a52f16988335bb3489b4",
|
||||
"reference": "fb11f5e3ec422e948fb1a52f16988335bb3489b4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"christian-riesen/base32": "1.6.0",
|
||||
"claviska/simpleimage": "4.2.1",
|
||||
"composer/semver": "3.4.3",
|
||||
"composer/semver": "3.4.4",
|
||||
"ext-ctype": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-dom": "*",
|
||||
|
|
@ -305,9 +301,9 @@
|
|||
"michelf/php-smartypants": "1.8.1",
|
||||
"php": "~8.2.0 || ~8.3.0 || ~8.4.0",
|
||||
"phpmailer/phpmailer": "6.10.0",
|
||||
"symfony/polyfill-intl-idn": "1.32.0",
|
||||
"symfony/polyfill-mbstring": "1.32.0",
|
||||
"symfony/yaml": "7.3.2"
|
||||
"symfony/polyfill-intl-idn": "1.33.0",
|
||||
"symfony/polyfill-mbstring": "1.33.0",
|
||||
"symfony/yaml": "7.3.3"
|
||||
},
|
||||
"replace": {
|
||||
"symfony/polyfill-php72": "*"
|
||||
|
|
@ -317,6 +313,7 @@
|
|||
"ext-apcu": "Support for the Apcu cache driver",
|
||||
"ext-exif": "Support for exif information from images",
|
||||
"ext-fileinfo": "Improved mime type detection for files",
|
||||
"ext-imagick": "Improved thumbnail generation",
|
||||
"ext-intl": "Improved i18n number formatting",
|
||||
"ext-memcached": "Support for the Memcached cache driver",
|
||||
"ext-redis": "Support for the Redis cache driver",
|
||||
|
|
@ -372,7 +369,7 @@
|
|||
"type": "custom"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-19T07:39:58+00:00"
|
||||
"time": "2025-09-16T13:06:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "getkirby/composer-installer",
|
||||
|
|
@ -935,7 +932,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-idn",
|
||||
"version": "v1.32.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-idn.git",
|
||||
|
|
@ -998,7 +995,7 @@
|
|||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -1009,6 +1006,10 @@
|
|||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
|
|
@ -1103,7 +1104,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.32.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
|
|
@ -1164,7 +1165,7 @@
|
|||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0"
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -1175,6 +1176,10 @@
|
|||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
|
|
@ -1184,16 +1189,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v7.3.2",
|
||||
"version": "v7.3.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30"
|
||||
"reference": "d4f4a66866fe2451f61296924767280ab5732d9d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/b8d7d868da9eb0919e99c8830431ea087d6aae30",
|
||||
"reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/d4f4a66866fe2451f61296924767280ab5732d9d",
|
||||
"reference": "d4f4a66866fe2451f61296924767280ab5732d9d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -1236,7 +1241,7 @@
|
|||
"description": "Loads and dumps YAML files",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/yaml/tree/v7.3.2"
|
||||
"source": "https://github.com/symfony/yaml/tree/v7.3.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -1256,7 +1261,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-10T08:47:49+00:00"
|
||||
"time": "2025-08-27T11:34:33+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
##
|
||||
## Bundle of CA Root Certificates
|
||||
##
|
||||
## Certificate data from Mozilla as of: Tue Aug 12 03:12:01 2025 GMT
|
||||
## Certificate data from Mozilla as of: Tue Sep 9 03:12:01 2025 GMT
|
||||
##
|
||||
## Find updated versions here: https://curl.se/docs/caextract.html
|
||||
##
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
## Just configure this file as the SSLCACertificateFile.
|
||||
##
|
||||
## Conversion done with mk-ca-bundle.pl version 1.29.
|
||||
## SHA256: c185b859c19b05f104c50e1b0b2a6c775149a1d9bb731d414d73b1722892a66c
|
||||
## SHA256: 0078e6bdd280fd89e1b883174387aae84b3eae2ee263416a5f8a14ee7f179ae9
|
||||
##
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"description": "The Kirby core",
|
||||
"license": "proprietary",
|
||||
"type": "kirby-cms",
|
||||
"version": "5.0.4",
|
||||
"version": "5.1.1",
|
||||
"keywords": [
|
||||
"kirby",
|
||||
"cms",
|
||||
|
|
@ -38,15 +38,15 @@
|
|||
"ext-openssl": "*",
|
||||
"christian-riesen/base32": "1.6.0",
|
||||
"claviska/simpleimage": "4.2.1",
|
||||
"composer/semver": "3.4.3",
|
||||
"composer/semver": "3.4.4",
|
||||
"filp/whoops": "2.18.4",
|
||||
"getkirby/composer-installer": "^1.2.1",
|
||||
"laminas/laminas-escaper": "2.17.0",
|
||||
"michelf/php-smartypants": "1.8.1",
|
||||
"phpmailer/phpmailer": "6.10.0",
|
||||
"symfony/polyfill-intl-idn": "1.32.0",
|
||||
"symfony/polyfill-mbstring": "1.32.0",
|
||||
"symfony/yaml": "7.3.2"
|
||||
"symfony/polyfill-intl-idn": "1.33.0",
|
||||
"symfony/polyfill-mbstring": "1.33.0",
|
||||
"symfony/yaml": "7.3.3"
|
||||
},
|
||||
"replace": {
|
||||
"symfony/polyfill-php72": "*"
|
||||
|
|
@ -56,6 +56,7 @@
|
|||
"ext-apcu": "Support for the Apcu cache driver",
|
||||
"ext-exif": "Support for exif information from images",
|
||||
"ext-fileinfo": "Improved mime type detection for files",
|
||||
"ext-imagick": "Improved thumbnail generation",
|
||||
"ext-intl": "Improved i18n number formatting",
|
||||
"ext-memcached": "Support for the Memcached cache driver",
|
||||
"ext-redis": "Support for the Redis cache driver",
|
||||
|
|
|
|||
62
public/kirby/composer.lock
generated
62
public/kirby/composer.lock
generated
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "dcb9ca6ed7a038f2028aba54fada5f7b",
|
||||
"content-hash": "8d08002d38005e6ec1ff6a97deac20e2",
|
||||
"packages": [
|
||||
{
|
||||
"name": "christian-riesen/base32",
|
||||
|
|
@ -120,16 +120,16 @@
|
|||
},
|
||||
{
|
||||
"name": "composer/semver",
|
||||
"version": "3.4.3",
|
||||
"version": "3.4.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/semver.git",
|
||||
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12"
|
||||
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
|
||||
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95",
|
||||
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -181,7 +181,7 @@
|
|||
"support": {
|
||||
"irc": "ircs://irc.libera.chat:6697/composer",
|
||||
"issues": "https://github.com/composer/semver/issues",
|
||||
"source": "https://github.com/composer/semver/tree/3.4.3"
|
||||
"source": "https://github.com/composer/semver/tree/3.4.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -191,13 +191,9 @@
|
|||
{
|
||||
"url": "https://github.com/composer",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-19T14:15:21+00:00"
|
||||
"time": "2025-08-20T19:15:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filp/whoops",
|
||||
|
|
@ -693,7 +689,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.32.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
|
|
@ -752,7 +748,7 @@
|
|||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0"
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -763,6 +759,10 @@
|
|||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
|
|
@ -772,7 +772,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-idn",
|
||||
"version": "v1.32.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-idn.git",
|
||||
|
|
@ -835,7 +835,7 @@
|
|||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -846,6 +846,10 @@
|
|||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
|
|
@ -855,7 +859,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-normalizer",
|
||||
"version": "v1.32.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
||||
|
|
@ -916,7 +920,7 @@
|
|||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -927,6 +931,10 @@
|
|||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
|
|
@ -936,7 +944,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.32.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
|
|
@ -997,7 +1005,7 @@
|
|||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0"
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -1008,6 +1016,10 @@
|
|||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
|
|
@ -1017,16 +1029,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v7.3.2",
|
||||
"version": "v7.3.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30"
|
||||
"reference": "d4f4a66866fe2451f61296924767280ab5732d9d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/b8d7d868da9eb0919e99c8830431ea087d6aae30",
|
||||
"reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/d4f4a66866fe2451f61296924767280ab5732d9d",
|
||||
"reference": "d4f4a66866fe2451f61296924767280ab5732d9d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -1069,7 +1081,7 @@
|
|||
"description": "Loads and dumps YAML files",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/yaml/tree/v7.3.2"
|
||||
"source": "https://github.com/symfony/yaml/tree/v7.3.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -1089,7 +1101,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-10T08:47:49+00:00"
|
||||
"time": "2025-08-27T11:34:33+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
|
|
|||
|
|
@ -5,101 +5,61 @@ use Kirby\Panel\UserTotpEnableDialog;
|
|||
$dialogs = require __DIR__ . '/../users/dialogs.php';
|
||||
|
||||
return [
|
||||
// change email
|
||||
'account.changeEmail' => [
|
||||
...$dialogs['user.changeEmail'],
|
||||
'pattern' => '(account)/changeEmail',
|
||||
'load' => $dialogs['user.changeEmail']['load'],
|
||||
'submit' => $dialogs['user.changeEmail']['submit'],
|
||||
],
|
||||
|
||||
// change language
|
||||
'account.changeLanguage' => [
|
||||
...$dialogs['user.changeLanguage'],
|
||||
'pattern' => '(account)/changeLanguage',
|
||||
'load' => $dialogs['user.changeLanguage']['load'],
|
||||
'submit' => $dialogs['user.changeLanguage']['submit'],
|
||||
],
|
||||
|
||||
// change name
|
||||
'account.changeName' => [
|
||||
...$dialogs['user.changeName'],
|
||||
'pattern' => '(account)/changeName',
|
||||
'load' => $dialogs['user.changeName']['load'],
|
||||
'submit' => $dialogs['user.changeName']['submit'],
|
||||
],
|
||||
|
||||
// change password
|
||||
'account.changePassword' => [
|
||||
...$dialogs['user.changePassword'],
|
||||
'pattern' => '(account)/changePassword',
|
||||
'load' => $dialogs['user.changePassword']['load'],
|
||||
'submit' => $dialogs['user.changePassword']['submit'],
|
||||
],
|
||||
|
||||
// change role
|
||||
'account.changeRole' => [
|
||||
...$dialogs['user.changeRole'],
|
||||
'pattern' => '(account)/changeRole',
|
||||
'load' => $dialogs['user.changeRole']['load'],
|
||||
'submit' => $dialogs['user.changeRole']['submit'],
|
||||
],
|
||||
|
||||
// delete
|
||||
'account.delete' => [
|
||||
...$dialogs['user.delete'],
|
||||
'pattern' => '(account)/delete',
|
||||
'load' => $dialogs['user.delete']['load'],
|
||||
'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']
|
||||
...$dialogs['user.fields'],
|
||||
'pattern' => '(account)/fields/(:any)/(:all?)',
|
||||
],
|
||||
|
||||
// change file name
|
||||
'account.file.changeName' => [
|
||||
...$dialogs['user.file.changeName'],
|
||||
'pattern' => '(account)/files/(:any)/changeName',
|
||||
'load' => $dialogs['user.file.changeName']['load'],
|
||||
'submit' => $dialogs['user.file.changeName']['submit'],
|
||||
],
|
||||
|
||||
// change file sort
|
||||
'account.file.changeSort' => [
|
||||
...$dialogs['user.file.changeSort'],
|
||||
'pattern' => '(account)/files/(:any)/changeSort',
|
||||
'load' => $dialogs['user.file.changeSort']['load'],
|
||||
'submit' => $dialogs['user.file.changeSort']['submit'],
|
||||
],
|
||||
|
||||
// change file template
|
||||
'account.file.changeTemplate' => [
|
||||
...$dialogs['user.file.changeTemplate'],
|
||||
'pattern' => '(account)/files/(:any)/changeTemplate',
|
||||
'load' => $dialogs['user.file.changeTemplate']['load'],
|
||||
'submit' => $dialogs['user.file.changeTemplate']['submit'],
|
||||
],
|
||||
|
||||
// delete
|
||||
'account.file.delete' => [
|
||||
...$dialogs['user.file.delete'],
|
||||
'pattern' => '(account)/files/(:any)/delete',
|
||||
'load' => $dialogs['user.file.delete']['load'],
|
||||
'submit' => $dialogs['user.file.delete']['submit'],
|
||||
],
|
||||
|
||||
// account file fields dialogs
|
||||
'account.file.fields' => [
|
||||
...$dialogs['user.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']
|
||||
...$dialogs['user.totp.disable'],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -3,17 +3,12 @@
|
|||
$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']
|
||||
...$drawers['user.fields'],
|
||||
'pattern' => '(account)/fields/(:any)/(:all?)',
|
||||
],
|
||||
|
||||
// account file fields drawers
|
||||
'account.file.fields' => [
|
||||
...$drawers['user.file.fields'],
|
||||
'pattern' => '(account)/files/(:any)/fields/(:any)/(:all?)',
|
||||
'load' => $drawers['user.file.fields']['load'],
|
||||
'submit' => $drawers['user.file.fields']['submit']
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -4,19 +4,19 @@ $dropdowns = require __DIR__ . '/../users/dropdowns.php';
|
|||
|
||||
return [
|
||||
'account' => [
|
||||
...$dropdowns['user'],
|
||||
'pattern' => '(account)',
|
||||
'options' => $dropdowns['user']['options']
|
||||
],
|
||||
'account.languages' => [
|
||||
...$dropdowns['user.languages'],
|
||||
'pattern' => '(account)/languages',
|
||||
'options' => $dropdowns['user.languages']['options']
|
||||
],
|
||||
'account.file' => [
|
||||
...$dropdowns['user.file'],
|
||||
'pattern' => '(account)/files/(:any)',
|
||||
'options' => $dropdowns['user.file']['options']
|
||||
],
|
||||
'account.file.languages' => [
|
||||
...$dropdowns['user.file.languages'],
|
||||
'pattern' => '(account)/files/(:any)/languages',
|
||||
'options' => $files['language']
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -16,12 +16,14 @@ return [
|
|||
];
|
||||
}
|
||||
|
||||
$doc = Doc::factory($component);
|
||||
|
||||
return [
|
||||
'component' => 'k-lab-docs-drawer',
|
||||
'props' => [
|
||||
'icon' => 'book',
|
||||
'title' => $component,
|
||||
'docs' => Doc::factory($component)->toArray()
|
||||
'docs' => $doc->toArray()
|
||||
]
|
||||
];
|
||||
},
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ $fields = require __DIR__ . '/../fields/dialogs.php';
|
|||
$files = require __DIR__ . '/../files/dialogs.php';
|
||||
|
||||
return [
|
||||
|
||||
// change page position
|
||||
'page.changeSort' => [
|
||||
'pattern' => 'pages/(:any)/changeSort',
|
||||
'load' => function (string $id) {
|
||||
|
|
@ -61,7 +59,6 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// change page status
|
||||
'page.changeStatus' => [
|
||||
'pattern' => 'pages/(:any)/changeStatus',
|
||||
'load' => function (string $id) {
|
||||
|
|
@ -140,7 +137,6 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// change template
|
||||
'page.changeTemplate' => [
|
||||
'pattern' => 'pages/(:any)/changeTemplate',
|
||||
'load' => function (string $id) {
|
||||
|
|
@ -187,7 +183,6 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// change title
|
||||
'page.changeTitle' => [
|
||||
'pattern' => 'pages/(:any)/changeTitle',
|
||||
'load' => function (string $id) {
|
||||
|
|
@ -282,7 +277,6 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// create a new page
|
||||
'page.create' => [
|
||||
'pattern' => 'pages/create',
|
||||
'load' => function () {
|
||||
|
|
@ -293,6 +287,7 @@ return [
|
|||
slug: $request->get('slug'),
|
||||
template: $request->get('template'),
|
||||
title: $request->get('title'),
|
||||
uuid: $request->get('uuid'),
|
||||
viewId: $request->get('view'),
|
||||
);
|
||||
|
||||
|
|
@ -306,6 +301,7 @@ return [
|
|||
slug: $request->get('slug'),
|
||||
template: $request->get('template'),
|
||||
title: $request->get('title'),
|
||||
uuid: $request->get('uuid'),
|
||||
viewId: $request->get('view'),
|
||||
);
|
||||
|
||||
|
|
@ -313,7 +309,6 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// delete page
|
||||
'page.delete' => [
|
||||
'pattern' => 'pages/(:any)/delete',
|
||||
'load' => function (string $id) {
|
||||
|
|
@ -385,7 +380,6 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// duplicate page
|
||||
'page.duplicate' => [
|
||||
'pattern' => 'pages/(:any)/duplicate',
|
||||
'load' => function (string $id) {
|
||||
|
|
@ -474,49 +468,31 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// page field dialogs
|
||||
'page.fields' => [
|
||||
'pattern' => '(pages/.*?)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['model']['load'],
|
||||
'submit' => $fields['model']['submit']
|
||||
...$fields['model'],
|
||||
'pattern' => '(pages/[^/]+)/fields/(:any)/(:all?)',
|
||||
],
|
||||
|
||||
// change filename
|
||||
'page.file.changeName' => [
|
||||
'pattern' => '(pages/.*?)/files/(:any)/changeName',
|
||||
'load' => $files['changeName']['load'],
|
||||
'submit' => $files['changeName']['submit'],
|
||||
...$files['changeName'],
|
||||
'pattern' => '(pages/[^/]+)/files/(:any)/changeName',
|
||||
],
|
||||
|
||||
// change sort
|
||||
'page.file.changeSort' => [
|
||||
'pattern' => '(pages/.*?)/files/(:any)/changeSort',
|
||||
'load' => $files['changeSort']['load'],
|
||||
'submit' => $files['changeSort']['submit'],
|
||||
...$files['changeSort'],
|
||||
'pattern' => '(pages/[^/]+)/files/(:any)/changeSort',
|
||||
],
|
||||
|
||||
// change template
|
||||
'page.file.changeTemplate' => [
|
||||
'pattern' => '(pages/.*?)/files/(:any)/changeTemplate',
|
||||
'load' => $files['changeTemplate']['load'],
|
||||
'submit' => $files['changeTemplate']['submit'],
|
||||
...$files['changeTemplate'],
|
||||
'pattern' => '(pages/[^/]+)/files/(:any)/changeTemplate',
|
||||
],
|
||||
|
||||
// delete
|
||||
'page.file.delete' => [
|
||||
'pattern' => '(pages/.*?)/files/(:any)/delete',
|
||||
'load' => $files['delete']['load'],
|
||||
'submit' => $files['delete']['submit'],
|
||||
...$files['delete'],
|
||||
'pattern' => '(pages/[^/]+)/files/(:any)/delete',
|
||||
],
|
||||
|
||||
// page file field dialogs
|
||||
'page.file.fields' => [
|
||||
'pattern' => '(pages/.*?)/files/(:any)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['file']['load'],
|
||||
'submit' => $fields['file']['submit'],
|
||||
...$fields['file'],
|
||||
'pattern' => '(pages/[^/]+)/files/(:any)/fields/(:any)/(:all?)',
|
||||
],
|
||||
|
||||
// move page
|
||||
'page.move' => [
|
||||
'pattern' => 'pages/(:any)/move',
|
||||
'load' => function (string $id) {
|
||||
|
|
@ -553,7 +529,6 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// change site title
|
||||
'site.changeTitle' => [
|
||||
'pattern' => 'site/changeTitle',
|
||||
'load' => function () {
|
||||
|
|
@ -583,49 +558,31 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// site field dialogs
|
||||
'site.fields' => [
|
||||
...$fields['model'],
|
||||
'pattern' => '(site)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['model']['load'],
|
||||
'submit' => $fields['model']['submit'],
|
||||
],
|
||||
|
||||
// change filename
|
||||
'site.file.changeName' => [
|
||||
...$files['changeName'],
|
||||
'pattern' => '(site)/files/(:any)/changeName',
|
||||
'load' => $files['changeName']['load'],
|
||||
'submit' => $files['changeName']['submit'],
|
||||
],
|
||||
|
||||
// change sort
|
||||
'site.file.changeSort' => [
|
||||
...$files['changeSort'],
|
||||
'pattern' => '(site)/files/(:any)/changeSort',
|
||||
'load' => $files['changeSort']['load'],
|
||||
'submit' => $files['changeSort']['submit'],
|
||||
],
|
||||
|
||||
// change template
|
||||
'site.file.changeTemplate' => [
|
||||
...$files['changeTemplate'],
|
||||
'pattern' => '(site)/files/(:any)/changeTemplate',
|
||||
'load' => $files['changeTemplate']['load'],
|
||||
'submit' => $files['changeTemplate']['submit'],
|
||||
],
|
||||
|
||||
// delete
|
||||
'site.file.delete' => [
|
||||
...$files['delete'],
|
||||
'pattern' => '(site)/files/(:any)/delete',
|
||||
'load' => $files['delete']['load'],
|
||||
'submit' => $files['delete']['submit'],
|
||||
],
|
||||
|
||||
// site file field dialogs
|
||||
'site.file.fields' => [
|
||||
...$fields['file'],
|
||||
'pattern' => '(site)/files/(:any)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['file']['load'],
|
||||
'submit' => $fields['file']['submit'],
|
||||
],
|
||||
|
||||
// content changes
|
||||
'changes' => [
|
||||
'pattern' => 'changes',
|
||||
'load' => function () {
|
||||
|
|
|
|||
|
|
@ -3,31 +3,20 @@
|
|||
$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']
|
||||
...$fields['model'],
|
||||
'pattern' => '(pages/[^/]+)/fields/(:any)/(:all?)',
|
||||
],
|
||||
|
||||
// page file field drawers
|
||||
'page.file.fields' => [
|
||||
'pattern' => '(pages/.*?)/files/(:any)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['file']['load'],
|
||||
'submit' => $fields['file']['submit'],
|
||||
...$fields['file'],
|
||||
'pattern' => '(pages/[^/]+)/files/(:any)/fields/(:any)/(:all?)',
|
||||
],
|
||||
|
||||
// site field drawers
|
||||
'site.fields' => [
|
||||
...$fields['model'],
|
||||
'pattern' => '(site)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['model']['load'],
|
||||
'submit' => $fields['model']['submit'],
|
||||
],
|
||||
|
||||
// site file field drawers
|
||||
'site.file.fields' => [
|
||||
...$fields['file'],
|
||||
'pattern' => '(site)/files/(:any)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['file']['load'],
|
||||
'submit' => $fields['file']['submit'],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ return [
|
|||
}
|
||||
],
|
||||
'page.file' => [
|
||||
'pattern' => '(pages/.*?)/files/(:any)',
|
||||
'pattern' => '(pages/[^/]+)/files/(:any)',
|
||||
'options' => $files['file']
|
||||
],
|
||||
'page.file.languages' => [
|
||||
'pattern' => '(pages/.*?)/files/(:any)/languages',
|
||||
'pattern' => '(pages/[^/]+)/files/(:any)/languages',
|
||||
'options' => $files['language']
|
||||
],
|
||||
'site.languages' => [
|
||||
|
|
|
|||
|
|
@ -57,6 +57,28 @@ return [
|
|||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
],
|
||||
'license/remove' => [
|
||||
'load' => function () {
|
||||
return [
|
||||
'component' => 'k-remove-dialog',
|
||||
'props' => [
|
||||
'text' => I18n::translate('license.remove.text'),
|
||||
'size' => 'medium',
|
||||
'submitButton' => [
|
||||
'icon' => 'trash',
|
||||
'text' => I18n::translate('remove'),
|
||||
'theme' => 'negative',
|
||||
],
|
||||
]
|
||||
];
|
||||
},
|
||||
'submit' => function () {
|
||||
// @codeCoverageIgnoreStart
|
||||
App::instance()->system()->license()->delete();
|
||||
return true;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
],
|
||||
// license registration
|
||||
'registration' => [
|
||||
'load' => function () {
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ $fields = require __DIR__ . '/../fields/dialogs.php';
|
|||
$files = require __DIR__ . '/../files/dialogs.php';
|
||||
|
||||
return [
|
||||
|
||||
// create
|
||||
'user.create' => [
|
||||
'pattern' => 'users/create',
|
||||
'load' => function () {
|
||||
|
|
@ -79,7 +77,6 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// change email
|
||||
'user.changeEmail' => [
|
||||
'pattern' => 'users/(:any)/changeEmail',
|
||||
'load' => function (string $id) {
|
||||
|
|
@ -114,7 +111,6 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// change language
|
||||
'user.changeLanguage' => [
|
||||
'pattern' => 'users/(:any)/changeLanguage',
|
||||
'load' => function (string $id) {
|
||||
|
|
@ -147,7 +143,6 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// change name
|
||||
'user.changeName' => [
|
||||
'pattern' => 'users/(:any)/changeName',
|
||||
'load' => function (string $id) {
|
||||
|
|
@ -179,7 +174,6 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// change password
|
||||
'user.changePassword' => [
|
||||
'pattern' => 'users/(:any)/changePassword',
|
||||
'load' => function (string $id) {
|
||||
|
|
@ -245,7 +239,6 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// change role
|
||||
'user.changeRole' => [
|
||||
'pattern' => 'users/(:any)/changeRole',
|
||||
'load' => function (string $id) {
|
||||
|
|
@ -282,7 +275,6 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// delete
|
||||
'user.delete' => [
|
||||
'pattern' => 'users/(:any)/delete',
|
||||
'load' => function (string $id) {
|
||||
|
|
@ -324,49 +316,36 @@ return [
|
|||
}
|
||||
],
|
||||
|
||||
// user field dialogs
|
||||
'user.fields' => [
|
||||
'pattern' => '(users/.*?)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['model']['load'],
|
||||
'submit' => $fields['model']['submit']
|
||||
...$fields['model'],
|
||||
'pattern' => '(users/[^/]+)/fields/(:any)/(:all?)',
|
||||
],
|
||||
|
||||
// change file name
|
||||
'user.file.changeName' => [
|
||||
'pattern' => '(users/.*?)/files/(:any)/changeName',
|
||||
'load' => $files['changeName']['load'],
|
||||
'submit' => $files['changeName']['submit'],
|
||||
...$files['changeName'],
|
||||
'pattern' => '(users/[^/]+)/files/(:any)/changeName',
|
||||
],
|
||||
|
||||
// change file sort
|
||||
'user.file.changeSort' => [
|
||||
'pattern' => '(users/.*?)/files/(:any)/changeSort',
|
||||
'load' => $files['changeSort']['load'],
|
||||
'submit' => $files['changeSort']['submit'],
|
||||
...$files['changeSort'],
|
||||
'pattern' => '(users/[^/]+)/files/(:any)/changeSort',
|
||||
],
|
||||
|
||||
// change file template
|
||||
'user.file.changeTemplate' => [
|
||||
'pattern' => '(users/.*?)/files/(:any)/changeTemplate',
|
||||
'load' => $files['changeTemplate']['load'],
|
||||
'submit' => $files['changeTemplate']['submit'],
|
||||
...$files['changeTemplate'],
|
||||
'pattern' => '(users/[^/]+)/files/(:any)/changeTemplate',
|
||||
],
|
||||
|
||||
// delete file
|
||||
'user.file.delete' => [
|
||||
'pattern' => '(users/.*?)/files/(:any)/delete',
|
||||
'load' => $files['delete']['load'],
|
||||
'submit' => $files['delete']['submit'],
|
||||
...$files['delete'],
|
||||
'pattern' => '(users/[^/]+)/files/(:any)/delete',
|
||||
],
|
||||
|
||||
// user file fields dialogs
|
||||
'user.file.fields' => [
|
||||
'pattern' => '(users/.*?)/files/(:any)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['file']['load'],
|
||||
'submit' => $fields['file']['submit']
|
||||
...$fields['file'],
|
||||
'pattern' => '(users/[^/]+)/files/(:any)/fields/(:any)/(:all?)',
|
||||
],
|
||||
|
||||
// user disable TOTP
|
||||
'user.totp.disable' => [
|
||||
'pattern' => 'users/(:any)/totp/disable',
|
||||
'load' => fn (string $id) => (new UserTotpDisableDialog($id))->load(),
|
||||
|
|
|
|||
|
|
@ -3,16 +3,12 @@
|
|||
$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']
|
||||
...$fields['model'],
|
||||
'pattern' => '(users/[^/]+)/fields/(:any)/(:all?)',
|
||||
],
|
||||
// user file fields drawers
|
||||
'user.file.fields' => [
|
||||
'pattern' => '(users/.*?)/files/(:any)/fields/(:any)/(:all?)',
|
||||
'load' => $fields['file']['load'],
|
||||
'submit' => $fields['file']['submit']
|
||||
...$fields['file'],
|
||||
'pattern' => '(users/[^/]+)/files/(:any)/fields/(:any)/(:all?)',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@ return [
|
|||
}
|
||||
],
|
||||
'user.file' => [
|
||||
'pattern' => '(users/.*?)/files/(:any)',
|
||||
'pattern' => '(users/[^/]+)/files/(:any)',
|
||||
'options' => $files['file']
|
||||
],
|
||||
'user.file.languages' => [
|
||||
'pattern' => '(users/.*?)/files/(:any)/languages',
|
||||
'pattern' => '(users/[^/]+)/files/(:any)/languages',
|
||||
'options' => $files['language']
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Panel\Collector\UsersCollector;
|
||||
use Kirby\Panel\Ui\Buttons\ViewButtons;
|
||||
use Kirby\Toolkit\Escape;
|
||||
use Kirby\Panel\Ui\Item\UserItem;
|
||||
|
||||
return [
|
||||
'users' => [
|
||||
|
|
@ -31,29 +32,17 @@ return [
|
|||
},
|
||||
'roles' => array_values($roles),
|
||||
'users' => function () use ($kirby, $role) {
|
||||
$users = $kirby->users();
|
||||
$collector = new UsersCollector(
|
||||
limit: 20,
|
||||
page: $kirby->request()->get('page', 1),
|
||||
role: $role,
|
||||
sortBy: 'username asc',
|
||||
);
|
||||
|
||||
if (empty($role) === false) {
|
||||
$users = $users->role($role);
|
||||
}
|
||||
|
||||
// sort users alphabetically
|
||||
$users = $users->sortBy('username', 'asc');
|
||||
|
||||
// paginate
|
||||
$users = $users->paginate([
|
||||
'limit' => 20,
|
||||
'page' => $kirby->request()->get('page')
|
||||
]);
|
||||
$users = $collector->models(paginated: true);
|
||||
|
||||
return [
|
||||
'data' => $users->values(fn ($user) => [
|
||||
'id' => $user->id(),
|
||||
'image' => $user->panel()->image(),
|
||||
'info' => Escape::html($user->role()->title()),
|
||||
'link' => $user->panel()->url(true),
|
||||
'text' => Escape::html($user->username())
|
||||
]),
|
||||
'data' => $users->values(fn ($user) => (new UserItem(user: $user))->props()),
|
||||
'pagination' => $users->pagination()->toArray()
|
||||
];
|
||||
},
|
||||
|
|
|
|||
|
|
@ -421,10 +421,7 @@ return [
|
|||
// support UUIDs
|
||||
if (
|
||||
$path !== null &&
|
||||
(
|
||||
Uuid::is($path, 'page') === true ||
|
||||
Uuid::is($path, 'file') === true
|
||||
)
|
||||
Uuid::is($path, ['page', 'file']) === true
|
||||
) {
|
||||
$model = Uuid::for($path)->model();
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ return [
|
|||
return [];
|
||||
}
|
||||
|
||||
return $this->form()->fields()->toArray();
|
||||
return $this->form()->fields()->toProps();
|
||||
},
|
||||
'value' => function () {
|
||||
$data = Data::decode($this->value, 'yaml');
|
||||
|
|
|
|||
|
|
@ -19,6 +19,13 @@ return [
|
|||
'icon' => null,
|
||||
'placeholder' => null,
|
||||
|
||||
/**
|
||||
* Whether to enable batch editing
|
||||
*/
|
||||
'batch' => function (bool $batch = false) {
|
||||
return $batch;
|
||||
},
|
||||
|
||||
/**
|
||||
* Optional columns definition to only show selected fields in the structure table.
|
||||
*/
|
||||
|
|
@ -105,7 +112,7 @@ return [
|
|||
return [];
|
||||
}
|
||||
|
||||
return $this->form()->fields()->toArray();
|
||||
return $this->form()->fields()->toProps();
|
||||
},
|
||||
'columns' => function () {
|
||||
$columns = [];
|
||||
|
|
|
|||
|
|
@ -63,12 +63,12 @@ return [
|
|||
$users = [];
|
||||
$kirby = App::instance();
|
||||
|
||||
foreach (Data::decode($value, 'yaml') as $email) {
|
||||
if (is_array($email) === true) {
|
||||
$email = $email['email'] ?? null;
|
||||
foreach (Data::decode($value, 'yaml') as $id) {
|
||||
if (is_array($id) === true) {
|
||||
$id = $id['uuid'] ?? $id['id'] ?? $id['email'] ?? null;
|
||||
}
|
||||
|
||||
if ($email !== null && ($user = $kirby->user($email))) {
|
||||
if ($id !== null && ($user = $kirby->user($id))) {
|
||||
$users[] = $this->userResponse($user);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\File;
|
||||
use Kirby\Cms\Files;
|
||||
use Kirby\Panel\Collector\FilesCollector;
|
||||
use Kirby\Panel\Ui\Item\FileItem;
|
||||
use Kirby\Panel\Ui\Upload;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
return [
|
||||
|
|
@ -56,86 +58,40 @@ return [
|
|||
'parent' => function () {
|
||||
return $this->parentModel();
|
||||
},
|
||||
'collector' => function () {
|
||||
return $this->collector ??= new FilesCollector(
|
||||
flip: $this->flip(),
|
||||
limit: $this->limit(),
|
||||
page: $this->page() ?? 1,
|
||||
parent: $this->parent(),
|
||||
query: $this->query(),
|
||||
search: $this->searchterm(),
|
||||
sortBy: $this->sortBy(),
|
||||
template: $this->template(),
|
||||
);
|
||||
},
|
||||
'models' => function () {
|
||||
if ($this->query !== null) {
|
||||
$files = $this->parent->query($this->query, Files::class) ?? new Files([]);
|
||||
} else {
|
||||
$files = $this->parent->files();
|
||||
}
|
||||
|
||||
// filter files by template
|
||||
$files = $files->template($this->template);
|
||||
|
||||
// filter out all protected and hidden files
|
||||
$files = $files->filter('isListable', true);
|
||||
|
||||
// search
|
||||
if ($this->search === true && empty($this->searchterm()) === false) {
|
||||
$files = $files->search($this->searchterm());
|
||||
|
||||
// disable flip and sortBy while searching
|
||||
// to show most relevant results
|
||||
$this->flip = false;
|
||||
$this->sortBy = null;
|
||||
}
|
||||
|
||||
// sort
|
||||
if ($this->sortBy) {
|
||||
$files = $files->sort(...$files::sortArgs($this->sortBy));
|
||||
} else {
|
||||
$files = $files->sorted();
|
||||
}
|
||||
|
||||
// flip
|
||||
if ($this->flip === true) {
|
||||
$files = $files->flip();
|
||||
}
|
||||
|
||||
return $files;
|
||||
return $this->collector()->models();
|
||||
},
|
||||
'modelsPaginated' => function () {
|
||||
// apply the default pagination
|
||||
return $this->models()->paginate([
|
||||
'page' => $this->page,
|
||||
'limit' => $this->limit,
|
||||
'method' => 'none' // the page is manually provided
|
||||
]);
|
||||
return $this->collector()->models(paginated: true);
|
||||
},
|
||||
'files' => function () {
|
||||
return $this->models;
|
||||
},
|
||||
'data' => function () {
|
||||
$data = [];
|
||||
$dragTextIsAbsolute = $this->model->is($this->parent) === false;
|
||||
|
||||
foreach ($this->modelsPaginated() as $file) {
|
||||
$panel = $file->panel();
|
||||
$permissions = $file->permissions();
|
||||
|
||||
$item = [
|
||||
'dragText' => $panel->dragText(
|
||||
// the drag text needs to be absolute
|
||||
// when the files come from a different parent model
|
||||
absolute: $this->model->is($this->parent) === false
|
||||
),
|
||||
'extension' => $file->extension(),
|
||||
'filename' => $file->filename(),
|
||||
'id' => $file->id(),
|
||||
'image' => $panel->image(
|
||||
$this->image,
|
||||
$this->layout === 'table' ? 'list' : $this->layout
|
||||
),
|
||||
'info' => $file->toSafeString($this->info ?? false),
|
||||
'link' => $panel->url(true),
|
||||
'mime' => $file->mime(),
|
||||
'parent' => $file->parent()->panel()->path(),
|
||||
'permissions' => [
|
||||
'delete' => $permissions->can('delete'),
|
||||
'sort' => $permissions->can('sort'),
|
||||
],
|
||||
'template' => $file->template(),
|
||||
'text' => $file->toSafeString($this->text),
|
||||
'url' => $file->url(),
|
||||
];
|
||||
$item = (new FileItem(
|
||||
file: $file,
|
||||
dragTextIsAbsolute: $dragTextIsAbsolute,
|
||||
image: $this->image,
|
||||
layout: $this->layout,
|
||||
info: $this->info,
|
||||
text: $this->text,
|
||||
))->props();
|
||||
|
||||
if ($this->layout === 'table') {
|
||||
$item = $this->columnsValues($item, $file);
|
||||
|
|
@ -185,26 +141,16 @@ return [
|
|||
return false;
|
||||
}
|
||||
|
||||
// count all uploaded files
|
||||
$max = $this->max ? $this->max - $this->total : null;
|
||||
$multiple = !$max || $max > 1;
|
||||
$template = $this->template === 'default' ? null : $this->template;
|
||||
$settings = new Upload(
|
||||
api: $this->parent->apiUrl(true) . '/files',
|
||||
accept: $this->accept,
|
||||
max: $this->max ? $this->max - $this->total : null,
|
||||
preview: $this->image,
|
||||
sort: $this->sortable === true ? $this->total + 1 : null,
|
||||
template: $this->template,
|
||||
);
|
||||
|
||||
return [
|
||||
'accept' => $this->accept,
|
||||
'multiple' => $multiple,
|
||||
'max' => $max,
|
||||
'api' => $this->parent->apiUrl(true) . '/files',
|
||||
'preview' => $this->image,
|
||||
'attributes' => [
|
||||
// TODO: an edge issue that needs to be solved:
|
||||
// if multiple users load the same section
|
||||
// at the same time and upload a file,
|
||||
// uploaded files have the same sort number
|
||||
'sort' => $this->sortable === true ? $this->total + 1 : null,
|
||||
'template' => $template
|
||||
]
|
||||
];
|
||||
return $settings->props();
|
||||
}
|
||||
],
|
||||
// @codeCoverageIgnoreStart
|
||||
|
|
|
|||
|
|
@ -13,7 +13,11 @@ return [
|
|||
],
|
||||
'methods' => [
|
||||
'searchterm' => function (): string|null {
|
||||
return App::instance()->request()->get('searchterm');
|
||||
if ($this->search() === true) {
|
||||
return App::instance()->request()->get('searchterm') ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ use Kirby\Cms\Page;
|
|||
use Kirby\Cms\Pages;
|
||||
use Kirby\Cms\Site;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Panel\Collector\PagesCollector;
|
||||
use Kirby\Panel\Ui\Item\PageItem;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
|
|
@ -85,83 +87,28 @@ return [
|
|||
|
||||
return $parent;
|
||||
},
|
||||
'collector' => function () {
|
||||
return $this->collector ??= new PagesCollector(
|
||||
limit: $this->limit(),
|
||||
page: $this->page() ?? 1,
|
||||
parent: $this->parent(),
|
||||
query: $this->query(),
|
||||
status: $this->status(),
|
||||
templates: $this->templates(),
|
||||
templatesIgnore: $this->templatesIgnore(),
|
||||
search: $this->searchterm(),
|
||||
sortBy: $this->sortBy(),
|
||||
flip: $this->flip()
|
||||
);
|
||||
},
|
||||
'models' => function () {
|
||||
if ($this->query !== null) {
|
||||
$pages = $this->parent->query($this->query, Pages::class) ?? new Pages([]);
|
||||
} else {
|
||||
$pages = match ($this->status) {
|
||||
'draft' => $this->parent->drafts(),
|
||||
'listed' => $this->parent->children()->listed(),
|
||||
'published' => $this->parent->children(),
|
||||
'unlisted' => $this->parent->children()->unlisted(),
|
||||
default => $this->parent->childrenAndDrafts()
|
||||
};
|
||||
}
|
||||
|
||||
// filters pages that are protected and not in the templates list
|
||||
// internal `filter()` method used instead of foreach loop that previously included `unset()`
|
||||
// because `unset()` is updating the original data, `filter()` is just filtering
|
||||
// also it has been tested that there is no performance difference
|
||||
// even in 0.1 seconds on 100k virtual pages
|
||||
$pages = $pages->filter(function ($page) {
|
||||
// remove all protected and hidden pages
|
||||
if ($page->isListable() === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$intendedTemplate = $page->intendedTemplate()->name();
|
||||
|
||||
// filter by all set templates
|
||||
if (
|
||||
$this->templates &&
|
||||
in_array($intendedTemplate, $this->templates, true) === false
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// exclude by all ignored templates
|
||||
if (
|
||||
$this->templatesIgnore &&
|
||||
in_array($intendedTemplate, $this->templatesIgnore, true) === true
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// search
|
||||
if ($this->search === true && empty($this->searchterm()) === false) {
|
||||
$pages = $pages->search($this->searchterm());
|
||||
|
||||
// disable flip and sortBy while searching
|
||||
// to show most relevant results
|
||||
$this->flip = false;
|
||||
$this->sortBy = null;
|
||||
}
|
||||
|
||||
// sort
|
||||
if ($this->sortBy) {
|
||||
$pages = $pages->sort(...$pages::sortArgs($this->sortBy));
|
||||
}
|
||||
|
||||
// flip
|
||||
if ($this->flip === true) {
|
||||
$pages = $pages->flip();
|
||||
}
|
||||
|
||||
return $pages;
|
||||
return $this->collector()->models();
|
||||
},
|
||||
'modelsPaginated' => function () {
|
||||
// pagination
|
||||
return $this->models()->paginate([
|
||||
'page' => $this->page,
|
||||
'limit' => $this->limit,
|
||||
'method' => 'none' // the page is manually provided
|
||||
]);
|
||||
return $this->collector()->models(paginated: true);
|
||||
},
|
||||
'pages' => function () {
|
||||
return $this->models;
|
||||
return $this->models();
|
||||
},
|
||||
'total' => function () {
|
||||
return $this->models()->count();
|
||||
|
|
@ -170,30 +117,13 @@ return [
|
|||
$data = [];
|
||||
|
||||
foreach ($this->modelsPaginated() as $page) {
|
||||
$panel = $page->panel();
|
||||
$permissions = $page->permissions();
|
||||
|
||||
$item = [
|
||||
'dragText' => $panel->dragText(),
|
||||
'id' => $page->id(),
|
||||
'image' => $panel->image(
|
||||
$this->image,
|
||||
$this->layout === 'table' ? 'list' : $this->layout
|
||||
),
|
||||
'info' => $page->toSafeString($this->info ?? false),
|
||||
'link' => $panel->url(true),
|
||||
'parent' => $page->parentId(),
|
||||
'permissions' => [
|
||||
'delete' => $permissions->can('delete'),
|
||||
'changeSlug' => $permissions->can('changeSlug'),
|
||||
'changeStatus' => $permissions->can('changeStatus'),
|
||||
'changeTitle' => $permissions->can('changeTitle'),
|
||||
'sort' => $permissions->can('sort'),
|
||||
],
|
||||
'status' => $page->status(),
|
||||
'template' => $page->intendedTemplate()->name(),
|
||||
'text' => $page->toSafeString($this->text),
|
||||
];
|
||||
$item = (new PageItem(
|
||||
page: $page,
|
||||
image: $this->image,
|
||||
layout: $this->layout,
|
||||
info: $this->info,
|
||||
text: $this->text,
|
||||
))->props();
|
||||
|
||||
if ($this->layout === 'table') {
|
||||
$item = $this->columnsValues($item, $page);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Toolkit\I18n;
|
||||
use Kirby\Panel\Ui\Stats;
|
||||
|
||||
return [
|
||||
'mixins' => [
|
||||
|
|
@ -10,20 +10,8 @@ return [
|
|||
/**
|
||||
* Array or query string for reports. Each report needs a `label` and `value` and can have additional `info`, `link`, `icon` and `theme` settings.
|
||||
*/
|
||||
'reports' => function ($reports = null) {
|
||||
if ($reports === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (is_string($reports) === true) {
|
||||
$reports = $this->model()->query($reports);
|
||||
}
|
||||
|
||||
if (is_array($reports) === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $reports;
|
||||
'reports' => function (array|string|null $reports = null) {
|
||||
return $reports ?? [];
|
||||
},
|
||||
/**
|
||||
* The size of the report cards. Available sizes: `tiny`, `small`, `medium`, `large`
|
||||
|
|
@ -33,36 +21,18 @@ return [
|
|||
}
|
||||
],
|
||||
'computed' => [
|
||||
'reports' => function () {
|
||||
$reports = [];
|
||||
$model = $this->model();
|
||||
$toString = fn ($value) => $value === null ? null : $model->toString($value);
|
||||
|
||||
foreach ($this->reports as $report) {
|
||||
if (is_string($report) === true) {
|
||||
$report = $model->query($report);
|
||||
}
|
||||
|
||||
if (is_array($report) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$info = $report['info'] ?? null;
|
||||
$label = $report['label'] ?? null;
|
||||
$link = $report['link'] ?? null;
|
||||
$value = $report['value'] ?? null;
|
||||
|
||||
$reports[] = [
|
||||
'icon' => $toString($report['icon'] ?? null),
|
||||
'info' => $toString(I18n::translate($info, $info)),
|
||||
'label' => $toString(I18n::translate($label, $label)),
|
||||
'link' => $toString(I18n::translate($link, $link)),
|
||||
'theme' => $toString($report['theme'] ?? null),
|
||||
'value' => $toString(I18n::translate($value, $value))
|
||||
];
|
||||
}
|
||||
|
||||
return $reports;
|
||||
'stats' => function (): Stats {
|
||||
return $this->stats ??= Stats::from(
|
||||
model: $this->model(),
|
||||
reports: $this->reports(),
|
||||
size: $this->size()
|
||||
);
|
||||
},
|
||||
'reports' => function (): array {
|
||||
return $this->stats->reports();
|
||||
},
|
||||
'size' => function (): string {
|
||||
return $this->stats->size();
|
||||
}
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -206,11 +206,8 @@ return [
|
|||
|
||||
// if value is a UUID, resolve to page/file model
|
||||
// and use the URL as value
|
||||
if (
|
||||
Uuid::is($tag->value, 'page') === true ||
|
||||
Uuid::is($tag->value, 'file') === true
|
||||
) {
|
||||
$tag->value = Uuid::for($tag->value)->model()?->url();
|
||||
if (Uuid::is($tag->value, ['page', 'file']) === true) {
|
||||
$tag->value = Uuid::for($tag->value)?->toUrl();
|
||||
}
|
||||
|
||||
// if url is empty, throw exception or link to the error page
|
||||
|
|
|
|||
|
|
@ -376,6 +376,7 @@
|
|||
|
||||
"field.structure.delete.confirm": "Do you really want to delete this row?",
|
||||
"field.structure.delete.confirm.all": "Do you really want to delete all entries?",
|
||||
"field.structure.delete.confirm.selected": "Do you really want to delete the selected entries?",
|
||||
"field.structure.empty": "No entries yet",
|
||||
|
||||
"field.users.empty": "No users selected yet",
|
||||
|
|
@ -474,6 +475,7 @@
|
|||
"license.code": "Code",
|
||||
"license.code.help": "You received your license code after the purchase via email. Please copy and paste it here.",
|
||||
"license.code.label": "Please enter your license code",
|
||||
"license.remove.text": "<p>Removing the license will irreversibly delete the license file from this site. You can then activate this site with a different license key or re-register the same license key if the domain remains the same.</p><p>To change the domain associated with the license, please contact the Kirby team. <a href='https://getkirby.com/license'>Read more →</a></p>",
|
||||
"license.status.active.info": "Includes new major versions until {date}",
|
||||
"license.status.active.label": "Valid license",
|
||||
"license.status.demo.info": "This is a demo installation",
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
"copy.all": "모두 복사",
|
||||
"copy.success": "복사되었습니다. ({count})",
|
||||
"copy.success.multiple": "복사되었습니다. ({count})",
|
||||
"copy.url": "Copy URL",
|
||||
"copy.url": "URL 복사",
|
||||
"create": "등록",
|
||||
"custom": "개인화",
|
||||
|
||||
|
|
@ -92,23 +92,23 @@
|
|||
|
||||
"error.cache.type.invalid": "캐시 형식(({type})이 올바르지 않습니다.",
|
||||
|
||||
"error.content.lock.delete": "The version is locked and cannot be deleted",
|
||||
"error.content.lock.move": "The source version is locked and cannot be moved",
|
||||
"error.content.lock.publish": "This version is already published",
|
||||
"error.content.lock.replace": "The version is locked and cannot be replaced",
|
||||
"error.content.lock.update": "The version is locked and cannot be updated",
|
||||
"error.content.lock.delete": "잠긴 버전은 삭제할 수 없습니다.",
|
||||
"error.content.lock.move": "잠긴 버전은 이동할 수 없습니다.",
|
||||
"error.content.lock.publish": "이미 발행되었습니다.",
|
||||
"error.content.lock.replace": "잠긴 버전은 교체할 수 없습니다.",
|
||||
"error.content.lock.update": "잠긴 버전은 업데이트할 수 없습니다.",
|
||||
|
||||
"error.entries.max.plural": "You must not add more than {max} entries",
|
||||
"error.entries.max.singular": "You must not add more than one entry",
|
||||
"error.entries.min.plural": "You must add at least {min} entries",
|
||||
"error.entries.min.singular": "You must add at least one entry",
|
||||
"error.entries.supports": "\"{type}\" field type is not supported for the entries field",
|
||||
"error.entries.max.plural": "엔트리를 {max}개 이상 추가할 수 없습니다.",
|
||||
"error.entries.max.singular": "엔트리를 하나 이상 추가할 수 없습니다.",
|
||||
"error.entries.min.plural": "엔트리를 {min}개 이상 추가하세요.",
|
||||
"error.entries.min.singular": "엔트리를 하나 이상 추가하세요.",
|
||||
"error.entries.supports": "{type} 필드 타입은 지원하지 않습니다.",
|
||||
"error.entries.validation": "{index}번째 필드({field})에 오류가 있습니다.",
|
||||
|
||||
"error.email.preset.notFound": "기본 이메일 주소({name})가 없습니다.",
|
||||
|
||||
"error.field.converter.invalid": "컨버터({converter})가 올바르지 않습니다.",
|
||||
"error.field.link.options": "Invalid options: {options}",
|
||||
"error.field.link.options": "설정({options})이 올바르지 않습니다.",
|
||||
"error.field.type.missing": "필드({name}): 필드 타입({type})이 없습니다.",
|
||||
|
||||
"error.file.changeName.empty": "이름을 입력하세요.",
|
||||
|
|
@ -116,7 +116,7 @@
|
|||
"error.file.changeTemplate.invalid": "파일({id}) 템플릿을 다음 템플릿({template})으로 변경할 수 없습니다. (valid: \"{blueprints}\")",
|
||||
"error.file.changeTemplate.permission": "파일({id}) 템플릿을 변경할 수 없습니다.",
|
||||
|
||||
"error.file.delete.multiple": "Not all files could be deleted. Try each remaining file individually to see the specific error that prevents deletion.",
|
||||
"error.file.delete.multiple": "모든 파일을 삭제할 수 없습니다. 각 파일을 확인하세요.",
|
||||
"error.file.duplicate": "파일명이 같은 파일({filename})이 있습니다.",
|
||||
"error.file.extension.forbidden": "이 확장자({extension})는 업로드할 수 없습니다.",
|
||||
"error.file.extension.invalid": "확장자({extension})가 올바르지 않습니다.",
|
||||
|
|
@ -135,7 +135,7 @@
|
|||
"error.file.name.missing": "파일명을 입력하세요.",
|
||||
"error.file.notFound": "파일({filename})이 없습니다.",
|
||||
"error.file.orientation": "이미지의 비율({orientation})을 확인하세요.",
|
||||
"error.file.sort.permission": "You are not allowed to change the sorting of \"{filename}\"",
|
||||
"error.file.sort.permission": "파일({filename})을 정렬할 권한이 없습니다.",
|
||||
"error.file.type.forbidden": "이 형식({type})의 파일을 업로드할 권한이 없습니다.",
|
||||
"error.file.type.invalid": "파일 형식({type})이 올바르지 않습니다.",
|
||||
"error.file.undefined": "\ud30c\uc77c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4.",
|
||||
|
|
@ -179,7 +179,7 @@
|
|||
"error.page.delete": "페이지({slug})를 삭제할 수 없습니다.",
|
||||
"error.page.delete.confirm": "페이지를 삭제하려면 페이지의 제목을 입력하세요.",
|
||||
"error.page.delete.hasChildren": "하위 페이지가 있는 페이지는 삭제할 수 없습니다.",
|
||||
"error.page.delete.multiple": "Not all pages could be deleted. Try each remaining page individually to see the specific error that prevents deletion.",
|
||||
"error.page.delete.multiple": "모든 페이지를 삭제할 수 없습니다. 각 페이지를 확인하세요.",
|
||||
"error.page.delete.permission": "페이지({slug})를 삭제할 권한이 없습니다.",
|
||||
"error.page.draft.duplicate": "고유 주소({slug})가 같은 초안 페이지가 있습니다.",
|
||||
"error.page.duplicate": "고유 주소({slug})가 같은 페이지가 있습니다.",
|
||||
|
|
@ -187,7 +187,7 @@
|
|||
"error.page.move.ancestor": "해당 페이지로 이동할 수 없습니다.",
|
||||
"error.page.move.directory": "페이지 디렉토리는 이동할 수 없습니다.",
|
||||
"error.page.move.duplicate": "고유 주소({slug})가 같은 서브 페이지가 있습니다.",
|
||||
"error.page.move.noSections": "The page \"{parent}\" cannot be a parent of any page because it lacks any pages sections in its blueprint",
|
||||
"error.page.move.noSections": "부모 페이지({parent})의 블루프린트에 해당 섹션이 없습니다.",
|
||||
"error.page.move.notFound": "이동된 페이지를 찾을 수 없습니다.",
|
||||
"error.page.move.permission": "페이지({slug})를 이동할 권한이 없습니다.",
|
||||
"error.page.move.template": "이 템플릿({template})은 이 페이지({parent})의 서브 페이지로 이동할 수 없습니다.",
|
||||
|
|
@ -317,9 +317,9 @@
|
|||
"field.blocks.heading.name": "제목",
|
||||
"field.blocks.heading.text": "제목",
|
||||
"field.blocks.heading.placeholder": "제목",
|
||||
"field.blocks.figure.back.plain": "Plain",
|
||||
"field.blocks.figure.back.pattern.light": "Pattern (light)",
|
||||
"field.blocks.figure.back.pattern.dark": "Pattern (dark)",
|
||||
"field.blocks.figure.back.plain": "플레인",
|
||||
"field.blocks.figure.back.pattern.light": "패턴(밝음)",
|
||||
"field.blocks.figure.back.pattern.dark": "패턴(어두움)",
|
||||
"field.blocks.image.alt": "대체 텍스트",
|
||||
"field.blocks.image.caption": "캡션",
|
||||
"field.blocks.image.crop": "자르기",
|
||||
|
|
@ -360,7 +360,7 @@
|
|||
"field.entries.empty": "항목이 없습니다.",
|
||||
|
||||
"field.files.empty": "선택한 파일이 없습니다.",
|
||||
"field.files.empty.single": "No file selected yet",
|
||||
"field.files.empty.single": "선택한 파일이 없습니다.",
|
||||
|
||||
"field.layout.change": "레이아웃 변경",
|
||||
"field.layout.delete": "레이아웃 삭제",
|
||||
|
|
@ -372,14 +372,14 @@
|
|||
"field.object.empty": "정보가 없습니다.",
|
||||
|
||||
"field.pages.empty": "선택한 페이지가 없습니다.",
|
||||
"field.pages.empty.single": "No page selected yet",
|
||||
"field.pages.empty.single": "선택한 페이지가 없습니다.",
|
||||
|
||||
"field.structure.delete.confirm": "이 항목을 삭제할까요?",
|
||||
"field.structure.delete.confirm.all": "모든 항목을 삭제할까요?",
|
||||
"field.structure.empty": "항목이 없습니다.",
|
||||
|
||||
"field.users.empty": "선택한 사용자가 없습니다.",
|
||||
"field.users.empty.single": "No user selected yet",
|
||||
"field.users.empty.single": "선택한 사용자가 없습니다.",
|
||||
|
||||
"fields.empty": "필드가 없습니다.",
|
||||
|
||||
|
|
@ -394,17 +394,17 @@
|
|||
"file.sort": "순서 변경",
|
||||
|
||||
"files": "파일",
|
||||
"files.delete.confirm.selected": "Do you really want to delete the selected files? This action cannot be undone.",
|
||||
"files.delete.confirm.selected": "선택한 파일을 삭제할까요?",
|
||||
"files.empty": "파일이 없습니다.",
|
||||
|
||||
"filter": "필터",
|
||||
|
||||
"form.discard": "Discard changes",
|
||||
"form.discard.confirm": "Do you really want to <strong>discard all your changes</strong>?",
|
||||
"form.locked": "This content is disabled for you as it is currently edited by another user",
|
||||
"form.unsaved": "The current changes have not yet been saved",
|
||||
"form.preview": "Preview changes",
|
||||
"form.preview.draft": "Preview draft",
|
||||
"form.discard": "저장되지 않은 항목이 있습니다.",
|
||||
"form.discard.confirm": "<strong>저장되지 않은 내용</strong>을 삭제할까요?",
|
||||
"form.locked": "다른 사용자가 편집 중입니다.",
|
||||
"form.unsaved": "변경 사항이 저장되지 않았습니다.",
|
||||
"form.preview": "변경 사항 미리 보기",
|
||||
"form.preview.draft": "초안 미리 보기",
|
||||
|
||||
"hide": "숨기기",
|
||||
"hour": "시",
|
||||
|
|
@ -449,12 +449,12 @@
|
|||
"language.variables.empty": "번역이 없습니다.",
|
||||
|
||||
"language.variable.delete.confirm": "변수({key})를 삭제할까요?",
|
||||
"language.variable.entries": "Values",
|
||||
"language.variable.entries.help": "Each string will be used for its matching count, e.g. three strings will match in order to counts <code>0</code>, <code>1</code>, <code>2 and more</code>. Use the <code>{count}</code> placeholder to insert the actual count.",
|
||||
"language.variable.entries": "값",
|
||||
"language.variable.entries.help": "각 문자열은 해당하는 개수에 맞게 사용됩니다. 예를 들어 세 개의 문자열은 <code>0</code>, <code>1</code>, <code>2 및 그 이상</code>의 개수에 순서대로 대응합니다. 실제 개수를 표시하려면 <code>{Count}</code>를 사용하세요.",
|
||||
"language.variable.key": "키",
|
||||
"language.variable.multiple": "Countable?",
|
||||
"language.variable.multiple.text": "Use different translation strings",
|
||||
"language.variable.multiple.help": "You can use different values depending on a count you pass along with the language variable, allowing you to create dynamic translations, e.g. singular and plural.",
|
||||
"language.variable.multiple": "셀 수 있나요?",
|
||||
"language.variable.multiple.text": "다른 번역 문자열을 사용하세요.",
|
||||
"language.variable.multiple.help": "언어 변수와 함께 전달하는 개수에 따라 다른 값을 사용할 수 있으므로 단수형이나 복수형 같은 동적 번역을 구현할 수 있습니다.",
|
||||
"language.variable.notFound": "변수를 찾을 수 없습니다.",
|
||||
"language.variable.value": "값",
|
||||
|
||||
|
|
@ -486,8 +486,8 @@
|
|||
"license.status.missing.bubble": "사이트를 공개합니다.",
|
||||
"license.status.missing.info": "유효한 라이선스가 없습니다.",
|
||||
"license.status.missing.label": "라이선스를 활성화하세요.",
|
||||
"license.status.unknown.info": "The license status is unknown",
|
||||
"license.status.unknown.label": "Unknown",
|
||||
"license.status.unknown.info": "라이선스 상태를 알 수 없습니다.",
|
||||
"license.status.unknown.label": "알 수 없음",
|
||||
"license.manage": "라이선스 관리",
|
||||
"license.purchased": "구입했습니다.",
|
||||
"license.success": "Kirby와 함께해주셔서 감사합니다.",
|
||||
|
|
@ -500,9 +500,9 @@
|
|||
|
||||
"lock.unsaved": "저장되지 않은 항목이 있습니다.",
|
||||
"lock.unsaved.empty": "모든 페이지를 저장했습니다.",
|
||||
"lock.unsaved.files": "Unsaved files",
|
||||
"lock.unsaved.pages": "Unsaved pages",
|
||||
"lock.unsaved.users": "Unsaved accounts",
|
||||
"lock.unsaved.files": "저장되지 않은 파일이 있습니다.",
|
||||
"lock.unsaved.pages": "저장되지 않은 페이지가 있습니다.",
|
||||
"lock.unsaved.users": "저장되지 않은 계정이 있습니다.",
|
||||
"lock.isLocked": "사용자({email})의 변경 사항이 저장되지 않았습니다.",
|
||||
"lock.unlock": "잠금 해제",
|
||||
"lock.unlock.submit": "사용자({email})의 저장되지 않은 변경 사항을 해제하고 덮어쓰기",
|
||||
|
|
@ -609,7 +609,7 @@
|
|||
"page.status.unlisted.description": "오직 URL을 통해 접근할 수 있습니다.",
|
||||
|
||||
"pages": "페이지",
|
||||
"pages.delete.confirm.selected": "Do you really want to delete the selected pages? This action cannot be undone.",
|
||||
"pages.delete.confirm.selected": "선택한 페이지를 삭제할까요?",
|
||||
"pages.empty": "페이지가 없습니다.",
|
||||
"pages.status.draft": "초안",
|
||||
"pages.status.listed": "발행",
|
||||
|
|
@ -627,7 +627,7 @@
|
|||
"prev": "이전",
|
||||
"preview": "미리 보기",
|
||||
|
||||
"publish": "Publish",
|
||||
"publish": "발행",
|
||||
"published": "발행",
|
||||
|
||||
"remove": "삭제",
|
||||
|
|
@ -649,9 +649,9 @@
|
|||
"role.nobody.title": "사용자가 없습니다.",
|
||||
|
||||
"save": "\uc800\uc7a5",
|
||||
"saved": "Saved",
|
||||
"saved": "저장했습니다.",
|
||||
"search": "검색",
|
||||
"searching": "Searching",
|
||||
"searching": "검색 중",
|
||||
"search.min": "{min}자 이상 입력하세요.",
|
||||
"search.all": "모든 결과({count}) 보기",
|
||||
"search.results.none": "해당하는 결과가 없습니다.",
|
||||
|
|
@ -684,9 +684,9 @@
|
|||
"system.issues.git": "<code>/.git</code> 폴더의 권한을 확인하세요.",
|
||||
"system.issues.https": "HTTPS를 권장합니다.",
|
||||
"system.issues.kirby": "<code>/kirby</code> 폴더의 권한을 확인하세요.",
|
||||
"system.issues.local": "The site is running locally with relaxed security checks",
|
||||
"system.issues.local": "이 사이트는 로컬에서 구동 중입니다.",
|
||||
"system.issues.site": "<code>/site</code> 폴더의 권한을 확인하세요.",
|
||||
"system.issues.vue.compiler": "The Vue template compiler is enabled",
|
||||
"system.issues.vue.compiler": "Vue 템플릿 컴파일러를 활성화했습니다.",
|
||||
"system.issues.vulnerability.kirby": "설치한 시스템에 취약점이 있습니다.\n심각도: {severity}\n{description}",
|
||||
"system.issues.vulnerability.plugin": "설치한 플러그인({plugin})에 취약점이 있습니다.\n심각도: {severity}\n{ description }",
|
||||
"system.updateStatus": "업데이트 상태",
|
||||
|
|
@ -703,10 +703,10 @@
|
|||
"tel.placeholder": "+49123456789",
|
||||
"template": "\ud15c\ud50c\ub9bf",
|
||||
|
||||
"theme": "Theme",
|
||||
"theme.light": "Lights on",
|
||||
"theme.dark": "Lights off",
|
||||
"theme.automatic": "Match system default",
|
||||
"theme": "테마",
|
||||
"theme.light": "밝게",
|
||||
"theme.dark": "어둡게",
|
||||
"theme.automatic": "시스템 기본값과 일치",
|
||||
|
||||
"title": "제목",
|
||||
"today": "오늘",
|
||||
|
|
@ -766,7 +766,7 @@
|
|||
"user.changeLanguage": "언어 변경",
|
||||
"user.changeName": "사용자명 변경",
|
||||
"user.changePassword": "암호 변경",
|
||||
"user.changePassword.current": "Your current password",
|
||||
"user.changePassword.current": "현재 암호",
|
||||
"user.changePassword.new": "새 암호",
|
||||
"user.changePassword.new.confirm": "새 암호 확인",
|
||||
"user.changeRole": "역할 변경",
|
||||
|
|
@ -778,13 +778,13 @@
|
|||
"users": "사용자",
|
||||
|
||||
"version": "버전",
|
||||
"version.changes": "Changed version",
|
||||
"version.compare": "Compare versions",
|
||||
"version.changes": "버전 변경",
|
||||
"version.compare": "버전을 교체했습니다.",
|
||||
"version.current": "현재 버전",
|
||||
"version.latest": "최신 버전",
|
||||
"versionInformation": "버전 정보",
|
||||
|
||||
"view": "View",
|
||||
"view": "뷰",
|
||||
"view.account": "계정",
|
||||
"view.installation": "\uc124\uce58",
|
||||
"view.languages": "언어",
|
||||
|
|
|
|||
|
|
@ -449,12 +449,12 @@
|
|||
"language.variables.empty": "Nog geen vertalingen",
|
||||
|
||||
"language.variable.delete.confirm": "Weet je zeker dat je de variabele voor {key} wil verwijderen?",
|
||||
"language.variable.entries": "Values",
|
||||
"language.variable.entries.help": "Each string will be used for its matching count, e.g. three strings will match in order to counts <code>0</code>, <code>1</code>, <code>2 and more</code>. Use the <code>{count}</code> placeholder to insert the actual count.",
|
||||
"language.variable.entries": "Waardes",
|
||||
"language.variable.entries.help": "Elke regel wordt gebruikt voor het bijbehorende aantal, bijvoorbeeld drie regels komen overeen met de aantallen <code>0</code>, <code>1</code>, <code>2 en meer</code>. Gebruik de placeholder <code>{count}</code> om het werkelijke aantal in te voegen.",
|
||||
"language.variable.key": "Key",
|
||||
"language.variable.multiple": "Countable?",
|
||||
"language.variable.multiple.text": "Use different translation strings",
|
||||
"language.variable.multiple.help": "You can use different values depending on a count you pass along with the language variable, allowing you to create dynamic translations, e.g. singular and plural.",
|
||||
"language.variable.multiple": "Telbaar?",
|
||||
"language.variable.multiple.text": "Gebruik verschillende vertalingen",
|
||||
"language.variable.multiple.help": "Je kan verschillende waarden gebruiken, afhankelijk van een getal dat je samen met de taalvariabele doorgeeft, waardoor je dynamische vertalingen kan maken, bijvoorbeeld enkelvoud en meervoud.",
|
||||
"language.variable.notFound": "De variabele kan niet gevonden worden",
|
||||
"language.variable.value": "Waarde",
|
||||
|
||||
|
|
|
|||
|
|
@ -443,15 +443,15 @@ class App
|
|||
array $arguments = [],
|
||||
string $contentType = 'html'
|
||||
): array {
|
||||
$name = basename(strtolower($name));
|
||||
$name = strtolower($name);
|
||||
$data = [];
|
||||
|
||||
// always use the site controller as defaults, if available
|
||||
// (unless the controller is a snippet controller)
|
||||
if (strpos($name, '/') === false) {
|
||||
$site = $this->controllerLookup('site', $contentType);
|
||||
$site ??= $this->controllerLookup('site');
|
||||
|
||||
if ($site !== null) {
|
||||
$data = (array)$site->call($this, $arguments);
|
||||
$data = (array)$site?->call($this, $arguments) ?? [];
|
||||
}
|
||||
|
||||
// try to find a specific representation controller
|
||||
|
|
@ -460,16 +460,12 @@ class App
|
|||
// let's try the html controller instead
|
||||
$controller ??= $this->controllerLookup($name);
|
||||
|
||||
if ($controller !== null) {
|
||||
return [
|
||||
...$data,
|
||||
...(array)$controller->call($this, $arguments)
|
||||
...(array)$controller?->call($this, $arguments) ?? []
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to find a controller by name
|
||||
*/
|
||||
|
|
@ -482,7 +478,11 @@ class App
|
|||
}
|
||||
|
||||
// controller from site root
|
||||
$controller = Controller::load($this->root('controllers') . '/' . $name . '.php', $this->root('controllers'));
|
||||
$controller = Controller::load(
|
||||
file: $this->root('controllers') . '/' . $name . '.php',
|
||||
in: $this->root('controllers')
|
||||
);
|
||||
|
||||
// controller from extension
|
||||
$controller ??= $this->extension('controllers', $name);
|
||||
|
||||
|
|
@ -580,7 +580,16 @@ class App
|
|||
$visitor = $this->visitor();
|
||||
|
||||
foreach ($visitor->acceptedLanguages() as $acceptedLang) {
|
||||
$closure = static function ($language) use ($acceptedLang) {
|
||||
// Find locale matches (e.g. en_GB => en_GB)
|
||||
$matchLocale = function ($language) use ($acceptedLang) {
|
||||
$languageLocale = $language->locale(LC_ALL);
|
||||
$acceptedLocale = $acceptedLang->locale();
|
||||
|
||||
return Str::substr($languageLocale, 0, 5) === Str::substr($acceptedLocale, 0, 5);
|
||||
};
|
||||
|
||||
// Find language matches (e.g. en_GB => en)
|
||||
$matchLanguage = function ($language) use ($acceptedLang) {
|
||||
$languageLocale = $language->locale(LC_ALL);
|
||||
$acceptedLocale = $acceptedLang->locale();
|
||||
|
||||
|
|
@ -589,7 +598,11 @@ class App
|
|||
$acceptedLocale === Str::substr($languageLocale, 0, 2);
|
||||
};
|
||||
|
||||
if ($language = $languages->filter($closure)?->first()) {
|
||||
if ($language = $languages->filter($matchLocale)?->first()) {
|
||||
return $language;
|
||||
}
|
||||
|
||||
if ($language = $languages->filter($matchLanguage)?->first()) {
|
||||
return $language;
|
||||
}
|
||||
}
|
||||
|
|
@ -768,15 +781,7 @@ class App
|
|||
|
||||
// Responses
|
||||
if ($input instanceof Response) {
|
||||
$data = $input->toArray();
|
||||
|
||||
// inject headers from the global response configuration
|
||||
// lazily (only if they are not already set);
|
||||
// the case-insensitive nature of headers will be
|
||||
// handled by PHP's `header()` function
|
||||
$data['headers'] = [...$response->headers(), ...$data['headers']];
|
||||
|
||||
return new Response($data);
|
||||
return $response->send($input);
|
||||
}
|
||||
|
||||
// Pages
|
||||
|
|
|
|||
|
|
@ -88,6 +88,12 @@ trait AppTranslations
|
|||
*/
|
||||
public function panelLanguage(): string
|
||||
{
|
||||
$translation = $this->request()->get('translation');
|
||||
|
||||
if ($translation !== null && $this->translations()->find($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
if ($this->multilang() === true) {
|
||||
$defaultCode = $this->defaultLanguage()->code();
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use Kirby\Cms\Auth\TotpChallenge;
|
|||
use Kirby\Form\Field\BlocksField;
|
||||
use Kirby\Form\Field\EntriesField;
|
||||
use Kirby\Form\Field\LayoutField;
|
||||
use Kirby\Form\Field\StatsField;
|
||||
use Kirby\Panel\Ui\FilePreviews\AudioFilePreview;
|
||||
use Kirby\Panel\Ui\FilePreviews\ImageFilePreview;
|
||||
use Kirby\Panel\Ui\FilePreviews\PdfFilePreview;
|
||||
|
|
@ -270,6 +271,7 @@ class Core
|
|||
'range' => $this->root . '/fields/range.php',
|
||||
'select' => $this->root . '/fields/select.php',
|
||||
'slug' => $this->root . '/fields/slug.php',
|
||||
'stats' => StatsField::class,
|
||||
'structure' => $this->root . '/fields/structure.php',
|
||||
'tags' => $this->root . '/fields/tags.php',
|
||||
'tel' => $this->root . '/fields/tel.php',
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ use Kirby\Toolkit\Str;
|
|||
* @license https://getkirby.com/license
|
||||
*
|
||||
* @use \Kirby\Cms\HasSiblings<\Kirby\Cms\Files>
|
||||
* @method \Kirby\Uuid\FileUuid uuid()
|
||||
*/
|
||||
class File extends ModelWithContent
|
||||
{
|
||||
|
|
@ -513,7 +514,7 @@ class File extends ModelWithContent
|
|||
*/
|
||||
public function permalink(): string|null
|
||||
{
|
||||
return $this->uuid()?->url();
|
||||
return $this->uuid()?->toPermalink();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -116,6 +116,15 @@ class LanguageVariable
|
|||
return $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent language
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function language(): Language
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new value for the language variable
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ class License
|
|||
|
||||
protected const SALT = 'kwAHMLyLPBnHEskzH9pPbJsBxQhKXZnX';
|
||||
|
||||
protected App $kirby;
|
||||
|
||||
// cache
|
||||
protected LicenseStatus $status;
|
||||
protected LicenseType $type;
|
||||
|
|
@ -50,6 +52,8 @@ class License
|
|||
if ($email !== null) {
|
||||
$this->email = $this->normalizeEmail($email);
|
||||
}
|
||||
|
||||
$this->kirby = App::instance();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -100,6 +104,15 @@ class License
|
|||
return $this->date !== null ? Str::date(strtotime($this->date), $format, $handler) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the license file if it exists
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function delete(): bool
|
||||
{
|
||||
return F::remove($this->root());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the activation domain if available
|
||||
*/
|
||||
|
|
@ -179,7 +192,7 @@ class License
|
|||
}
|
||||
|
||||
// get release date of current major version
|
||||
$major = Str::before(App::instance()->version(), '.');
|
||||
$major = Str::before($this->kirby->version(), '.');
|
||||
$release = strtotime(static::HISTORY[$major] ?? '');
|
||||
|
||||
// if there's no matching version in the history
|
||||
|
|
@ -219,7 +232,7 @@ class License
|
|||
}
|
||||
|
||||
// compare domains
|
||||
if ($this->normalizeDomain(App::instance()->system()->indexUrl()) !== $this->normalizeDomain($this->domain)) {
|
||||
if ($this->normalizeDomain($this->kirby->system()->indexUrl()) !== $this->normalizeDomain($this->domain)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -236,7 +249,7 @@ class License
|
|||
}
|
||||
|
||||
// get the public key
|
||||
$pubKey = F::read(App::instance()->root('kirby') . '/kirby.pub');
|
||||
$pubKey = F::read($this->kirby->root('kirby') . '/kirby.pub');
|
||||
|
||||
// verify the license signature
|
||||
$data = json_encode($this->signatureData());
|
||||
|
|
@ -328,7 +341,7 @@ class License
|
|||
public static function read(): static
|
||||
{
|
||||
try {
|
||||
$license = Json::read(App::instance()->root('license'));
|
||||
$license = Json::read(static::root());
|
||||
} catch (Throwable) {
|
||||
return new static();
|
||||
}
|
||||
|
|
@ -409,6 +422,15 @@ class License
|
|||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root path to the license file
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public static function root(): string
|
||||
{
|
||||
return App::instance()->root('license');
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the license in the config folder
|
||||
*/
|
||||
|
|
@ -420,11 +442,11 @@ class License
|
|||
);
|
||||
}
|
||||
|
||||
// where to store the license file
|
||||
$file = App::instance()->root('license');
|
||||
|
||||
// save the license information
|
||||
return Json::write($file, $this->content());
|
||||
return Json::write(
|
||||
file: $this->root(),
|
||||
data: $this->content()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ use Throwable;
|
|||
* @license https://getkirby.com/license
|
||||
*
|
||||
* @use \Kirby\Cms\HasSiblings<\Kirby\Cms\Pages>
|
||||
* @method \Kirby\Uuid\PageUuid uuid()
|
||||
*/
|
||||
class Page extends ModelWithContent
|
||||
{
|
||||
|
|
@ -871,7 +872,7 @@ class Page extends ModelWithContent
|
|||
*/
|
||||
public function permalink(): string|null
|
||||
{
|
||||
return $this->uuid()?->url();
|
||||
return $this->uuid()?->toPermalink();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -865,6 +865,9 @@ trait PageActions
|
|||
'template' => $this->intendedTemplate()->name(),
|
||||
]);
|
||||
|
||||
// remove the media directory
|
||||
Dir::remove($this->mediaRoot());
|
||||
|
||||
// actually do it on disk
|
||||
if ($this->exists() === true) {
|
||||
if (Dir::move($this->root(), $page->root()) !== true) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace Kirby\Cms;
|
|||
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Filesystem\Mime;
|
||||
use Kirby\Http\Response as HttpResponse;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Stringable;
|
||||
|
||||
|
|
@ -337,8 +338,15 @@ class Responder implements Stringable
|
|||
/**
|
||||
* Creates and returns the response object from the config
|
||||
*/
|
||||
public function send(string|null $body = null): Response
|
||||
public function send(HttpResponse|string|null $body = null): HttpResponse
|
||||
{
|
||||
if ($body instanceof HttpResponse) {
|
||||
// inject headers from the responder into the response
|
||||
// (only if they are not already set);
|
||||
$body->setHeaderFallbacks($this->headers());
|
||||
return $body;
|
||||
}
|
||||
|
||||
if ($body !== null) {
|
||||
$this->body($body);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ use Kirby\Toolkit\A;
|
|||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*
|
||||
* @method \Kirby\Uuid\SiteUuid uuid()
|
||||
*/
|
||||
class Site extends ModelWithContent
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ use SensitiveParameter;
|
|||
* @license https://getkirby.com/license
|
||||
*
|
||||
* @use \Kirby\Cms\HasSiblings<\Kirby\Cms\Users>
|
||||
* @method \Kirby\Uuid\UserUuid uuid()
|
||||
*/
|
||||
class User extends ModelWithContent
|
||||
{
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ class Mime
|
|||
'tgz' => ['application/x-tar', 'application/x-gzip-compressed'],
|
||||
'tif' => 'image/tiff',
|
||||
'tiff' => 'image/tiff',
|
||||
'wav' => 'audio/x-wav',
|
||||
'wav' => ['audio/wav', 'audio/x-wav', 'audio/vnd.wave', 'audio/wave'],
|
||||
'wbxml' => 'application/wbxml',
|
||||
'webm' => ['video/webm', 'audio/webm'],
|
||||
'webp' => 'image/webp',
|
||||
|
|
|
|||
74
public/kirby/src/Form/Field/StatsField.php
Normal file
74
public/kirby/src/Form/Field/StatsField.php
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Form\Field;
|
||||
|
||||
use Kirby\Form\FieldClass;
|
||||
use Kirby\Panel\Ui\Stats;
|
||||
|
||||
/**
|
||||
* Stats field
|
||||
*
|
||||
* @package Kirby Field
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
* @since 5.1.0
|
||||
*/
|
||||
class StatsField extends FieldClass
|
||||
{
|
||||
/**
|
||||
* Array or query string for reports. Each report needs a `label` and `value` and can have additional `info`, `link`, `icon` and `theme` settings.
|
||||
*/
|
||||
protected array|string $reports;
|
||||
|
||||
/**
|
||||
* The size of the report cards. Available sizes: `tiny`, `small`, `medium`, `large`
|
||||
*/
|
||||
protected string $size;
|
||||
|
||||
/**
|
||||
* Cache for the Stats UI component
|
||||
*/
|
||||
protected Stats $stats;
|
||||
|
||||
public function __construct(array $params)
|
||||
{
|
||||
parent::__construct($params);
|
||||
|
||||
$this->reports = $params['reports'] ?? [];
|
||||
$this->size = $params['size'] ?? 'large';
|
||||
}
|
||||
|
||||
public function hasValue(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function reports(): array
|
||||
{
|
||||
return $this->stats()->reports();
|
||||
}
|
||||
|
||||
public function size(): string
|
||||
{
|
||||
return $this->stats()->size();
|
||||
}
|
||||
|
||||
public function stats(): Stats
|
||||
{
|
||||
return $this->stats ??= Stats::from(
|
||||
model: $this->model,
|
||||
reports: $this->reports,
|
||||
size: $this->size
|
||||
);
|
||||
}
|
||||
|
||||
public function props(): array
|
||||
{
|
||||
return [
|
||||
...parent::props(),
|
||||
...$this->stats()->props()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ namespace Kirby\Form;
|
|||
|
||||
use Kirby\Cms\HasSiblings;
|
||||
use Kirby\Toolkit\I18n;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
* Abstract field class to be used instead
|
||||
|
|
@ -22,24 +21,24 @@ use Kirby\Toolkit\Str;
|
|||
abstract class FieldClass
|
||||
{
|
||||
use HasSiblings;
|
||||
use Mixin\After;
|
||||
use Mixin\Api;
|
||||
use Mixin\Autofocus;
|
||||
use Mixin\Before;
|
||||
use Mixin\Help;
|
||||
use Mixin\Icon;
|
||||
use Mixin\Label;
|
||||
use Mixin\Model;
|
||||
use Mixin\Placeholder;
|
||||
use Mixin\Translatable;
|
||||
use Mixin\Validation;
|
||||
use Mixin\Value;
|
||||
use Mixin\When;
|
||||
use Mixin\Width;
|
||||
|
||||
protected string|null $after;
|
||||
protected bool $autofocus;
|
||||
protected string|null $before;
|
||||
protected bool $disabled;
|
||||
protected string|null $help;
|
||||
protected string|null $icon;
|
||||
protected string|null $label;
|
||||
protected string|null $name;
|
||||
protected string|null $placeholder;
|
||||
protected Fields $siblings;
|
||||
protected string|null $width;
|
||||
|
||||
public function __construct(
|
||||
protected array $params = []
|
||||
|
|
@ -75,21 +74,6 @@ abstract class FieldClass
|
|||
return $this->params[$param] ?? null;
|
||||
}
|
||||
|
||||
public function after(): string|null
|
||||
{
|
||||
return $this->stringTemplate($this->after);
|
||||
}
|
||||
|
||||
public function autofocus(): bool
|
||||
{
|
||||
return $this->autofocus;
|
||||
}
|
||||
|
||||
public function before(): string|null
|
||||
{
|
||||
return $this->stringTemplate($this->before);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns optional dialog routes for the field
|
||||
*/
|
||||
|
|
@ -114,33 +98,11 @@ abstract class FieldClass
|
|||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional help text below the field
|
||||
*/
|
||||
public function help(): string|null
|
||||
{
|
||||
if (empty($this->help) === false) {
|
||||
$help = $this->stringTemplate($this->help);
|
||||
$help = $this->kirby()->kirbytext($help);
|
||||
return $help;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function i18n(string|array|null $param = null): string|null
|
||||
{
|
||||
return empty($param) === false ? I18n::translate($param, $param) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional icon that will be shown at the end of the field
|
||||
*/
|
||||
public function icon(): string|null
|
||||
{
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
public function id(): string
|
||||
{
|
||||
return $this->name();
|
||||
|
|
@ -156,16 +118,6 @@ abstract class FieldClass
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The field label can be set as string or associative array with translations
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return $this->stringTemplate(
|
||||
$this->label ?? Str::ucfirst($this->name())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field name
|
||||
*/
|
||||
|
|
@ -182,14 +134,6 @@ abstract class FieldClass
|
|||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional placeholder value that will be shown when the field is empty
|
||||
*/
|
||||
public function placeholder(): string|null
|
||||
{
|
||||
return $this->stringTemplate($this->placeholder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the props that will be sent to
|
||||
* the Vue component
|
||||
|
|
@ -217,67 +161,21 @@ abstract class FieldClass
|
|||
];
|
||||
}
|
||||
|
||||
protected function setAfter(array|string|null $after = null): void
|
||||
{
|
||||
$this->after = $this->i18n($after);
|
||||
}
|
||||
|
||||
protected function setAutofocus(bool $autofocus = false): void
|
||||
{
|
||||
$this->autofocus = $autofocus;
|
||||
}
|
||||
|
||||
protected function setBefore(array|string|null $before = null): void
|
||||
{
|
||||
$this->before = $this->i18n($before);
|
||||
}
|
||||
|
||||
protected function setDisabled(bool $disabled = false): void
|
||||
{
|
||||
$this->disabled = $disabled;
|
||||
}
|
||||
|
||||
protected function setHelp(array|string|null $help = null): void
|
||||
{
|
||||
$this->help = $this->i18n($help);
|
||||
}
|
||||
|
||||
protected function setIcon(string|null $icon = null): void
|
||||
{
|
||||
$this->icon = $icon;
|
||||
}
|
||||
|
||||
protected function setLabel(array|string|null $label = null): void
|
||||
{
|
||||
$this->label = $this->i18n($label);
|
||||
}
|
||||
|
||||
protected function setName(string|null $name = null): void
|
||||
{
|
||||
$this->name = strtolower($name ?? $this->type());
|
||||
}
|
||||
|
||||
protected function setPlaceholder(array|string|null $placeholder = null): void
|
||||
{
|
||||
$this->placeholder = $this->i18n($placeholder);
|
||||
}
|
||||
|
||||
protected function setSiblings(Fields|null $siblings = null): void
|
||||
{
|
||||
$this->siblings = $siblings ?? new Fields([$this]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the field width
|
||||
*/
|
||||
protected function setWidth(string|null $width = null): void
|
||||
{
|
||||
$this->width = $width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all sibling fields for the HasSiblings trait
|
||||
*/
|
||||
protected function siblingsCollection(): Fields
|
||||
{
|
||||
return $this->siblings;
|
||||
|
|
@ -314,13 +212,4 @@ abstract class FieldClass
|
|||
{
|
||||
return lcfirst(basename(str_replace(['\\', 'Field'], ['/', ''], static::class)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the width of the field in
|
||||
* the Panel grid
|
||||
*/
|
||||
public function width(): string
|
||||
{
|
||||
return $this->width ?? '1/1';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
21
public/kirby/src/Form/Mixin/After.php
Normal file
21
public/kirby/src/Form/Mixin/After.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Form\Mixin;
|
||||
|
||||
trait After
|
||||
{
|
||||
/**
|
||||
* Optional text that will be shown after the input
|
||||
*/
|
||||
protected string|null $after;
|
||||
|
||||
public function after(): string|null
|
||||
{
|
||||
return $this->stringTemplate($this->after);
|
||||
}
|
||||
|
||||
protected function setAfter(array|string|null $after = null): void
|
||||
{
|
||||
$this->after = $this->i18n($after);
|
||||
}
|
||||
}
|
||||
21
public/kirby/src/Form/Mixin/Autofocus.php
Normal file
21
public/kirby/src/Form/Mixin/Autofocus.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Form\Mixin;
|
||||
|
||||
trait Autofocus
|
||||
{
|
||||
/**
|
||||
* Sets the focus on this field when the form loads. Only the first field with this label gets
|
||||
*/
|
||||
protected bool $autofocus;
|
||||
|
||||
public function autofocus(): bool
|
||||
{
|
||||
return $this->autofocus;
|
||||
}
|
||||
|
||||
protected function setAutofocus(bool $autofocus = false): void
|
||||
{
|
||||
$this->autofocus = $autofocus;
|
||||
}
|
||||
}
|
||||
21
public/kirby/src/Form/Mixin/Before.php
Normal file
21
public/kirby/src/Form/Mixin/Before.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Form\Mixin;
|
||||
|
||||
trait Before
|
||||
{
|
||||
/**
|
||||
* Optional text that will be shown before the input
|
||||
*/
|
||||
protected string|null $before;
|
||||
|
||||
public function before(): string|null
|
||||
{
|
||||
return $this->stringTemplate($this->before);
|
||||
}
|
||||
|
||||
protected function setBefore(array|string|null $before = null): void
|
||||
{
|
||||
$this->before = $this->i18n($before);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,9 @@ namespace Kirby\Form\Mixin;
|
|||
|
||||
trait EmptyState
|
||||
{
|
||||
/**
|
||||
* Sets the text for the empty state box
|
||||
*/
|
||||
protected string|null $empty;
|
||||
|
||||
protected function setEmpty(string|array|null $empty = null): void
|
||||
|
|
|
|||
34
public/kirby/src/Form/Mixin/Help.php
Normal file
34
public/kirby/src/Form/Mixin/Help.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Form\Mixin;
|
||||
|
||||
/**
|
||||
* @package Kirby Form
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*/
|
||||
trait Help
|
||||
{
|
||||
/**
|
||||
* Optional help text below the field
|
||||
*/
|
||||
protected string|null $help;
|
||||
|
||||
public function help(): string|null
|
||||
{
|
||||
if (empty($this->help) === false) {
|
||||
$help = $this->stringTemplate($this->help);
|
||||
$help = $this->kirby()->kirbytext($help);
|
||||
return $help;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function setHelp(array|string|null $help = null): void
|
||||
{
|
||||
$this->help = $this->i18n($help);
|
||||
}
|
||||
}
|
||||
28
public/kirby/src/Form/Mixin/Icon.php
Normal file
28
public/kirby/src/Form/Mixin/Icon.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Form\Mixin;
|
||||
|
||||
/**
|
||||
* @package Kirby Form
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*/
|
||||
trait Icon
|
||||
{
|
||||
/**
|
||||
* Optional icon that will be shown at the end of the field
|
||||
*/
|
||||
protected string|null $icon;
|
||||
|
||||
public function icon(): string|null
|
||||
{
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
protected function setIcon(string|null $icon = null): void
|
||||
{
|
||||
$this->icon = $icon;
|
||||
}
|
||||
}
|
||||
32
public/kirby/src/Form/Mixin/Label.php
Normal file
32
public/kirby/src/Form/Mixin/Label.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Form\Mixin;
|
||||
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
* @package Kirby Form
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*/
|
||||
trait Label
|
||||
{
|
||||
/**
|
||||
* The field label can be set as string or associative array with translations
|
||||
*/
|
||||
protected string|null $label;
|
||||
|
||||
public function label(): string|null
|
||||
{
|
||||
return $this->stringTemplate(
|
||||
$this->label ?? Str::ucfirst($this->name())
|
||||
);
|
||||
}
|
||||
|
||||
protected function setLabel(array|string|null $label = null): void
|
||||
{
|
||||
$this->label = $this->i18n($label);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,9 @@ namespace Kirby\Form\Mixin;
|
|||
|
||||
trait Max
|
||||
{
|
||||
/**
|
||||
* Sets the maximum number of allowed items in the field
|
||||
*/
|
||||
protected int|null $max;
|
||||
|
||||
public function max(): int|null
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ namespace Kirby\Form\Mixin;
|
|||
|
||||
trait Min
|
||||
{
|
||||
/**
|
||||
* Sets the minimum number of required items in the field
|
||||
*/
|
||||
protected int|null $min;
|
||||
|
||||
public function min(): int|null
|
||||
|
|
|
|||
30
public/kirby/src/Form/Mixin/Placeholder.php
Normal file
30
public/kirby/src/Form/Mixin/Placeholder.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Form\Mixin;
|
||||
|
||||
/**
|
||||
* @package Kirby Form
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*/
|
||||
trait Placeholder
|
||||
{
|
||||
/**
|
||||
* Optional placeholder value that will be shown when the field is empty
|
||||
*/
|
||||
protected array|string|null $placeholder;
|
||||
|
||||
public function placeholder(): string|null
|
||||
{
|
||||
return $this->stringTemplate(
|
||||
$this->placeholder
|
||||
);
|
||||
}
|
||||
|
||||
protected function setPlaceholder(array|string|null $placeholder = null): void
|
||||
{
|
||||
$this->placeholder = $this->i18n($placeholder);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,9 @@ use Kirby\Cms\Language;
|
|||
*/
|
||||
trait Translatable
|
||||
{
|
||||
/**
|
||||
* Should the field be translatable?
|
||||
*/
|
||||
protected bool $translate = true;
|
||||
|
||||
/**
|
||||
|
|
@ -29,17 +32,11 @@ trait Translatable
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the translatable status
|
||||
*/
|
||||
protected function setTranslate(bool $translate = true): void
|
||||
{
|
||||
$this->translate = $translate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the field be translatable?
|
||||
*/
|
||||
public function translate(): bool
|
||||
{
|
||||
return $this->translate;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ use Kirby\Toolkit\V;
|
|||
*/
|
||||
trait Validation
|
||||
{
|
||||
/**
|
||||
* If `true`, the field has to be filled in correctly to be saved.
|
||||
*/
|
||||
protected bool $required;
|
||||
|
||||
/**
|
||||
|
|
@ -94,9 +97,6 @@ trait Validation
|
|||
return $this->errors() === [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the required property
|
||||
*/
|
||||
public function required(): bool
|
||||
{
|
||||
return $this->required;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,14 @@ use Kirby\Cms\Language;
|
|||
*/
|
||||
trait Value
|
||||
{
|
||||
/**
|
||||
* Default value for the field, which will be used when a page/file/user is created
|
||||
*/
|
||||
protected mixed $default = null;
|
||||
|
||||
/**
|
||||
* The value of the field
|
||||
*/
|
||||
protected mixed $value = null;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@ namespace Kirby\Form\Mixin;
|
|||
*/
|
||||
trait When
|
||||
{
|
||||
/**
|
||||
* Conditions when the field will be shown
|
||||
*
|
||||
* @since 3.1.0
|
||||
*/
|
||||
protected array|null $when = null;
|
||||
|
||||
/**
|
||||
|
|
@ -40,17 +45,11 @@ trait When
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the `when` condition
|
||||
*/
|
||||
protected function setWhen(array|null $when = null): void
|
||||
{
|
||||
$this->when = $when;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `when` condition of the field
|
||||
*/
|
||||
public function when(): array|null
|
||||
{
|
||||
return $this->when;
|
||||
|
|
|
|||
29
public/kirby/src/Form/Mixin/Width.php
Normal file
29
public/kirby/src/Form/Mixin/Width.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Form\Mixin;
|
||||
|
||||
/**
|
||||
* @package Kirby Form
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*/
|
||||
trait Width
|
||||
{
|
||||
/**
|
||||
* The width of the field in the field grid.
|
||||
* Available widths: `1/1`, `1/2`, `1/3`, `1/4`, `2/3`, `3/4`
|
||||
*/
|
||||
protected string|null $width;
|
||||
|
||||
protected function setWidth(string|null $width = null): void
|
||||
{
|
||||
$this->width = $width;
|
||||
}
|
||||
|
||||
public function width(): string
|
||||
{
|
||||
return $this->width ?? '1/1';
|
||||
}
|
||||
}
|
||||
|
|
@ -222,7 +222,6 @@ class Cookie
|
|||
protected static function trackUsage(string $key): void
|
||||
{
|
||||
// lazily request the instance for non-CMS use cases
|
||||
$kirby = App::instance(null, true);
|
||||
$kirby?->response()->usesCookie($key);
|
||||
App::instance(lazy: true)?->response()->usesCookie($key);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,13 +13,13 @@ use Kirby\Toolkit\Str;
|
|||
* secure host and base URL detection, as
|
||||
* well as loading the dedicated
|
||||
* environment options.
|
||||
* @since 3.7.0
|
||||
*
|
||||
* @package Kirby Http
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
* @since 3.7.0
|
||||
*/
|
||||
class Environment
|
||||
{
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class Header
|
|||
|
||||
$header = 'Content-type: ' . $mime;
|
||||
|
||||
if (empty($charset) === false) {
|
||||
if ($charset !== '') {
|
||||
$header .= '; charset=' . $charset;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class Params extends Obj implements Stringable
|
|||
*/
|
||||
public static function extract(string|array|null $path = null): array
|
||||
{
|
||||
if (empty($path) === true) {
|
||||
if ($path === null || $path === '' || $path === []) {
|
||||
return [
|
||||
'path' => null,
|
||||
'params' => null,
|
||||
|
|
@ -62,12 +62,16 @@ class Params extends Obj implements Stringable
|
|||
continue;
|
||||
}
|
||||
|
||||
$paramParts = Str::split($p, $separator);
|
||||
$paramKey = $paramParts[0] ?? null;
|
||||
$paramValue = $paramParts[1] ?? null;
|
||||
$parts = Str::split($p, $separator);
|
||||
|
||||
if ($paramKey !== null) {
|
||||
$params[rawurldecode($paramKey)] = $paramValue !== null ? rawurldecode($paramValue) : null;
|
||||
if ($key = $parts[0] ?? null) {
|
||||
$key = rawurldecode($key);
|
||||
|
||||
if ($value = $parts[1] ?? null) {
|
||||
$value = rawurldecode($value);
|
||||
}
|
||||
|
||||
$params[$key] = $value;
|
||||
}
|
||||
|
||||
unset($path[$index]);
|
||||
|
|
@ -89,7 +93,7 @@ class Params extends Obj implements Stringable
|
|||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return empty((array)$this) === true;
|
||||
return (array)$this === [];
|
||||
}
|
||||
|
||||
public function isNotEmpty(): bool
|
||||
|
|
@ -97,6 +101,23 @@ class Params extends Obj implements Stringable
|
|||
return $this->isEmpty() === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the current params with the given params
|
||||
* @since 5.1.0
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function merge(array|string|null $params): static
|
||||
{
|
||||
$params = new static($params);
|
||||
|
||||
foreach ($params as $key => $value) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the param separator according
|
||||
* to the operating system.
|
||||
|
|
@ -106,15 +127,7 @@ class Params extends Obj implements Stringable
|
|||
*/
|
||||
public static function separator(): string
|
||||
{
|
||||
if (static::$separator !== null) {
|
||||
return static::$separator;
|
||||
}
|
||||
|
||||
if (DIRECTORY_SEPARATOR === '/') {
|
||||
return static::$separator = ':';
|
||||
}
|
||||
|
||||
return static::$separator = ';';
|
||||
return static::$separator ??= DIRECTORY_SEPARATOR === '/' ? ':' : ';';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -134,7 +147,9 @@ class Params extends Obj implements Stringable
|
|||
|
||||
foreach ($this as $key => $value) {
|
||||
if ($value !== null && $value !== '') {
|
||||
$params[] = rawurlencode($key) . $separator . rawurlencode($value);
|
||||
$key = rawurlencode($key);
|
||||
$value = rawurlencode($value);
|
||||
$params[] = $key . $separator . $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class Query extends Obj implements Stringable
|
|||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return empty((array)$this) === true;
|
||||
return (array)$this === [];
|
||||
}
|
||||
|
||||
public function isNotEmpty(): bool
|
||||
|
|
@ -37,11 +37,28 @@ class Query extends Obj implements Stringable
|
|||
return $this->isEmpty() === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the current query with the given query
|
||||
* @since 5.1.0
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function merge(string|array|null $query): static
|
||||
{
|
||||
$query = new static($query);
|
||||
|
||||
foreach ($query as $key => $value) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toString(bool $questionMark = false): string
|
||||
{
|
||||
$query = http_build_query($this, '', '&', PHP_QUERY_RFC3986);
|
||||
|
||||
if (empty($query) === true) {
|
||||
if ($query === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,13 +47,14 @@ class Remote
|
|||
public string $errorMessage;
|
||||
public array $headers = [];
|
||||
public array $info = [];
|
||||
public array $options = [];
|
||||
|
||||
/**
|
||||
* @throws \Exception when the curl request failed
|
||||
*/
|
||||
public function __construct(string $url, array $options = [])
|
||||
{
|
||||
public function __construct(
|
||||
string $url,
|
||||
public array $options = []
|
||||
) {
|
||||
$defaults = static::$defaults;
|
||||
|
||||
// use the system CA store by default if
|
||||
|
|
@ -71,11 +72,8 @@ class Remote
|
|||
$defaults = [...$defaults, ...$app->option('remote', [])];
|
||||
}
|
||||
|
||||
// set all options
|
||||
$this->options = [...$defaults, ...$options];
|
||||
|
||||
// add the url
|
||||
$this->options['url'] = $url;
|
||||
// set all options, incl. url
|
||||
$this->options = [...$defaults, ...$options, 'url' => $url];
|
||||
|
||||
// send the request
|
||||
$this->fetch();
|
||||
|
|
@ -277,7 +275,7 @@ class Remote
|
|||
|
||||
$query = http_build_query($options['data']);
|
||||
|
||||
if (empty($query) === false) {
|
||||
if ($query !== '') {
|
||||
$url = match (Url::hasQuery($url)) {
|
||||
true => $url . '&' . $query,
|
||||
default => $url . '?' . $query
|
||||
|
|
@ -339,7 +337,7 @@ class Remote
|
|||
*/
|
||||
protected function postfields($data)
|
||||
{
|
||||
if (is_object($data) || is_array($data)) {
|
||||
if (is_object($data) === true || is_array($data) === true) {
|
||||
return http_build_query($data);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,12 +67,6 @@ class Request
|
|||
*/
|
||||
protected string $method;
|
||||
|
||||
/**
|
||||
* All options that have been passed to
|
||||
* the request in the constructor
|
||||
*/
|
||||
protected array $options;
|
||||
|
||||
/**
|
||||
* The Query object is a wrapper around
|
||||
* the URL query string, which parses the
|
||||
|
|
@ -96,9 +90,9 @@ class Request
|
|||
* data via the $options array or use
|
||||
* the data from the incoming request.
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->options = $options;
|
||||
public function __construct(
|
||||
protected array $options = []
|
||||
) {
|
||||
$this->method = $this->detectRequestMethod($options['method'] ?? null);
|
||||
|
||||
if (isset($options['body']) === true) {
|
||||
|
|
@ -155,7 +149,7 @@ class Request
|
|||
}
|
||||
|
||||
// lazily request the instance for non-CMS use cases
|
||||
$kirby = App::instance(null, true);
|
||||
$kirby = App::instance(lazy: true);
|
||||
|
||||
// tell the CMS responder that the response relies on
|
||||
// the `Authorization` header and its value (even if
|
||||
|
|
@ -224,13 +218,26 @@ class Request
|
|||
public function detectRequestMethod(string|null $method = null): string
|
||||
{
|
||||
// all possible methods
|
||||
$methods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH'];
|
||||
$methods = [
|
||||
'CONNECT',
|
||||
'DELETE',
|
||||
'GET',
|
||||
'HEAD',
|
||||
'OPTIONS',
|
||||
'PATCH',
|
||||
'POST',
|
||||
'PUT',
|
||||
'TRACE',
|
||||
];
|
||||
|
||||
// the request method can be overwritten with a header
|
||||
$methodOverride = strtoupper(Environment::getGlobally('HTTP_X_HTTP_METHOD_OVERRIDE', ''));
|
||||
if ($method === null) {
|
||||
$override = Environment::getGlobally('HTTP_X_HTTP_METHOD_OVERRIDE', '');
|
||||
$override = strtoupper($override);
|
||||
|
||||
if (in_array($methodOverride, $methods, true) === true) {
|
||||
$method ??= $methodOverride;
|
||||
if (in_array($override, $methods, true) === true) {
|
||||
$method = $override;
|
||||
}
|
||||
}
|
||||
|
||||
// final chain of options to detect the method
|
||||
|
|
@ -410,14 +417,15 @@ class Request
|
|||
// both variants need to be checked separately
|
||||
// because empty strings are treated as invalid
|
||||
// but the `??` operator wouldn't do the fallback
|
||||
|
||||
$option = $this->options['auth'] ?? null;
|
||||
if (empty($option) === false) {
|
||||
|
||||
if (is_string($option) === true && $option !== '') {
|
||||
return $option;
|
||||
}
|
||||
|
||||
$header = $this->header('authorization');
|
||||
if (empty($header) === false) {
|
||||
|
||||
if (is_string($header) === true && $header !== '') {
|
||||
return $header;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,11 +20,6 @@ class Body implements Stringable
|
|||
{
|
||||
use Data;
|
||||
|
||||
/**
|
||||
* The raw body content
|
||||
*/
|
||||
protected string|array|null $contents;
|
||||
|
||||
/**
|
||||
* The parsed content as array
|
||||
*/
|
||||
|
|
@ -36,10 +31,12 @@ class Body implements Stringable
|
|||
* If null is being passed, the class will
|
||||
* fetch the body either from the $_POST global
|
||||
* or from php://input.
|
||||
*
|
||||
* @param array|string|null $contents The raw body content
|
||||
*/
|
||||
public function __construct(array|string|null $contents = null)
|
||||
{
|
||||
$this->contents = $contents;
|
||||
public function __construct(
|
||||
protected array|string|null $contents = null
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -52,7 +49,7 @@ class Body implements Stringable
|
|||
return $this->contents;
|
||||
}
|
||||
|
||||
if (empty($_POST) === false) {
|
||||
if ($_POST !== []) {
|
||||
return $this->contents = $_POST;
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +87,7 @@ class Body implements Stringable
|
|||
// try to parse the body as query string
|
||||
parse_str($contents, $parsed);
|
||||
|
||||
if (is_array($parsed)) {
|
||||
if (is_array($parsed) === true) {
|
||||
return $this->data = $parsed;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,9 +45,11 @@ trait Data
|
|||
{
|
||||
if (is_array($key) === true) {
|
||||
$result = [];
|
||||
|
||||
foreach ($key as $k) {
|
||||
$result[$k] = $this->get($k);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class Files
|
|||
$files ??= $_FILES;
|
||||
|
||||
foreach ($files as $key => $file) {
|
||||
if (is_array($file['name'])) {
|
||||
if (is_array($file['name']) === true) {
|
||||
foreach ($file['name'] as $i => $name) {
|
||||
$this->files[$key][] = [
|
||||
'name' => $file['name'][$i] ?? null,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class Query implements Stringable
|
|||
/**
|
||||
* The Query data array
|
||||
*/
|
||||
protected array|null $data = null;
|
||||
protected array $data;
|
||||
|
||||
/**
|
||||
* Creates a new Query object.
|
||||
|
|
@ -56,7 +56,7 @@ class Query implements Stringable
|
|||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return empty($this->data) === true;
|
||||
return $this->data === [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -64,7 +64,7 @@ class Query implements Stringable
|
|||
*/
|
||||
public function isNotEmpty(): bool
|
||||
{
|
||||
return empty($this->data) === false;
|
||||
return $this->data !== [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -281,8 +281,11 @@ class Response implements Stringable
|
|||
*
|
||||
* @since 5.0.3
|
||||
*/
|
||||
public static function refresh(string $location = '/', int $code = 302, int $refresh = 0): static
|
||||
{
|
||||
public static function refresh(
|
||||
string $location = '/',
|
||||
int $code = 302,
|
||||
int $refresh = 0
|
||||
): static {
|
||||
return new static([
|
||||
'code' => $code,
|
||||
'headers' => [
|
||||
|
|
@ -312,6 +315,19 @@ class Response implements Stringable
|
|||
return $this->body();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the provided headers in case they are not already set
|
||||
* @internal
|
||||
* @return $this
|
||||
*/
|
||||
public function setHeaderFallbacks(array $headers): static
|
||||
{
|
||||
// the case-insensitive nature of headers will be
|
||||
// handled by PHP's `header()` functions
|
||||
$this->headers = [...$headers, ...$this->headers];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts all relevant response attributes
|
||||
* to an associative array for debugging,
|
||||
|
|
|
|||
|
|
@ -13,26 +13,11 @@ use Closure;
|
|||
*/
|
||||
class Route
|
||||
{
|
||||
/**
|
||||
* The callback action function
|
||||
*/
|
||||
protected Closure $action;
|
||||
|
||||
/**
|
||||
* Listed of parsed arguments
|
||||
*/
|
||||
protected array $arguments = [];
|
||||
|
||||
/**
|
||||
* An array of all passed attributes
|
||||
*/
|
||||
protected array $attributes = [];
|
||||
|
||||
/**
|
||||
* The registered request method
|
||||
*/
|
||||
protected string $method;
|
||||
|
||||
/**
|
||||
* The registered pattern
|
||||
*/
|
||||
|
|
@ -74,13 +59,10 @@ class Route
|
|||
*/
|
||||
public function __construct(
|
||||
string $pattern,
|
||||
string $method,
|
||||
Closure $action,
|
||||
array $attributes = []
|
||||
protected string $method,
|
||||
protected Closure $action,
|
||||
protected array $attributes = []
|
||||
) {
|
||||
$this->action = $action;
|
||||
$this->attributes = $attributes;
|
||||
$this->method = $method;
|
||||
$this->pattern = $this->regex(ltrim($pattern, '/'));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -158,6 +158,9 @@ class Router
|
|||
* The Route's arguments method is used to
|
||||
* find matches and return all the found
|
||||
* arguments in the path.
|
||||
*
|
||||
* @param array|null $ignore (Passing null has been deprecated)
|
||||
* @todo Remove support for `$ignore = null` in v6
|
||||
*/
|
||||
public function find(
|
||||
string $path,
|
||||
|
|
@ -173,15 +176,13 @@ class Router
|
|||
|
||||
// remove leading and trailing slashes
|
||||
$path = trim($path, '/');
|
||||
$ignore ??= [];
|
||||
|
||||
foreach ($this->routes[$method] as $route) {
|
||||
$arguments = $route->parse($route->pattern(), $path);
|
||||
|
||||
if ($arguments !== false) {
|
||||
if (
|
||||
empty($ignore) === true ||
|
||||
in_array($route, $ignore, true) === false
|
||||
) {
|
||||
if (in_array($route, $ignore, true) === false) {
|
||||
return $this->route = $route;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -216,10 +216,10 @@ class Uri implements Stringable
|
|||
|
||||
if ($app = App::instance(null, true)) {
|
||||
$environment = $app->environment();
|
||||
} else {
|
||||
$environment = new Environment();
|
||||
}
|
||||
|
||||
$environment ??= new Environment();
|
||||
|
||||
return new static($environment->requestUrl(), $props);
|
||||
}
|
||||
|
||||
|
|
@ -230,7 +230,7 @@ class Uri implements Stringable
|
|||
*/
|
||||
public function domain(): string|null
|
||||
{
|
||||
if (empty($this->host) === true || $this->host === '/') {
|
||||
if ($this->host === null || $this->host === '' || $this->host === '/') {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -255,7 +255,7 @@ class Uri implements Stringable
|
|||
|
||||
public function hasFragment(): bool
|
||||
{
|
||||
return empty($this->fragment) === false;
|
||||
return $this->fragment !== null && $this->fragment !== '';
|
||||
}
|
||||
|
||||
public function hasPath(): bool
|
||||
|
|
@ -281,8 +281,9 @@ class Uri implements Stringable
|
|||
*/
|
||||
public function idn(): static
|
||||
{
|
||||
if (empty($this->host) === false) {
|
||||
$this->setHost(Idn::decode($this->host));
|
||||
if ($this->isAbsolute() === true) {
|
||||
$host = Idn::decode($this->host);
|
||||
$this->setHost($host);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
|
@ -295,10 +296,10 @@ class Uri implements Stringable
|
|||
{
|
||||
if ($app = App::instance(null, true)) {
|
||||
$url = $app->url('index');
|
||||
} else {
|
||||
$url = (new Environment())->baseUrl();
|
||||
}
|
||||
|
||||
$url ??= (new Environment())->baseUrl();
|
||||
|
||||
return new static($url, $props);
|
||||
}
|
||||
|
||||
|
|
@ -307,7 +308,16 @@ class Uri implements Stringable
|
|||
*/
|
||||
public function isAbsolute(): bool
|
||||
{
|
||||
return empty($this->host) === false;
|
||||
return $this->host !== null && $this->host !== '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fragment after the hash
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function fragment(): string|null
|
||||
{
|
||||
return $this->fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -465,7 +475,7 @@ class Uri implements Stringable
|
|||
$url = $this->base();
|
||||
$slash = true;
|
||||
|
||||
if (empty($url) === true) {
|
||||
if ($url === null || $url === '') {
|
||||
$url = '/';
|
||||
$slash = false;
|
||||
}
|
||||
|
|
@ -479,8 +489,8 @@ class Uri implements Stringable
|
|||
$url .= $path;
|
||||
$url .= $this->query->toString(true);
|
||||
|
||||
if (empty($this->fragment) === false) {
|
||||
$url .= '#' . $this->fragment;
|
||||
if ($this->hasFragment() === true) {
|
||||
$url .= '#' . $this->fragment();
|
||||
}
|
||||
|
||||
return $url;
|
||||
|
|
@ -494,8 +504,9 @@ class Uri implements Stringable
|
|||
*/
|
||||
public function unIdn(): static
|
||||
{
|
||||
if (empty($this->host) === false) {
|
||||
$this->setHost(Idn::encode($this->host));
|
||||
if ($this->isAbsolute() === true) {
|
||||
$host = Idn::encode($this->host);
|
||||
$this->setHost($host);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,8 +110,10 @@ class Url
|
|||
/**
|
||||
* Convert a relative path into an absolute URL
|
||||
*/
|
||||
public static function makeAbsolute(string|null $path = null, string|null $home = null): string
|
||||
{
|
||||
public static function makeAbsolute(
|
||||
string|null $path = null,
|
||||
string|null $home = null
|
||||
): string {
|
||||
if ($path === '' || $path === '/' || $path === null) {
|
||||
return $home ?? static::home();
|
||||
}
|
||||
|
|
@ -120,7 +122,7 @@ class Url
|
|||
return $path;
|
||||
}
|
||||
|
||||
if (static::isAbsolute($path)) {
|
||||
if (static::isAbsolute($path) === true) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
|
|
@ -128,11 +130,15 @@ class Url
|
|||
$path = ltrim($path, '/');
|
||||
$home ??= static::home();
|
||||
|
||||
if (empty($path) === true) {
|
||||
if ($path === '') {
|
||||
return $home;
|
||||
}
|
||||
|
||||
return $home === '/' ? '/' . $path : $home . '/' . $path;
|
||||
if ($home === '/') {
|
||||
return '/' . $path;
|
||||
}
|
||||
|
||||
return $home . '/' . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -165,16 +165,16 @@ class Visitor
|
|||
*/
|
||||
public function preferredMimeType(string ...$mimeTypes): string|null
|
||||
{
|
||||
foreach ($this->acceptedMimeTypes() as $acceptedMime) {
|
||||
foreach ($this->acceptedMimeTypes() as $accepted) {
|
||||
// look for direct matches
|
||||
if (in_array($acceptedMime->type(), $mimeTypes, true)) {
|
||||
return $acceptedMime->type();
|
||||
if (in_array($accepted->type(), $mimeTypes, true) === true) {
|
||||
return $accepted->type();
|
||||
}
|
||||
|
||||
// test each option against wildcard `Accept` values
|
||||
foreach ($mimeTypes as $expectedMime) {
|
||||
if (Mime::matches($expectedMime, $acceptedMime->type()) === true) {
|
||||
return $expectedMime;
|
||||
foreach ($mimeTypes as $expected) {
|
||||
if (Mime::matches($expected, $accepted->type()) === true) {
|
||||
return $expected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace Kirby\Image;
|
|||
use Exception;
|
||||
use Kirby\Image\Darkroom\GdLib;
|
||||
use Kirby\Image\Darkroom\ImageMagick;
|
||||
use Kirby\Image\Darkroom\Imagick;
|
||||
|
||||
/**
|
||||
* A wrapper around resizing and cropping
|
||||
|
|
@ -20,6 +21,7 @@ class Darkroom
|
|||
{
|
||||
public static array $types = [
|
||||
'gd' => GdLib::class,
|
||||
'imagick' => Imagick::class,
|
||||
'im' => ImageMagick::class
|
||||
];
|
||||
|
||||
|
|
@ -30,19 +32,18 @@ class Darkroom
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates a new Darkroom instance for the given
|
||||
* type/driver
|
||||
* Creates a new Darkroom instance
|
||||
* for the given type/driver
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function factory(string $type, array $settings = []): object
|
||||
public static function factory(string $type, array $settings = []): static
|
||||
{
|
||||
if (isset(static::$types[$type]) === false) {
|
||||
throw new Exception(message: 'Invalid Darkroom type');
|
||||
}
|
||||
|
||||
$class = static::$types[$type];
|
||||
return new $class($settings);
|
||||
return new static::$types[$type]($settings);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -69,7 +70,12 @@ class Darkroom
|
|||
*/
|
||||
protected function options(array $options = []): array
|
||||
{
|
||||
$options = [...$this->settings, ...$options];
|
||||
$options = [
|
||||
...$this->settings,
|
||||
...$options,
|
||||
// ensure quality isn't unset by provided options
|
||||
'quality' => $options['quality'] ?? $this->settings['quality']
|
||||
];
|
||||
|
||||
// normalize the crop option
|
||||
if ($options['crop'] === true) {
|
||||
|
|
@ -81,7 +87,7 @@ class Darkroom
|
|||
$options['blur'] = 10;
|
||||
}
|
||||
|
||||
// normalize the greyscale option
|
||||
// normalize the grayscale option
|
||||
if (isset($options['greyscale']) === true) {
|
||||
$options['grayscale'] = $options['greyscale'];
|
||||
unset($options['greyscale']);
|
||||
|
|
@ -98,8 +104,6 @@ class Darkroom
|
|||
$options['sharpen'] = 50;
|
||||
}
|
||||
|
||||
$options['quality'] ??= $this->settings['quality'];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use Kirby\Image\Darkroom;
|
|||
use Kirby\Image\Focus;
|
||||
|
||||
/**
|
||||
* GdLib
|
||||
* GdLib darkroom driver
|
||||
*
|
||||
* @package Kirby Image
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
|
|
|
|||
|
|
@ -8,13 +8,16 @@ use Kirby\Image\Darkroom;
|
|||
use Kirby\Image\Focus;
|
||||
|
||||
/**
|
||||
* ImageMagick
|
||||
* Legacy ImageMagick driver using the convert CLI
|
||||
*
|
||||
* @package Kirby Image
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*
|
||||
* @deprecated 5.1.0 Use `imagick` in the `thumbs.driver` config option instead
|
||||
* @todo Remove in 7.0.0
|
||||
*/
|
||||
class ImageMagick extends Darkroom
|
||||
{
|
||||
|
|
|
|||
292
public/kirby/src/Image/Darkroom/Imagick.php
Normal file
292
public/kirby/src/Image/Darkroom/Imagick.php
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Image\Darkroom;
|
||||
|
||||
use Exception;
|
||||
use Imagick as Image;
|
||||
use Kirby\Image\Darkroom;
|
||||
use Kirby\Image\Focus;
|
||||
|
||||
/**
|
||||
* Imagick darkroom driver
|
||||
*
|
||||
* @package Kirby Image
|
||||
* @author Nico Hoffmann <nico@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
* @since 5.1.0
|
||||
*/
|
||||
class Imagick extends Darkroom
|
||||
{
|
||||
protected function autoOrient(Image $image): Image
|
||||
{
|
||||
switch ($image->getImageOrientation()) {
|
||||
case Image::ORIENTATION_TOPLEFT:
|
||||
break;
|
||||
case Image::ORIENTATION_TOPRIGHT:
|
||||
$image->flopImage();
|
||||
break;
|
||||
case Image::ORIENTATION_BOTTOMRIGHT:
|
||||
$image->rotateImage('#000', 180);
|
||||
break;
|
||||
case Image::ORIENTATION_BOTTOMLEFT:
|
||||
$image->flopImage();
|
||||
$image->rotateImage('#000', 180);
|
||||
break;
|
||||
case Image::ORIENTATION_LEFTTOP:
|
||||
$image->flopImage();
|
||||
$image->rotateImage('#000', -90);
|
||||
break;
|
||||
case Image::ORIENTATION_RIGHTTOP:
|
||||
$image->rotateImage('#000', 90);
|
||||
break;
|
||||
case Image::ORIENTATION_RIGHTBOTTOM:
|
||||
$image->flopImage();
|
||||
$image->rotateImage('#000', 90);
|
||||
break;
|
||||
case Image::ORIENTATION_LEFTBOTTOM:
|
||||
$image->rotateImage('#000', -90);
|
||||
break;
|
||||
default: // Invalid orientation
|
||||
break;
|
||||
}
|
||||
|
||||
$image->setImageOrientation(Image::ORIENTATION_TOPLEFT);
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the blur settings
|
||||
*/
|
||||
protected function blur(Image $image, array $options): Image
|
||||
{
|
||||
if ($options['blur'] !== false) {
|
||||
$image->blurImage(0.0, $options['blur']);
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep animated gifs
|
||||
*/
|
||||
protected function coalesce(Image $image): Image
|
||||
{
|
||||
if ($image->getImageMimeType() === 'image/gif') {
|
||||
return $image->coalesceImages();
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns additional default parameters for imagemagick
|
||||
*/
|
||||
protected function defaults(): array
|
||||
{
|
||||
return parent::defaults() + [
|
||||
'interlace' => false,
|
||||
'profiles' => ['icc', 'icm'],
|
||||
'threads' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the correct settings for grayscale images
|
||||
*/
|
||||
protected function grayscale(Image $image, array $options): Image
|
||||
{
|
||||
if ($options['grayscale'] === true) {
|
||||
$image->setImageColorspace(Image::COLORSPACE_GRAY);
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the correct settings for interlaced JPEGs if
|
||||
* activated via options
|
||||
*/
|
||||
protected function interlace(Image $image, array $options): Image
|
||||
{
|
||||
if ($options['interlace'] === true) {
|
||||
$image->setInterlaceScheme(Image::INTERLACE_LINE);
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and runs the full imagemagick command
|
||||
* to process the image
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function process(string $file, array $options = []): array
|
||||
{
|
||||
$options = $this->preprocess($file, $options);
|
||||
|
||||
$image = new Image($file);
|
||||
$image = $this->threads($image, $options);
|
||||
$image = $this->interlace($image, $options);
|
||||
$image = $this->coalesce($image);
|
||||
$image = $this->grayscale($image, $options);
|
||||
$image = $this->autoOrient($image);
|
||||
$image = $this->resize($image, $options);
|
||||
$image = $this->quality($image, $options);
|
||||
$image = $this->blur($image, $options);
|
||||
$image = $this->sharpen($image, $options);
|
||||
$image = $this->strip($image, $options);
|
||||
|
||||
if ($this->save($image, $file, $options) === false) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new Exception(message: 'The imagemagick result could not be generated');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the correct JPEG compression quality settings
|
||||
*/
|
||||
protected function quality(Image $image, array $options): Image
|
||||
{
|
||||
$image->setImageCompressionQuality($options['quality']);
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the correct options to crop or resize the image
|
||||
* and translates the crop positions for imagemagick
|
||||
*/
|
||||
protected function resize(Image $image, array $options): Image
|
||||
{
|
||||
// simple resize
|
||||
if ($options['crop'] === false) {
|
||||
$image->thumbnailImage(
|
||||
$options['width'],
|
||||
$options['height'],
|
||||
true
|
||||
);
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
// crop based on focus point
|
||||
if (Focus::isFocalPoint($options['crop']) === true) {
|
||||
if ($focus = Focus::coords(
|
||||
$options['crop'],
|
||||
$options['sourceWidth'],
|
||||
$options['sourceHeight'],
|
||||
$options['width'],
|
||||
$options['height']
|
||||
)) {
|
||||
$image->cropImage(
|
||||
$focus['width'],
|
||||
$focus['height'],
|
||||
$focus['x1'],
|
||||
$focus['y1']
|
||||
);
|
||||
|
||||
$image->thumbnailImage(
|
||||
$options['width'],
|
||||
$options['height'],
|
||||
true
|
||||
);
|
||||
|
||||
return $image;
|
||||
}
|
||||
}
|
||||
|
||||
// translate the gravity option into something imagemagick understands
|
||||
$gravity = match ($options['crop'] ?? null) {
|
||||
'top left' => Image::GRAVITY_NORTHWEST,
|
||||
'top' => Image::GRAVITY_NORTH,
|
||||
'top right' => Image::GRAVITY_NORTHEAST,
|
||||
'left' => Image::GRAVITY_WEST,
|
||||
'right' => Image::GRAVITY_EAST,
|
||||
'bottom left' => Image::GRAVITY_SOUTHWEST,
|
||||
'bottom' => Image::GRAVITY_SOUTH,
|
||||
'bottom right' => Image::GRAVITY_SOUTHEAST,
|
||||
default => Image::GRAVITY_CENTER
|
||||
};
|
||||
|
||||
$landscape = $options['width'] >= $options['height'];
|
||||
|
||||
$image->thumbnailImage(
|
||||
$landscape ? $options['width'] : $image->getImageWidth(),
|
||||
$landscape ? $image->getImageHeight() : $options['height'],
|
||||
true
|
||||
);
|
||||
|
||||
$image->setGravity($gravity);
|
||||
$image->cropImage($options['width'], $options['height'], 0, 0);
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the option for the output file
|
||||
*/
|
||||
protected function save(Image $image, string $file, array $options): bool
|
||||
{
|
||||
if ($options['format'] !== null) {
|
||||
$file = pathinfo($file, PATHINFO_DIRNAME) . '/' . pathinfo($file, PATHINFO_FILENAME) . '.' . $options['format'];
|
||||
}
|
||||
|
||||
return $image->writeImages($file, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies sharpening if activated in the options.
|
||||
*/
|
||||
protected function sharpen(Image $image, array $options): Image
|
||||
{
|
||||
if (is_int($options['sharpen']) === false) {
|
||||
return $image;
|
||||
}
|
||||
|
||||
$amount = max(1, min(100, $options['sharpen'])) / 100;
|
||||
$image->sharpenImage(0.0, $amount);
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all metadata but ICC profiles from the image
|
||||
*/
|
||||
protected function strip(Image $image, array $options): Image
|
||||
{
|
||||
// strip all profiles but the ICC profile
|
||||
$profiles = $image->getImageProfiles('*', false);
|
||||
|
||||
foreach ($profiles as $profile) {
|
||||
if (in_array($profile, $options['profiles'] ?? [], true) === false) {
|
||||
$image->removeImageProfile($profile);
|
||||
}
|
||||
}
|
||||
|
||||
// strip all properties
|
||||
$properties = $image->getImageProperties('*', false);
|
||||
|
||||
foreach ($properties as $property) {
|
||||
$image->deleteImageProperty($property);
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets thread limit
|
||||
*/
|
||||
protected function threads(Image $image, array $options): Image
|
||||
{
|
||||
$image->setResourceLimit(
|
||||
Image::RESOURCETYPE_THREAD,
|
||||
$options['threads']
|
||||
);
|
||||
return $image;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Kirby\Image;
|
||||
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\V;
|
||||
|
||||
/**
|
||||
|
|
@ -25,7 +26,7 @@ class Exif
|
|||
protected string|null $exposure = null;
|
||||
protected string|null $focalLength = null;
|
||||
protected bool|null $isColor = null;
|
||||
protected string|null $iso = null;
|
||||
protected array|string|null $iso = null;
|
||||
protected Location|null $location = null;
|
||||
protected string|null $timestamp = null;
|
||||
protected int $orientation;
|
||||
|
|
@ -96,6 +97,10 @@ class Exif
|
|||
*/
|
||||
public function iso(): string|null
|
||||
{
|
||||
if (is_array($this->iso) === true) {
|
||||
return A::first($this->iso);
|
||||
}
|
||||
|
||||
return $this->iso;
|
||||
}
|
||||
|
||||
|
|
|
|||
73
public/kirby/src/Panel/Collector/FilesCollector.php
Normal file
73
public/kirby/src/Panel/Collector/FilesCollector.php
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Panel\Collector;
|
||||
|
||||
use Kirby\Cms\Files;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\Pages;
|
||||
use Kirby\Cms\Site;
|
||||
use Kirby\Cms\User;
|
||||
use Kirby\Cms\Users;
|
||||
|
||||
/**
|
||||
* @package Kirby Panel
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class FilesCollector extends ModelsCollector
|
||||
{
|
||||
public function __construct(
|
||||
protected bool $flip = false,
|
||||
protected int|null $limit = null,
|
||||
protected int $page = 1,
|
||||
protected Site|Page|User|null $parent = null,
|
||||
protected string|null $query = null,
|
||||
protected string|null $search = null,
|
||||
protected string|null $sortBy = null,
|
||||
protected string|null $template = null,
|
||||
) {
|
||||
}
|
||||
|
||||
protected function collect(): Files
|
||||
{
|
||||
return $this->parent()->files();
|
||||
}
|
||||
|
||||
protected function collectByQuery(): Files
|
||||
{
|
||||
return $this->parent()->query($this->query, Files::class) ?? new Files([]);
|
||||
}
|
||||
|
||||
protected function filter(Files|Pages|Users $models): Files
|
||||
{
|
||||
return $models->filter(function ($file) {
|
||||
// remove all protected and hidden files
|
||||
if ($file->isListable() === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// filter by template
|
||||
if ($this->template !== null && $file->template() !== $this->template) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public function isSorting(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function sort(Files|Pages|Users $models): Files
|
||||
{
|
||||
if ($this->sortBy === null || $this->isSearching() === true) {
|
||||
return $models->sorted();
|
||||
}
|
||||
|
||||
return parent::sort($models);
|
||||
}
|
||||
}
|
||||
130
public/kirby/src/Panel/Collector/ModelsCollector.php
Normal file
130
public/kirby/src/Panel/Collector/ModelsCollector.php
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Panel\Collector;
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Files;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\Pages;
|
||||
use Kirby\Cms\Pagination;
|
||||
use Kirby\Cms\Site;
|
||||
use Kirby\Cms\User;
|
||||
use Kirby\Cms\Users;
|
||||
|
||||
/**
|
||||
* @package Kirby Panel
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
abstract class ModelsCollector
|
||||
{
|
||||
protected Files|Pages|Users $models;
|
||||
protected Files|Pages|Users $paginated;
|
||||
|
||||
public function __construct(
|
||||
protected int|null $limit = null,
|
||||
protected int $page = 1,
|
||||
protected Site|Page|User|null $parent = null,
|
||||
protected string|null $query = null,
|
||||
protected string|null $search = null,
|
||||
protected string|null $sortBy = null,
|
||||
protected bool $flip = false,
|
||||
) {
|
||||
}
|
||||
|
||||
abstract protected function collect(): Files|Pages|Users;
|
||||
abstract protected function collectByQuery(): Files|Pages|Users;
|
||||
abstract protected function filter(Files|Pages|Users $models): Files|Pages|Users;
|
||||
|
||||
protected function flip(Files|Pages|Users $models): Files|Pages|Users
|
||||
{
|
||||
return $models->flip();
|
||||
}
|
||||
|
||||
public function isFlipping(): bool
|
||||
{
|
||||
if ($this->isSearching() === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->flip === true;
|
||||
}
|
||||
|
||||
public function isQuerying(): bool
|
||||
{
|
||||
return $this->query !== null;
|
||||
}
|
||||
|
||||
public function isSearching(): bool
|
||||
{
|
||||
return $this->search !== null && trim($this->search) !== '';
|
||||
}
|
||||
|
||||
public function isSorting(): bool
|
||||
{
|
||||
if ($this->isSearching() === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->sortBy !== null;
|
||||
}
|
||||
|
||||
public function models(bool $paginated = false): Files|Pages|Users
|
||||
{
|
||||
if ($paginated === true) {
|
||||
return $this->paginated ??= $this->models()->paginate([
|
||||
'limit' => $this->limit ?? 1000,
|
||||
'page' => $this->page,
|
||||
'method' => 'none' // the page is manually provided
|
||||
]);
|
||||
}
|
||||
|
||||
if (isset($this->models) === true) {
|
||||
return $this->models;
|
||||
}
|
||||
|
||||
if ($this->isQuerying() === true) {
|
||||
$models = $this->collectByQuery();
|
||||
} else {
|
||||
$models = $this->collect();
|
||||
}
|
||||
|
||||
$models = $this->filter($models);
|
||||
|
||||
if ($this->isSearching() === true) {
|
||||
$models = $this->search($models);
|
||||
}
|
||||
|
||||
if ($this->isSorting() === true) {
|
||||
$models = $this->sort($models);
|
||||
}
|
||||
|
||||
if ($this->isFlipping() === true) {
|
||||
$models = $this->flip($models);
|
||||
}
|
||||
|
||||
return $this->models ??= $models;
|
||||
}
|
||||
|
||||
public function pagination(): Pagination
|
||||
{
|
||||
return $this->models(paginated: true)->pagination();
|
||||
}
|
||||
|
||||
protected function parent(): Site|Page|User
|
||||
{
|
||||
return $this->parent ?? App::instance()->site();
|
||||
}
|
||||
|
||||
protected function search(Files|Pages|Users $models): Files|Pages|Users
|
||||
{
|
||||
return $models->search($this->search);
|
||||
}
|
||||
|
||||
protected function sort(Files|Pages|Users $models): Files|Pages|Users
|
||||
{
|
||||
return $models->sort(...$models::sortArgs($this->sortBy));
|
||||
}
|
||||
}
|
||||
85
public/kirby/src/Panel/Collector/PagesCollector.php
Normal file
85
public/kirby/src/Panel/Collector/PagesCollector.php
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Panel\Collector;
|
||||
|
||||
use Kirby\Cms\Files;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\Pages;
|
||||
use Kirby\Cms\Site;
|
||||
use Kirby\Cms\User;
|
||||
use Kirby\Cms\Users;
|
||||
|
||||
/**
|
||||
* @package Kirby Panel
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class PagesCollector extends ModelsCollector
|
||||
{
|
||||
public function __construct(
|
||||
protected int|null $limit = null,
|
||||
protected int $page = 1,
|
||||
protected Site|Page|User|null $parent = null,
|
||||
protected string|null $query = null,
|
||||
protected string|null $status = null,
|
||||
protected array $templates = [],
|
||||
protected array $templatesIgnore = [],
|
||||
protected string|null $search = null,
|
||||
protected string|null $sortBy = null,
|
||||
protected bool $flip = false,
|
||||
) {
|
||||
}
|
||||
|
||||
protected function collect(): Pages
|
||||
{
|
||||
return match ($this->status) {
|
||||
'draft' => $this->parent()->drafts(),
|
||||
'listed' => $this->parent()->children()->listed(),
|
||||
'published' => $this->parent()->children(),
|
||||
'unlisted' => $this->parent()->children()->unlisted(),
|
||||
default => $this->parent()->childrenAndDrafts()
|
||||
};
|
||||
}
|
||||
|
||||
protected function collectByQuery(): Pages
|
||||
{
|
||||
return $this->parent()->query($this->query, Pages::class) ?? new Pages([]);
|
||||
}
|
||||
|
||||
protected function filter(Files|Pages|Users $models): Pages
|
||||
{
|
||||
// filters pages that are protected and not in the templates list
|
||||
// internal `filter()` method used instead of foreach loop that previously included `unset()`
|
||||
// because `unset()` is updating the original data, `filter()` is just filtering
|
||||
// also it has been tested that there is no performance difference
|
||||
// even in 0.1 seconds on 100k virtual pages
|
||||
return $models->filter(function (Page $model): bool {
|
||||
// remove all protected and hidden pages
|
||||
if ($model->isListable() === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$intendedTemplate = $model->intendedTemplate()->name();
|
||||
|
||||
// filter by all set templates
|
||||
if (
|
||||
$this->templates &&
|
||||
in_array($intendedTemplate, $this->templates, true) === false
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// exclude by all ignored templates
|
||||
if (
|
||||
$this->templatesIgnore &&
|
||||
in_array($intendedTemplate, $this->templatesIgnore, true) === true
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
62
public/kirby/src/Panel/Collector/UsersCollector.php
Normal file
62
public/kirby/src/Panel/Collector/UsersCollector.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Panel\Collector;
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Files;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\Pages;
|
||||
use Kirby\Cms\Site;
|
||||
use Kirby\Cms\User;
|
||||
use Kirby\Cms\Users;
|
||||
|
||||
/**
|
||||
* @package Kirby Panel
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class UsersCollector extends ModelsCollector
|
||||
{
|
||||
public function __construct(
|
||||
protected bool $flip = false,
|
||||
protected int|null $limit = null,
|
||||
protected int $page = 1,
|
||||
protected Site|Page|User|null $parent = null,
|
||||
protected string|null $query = null,
|
||||
protected string|null $role = null,
|
||||
protected string|null $search = null,
|
||||
protected string|null $sortBy = null,
|
||||
) {
|
||||
}
|
||||
|
||||
protected function collect(): Users
|
||||
{
|
||||
return App::instance()->users();
|
||||
}
|
||||
|
||||
protected function collectByQuery(): Users
|
||||
{
|
||||
return $this->parent()->query($this->query, Users::class) ?? new Users([]);
|
||||
}
|
||||
|
||||
protected function filter(Files|Pages|Users $models): Users
|
||||
{
|
||||
$user = App::instance()->user();
|
||||
|
||||
if ($user === null) {
|
||||
return new Users([]);
|
||||
}
|
||||
|
||||
if ($user->role()->permissions()->for('access', 'users') === false) {
|
||||
return new Users([]);
|
||||
}
|
||||
|
||||
if ($this->role !== null) {
|
||||
$models = $models->role($this->role);
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,9 @@
|
|||
namespace Kirby\Panel\Controller;
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Toolkit\Escape;
|
||||
use Kirby\Panel\Ui\Item\FileItem;
|
||||
use Kirby\Panel\Ui\Item\PageItem;
|
||||
use Kirby\Panel\Ui\Item\UserItem;
|
||||
|
||||
/**
|
||||
* The Search controller takes care of the logic
|
||||
|
|
@ -40,13 +42,7 @@ class Search
|
|||
}
|
||||
|
||||
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()),
|
||||
'uuid' => $file->uuid()->toString(),
|
||||
]),
|
||||
'results' => $files->values(fn ($file) => (new FileItem(file: $file, info: '{{ file.id }}'))->props()),
|
||||
'pagination' => $files->pagination()?->toArray()
|
||||
];
|
||||
}
|
||||
|
|
@ -67,13 +63,7 @@ class Search
|
|||
}
|
||||
|
||||
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()),
|
||||
'uuid' => $page->uuid()?->toString(),
|
||||
]),
|
||||
'results' => $pages->values(fn ($page) => (new PageItem(page: $page, info: '{{ page.id }}'))->props()),
|
||||
'pagination' => $pages->pagination()?->toArray()
|
||||
];
|
||||
}
|
||||
|
|
@ -91,13 +81,7 @@ class Search
|
|||
}
|
||||
|
||||
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()),
|
||||
'uuid' => $user->uuid()->toString(),
|
||||
]),
|
||||
'results' => $users->values(fn ($user) => (new UserItem(user: $user))->props()),
|
||||
'pagination' => $users->pagination()?->toArray()
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,20 @@ class Dialog extends Json
|
|||
$pattern = trim($prefix . '/' . ($options['pattern'] ?? $id), '/');
|
||||
$type = str_replace('$', '', static::$key);
|
||||
|
||||
// create load/submit events from controller class
|
||||
if ($controller = $options['controller'] ?? null) {
|
||||
if (is_string($controller) === true) {
|
||||
if (method_exists($controller, 'for') === true) {
|
||||
$controller = $controller::for(...);
|
||||
} else {
|
||||
$controller = fn (...$args) => new $controller(...$args);
|
||||
}
|
||||
}
|
||||
|
||||
$options['load'] ??= fn (...$args) => $controller(...$args)->load();
|
||||
$options['submit'] ??= fn (...$args) => $controller(...$args)->submit();
|
||||
}
|
||||
|
||||
// load event
|
||||
$routes[] = [
|
||||
'pattern' => $pattern,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use Kirby\Cms\ModelWithContent;
|
|||
use Kirby\Filesystem\Asset;
|
||||
use Kirby\Panel\Ui\Buttons\ViewButtons;
|
||||
use Kirby\Panel\Ui\FilePreview;
|
||||
use Kirby\Panel\Ui\Item\FileItem;
|
||||
use Kirby\Toolkit\I18n;
|
||||
use Throwable;
|
||||
|
||||
|
|
@ -361,6 +362,7 @@ class File extends Model
|
|||
{
|
||||
$name = $this->model->filename();
|
||||
$id = $this->model->id();
|
||||
$absolute = false;
|
||||
|
||||
if (empty($params['model']) === false) {
|
||||
$parent = $this->model->parent();
|
||||
|
|
@ -374,15 +376,20 @@ class File extends Model
|
|||
};
|
||||
}
|
||||
|
||||
$params['text'] ??= '{{ file.filename }}';
|
||||
$item = new FileItem(
|
||||
file: $this->model,
|
||||
dragTextIsAbsolute: $absolute,
|
||||
image: $params['image'] ?? null,
|
||||
info: $params['info'] ?? null,
|
||||
layout: $params['layout'] ?? null,
|
||||
text: $params['text'] ?? null,
|
||||
);
|
||||
|
||||
return [
|
||||
...parent::pickerData($params),
|
||||
'dragText' => $this->dragText('auto', absolute: $absolute ?? false),
|
||||
'filename' => $name,
|
||||
...$item->props(),
|
||||
'id' => $id,
|
||||
'sortable' => true,
|
||||
'type' => $this->model->type(),
|
||||
'url' => $this->model->url()
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use Kirby\Cms\ModelWithContent;
|
|||
use Kirby\Filesystem\Asset;
|
||||
use Kirby\Form\Fields;
|
||||
use Kirby\Http\Uri;
|
||||
use Kirby\Panel\Ui\Item\ModelItem;
|
||||
use Kirby\Toolkit\A;
|
||||
|
||||
/**
|
||||
|
|
@ -338,17 +339,18 @@ abstract class Model
|
|||
*/
|
||||
public function pickerData(array $params = []): array
|
||||
{
|
||||
$item = new ModelItem(
|
||||
model: $this->model,
|
||||
image: $params['image'] ?? null,
|
||||
info: $params['info'] ?? null,
|
||||
layout: $params['layout'] ?? null,
|
||||
text: $params['text'] ?? null,
|
||||
);
|
||||
|
||||
return [
|
||||
'id' => $this->model->id(),
|
||||
'image' => $this->image(
|
||||
$params['image'] ?? [],
|
||||
$params['layout'] ?? 'list'
|
||||
),
|
||||
'info' => $this->model->toSafeString($params['info'] ?? false),
|
||||
'link' => $this->url(true),
|
||||
...$item->props(),
|
||||
'sortable' => true,
|
||||
'text' => $this->model->toSafeString($params['text'] ?? false),
|
||||
'uuid' => $this->model->uuid()?->toString()
|
||||
'url' => $this->url(true)
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use Kirby\Cms\File as CmsFile;
|
|||
use Kirby\Cms\ModelWithContent;
|
||||
use Kirby\Filesystem\Asset;
|
||||
use Kirby\Panel\Ui\Buttons\ViewButtons;
|
||||
use Kirby\Panel\Ui\Item\PageItem;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
/**
|
||||
|
|
@ -254,13 +255,18 @@ class Page extends Model
|
|||
*/
|
||||
public function pickerData(array $params = []): array
|
||||
{
|
||||
$params['text'] ??= '{{ page.title }}';
|
||||
$item = new PageItem(
|
||||
page: $this->model,
|
||||
image: $params['image'] ?? null,
|
||||
info: $params['info'] ?? null,
|
||||
layout: $params['layout'] ?? null,
|
||||
text: $params['text'] ?? null,
|
||||
);
|
||||
|
||||
return [
|
||||
...parent::pickerData($params),
|
||||
'dragText' => $this->dragText(),
|
||||
...$item->props(),
|
||||
'hasChildren' => $this->model->hasChildren(),
|
||||
'url' => $this->model->url()
|
||||
'sortable' => true
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,10 +9,13 @@ use Kirby\Cms\PageBlueprint;
|
|||
use Kirby\Cms\PageRules;
|
||||
use Kirby\Cms\Site;
|
||||
use Kirby\Cms\User;
|
||||
use Kirby\Content\MemoryStorage;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Form\Form;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\I18n;
|
||||
use Kirby\Uuid\Uuid;
|
||||
use Kirby\Uuid\Uuids;
|
||||
|
||||
/**
|
||||
* Manages the Panel dialog to create new pages
|
||||
|
|
@ -34,6 +37,7 @@ class PageCreateDialog
|
|||
protected string|null $slug;
|
||||
protected string|null $template;
|
||||
protected string|null $title;
|
||||
protected string|null $uuid;
|
||||
protected Page|Site|User|File $view;
|
||||
protected string|null $viewId;
|
||||
|
||||
|
|
@ -69,6 +73,7 @@ class PageCreateDialog
|
|||
// optional
|
||||
string|null $slug = null,
|
||||
string|null $title = null,
|
||||
string|null $uuid = null,
|
||||
) {
|
||||
$this->parentId = $parentId ?? 'site';
|
||||
$this->parent = Find::parent($this->parentId);
|
||||
|
|
@ -76,6 +81,7 @@ class PageCreateDialog
|
|||
$this->slug = $slug;
|
||||
$this->template = $template;
|
||||
$this->title = $title;
|
||||
$this->uuid = $uuid;
|
||||
$this->viewId = $viewId;
|
||||
$this->view = Find::parent($this->viewId ?? $this->parentId);
|
||||
}
|
||||
|
|
@ -139,6 +145,13 @@ class PageCreateDialog
|
|||
]);
|
||||
}
|
||||
|
||||
// pass uuid field to the dialog if uuids are enabled
|
||||
// to use the same uuid and prevent generating a new one
|
||||
// when the page is created
|
||||
if (Uuids::enabled() === true) {
|
||||
$fields['uuid'] = Field::hidden();
|
||||
}
|
||||
|
||||
return [
|
||||
...$fields,
|
||||
'parent' => Field::hidden(),
|
||||
|
|
@ -154,7 +167,7 @@ class PageCreateDialog
|
|||
public function customFields(): array
|
||||
{
|
||||
$custom = [];
|
||||
$ignore = ['title', 'slug', 'parent', 'template'];
|
||||
$ignore = ['title', 'slug', 'parent', 'template', 'uuid'];
|
||||
$blueprint = $this->blueprint();
|
||||
$fields = $blueprint->fields();
|
||||
|
||||
|
|
@ -255,12 +268,33 @@ class PageCreateDialog
|
|||
*/
|
||||
public function model(): Page
|
||||
{
|
||||
return $this->model ??= Page::factory([
|
||||
if (isset($this->model) === true) {
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
$props = [
|
||||
'slug' => '__new__',
|
||||
'template' => $this->template,
|
||||
'model' => $this->template,
|
||||
'parent' => $this->parent instanceof Page ? $this->parent : null
|
||||
]);
|
||||
];
|
||||
|
||||
// make sure that a UUID gets generated
|
||||
// and added to content right away
|
||||
if (Uuids::enabled() === true) {
|
||||
$props['content'] = [
|
||||
'uuid' => $this->uuid = Uuid::generate()
|
||||
];
|
||||
}
|
||||
|
||||
$this->model = Page::factory($props);
|
||||
|
||||
// change the storage to memory immediately
|
||||
// since this is a temporary model
|
||||
// so that the model does not write to disk
|
||||
$this->model->changeStorage(MemoryStorage::class);
|
||||
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -294,10 +328,15 @@ class PageCreateDialog
|
|||
{
|
||||
$input['title'] ??= $this->title ?? '';
|
||||
$input['slug'] ??= $this->slug ?? '';
|
||||
$input['uuid'] ??= $this->uuid ?? null;
|
||||
|
||||
$input = $this->resolveFieldTemplates($input);
|
||||
$content = ['title' => trim($input['title'])];
|
||||
|
||||
if ($uuid = $input['uuid'] ?? null) {
|
||||
$content['uuid'] = $uuid;
|
||||
}
|
||||
|
||||
foreach ($this->customFields() as $name => $field) {
|
||||
$content[$name] = $input[$name] ?? null;
|
||||
}
|
||||
|
|
@ -377,6 +416,7 @@ class PageCreateDialog
|
|||
'slug' => $this->slug ?? '',
|
||||
'template' => $this->template,
|
||||
'title' => $this->title ?? '',
|
||||
'uuid' => $this->uuid,
|
||||
'view' => $this->viewId,
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,10 @@ abstract class Component
|
|||
return [
|
||||
'component' => $this->component,
|
||||
'key' => $this->key(),
|
||||
'props' => array_filter($this->props())
|
||||
'props' => array_filter(
|
||||
$this->props(),
|
||||
fn ($prop) => $prop !== null
|
||||
)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
74
public/kirby/src/Panel/Ui/Item/FileItem.php
Normal file
74
public/kirby/src/Panel/Ui/Item/FileItem.php
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Panel\Ui\Item;
|
||||
|
||||
use Kirby\Cms\File;
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
use Kirby\Panel\Model;
|
||||
|
||||
/**
|
||||
* @package Kirby Panel
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
* @since 5.1.0
|
||||
*/
|
||||
class FileItem extends ModelItem
|
||||
{
|
||||
/**
|
||||
* @var \Kirby\Cms\File
|
||||
*/
|
||||
protected ModelWithContent $model;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Panel\File
|
||||
*/
|
||||
protected Model $panel;
|
||||
|
||||
public function __construct(
|
||||
File $file,
|
||||
protected bool $dragTextIsAbsolute = false,
|
||||
string|array|false|null $image = [],
|
||||
string|null $info = null,
|
||||
string|null $layout = null,
|
||||
string|null $text = null,
|
||||
) {
|
||||
parent::__construct(
|
||||
model: $file,
|
||||
image: $image,
|
||||
info: $info,
|
||||
layout: $layout,
|
||||
text: $text ?? '{{ file.filename }}',
|
||||
);
|
||||
}
|
||||
|
||||
protected function dragText(): string
|
||||
{
|
||||
return $this->panel->dragText(absolute: $this->dragTextIsAbsolute);
|
||||
}
|
||||
|
||||
protected function permissions(): array
|
||||
{
|
||||
$permissions = $this->model->permissions();
|
||||
|
||||
return [
|
||||
'delete' => $permissions->can('delete'),
|
||||
'sort' => $permissions->can('sort'),
|
||||
];
|
||||
}
|
||||
|
||||
public function props(): array
|
||||
{
|
||||
return [
|
||||
...parent::props(),
|
||||
'dragText' => $this->dragText(),
|
||||
'extension' => $this->model->extension(),
|
||||
'filename' => $this->model->filename(),
|
||||
'mime' => $this->model->mime(),
|
||||
'parent' => $this->model->parent()->panel()->path(),
|
||||
'template' => $this->model->template(),
|
||||
'url' => $this->model->url(),
|
||||
];
|
||||
}
|
||||
}
|
||||
74
public/kirby/src/Panel/Ui/Item/ModelItem.php
Normal file
74
public/kirby/src/Panel/Ui/Item/ModelItem.php
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Panel\Ui\Item;
|
||||
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
use Kirby\Panel\Model as Panel;
|
||||
use Kirby\Panel\Ui\Component;
|
||||
|
||||
/**
|
||||
* @package Kirby Panel
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
* @since 5.1.0
|
||||
*/
|
||||
class ModelItem extends Component
|
||||
{
|
||||
protected string $layout;
|
||||
protected Panel $panel;
|
||||
protected string $text;
|
||||
|
||||
public function __construct(
|
||||
protected ModelWithContent $model,
|
||||
protected string|array|false|null $image = [],
|
||||
protected string|null $info = null,
|
||||
string|null $layout = null,
|
||||
string|null $text = null,
|
||||
) {
|
||||
parent::__construct(component: 'k-item');
|
||||
|
||||
$this->layout = $layout ?? 'list';
|
||||
$this->panel = $this->model->panel();
|
||||
$this->text = $text ?? '{{ model.title }}';
|
||||
}
|
||||
|
||||
protected function info(): string|null
|
||||
{
|
||||
return $this->model->toSafeString($this->info ?? false);
|
||||
}
|
||||
|
||||
protected function image(): array|null
|
||||
{
|
||||
return $this->panel->image($this->image, $this->layout);
|
||||
}
|
||||
|
||||
protected function link(): string
|
||||
{
|
||||
return $this->panel->url(true);
|
||||
}
|
||||
|
||||
protected function permissions(): array
|
||||
{
|
||||
return $this->model->permissions()->toArray();
|
||||
}
|
||||
|
||||
public function props(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->model->id(),
|
||||
'image' => $this->image(),
|
||||
'info' => $this->info(),
|
||||
'link' => $this->link(),
|
||||
'permissions' => $this->permissions(),
|
||||
'text' => $this->text(),
|
||||
'uuid' => $this->model->uuid()?->toString(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function text(): string
|
||||
{
|
||||
return $this->model->toSafeString($this->text);
|
||||
}
|
||||
}
|
||||
74
public/kirby/src/Panel/Ui/Item/PageItem.php
Normal file
74
public/kirby/src/Panel/Ui/Item/PageItem.php
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Panel\Ui\Item;
|
||||
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Panel\Model;
|
||||
|
||||
/**
|
||||
* @package Kirby Panel
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
* @since 5.1.0
|
||||
*/
|
||||
class PageItem extends ModelItem
|
||||
{
|
||||
/**
|
||||
* @var \Kirby\Cms\Page
|
||||
*/
|
||||
protected ModelWithContent $model;
|
||||
|
||||
/**
|
||||
* @var \Kirby\Panel\Page
|
||||
*/
|
||||
protected Model $panel;
|
||||
|
||||
public function __construct(
|
||||
Page $page,
|
||||
string|array|false|null $image = [],
|
||||
string|null $info = null,
|
||||
string|null $layout = null,
|
||||
string|null $text = null,
|
||||
) {
|
||||
parent::__construct(
|
||||
model: $page,
|
||||
image: $image,
|
||||
info: $info,
|
||||
layout: $layout,
|
||||
text: $text ?? '{{ page.title }}',
|
||||
);
|
||||
}
|
||||
|
||||
protected function dragText(): string
|
||||
{
|
||||
return $this->panel->dragText();
|
||||
}
|
||||
|
||||
protected function permissions(): array
|
||||
{
|
||||
$permissions = $this->model->permissions();
|
||||
|
||||
return [
|
||||
'changeSlug' => $permissions->can('changeSlug'),
|
||||
'changeStatus' => $permissions->can('changeStatus'),
|
||||
'changeTitle' => $permissions->can('changeTitle'),
|
||||
'delete' => $permissions->can('delete'),
|
||||
'sort' => $permissions->can('sort'),
|
||||
];
|
||||
}
|
||||
|
||||
public function props(): array
|
||||
{
|
||||
return [
|
||||
...parent::props(),
|
||||
'dragText' => $this->dragText(),
|
||||
'parent' => $this->model->parentId(),
|
||||
'status' => $this->model->status(),
|
||||
'template' => $this->model->intendedTemplate()->name(),
|
||||
'url' => $this->model->url(),
|
||||
];
|
||||
}
|
||||
}
|
||||
38
public/kirby/src/Panel/Ui/Item/UserItem.php
Normal file
38
public/kirby/src/Panel/Ui/Item/UserItem.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Panel\Ui\Item;
|
||||
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
use Kirby\Cms\User;
|
||||
|
||||
/**
|
||||
* @package Kirby Panel
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
* @since 5.1.0
|
||||
*/
|
||||
class UserItem extends ModelItem
|
||||
{
|
||||
/**
|
||||
* @var \Kirby\Cms\User
|
||||
*/
|
||||
protected ModelWithContent $model;
|
||||
|
||||
public function __construct(
|
||||
User $user,
|
||||
string|array|false|null $image = [],
|
||||
string|null $info = '{{ user.role.title }}',
|
||||
string|null $layout = null,
|
||||
string|null $text = null,
|
||||
) {
|
||||
parent::__construct(
|
||||
model: $user,
|
||||
image: $image,
|
||||
info: $info,
|
||||
layout: $layout,
|
||||
text: $text ?? '{{ user.username }}',
|
||||
);
|
||||
}
|
||||
}
|
||||
140
public/kirby/src/Panel/Ui/Stat.php
Normal file
140
public/kirby/src/Panel/Ui/Stat.php
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Panel\Ui;
|
||||
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
/**
|
||||
* @package Kirby Panel
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
* @since 5.1.0
|
||||
*/
|
||||
class Stat extends Component
|
||||
{
|
||||
public function __construct(
|
||||
public array|string $label,
|
||||
public array|string $value,
|
||||
public string $component = 'k-stat',
|
||||
public array|string|null $dialog = null,
|
||||
public array|string|null $drawer = null,
|
||||
public string|null $icon = null,
|
||||
public array|string|null $info = null,
|
||||
public array|string|null $link = null,
|
||||
public ModelWithContent|null $model = null,
|
||||
public string|null $theme = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function dialog(): string|null
|
||||
{
|
||||
return $this->stringTemplate(
|
||||
$this->i18n($this->dialog)
|
||||
);
|
||||
}
|
||||
|
||||
public function drawer(): string|null
|
||||
{
|
||||
return $this->stringTemplate(
|
||||
$this->i18n($this->drawer)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress TooFewArguments
|
||||
*/
|
||||
public static function from(
|
||||
array|string $input,
|
||||
ModelWithContent|null $model = null,
|
||||
): static {
|
||||
if ($model !== null) {
|
||||
if (is_string($input) === true) {
|
||||
$input = $model->query($input);
|
||||
|
||||
if (is_array($input) === false) {
|
||||
throw new InvalidArgumentException(
|
||||
message: 'Invalid data from stat query. The query must return an array.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$input['model'] = $model;
|
||||
}
|
||||
|
||||
return new static(...$input);
|
||||
}
|
||||
|
||||
public function icon(): string|null
|
||||
{
|
||||
return $this->stringTemplate($this->icon);
|
||||
}
|
||||
|
||||
public function info(): string|null
|
||||
{
|
||||
return $this->stringTemplate(
|
||||
$this->i18n($this->info)
|
||||
);
|
||||
}
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return $this->stringTemplate(
|
||||
$this->i18n($this->label)
|
||||
);
|
||||
}
|
||||
|
||||
public function link(): string|null
|
||||
{
|
||||
return $this->stringTemplate(
|
||||
$this->i18n($this->link)
|
||||
);
|
||||
}
|
||||
|
||||
public function props(): array
|
||||
{
|
||||
return [
|
||||
'dialog' => $this->dialog(),
|
||||
'drawer' => $this->drawer(),
|
||||
'icon' => $this->icon(),
|
||||
'info' => $this->info(),
|
||||
'label' => $this->label(),
|
||||
'link' => $this->link(),
|
||||
'theme' => $this->theme(),
|
||||
'value' => $this->value(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function stringTemplate(string|null $string = null): string|null
|
||||
{
|
||||
if ($this->model === null) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
if ($string !== null) {
|
||||
return $this->model->toString($string);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function theme(): string|null
|
||||
{
|
||||
return $this->stringTemplate($this->theme);
|
||||
}
|
||||
|
||||
protected function i18n(string|array|null $param = null): string|null
|
||||
{
|
||||
return empty($param) === false ? I18n::translate($param, $param) : null;
|
||||
}
|
||||
|
||||
public function value(): string
|
||||
{
|
||||
return $this->stringTemplate(
|
||||
$this->i18n($this->value)
|
||||
);
|
||||
}
|
||||
}
|
||||
83
public/kirby/src/Panel/Ui/Stats.php
Normal file
83
public/kirby/src/Panel/Ui/Stats.php
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Panel\Ui;
|
||||
|
||||
use Kirby\Cms\ModelWithContent;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @package Kirby Panel
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
* @since 5.1.0
|
||||
*/
|
||||
class Stats extends Component
|
||||
{
|
||||
public function __construct(
|
||||
public string $component = 'k-stats',
|
||||
public ModelWithContent|null $model = null,
|
||||
public array $reports = [],
|
||||
public string $size = 'large',
|
||||
) {
|
||||
}
|
||||
|
||||
public static function from(
|
||||
ModelWithContent $model,
|
||||
array|string $reports,
|
||||
string $size = 'large'
|
||||
): static {
|
||||
if (is_string($reports) === true) {
|
||||
$reports = $model->query($reports);
|
||||
|
||||
if (is_array($reports) === false) {
|
||||
throw new InvalidArgumentException(
|
||||
message: 'Invalid data from stats query. The query must return an array.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new static(
|
||||
model: $model,
|
||||
reports: $reports,
|
||||
size: $size
|
||||
);
|
||||
}
|
||||
|
||||
public function props(): array
|
||||
{
|
||||
return [
|
||||
'reports' => $this->reports(),
|
||||
'size' => $this->size(),
|
||||
];
|
||||
}
|
||||
|
||||
public function reports(): array
|
||||
{
|
||||
$reports = [];
|
||||
|
||||
foreach ($this->reports as $stat) {
|
||||
// if not already a Stat object, convert it
|
||||
if ($stat instanceof Stat === false) {
|
||||
try {
|
||||
$stat = Stat::from(
|
||||
input: $stat,
|
||||
model: $this->model
|
||||
);
|
||||
} catch (InvalidArgumentException) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$reports[] = $stat->props();
|
||||
}
|
||||
|
||||
return $reports;
|
||||
}
|
||||
|
||||
public function size(): string
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
}
|
||||
62
public/kirby/src/Panel/Ui/Upload.php
Normal file
62
public/kirby/src/Panel/Ui/Upload.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Panel\Ui;
|
||||
|
||||
/**
|
||||
* @package Kirby Panel
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
* @since 5.1.0
|
||||
*/
|
||||
class Upload
|
||||
{
|
||||
public function __construct(
|
||||
protected string $api,
|
||||
protected string|null $accept = null,
|
||||
protected array $attributes = [],
|
||||
protected int|null $max = null,
|
||||
protected bool $multiple = true,
|
||||
protected array|bool|null $preview = null,
|
||||
protected int|null $sort = null,
|
||||
protected string|null $template = null,
|
||||
) {
|
||||
}
|
||||
|
||||
protected function attributes(): array
|
||||
{
|
||||
return [
|
||||
...$this->attributes,
|
||||
'sort' => $this->sort,
|
||||
'template' => $this->template()
|
||||
];
|
||||
}
|
||||
|
||||
protected function max(): int|null
|
||||
{
|
||||
return $this->multiple() === false ? 1 : $this->max;
|
||||
}
|
||||
|
||||
protected function multiple(): bool
|
||||
{
|
||||
return $this->multiple === true && ($this->max === null || $this->max > 1);
|
||||
}
|
||||
|
||||
public function props(): array
|
||||
{
|
||||
return [
|
||||
'accept' => $this->accept,
|
||||
'api' => $this->api,
|
||||
'attributes' => $this->attributes(),
|
||||
'max' => $this->max(),
|
||||
'multiple' => $this->multiple(),
|
||||
'preview' => $this->preview,
|
||||
];
|
||||
}
|
||||
|
||||
protected function template(): string|null
|
||||
{
|
||||
return $this->template === 'default' ? null : $this->template;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ use Kirby\Cms\Translation;
|
|||
use Kirby\Cms\Url;
|
||||
use Kirby\Filesystem\Asset;
|
||||
use Kirby\Panel\Ui\Buttons\ViewButtons;
|
||||
use Kirby\Panel\Ui\Item\UserItem;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
/**
|
||||
|
|
@ -200,11 +201,18 @@ class User extends Model
|
|||
*/
|
||||
public function pickerData(array $params = []): array
|
||||
{
|
||||
$params['text'] ??= '{{ user.username }}';
|
||||
$item = new UserItem(
|
||||
user: $this->model,
|
||||
image: $params['image'] ?? null,
|
||||
info: $params['info'] ?? null,
|
||||
layout: $params['layout'] ?? null,
|
||||
text: $params['text'] ?? null,
|
||||
);
|
||||
|
||||
return [
|
||||
...parent::pickerData($params),
|
||||
...$item->props(),
|
||||
'email' => $this->model->email(),
|
||||
'sortable' => true,
|
||||
'username' => $this->model->username(),
|
||||
];
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue