VYPR
Moderate severityOSV Advisory· Published Jan 18, 2026· Updated Jan 20, 2026

Kimai Vulnerable to Authenticated Server-Side Template Injection (SSTI)

CVE-2026-23626

Description

Kimai is a web-based multi-user time-tracking application. Prior to version 2.46.0, Kimai's export functionality uses a Twig sandbox with an overly permissive security policy (DefaultPolicy) that allows arbitrary method calls on objects available in the template context. An authenticated user with export permissions can deploy a malicious Twig template that extracts sensitive information including environment variables, all user password hashes, serialized session tokens, and CSRF tokens. Version 2.46.0 patches this issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
kimai/kimaiPackagist
< 2.46.02.46.0

Affected products

1

Patches

1
6a86afb5fd79

Release 2.46 (#5757)

https://github.com/kimai/kimaiKevin PapstJan 6, 2026via ghsa
55 files changed · +1578 1191
  • assets/js/forms/KimaiAutocompleteTags.js+1 1 modified
    @@ -24,7 +24,7 @@ export default class KimaiAutocompleteTags extends KimaiAutocomplete {
             API.get(apiUrl, {'name': query}, (data) => {
                 let results = [];
                 for (let item of data) {
    -                results.push({text: item.name, value: item.name, color: item.color});
    +                results.push({text: item.name, value: item.name, color: item['color-safe']});
                 }
                 callback(results);
             }, () => {
    
  • composer.lock+195 202 modified
    @@ -8,16 +8,16 @@
         "packages": [
             {
                 "name": "azuyalabs/yasumi",
    -            "version": "2.8.0",
    +            "version": "2.9.0",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/azuyalabs/yasumi.git",
    -                "reference": "cc07874da062070fdc201a4297a7724f7d6aafb2"
    +                "reference": "36005a0796b64bc07257f39c11c42f147fe5320f"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/azuyalabs/yasumi/zipball/cc07874da062070fdc201a4297a7724f7d6aafb2",
    -                "reference": "cc07874da062070fdc201a4297a7724f7d6aafb2",
    +                "url": "https://api.github.com/repos/azuyalabs/yasumi/zipball/36005a0796b64bc07257f39c11c42f147fe5320f",
    +                "reference": "36005a0796b64bc07257f39c11c42f147fe5320f",
                     "shasum": ""
                 },
                 "require": {
    @@ -30,8 +30,7 @@
                     "mikey179/vfsstream": "^1.6",
                     "phpstan/phpstan": "^2.1",
                     "phpstan/phpstan-deprecation-rules": "^2.0",
    -                "phpunit/phpunit": "^8.5 || ^9.6",
    -                "vimeo/psalm": "^6.12"
    +                "phpunit/phpunit": "^8.5 || ^9.6"
                 },
                 "suggest": {
                     "ext-calendar": "For calculating the date of Easter"
    @@ -78,7 +77,7 @@
                         "type": "other"
                     }
                 ],
    -            "time": "2025-07-13T15:40:05+00:00"
    +            "time": "2025-12-29T16:13:54+00:00"
             },
             {
                 "name": "bacon/bacon-qr-code",
    @@ -2798,16 +2797,16 @@
             },
             {
                 "name": "league/csv",
    -            "version": "9.27.1",
    +            "version": "9.28.0",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/thephpleague/csv.git",
    -                "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797"
    +                "reference": "6582ace29ae09ba5b07049d40ea13eb19c8b5073"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/thephpleague/csv/zipball/26de738b8fccf785397d05ee2fc07b6cd8749797",
    -                "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797",
    +                "url": "https://api.github.com/repos/thephpleague/csv/zipball/6582ace29ae09ba5b07049d40ea13eb19c8b5073",
    +                "reference": "6582ace29ae09ba5b07049d40ea13eb19c8b5073",
                     "shasum": ""
                 },
                 "require": {
    @@ -2817,14 +2816,14 @@
                 "require-dev": {
                     "ext-dom": "*",
                     "ext-xdebug": "*",
    -                "friendsofphp/php-cs-fixer": "^3.75.0",
    -                "phpbench/phpbench": "^1.4.1",
    -                "phpstan/phpstan": "^1.12.27",
    +                "friendsofphp/php-cs-fixer": "^3.92.3",
    +                "phpbench/phpbench": "^1.4.3",
    +                "phpstan/phpstan": "^1.12.32",
                     "phpstan/phpstan-deprecation-rules": "^1.2.1",
                     "phpstan/phpstan-phpunit": "^1.4.2",
                     "phpstan/phpstan-strict-rules": "^1.6.2",
    -                "phpunit/phpunit": "^10.5.16 || ^11.5.22 || ^12.3.6",
    -                "symfony/var-dumper": "^6.4.8 || ^7.3.0"
    +                "phpunit/phpunit": "^10.5.16 || ^11.5.22 || ^12.5.4",
    +                "symfony/var-dumper": "^6.4.8 || ^7.4.0 || ^8.0"
                 },
                 "suggest": {
                     "ext-dom": "Required to use the XMLConverter and the HTMLConverter classes",
    @@ -2885,7 +2884,7 @@
                         "type": "github"
                     }
                 ],
    -            "time": "2025-10-25T08:35:20+00:00"
    +            "time": "2025-12-27T15:18:42+00:00"
             },
             {
                 "name": "lorenzo/pinky",
    @@ -3126,16 +3125,16 @@
             },
             {
                 "name": "monolog/monolog",
    -            "version": "3.9.0",
    +            "version": "3.10.0",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/Seldaek/monolog.git",
    -                "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6"
    +                "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6",
    -                "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6",
    +                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0",
    +                "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0",
                     "shasum": ""
                 },
                 "require": {
    @@ -3153,7 +3152,7 @@
                     "graylog2/gelf-php": "^1.4.2 || ^2.0",
                     "guzzlehttp/guzzle": "^7.4.5",
                     "guzzlehttp/psr7": "^2.2",
    -                "mongodb/mongodb": "^1.8",
    +                "mongodb/mongodb": "^1.8 || ^2.0",
                     "php-amqplib/php-amqplib": "~2.4 || ^3",
                     "php-console/php-console": "^3.1.8",
                     "phpstan/phpstan": "^2",
    @@ -3213,7 +3212,7 @@
                 ],
                 "support": {
                     "issues": "https://github.com/Seldaek/monolog/issues",
    -                "source": "https://github.com/Seldaek/monolog/tree/3.9.0"
    +                "source": "https://github.com/Seldaek/monolog/tree/3.10.0"
                 },
                 "funding": [
                     {
    @@ -3225,7 +3224,7 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-03-24T10:02:05+00:00"
    +            "time": "2026-01-02T08:56:05+00:00"
             },
             {
                 "name": "mpdf/mpdf",
    @@ -5412,38 +5411,26 @@
             },
             {
                 "name": "spomky-labs/otphp",
    -            "version": "11.3.0",
    +            "version": "11.4.1",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/Spomky-Labs/otphp.git",
    -                "reference": "2d8ccb5fc992b9cc65ef321fa4f00fefdb3f4b33"
    +                "reference": "126c99b6cbbc18992cf3fba3b87931ba4e312482"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/2d8ccb5fc992b9cc65ef321fa4f00fefdb3f4b33",
    -                "reference": "2d8ccb5fc992b9cc65ef321fa4f00fefdb3f4b33",
    +                "url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/126c99b6cbbc18992cf3fba3b87931ba4e312482",
    +                "reference": "126c99b6cbbc18992cf3fba3b87931ba4e312482",
                     "shasum": ""
                 },
                 "require": {
    -                "ext-mbstring": "*",
                     "paragonie/constant_time_encoding": "^2.0 || ^3.0",
                     "php": ">=8.1",
                     "psr/clock": "^1.0",
                     "symfony/deprecation-contracts": "^3.2"
                 },
                 "require-dev": {
    -                "ekino/phpstan-banned-code": "^1.0",
    -                "infection/infection": "^0.26|^0.27|^0.28|^0.29",
    -                "php-parallel-lint/php-parallel-lint": "^1.3",
    -                "phpstan/phpstan": "^1.0",
    -                "phpstan/phpstan-deprecation-rules": "^1.0",
    -                "phpstan/phpstan-phpunit": "^1.0",
    -                "phpstan/phpstan-strict-rules": "^1.0",
    -                "phpunit/phpunit": "^9.5.26|^10.0|^11.0",
    -                "qossmic/deptrac-shim": "^1.0",
    -                "rector/rector": "^1.0",
    -                "symfony/phpunit-bridge": "^6.1|^7.0",
    -                "symplify/easy-coding-standard": "^12.0"
    +                "symfony/error-handler": "^6.4|^7.0|^8.0"
                 },
                 "type": "library",
                 "autoload": {
    @@ -5478,7 +5465,7 @@
                 ],
                 "support": {
                     "issues": "https://github.com/Spomky-Labs/otphp/issues",
    -                "source": "https://github.com/Spomky-Labs/otphp/tree/11.3.0"
    +                "source": "https://github.com/Spomky-Labs/otphp/tree/11.4.1"
                 },
                 "funding": [
                     {
    @@ -5490,7 +5477,7 @@
                         "type": "patreon"
                     }
                 ],
    -            "time": "2024-06-12T11:22:32+00:00"
    +            "time": "2026-01-05T13:20:36+00:00"
             },
             {
                 "name": "symfony/asset",
    @@ -5567,16 +5554,16 @@
             },
             {
                 "name": "symfony/cache",
    -            "version": "v6.4.30",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/cache.git",
    -                "reference": "eb3272ed2daed13ed24816e862d73f73d995972a"
    +                "reference": "a1b306757c34b96fe97c0c586f50dceed05c7adb"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/cache/zipball/eb3272ed2daed13ed24816e862d73f73d995972a",
    -                "reference": "eb3272ed2daed13ed24816e862d73f73d995972a",
    +                "url": "https://api.github.com/repos/symfony/cache/zipball/a1b306757c34b96fe97c0c586f50dceed05c7adb",
    +                "reference": "a1b306757c34b96fe97c0c586f50dceed05c7adb",
                     "shasum": ""
                 },
                 "require": {
    @@ -5643,7 +5630,7 @@
                     "psr6"
                 ],
                 "support": {
    -                "source": "https://github.com/symfony/cache/tree/v6.4.30"
    +                "source": "https://github.com/symfony/cache/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -5663,7 +5650,7 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-12-01T16:41:59+00:00"
    +            "time": "2025-12-27T18:26:25+00:00"
             },
             {
                 "name": "symfony/cache-contracts",
    @@ -5900,16 +5887,16 @@
             },
             {
                 "name": "symfony/console",
    -            "version": "v6.4.30",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/console.git",
    -                "reference": "1b2813049506b39eb3d7e64aff033fd5ca26c97e"
    +                "reference": "f9f8a889f54c264f9abac3fc0f7a371ffca51997"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/console/zipball/1b2813049506b39eb3d7e64aff033fd5ca26c97e",
    -                "reference": "1b2813049506b39eb3d7e64aff033fd5ca26c97e",
    +                "url": "https://api.github.com/repos/symfony/console/zipball/f9f8a889f54c264f9abac3fc0f7a371ffca51997",
    +                "reference": "f9f8a889f54c264f9abac3fc0f7a371ffca51997",
                     "shasum": ""
                 },
                 "require": {
    @@ -5974,7 +5961,7 @@
                     "terminal"
                 ],
                 "support": {
    -                "source": "https://github.com/symfony/console/tree/v6.4.30"
    +                "source": "https://github.com/symfony/console/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -5994,7 +5981,7 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-12-05T13:47:41+00:00"
    +            "time": "2025-12-22T08:30:34+00:00"
             },
             {
                 "name": "symfony/css-selector",
    @@ -6067,16 +6054,16 @@
             },
             {
                 "name": "symfony/dependency-injection",
    -            "version": "v6.4.30",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/dependency-injection.git",
    -                "reference": "5328f994cbb0855ba25c3a54f4a31a279511640f"
    +                "reference": "10058832a74a33648870aa2057e3fdc8796a6566"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/5328f994cbb0855ba25c3a54f4a31a279511640f",
    -                "reference": "5328f994cbb0855ba25c3a54f4a31a279511640f",
    +                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/10058832a74a33648870aa2057e3fdc8796a6566",
    +                "reference": "10058832a74a33648870aa2057e3fdc8796a6566",
                     "shasum": ""
                 },
                 "require": {
    @@ -6128,7 +6115,7 @@
                 "description": "Allows you to standardize and centralize the way objects are constructed in your application",
                 "homepage": "https://symfony.com",
                 "support": {
    -                "source": "https://github.com/symfony/dependency-injection/tree/v6.4.30"
    +                "source": "https://github.com/symfony/dependency-injection/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -6148,7 +6135,7 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-12-07T09:29:59+00:00"
    +            "time": "2025-12-23T13:34:50+00:00"
             },
             {
                 "name": "symfony/deprecation-contracts",
    @@ -6786,16 +6773,16 @@
             },
             {
                 "name": "symfony/finder",
    -            "version": "v6.4.27",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/finder.git",
    -                "reference": "a1b6aa435d2fba50793b994a839c32b6064f063b"
    +                "reference": "5547f2e1f0ca8e2e7abe490156b62da778cfbe2b"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/finder/zipball/a1b6aa435d2fba50793b994a839c32b6064f063b",
    -                "reference": "a1b6aa435d2fba50793b994a839c32b6064f063b",
    +                "url": "https://api.github.com/repos/symfony/finder/zipball/5547f2e1f0ca8e2e7abe490156b62da778cfbe2b",
    +                "reference": "5547f2e1f0ca8e2e7abe490156b62da778cfbe2b",
                     "shasum": ""
                 },
                 "require": {
    @@ -6830,7 +6817,7 @@
                 "description": "Finds files and directories via an intuitive fluent interface",
                 "homepage": "https://symfony.com",
                 "support": {
    -                "source": "https://github.com/symfony/finder/tree/v6.4.27"
    +                "source": "https://github.com/symfony/finder/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -6850,7 +6837,7 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-10-15T18:32:00+00:00"
    +            "time": "2025-12-11T14:52:17+00:00"
             },
             {
                 "name": "symfony/flex",
    @@ -6927,16 +6914,16 @@
             },
             {
                 "name": "symfony/form",
    -            "version": "v6.4.30",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/form.git",
    -                "reference": "e6378bc56d5d56db65754a09c91a7cf7c35b44db"
    +                "reference": "a518332215c2a54784bf85bdd293a7f700f1d153"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/form/zipball/e6378bc56d5d56db65754a09c91a7cf7c35b44db",
    -                "reference": "e6378bc56d5d56db65754a09c91a7cf7c35b44db",
    +                "url": "https://api.github.com/repos/symfony/form/zipball/a518332215c2a54784bf85bdd293a7f700f1d153",
    +                "reference": "a518332215c2a54784bf85bdd293a7f700f1d153",
                     "shasum": ""
                 },
                 "require": {
    @@ -7004,7 +6991,7 @@
                 "description": "Allows to easily create, process and reuse HTML forms",
                 "homepage": "https://symfony.com",
                 "support": {
    -                "source": "https://github.com/symfony/form/tree/v6.4.30"
    +                "source": "https://github.com/symfony/form/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -7024,20 +7011,20 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-12-05T07:26:17+00:00"
    +            "time": "2025-12-23T08:34:29+00:00"
             },
             {
                 "name": "symfony/framework-bundle",
    -            "version": "v6.4.30",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/framework-bundle.git",
    -                "reference": "3c212ec5cac588da8357f5c061194363a4e91010"
    +                "reference": "0ab60c05570b9e2bfab92b9944b938b8ffb5ba96"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/3c212ec5cac588da8357f5c061194363a4e91010",
    -                "reference": "3c212ec5cac588da8357f5c061194363a4e91010",
    +                "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/0ab60c05570b9e2bfab92b9944b938b8ffb5ba96",
    +                "reference": "0ab60c05570b9e2bfab92b9944b938b8ffb5ba96",
                     "shasum": ""
                 },
                 "require": {
    @@ -7157,7 +7144,7 @@
                 "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework",
                 "homepage": "https://symfony.com",
                 "support": {
    -                "source": "https://github.com/symfony/framework-bundle/tree/v6.4.30"
    +                "source": "https://github.com/symfony/framework-bundle/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -7177,20 +7164,20 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-11-29T11:31:32+00:00"
    +            "time": "2025-12-23T14:16:13+00:00"
             },
             {
                 "name": "symfony/http-client",
    -            "version": "v6.4.30",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/http-client.git",
    -                "reference": "a67de2002d72f76ce7c57f830c4df503febc8d77"
    +                "reference": "f166fe476c996237666bcf7ec2cf827cd82ad573"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/http-client/zipball/a67de2002d72f76ce7c57f830c4df503febc8d77",
    -                "reference": "a67de2002d72f76ce7c57f830c4df503febc8d77",
    +                "url": "https://api.github.com/repos/symfony/http-client/zipball/f166fe476c996237666bcf7ec2cf827cd82ad573",
    +                "reference": "f166fe476c996237666bcf7ec2cf827cd82ad573",
                     "shasum": ""
                 },
                 "require": {
    @@ -7255,7 +7242,7 @@
                     "http"
                 ],
                 "support": {
    -                "source": "https://github.com/symfony/http-client/tree/v6.4.30"
    +                "source": "https://github.com/symfony/http-client/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -7275,7 +7262,7 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-12-04T16:42:50+00:00"
    +            "time": "2025-12-23T14:19:38+00:00"
             },
             {
                 "name": "symfony/http-client-contracts",
    @@ -7357,16 +7344,16 @@
             },
             {
                 "name": "symfony/http-foundation",
    -            "version": "v6.4.30",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/http-foundation.git",
    -                "reference": "0384c62b79d96e9b22d77bc1272c9e83342ba3a6"
    +                "reference": "a35ee6f47e4775179704d7877a8b0da3cb09241a"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/0384c62b79d96e9b22d77bc1272c9e83342ba3a6",
    -                "reference": "0384c62b79d96e9b22d77bc1272c9e83342ba3a6",
    +                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a35ee6f47e4775179704d7877a8b0da3cb09241a",
    +                "reference": "a35ee6f47e4775179704d7877a8b0da3cb09241a",
                     "shasum": ""
                 },
                 "require": {
    @@ -7414,7 +7401,7 @@
                 "description": "Defines an object-oriented layer for the HTTP specification",
                 "homepage": "https://symfony.com",
                 "support": {
    -                "source": "https://github.com/symfony/http-foundation/tree/v6.4.30"
    +                "source": "https://github.com/symfony/http-foundation/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -7434,20 +7421,20 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-12-01T20:07:31+00:00"
    +            "time": "2025-12-17T10:10:57+00:00"
             },
             {
                 "name": "symfony/http-kernel",
    -            "version": "v6.4.30",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/http-kernel.git",
    -                "reference": "ceac681e74e824bbf90468eb231d40988d6d18a5"
    +                "reference": "16b0d46d8e11f480345c15b229cfc827a8a0f731"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/ceac681e74e824bbf90468eb231d40988d6d18a5",
    -                "reference": "ceac681e74e824bbf90468eb231d40988d6d18a5",
    +                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/16b0d46d8e11f480345c15b229cfc827a8a0f731",
    +                "reference": "16b0d46d8e11f480345c15b229cfc827a8a0f731",
                     "shasum": ""
                 },
                 "require": {
    @@ -7532,7 +7519,7 @@
                 "description": "Provides a structured process for converting a Request into a Response",
                 "homepage": "https://symfony.com",
                 "support": {
    -                "source": "https://github.com/symfony/http-kernel/tree/v6.4.30"
    +                "source": "https://github.com/symfony/http-kernel/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -7552,7 +7539,7 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-12-07T15:49:34+00:00"
    +            "time": "2025-12-31T08:27:27+00:00"
             },
             {
                 "name": "symfony/intl",
    @@ -7643,16 +7630,16 @@
             },
             {
                 "name": "symfony/mailer",
    -            "version": "v6.4.27",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/mailer.git",
    -                "reference": "2f096718ed718996551f66e3a24e12b2ed027f95"
    +                "reference": "8835f93333474780fda1b987cae37e33c3e026ca"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/mailer/zipball/2f096718ed718996551f66e3a24e12b2ed027f95",
    -                "reference": "2f096718ed718996551f66e3a24e12b2ed027f95",
    +                "url": "https://api.github.com/repos/symfony/mailer/zipball/8835f93333474780fda1b987cae37e33c3e026ca",
    +                "reference": "8835f93333474780fda1b987cae37e33c3e026ca",
                     "shasum": ""
                 },
                 "require": {
    @@ -7703,7 +7690,7 @@
                 "description": "Helps sending emails",
                 "homepage": "https://symfony.com",
                 "support": {
    -                "source": "https://github.com/symfony/mailer/tree/v6.4.27"
    +                "source": "https://github.com/symfony/mailer/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -7723,7 +7710,7 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-10-24T13:29:09+00:00"
    +            "time": "2025-12-12T07:33:25+00:00"
             },
             {
                 "name": "symfony/mime",
    @@ -8286,16 +8273,16 @@
             },
             {
                 "name": "symfony/process",
    -            "version": "v6.4.26",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/process.git",
    -                "reference": "48bad913268c8cafabbf7034b39c8bb24fbc5ab8"
    +                "reference": "8541b7308fca001320e90bca8a73a28aa5604a6e"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/process/zipball/48bad913268c8cafabbf7034b39c8bb24fbc5ab8",
    -                "reference": "48bad913268c8cafabbf7034b39c8bb24fbc5ab8",
    +                "url": "https://api.github.com/repos/symfony/process/zipball/8541b7308fca001320e90bca8a73a28aa5604a6e",
    +                "reference": "8541b7308fca001320e90bca8a73a28aa5604a6e",
                     "shasum": ""
                 },
                 "require": {
    @@ -8327,7 +8314,7 @@
                 "description": "Executes commands in sub-processes",
                 "homepage": "https://symfony.com",
                 "support": {
    -                "source": "https://github.com/symfony/process/tree/v6.4.26"
    +                "source": "https://github.com/symfony/process/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -8347,26 +8334,26 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-09-11T09:57:09+00:00"
    +            "time": "2025-12-15T19:26:35+00:00"
             },
             {
                 "name": "symfony/property-access",
    -            "version": "v6.4.25",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/property-access.git",
    -                "reference": "fedc771326d4978a7d3167fa009a509b06a2e168"
    +                "reference": "1b1044599d7fb93cdb82f5a1291ba66f1caf6119"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/property-access/zipball/fedc771326d4978a7d3167fa009a509b06a2e168",
    -                "reference": "fedc771326d4978a7d3167fa009a509b06a2e168",
    +                "url": "https://api.github.com/repos/symfony/property-access/zipball/1b1044599d7fb93cdb82f5a1291ba66f1caf6119",
    +                "reference": "1b1044599d7fb93cdb82f5a1291ba66f1caf6119",
                     "shasum": ""
                 },
                 "require": {
                     "php": ">=8.1",
                     "symfony/deprecation-contracts": "^2.5|^3",
    -                "symfony/property-info": "^5.4|^6.0|^7.0"
    +                "symfony/property-info": "^6.4.31|~7.3.9|^7.4.2"
                 },
                 "require-dev": {
                     "symfony/cache": "^5.4|^6.0|^7.0"
    @@ -8408,7 +8395,7 @@
                     "reflection"
                 ],
                 "support": {
    -                "source": "https://github.com/symfony/property-access/tree/v6.4.25"
    +                "source": "https://github.com/symfony/property-access/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -8428,20 +8415,20 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-08-12T15:42:57+00:00"
    +            "time": "2025-12-18T08:11:26+00:00"
             },
             {
                 "name": "symfony/property-info",
    -            "version": "v6.4.30",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/property-info.git",
    -                "reference": "13243e748cb77b3d2300c0bffa21c2d325dd6e98"
    +                "reference": "f155cef234af1c16ed47791d182e146df237b35f"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/property-info/zipball/13243e748cb77b3d2300c0bffa21c2d325dd6e98",
    -                "reference": "13243e748cb77b3d2300c0bffa21c2d325dd6e98",
    +                "url": "https://api.github.com/repos/symfony/property-info/zipball/f155cef234af1c16ed47791d182e146df237b35f",
    +                "reference": "f155cef234af1c16ed47791d182e146df237b35f",
                     "shasum": ""
                 },
                 "require": {
    @@ -8498,7 +8485,7 @@
                     "validator"
                 ],
                 "support": {
    -                "source": "https://github.com/symfony/property-info/tree/v6.4.30"
    +                "source": "https://github.com/symfony/property-info/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -8518,20 +8505,20 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-11-29T16:02:37+00:00"
    +            "time": "2025-12-16T19:55:30+00:00"
             },
             {
                 "name": "symfony/rate-limiter",
    -            "version": "v6.4.24",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/rate-limiter.git",
    -                "reference": "15a9a10fd0f060243c88ce98d5c29e9cc4b886e7"
    +                "reference": "0e4128071df0b0b75617bd51304ccd94c2f1fe04"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/15a9a10fd0f060243c88ce98d5c29e9cc4b886e7",
    -                "reference": "15a9a10fd0f060243c88ce98d5c29e9cc4b886e7",
    +                "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/0e4128071df0b0b75617bd51304ccd94c2f1fe04",
    +                "reference": "0e4128071df0b0b75617bd51304ccd94c2f1fe04",
                     "shasum": ""
                 },
                 "require": {
    @@ -8573,7 +8560,7 @@
                     "rate-limiter"
                 ],
                 "support": {
    -                "source": "https://github.com/symfony/rate-limiter/tree/v6.4.24"
    +                "source": "https://github.com/symfony/rate-limiter/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -8593,7 +8580,7 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-07-10T08:14:14+00:00"
    +            "time": "2025-12-14T07:59:41+00:00"
             },
             {
                 "name": "symfony/routing",
    @@ -8883,16 +8870,16 @@
             },
             {
                 "name": "symfony/security-core",
    -            "version": "v6.4.27",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/security-core.git",
    -                "reference": "673018434b38e504eb04ca3c6d7e2e7c86735bfb"
    +                "reference": "fa269ad61a021cc54329dc96e57bed78ba720bfe"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/security-core/zipball/673018434b38e504eb04ca3c6d7e2e7c86735bfb",
    -                "reference": "673018434b38e504eb04ca3c6d7e2e7c86735bfb",
    +                "url": "https://api.github.com/repos/symfony/security-core/zipball/fa269ad61a021cc54329dc96e57bed78ba720bfe",
    +                "reference": "fa269ad61a021cc54329dc96e57bed78ba720bfe",
                     "shasum": ""
                 },
                 "require": {
    @@ -8949,7 +8936,7 @@
                 "description": "Symfony Security Component - Core Library",
                 "homepage": "https://symfony.com",
                 "support": {
    -                "source": "https://github.com/symfony/security-core/tree/v6.4.27"
    +                "source": "https://github.com/symfony/security-core/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -8969,20 +8956,20 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-10-23T19:49:35+00:00"
    +            "time": "2025-12-17T22:32:13+00:00"
             },
             {
                 "name": "symfony/security-csrf",
    -            "version": "v6.4.24",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/security-csrf.git",
    -                "reference": "9a1efc8c10b86bcedc9233affd10c716b54ca1b7"
    +                "reference": "52f62836fcb19cd351ef3a2aa9cf61a489e8990f"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/security-csrf/zipball/9a1efc8c10b86bcedc9233affd10c716b54ca1b7",
    -                "reference": "9a1efc8c10b86bcedc9233affd10c716b54ca1b7",
    +                "url": "https://api.github.com/repos/symfony/security-csrf/zipball/52f62836fcb19cd351ef3a2aa9cf61a489e8990f",
    +                "reference": "52f62836fcb19cd351ef3a2aa9cf61a489e8990f",
                     "shasum": ""
                 },
                 "require": {
    @@ -9021,7 +9008,7 @@
                 "description": "Symfony Security Component - CSRF Library",
                 "homepage": "https://symfony.com",
                 "support": {
    -                "source": "https://github.com/symfony/security-csrf/tree/v6.4.24"
    +                "source": "https://github.com/symfony/security-csrf/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -9041,20 +9028,20 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-07-10T08:14:14+00:00"
    +            "time": "2025-12-17T22:32:13+00:00"
             },
             {
                 "name": "symfony/security-http",
    -            "version": "v6.4.30",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/security-http.git",
    -                "reference": "5705ae14ed77b38ac99990959d8c6dbcba86e513"
    +                "reference": "2f4ddc4a79b0e6f7dcc72e5ebd7109b3436b967a"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/security-http/zipball/5705ae14ed77b38ac99990959d8c6dbcba86e513",
    -                "reference": "5705ae14ed77b38ac99990959d8c6dbcba86e513",
    +                "url": "https://api.github.com/repos/symfony/security-http/zipball/2f4ddc4a79b0e6f7dcc72e5ebd7109b3436b967a",
    +                "reference": "2f4ddc4a79b0e6f7dcc72e5ebd7109b3436b967a",
                     "shasum": ""
                 },
                 "require": {
    @@ -9113,7 +9100,7 @@
                 "description": "Symfony Security Component - HTTP Integration",
                 "homepage": "https://symfony.com",
                 "support": {
    -                "source": "https://github.com/symfony/security-http/tree/v6.4.30"
    +                "source": "https://github.com/symfony/security-http/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -9133,20 +9120,20 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-11-12T13:33:48+00:00"
    +            "time": "2025-12-17T22:32:13+00:00"
             },
             {
                 "name": "symfony/serializer",
    -            "version": "v6.4.30",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/serializer.git",
    -                "reference": "d7976be554af097c788d7df25e10dd99facbfe65"
    +                "reference": "abf80f880943224afca831d7da6eff584c3af751"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/serializer/zipball/d7976be554af097c788d7df25e10dd99facbfe65",
    -                "reference": "d7976be554af097c788d7df25e10dd99facbfe65",
    +                "url": "https://api.github.com/repos/symfony/serializer/zipball/abf80f880943224afca831d7da6eff584c3af751",
    +                "reference": "abf80f880943224afca831d7da6eff584c3af751",
                     "shasum": ""
                 },
                 "require": {
    @@ -9215,7 +9202,7 @@
                 "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.",
                 "homepage": "https://symfony.com",
                 "support": {
    -                "source": "https://github.com/symfony/serializer/tree/v6.4.30"
    +                "source": "https://github.com/symfony/serializer/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -9235,7 +9222,7 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-11-12T13:46:18+00:00"
    +            "time": "2025-12-19T17:17:42+00:00"
             },
             {
                 "name": "symfony/service-contracts",
    @@ -9481,16 +9468,16 @@
             },
             {
                 "name": "symfony/translation",
    -            "version": "v6.4.30",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/translation.git",
    -                "reference": "d1fdeefd0707d15eb150c04e8837bf0b15ebea39"
    +                "reference": "81579408ecf7dc5aa2d8462a6d5c3a430a80e6f2"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/translation/zipball/d1fdeefd0707d15eb150c04e8837bf0b15ebea39",
    -                "reference": "d1fdeefd0707d15eb150c04e8837bf0b15ebea39",
    +                "url": "https://api.github.com/repos/symfony/translation/zipball/81579408ecf7dc5aa2d8462a6d5c3a430a80e6f2",
    +                "reference": "81579408ecf7dc5aa2d8462a6d5c3a430a80e6f2",
                     "shasum": ""
                 },
                 "require": {
    @@ -9556,7 +9543,7 @@
                 "description": "Provides tools to internationalize your application",
                 "homepage": "https://symfony.com",
                 "support": {
    -                "source": "https://github.com/symfony/translation/tree/v6.4.30"
    +                "source": "https://github.com/symfony/translation/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -9576,7 +9563,7 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-11-24T13:57:00+00:00"
    +            "time": "2025-12-18T11:37:55+00:00"
             },
             {
                 "name": "symfony/translation-contracts",
    @@ -9662,16 +9649,16 @@
             },
             {
                 "name": "symfony/twig-bridge",
    -            "version": "v6.4.30",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/twig-bridge.git",
    -                "reference": "d77a78c7fffaf7cb0158d28db824ba78d89a9f34"
    +                "reference": "24a498d80fd2a28087fbac0a96e0721ce2756b65"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/d77a78c7fffaf7cb0158d28db824ba78d89a9f34",
    -                "reference": "d77a78c7fffaf7cb0158d28db824ba78d89a9f34",
    +                "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/24a498d80fd2a28087fbac0a96e0721ce2756b65",
    +                "reference": "24a498d80fd2a28087fbac0a96e0721ce2756b65",
                     "shasum": ""
                 },
                 "require": {
    @@ -9751,7 +9738,7 @@
                 "description": "Provides integration for Twig with various Symfony components",
                 "homepage": "https://symfony.com",
                 "support": {
    -                "source": "https://github.com/symfony/twig-bridge/tree/v6.4.30"
    +                "source": "https://github.com/symfony/twig-bridge/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -9771,20 +9758,20 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-12-05T13:01:31+00:00"
    +            "time": "2025-12-11T18:16:47+00:00"
             },
             {
                 "name": "symfony/twig-bundle",
    -            "version": "v6.4.24",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/twig-bundle.git",
    -                "reference": "3b48b6e8225495c6d2438828982b4d219ca565ba"
    +                "reference": "df14e1b81483b04534317e8f61617560ab60777d"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/3b48b6e8225495c6d2438828982b4d219ca565ba",
    -                "reference": "3b48b6e8225495c6d2438828982b4d219ca565ba",
    +                "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/df14e1b81483b04534317e8f61617560ab60777d",
    +                "reference": "df14e1b81483b04534317e8f61617560ab60777d",
                     "shasum": ""
                 },
                 "require": {
    @@ -9839,7 +9826,7 @@
                 "description": "Provides a tight integration of Twig into the Symfony full-stack framework",
                 "homepage": "https://symfony.com",
                 "support": {
    -                "source": "https://github.com/symfony/twig-bundle/tree/v6.4.24"
    +                "source": "https://github.com/symfony/twig-bundle/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -9859,20 +9846,20 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-07-10T08:14:14+00:00"
    +            "time": "2025-12-18T03:19:37+00:00"
             },
             {
                 "name": "symfony/validator",
    -            "version": "v6.4.30",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/validator.git",
    -                "reference": "572dcc789ddf53174c61551aa5a3ec58d6a48b9b"
    +                "reference": "0c3f60adce4e6fc86583b0c7e363ce90fe3ca3e7"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/validator/zipball/572dcc789ddf53174c61551aa5a3ec58d6a48b9b",
    -                "reference": "572dcc789ddf53174c61551aa5a3ec58d6a48b9b",
    +                "url": "https://api.github.com/repos/symfony/validator/zipball/0c3f60adce4e6fc86583b0c7e363ce90fe3ca3e7",
    +                "reference": "0c3f60adce4e6fc86583b0c7e363ce90fe3ca3e7",
                     "shasum": ""
                 },
                 "require": {
    @@ -9940,7 +9927,7 @@
                 "description": "Provides tools to validate values",
                 "homepage": "https://symfony.com",
                 "support": {
    -                "source": "https://github.com/symfony/validator/tree/v6.4.30"
    +                "source": "https://github.com/symfony/validator/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -9960,7 +9947,7 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-12-05T09:41:49+00:00"
    +            "time": "2025-12-24T09:35:58+00:00"
             },
             {
                 "name": "symfony/var-dumper",
    @@ -10920,16 +10907,16 @@
             },
             {
                 "name": "zircote/swagger-php",
    -            "version": "5.7.6",
    +            "version": "5.7.7",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/zircote/swagger-php.git",
    -                "reference": "e4727bad28cf426b026421162af384f893c0142c"
    +                "reference": "a9cf18ac6610fcb1673f108380a76cf9b145a74e"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/zircote/swagger-php/zipball/e4727bad28cf426b026421162af384f893c0142c",
    -                "reference": "e4727bad28cf426b026421162af384f893c0142c",
    +                "url": "https://api.github.com/repos/zircote/swagger-php/zipball/a9cf18ac6610fcb1673f108380a76cf9b145a74e",
    +                "reference": "a9cf18ac6610fcb1673f108380a76cf9b145a74e",
                     "shasum": ""
                 },
                 "require": {
    @@ -10951,7 +10938,7 @@
                     "friendsofphp/php-cs-fixer": "^3.62.0",
                     "phpstan/phpstan": "^1.6 || ^2.0",
                     "phpunit/phpunit": "^9.0",
    -                "rector/rector": "^1.0 || ^2.0",
    +                "rector/rector": "^1.0 || 2.2.14",
                     "vimeo/psalm": "^4.30 || ^5.0"
                 },
                 "suggest": {
    @@ -11002,9 +10989,15 @@
                 ],
                 "support": {
                     "issues": "https://github.com/zircote/swagger-php/issues",
    -                "source": "https://github.com/zircote/swagger-php/tree/5.7.6"
    +                "source": "https://github.com/zircote/swagger-php/tree/5.7.7"
                 },
    -            "time": "2025-12-04T01:33:01+00:00"
    +            "funding": [
    +                {
    +                    "url": "https://github.com/zircote",
    +                    "type": "github"
    +                }
    +            ],
    +            "time": "2026-01-02T21:36:49+00:00"
             }
         ],
         "packages-dev": [
    @@ -11550,16 +11543,16 @@
             },
             {
                 "name": "friendsofphp/php-cs-fixer",
    -            "version": "v3.92.3",
    +            "version": "v3.92.4",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
    -                "reference": "2ba8f5a60f6f42fb65758cfb3768434fa2d1c7e8"
    +                "reference": "9e7488b19403423e02e8403cc1eb596baf4673b0"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/2ba8f5a60f6f42fb65758cfb3768434fa2d1c7e8",
    -                "reference": "2ba8f5a60f6f42fb65758cfb3768434fa2d1c7e8",
    +                "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/9e7488b19403423e02e8403cc1eb596baf4673b0",
    +                "reference": "9e7488b19403423e02e8403cc1eb596baf4673b0",
                     "shasum": ""
                 },
                 "require": {
    @@ -11591,17 +11584,17 @@
                 },
                 "require-dev": {
                     "facile-it/paraunit": "^1.3.1 || ^2.7",
    -                "infection/infection": "^0.31.0",
    -                "justinrainbow/json-schema": "^6.5",
    -                "keradus/cli-executor": "^2.2",
    +                "infection/infection": "^0.31",
    +                "justinrainbow/json-schema": "^6.6",
    +                "keradus/cli-executor": "^2.3",
                     "mikey179/vfsstream": "^1.6.12",
                     "php-coveralls/php-coveralls": "^2.9",
                     "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6",
                     "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6",
    -                "phpunit/phpunit": "^9.6.25 || ^10.5.53 || ^11.5.34",
    +                "phpunit/phpunit": "^9.6.31 || ^10.5.60 || ^11.5.46",
                     "symfony/polyfill-php85": "^1.33",
    -                "symfony/var-dumper": "^5.4.48 || ^6.4.24 || ^7.3.2 || ^8.0",
    -                "symfony/yaml": "^5.4.45 || ^6.4.24 || ^7.3.2 || ^8.0"
    +                "symfony/var-dumper": "^5.4.48 || ^6.4.26 || ^7.4.0 || ^8.0",
    +                "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0"
                 },
                 "suggest": {
                     "ext-dom": "For handling output formats in XML",
    @@ -11642,15 +11635,15 @@
                 ],
                 "support": {
                     "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
    -                "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.92.3"
    +                "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.92.4"
                 },
                 "funding": [
                     {
                         "url": "https://github.com/keradus",
                         "type": "github"
                     }
                 ],
    -            "time": "2025-12-18T10:45:02+00:00"
    +            "time": "2026-01-04T00:38:52+00:00"
             },
             {
                 "name": "masterminds/html5",
    @@ -14093,16 +14086,16 @@
             },
             {
                 "name": "symfony/browser-kit",
    -            "version": "v6.4.28",
    +            "version": "v6.4.31",
                 "source": {
                     "type": "git",
                     "url": "https://github.com/symfony/browser-kit.git",
    -                "reference": "067e301786bbb58048077fc10507aceb18226e23"
    +                "reference": "5b8564c882ca8eb9a06ed2840abc9b2a40f1e12a"
                 },
                 "dist": {
                     "type": "zip",
    -                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/067e301786bbb58048077fc10507aceb18226e23",
    -                "reference": "067e301786bbb58048077fc10507aceb18226e23",
    +                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/5b8564c882ca8eb9a06ed2840abc9b2a40f1e12a",
    +                "reference": "5b8564c882ca8eb9a06ed2840abc9b2a40f1e12a",
                     "shasum": ""
                 },
                 "require": {
    @@ -14141,7 +14134,7 @@
                 "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically",
                 "homepage": "https://symfony.com",
                 "support": {
    -                "source": "https://github.com/symfony/browser-kit/tree/v6.4.28"
    +                "source": "https://github.com/symfony/browser-kit/tree/v6.4.31"
                 },
                 "funding": [
                     {
    @@ -14161,7 +14154,7 @@
                         "type": "tidelift"
                     }
                 ],
    -            "time": "2025-10-16T22:35:35+00:00"
    +            "time": "2025-12-12T07:51:57+00:00"
             },
             {
                 "name": "symfony/debug-bundle",
    @@ -14562,5 +14555,5 @@
         "platform-overrides": {
             "php": "8.1.3"
         },
    -    "plugin-api-version": "2.6.0"
    +    "plugin-api-version": "2.9.0"
     }
    
  • CONTRIBUTING.md+1 1 modified
    @@ -10,7 +10,7 @@ Send your ideas, code reviews, pull requests and feature requests to help to imp
     - Make your changes in a new git branch, based on the latest code in `main`
     - Apply our code-style by running `composer codestyle-fix`
     - Run the static code analysis with `composer phpstan`
    -- Verify everything still works with `composer tests-unit` 
    +- Verify everything still works with `composer tests` 
     - Add tests for your changes
     
     Further documentation can be found in the [developer documentation](https://www.kimai.org/documentation/developers.html).
    
  • .gitattributes+2 0 modified
    @@ -5,8 +5,10 @@ tests export-ignore
     .codecov.yml export-ignore
     .editorconfig export-ignore
     eslint.config.js export-ignore
    +eslint.config.mjs export-ignore
     .gitattributes export-ignore
     .gitignore export-ignore
    +.php-cs-fixer.dist.php export-ignore
     php-cs-fixer.dist.php export-ignore
     babel.config.js export-ignore
     package.json export-ignore
    
  • .gitignore+3 0 modified
    @@ -3,6 +3,8 @@
     /.env-*
     /.idea/
     .DS_Store
    +var/templates/
    +
     
     # custom apache rules e.g. to deactivate ioncube loader
     /public/.user.ini
    @@ -14,6 +16,7 @@
     # YARN 2
     /.yarnrc.yml
     /.yarn
    +/.pnp.*
     
     # for keeping empty directories
     /config/packages/local.yaml
    
  • public/browserconfig.xml+1 1 modified
    @@ -4,7 +4,7 @@
             <tile>
                 <square150x150logo src="favicon/mstile-150x150.png"/>
                 <square310x310logo src="favicon/mstile-large.jpg"/>
    -            <TileColor>#00a300</TileColor>
    +            <TileColor>#ffffff</TileColor>
             </tile>
         </msapplication>
     </browserconfig>
    \ No newline at end of file
    
  • public/build/app.1899dc33.js+0 2 removed
  • public/build/app.307ea672.js+2 0 added
  • public/build/app.307ea672.js.LICENSE.txt+0 0 renamed
  • public/build/app.9662939e.js+0 2 removed
  • public/build/app-rtl.15853b82.js+0 1 removed
    @@ -1 +0,0 @@
    -(self.webpackChunkkimai=self.webpackChunkkimai||[]).push([[113],{876:function(i,n,u){"use strict";u.r(n)},2395:function(i,n,u){u(876)}},function(i){var n;n=2395,i(i.s=n)}]);
    \ No newline at end of file
    
  • public/build/app-rtl.fe7c5bf2.js+1 0 added
    @@ -0,0 +1 @@
    +(self.webpackChunkkimai=self.webpackChunkkimai||[]).push([[113],{177:function(i,n,u){u(7996)},7996:function(i,n,u){"use strict";u.r(n)}},function(i){var n;n=177,i(i.s=n)}]);
    \ No newline at end of file
    
  • public/build/calendar.b2d70caa.js+0 2 removed
  • public/build/calendar.cef61816.js+2 0 added
  • public/build/calendar.cef61816.js.LICENSE.txt+0 0 renamed
  • public/build/chart.2c85f027.js+2 2 renamed
  • public/build/chart.2c85f027.js.LICENSE.txt+0 0 renamed
  • public/build/dashboard.6ce7ac9c.js+0 2 removed
    @@ -1,2 +0,0 @@
    -/*! For license information please see dashboard.6ce7ac9c.js.LICENSE.txt */
    -"use strict";(self.webpackChunkkimai=self.webpackChunkkimai||[]).push([[945],{51:function(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.DDElement=void 0;const s=i(9354),o=i(6060),n=i(7540);class r{constructor(e){this.el=e}static init(e){return e.ddElement||(e.ddElement=new r(e)),e.ddElement}on(e,t){return this.ddDraggable&&["drag","dragstart","dragstop"].indexOf(e)>-1?this.ddDraggable.on(e,t):this.ddDroppable&&["drop","dropover","dropout"].indexOf(e)>-1?this.ddDroppable.on(e,t):this.ddResizable&&["resizestart","resize","resizestop"].indexOf(e)>-1&&this.ddResizable.on(e,t),this}off(e){return this.ddDraggable&&["drag","dragstart","dragstop"].indexOf(e)>-1?this.ddDraggable.off(e):this.ddDroppable&&["drop","dropover","dropout"].indexOf(e)>-1?this.ddDroppable.off(e):this.ddResizable&&["resizestart","resize","resizestop"].indexOf(e)>-1&&this.ddResizable.off(e),this}setupDraggable(e){return this.ddDraggable?this.ddDraggable.updateOption(e):this.ddDraggable=new o.DDDraggable(this.el,e),this}cleanDraggable(){return this.ddDraggable&&(this.ddDraggable.destroy(),delete this.ddDraggable),this}setupResizable(e){return this.ddResizable?this.ddResizable.updateOption(e):this.ddResizable=new s.DDResizable(this.el,e),this}cleanResizable(){return this.ddResizable&&(this.ddResizable.destroy(),delete this.ddResizable),this}setupDroppable(e){return this.ddDroppable?this.ddDroppable.updateOption(e):this.ddDroppable=new n.DDDroppable(this.el,e),this}cleanDroppable(){return this.ddDroppable&&(this.ddDroppable.destroy(),delete this.ddDroppable),this}}t.DDElement=r},241:function(e,t,i){i.r(t)},1120:function(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.pointerleave=t.pointerenter=t.pointerdown=t.touchend=t.touchmove=t.touchstart=t.isTouch=void 0;const s=i(1432);t.isTouch="undefined"!=typeof window&&"undefined"!=typeof document&&("ontouchstart"in document||"ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch||navigator.maxTouchPoints>0||navigator.msMaxTouchPoints>0);class o{}function n(e,t){if(e.touches.length>1)return;e.cancelable&&e.preventDefault();const i=e.changedTouches[0],s=document.createEvent("MouseEvents");s.initMouseEvent(t,!0,!0,window,1,i.screenX,i.screenY,i.clientX,i.clientY,!1,!1,!1,!1,0,null),e.target.dispatchEvent(s)}function r(e,t){e.cancelable&&e.preventDefault();const i=document.createEvent("MouseEvents");i.initMouseEvent(t,!0,!0,window,1,e.screenX,e.screenY,e.clientX,e.clientY,!1,!1,!1,!1,0,null),e.target.dispatchEvent(i)}t.touchstart=function(e){o.touchHandled||(o.touchHandled=!0,n(e,"mousedown"))},t.touchmove=function(e){o.touchHandled&&n(e,"mousemove")},t.touchend=function(e){if(!o.touchHandled)return;o.pointerLeaveTimeout&&(window.clearTimeout(o.pointerLeaveTimeout),delete o.pointerLeaveTimeout);const t=!!s.DDManager.dragElement;n(e,"mouseup"),t||n(e,"click"),o.touchHandled=!1},t.pointerdown=function(e){e.target.releasePointerCapture(e.pointerId)},t.pointerenter=function(e){s.DDManager.dragElement&&r(e,"mouseenter")},t.pointerleave=function(e){s.DDManager.dragElement&&(o.pointerLeaveTimeout=window.setTimeout((()=>{delete o.pointerLeaveTimeout,r(e,"mouseleave")}),10))}},1432:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.DDManager=void 0;t.DDManager=class{}},1661:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.Utils=t.obsoleteAttr=t.obsoleteOptsDel=t.obsoleteOpts=t.obsolete=void 0,t.obsolete=function(e,t,i,s,o){let n=(...n)=>(console.warn("gridstack.js: Function `"+i+"` is deprecated in "+o+" and has been replaced with `"+s+"`. It will be **removed** in a future release"),t.apply(e,n));return n.prototype=t.prototype,n},t.obsoleteOpts=function(e,t,i,s){void 0!==e[t]&&(e[i]=e[t],console.warn("gridstack.js: Option `"+t+"` is deprecated in "+s+" and has been replaced with `"+i+"`. It will be **removed** in a future release"))},t.obsoleteOptsDel=function(e,t,i,s){void 0!==e[t]&&console.warn("gridstack.js: Option `"+t+"` is deprecated in "+i+s)},t.obsoleteAttr=function(e,t,i,s){let o=e.getAttribute(t);null!==o&&(e.setAttribute(i,o),console.warn("gridstack.js: attribute `"+t+"`="+o+" is deprecated on this object in "+s+" and has been replaced with `"+i+"`. It will be **removed** in a future release"))};class i{static getElements(e){if("string"==typeof e){let t=document.querySelectorAll(e);return t.length||"."===e[0]||"#"===e[0]||(t=document.querySelectorAll("."+e),t.length||(t=document.querySelectorAll("#"+e))),Array.from(t)}return[e]}static getElement(e){if("string"==typeof e){if(!e.length)return null;if("#"===e[0])return document.getElementById(e.substring(1));if("."===e[0]||"["===e[0])return document.querySelector(e);if(!isNaN(+e[0]))return document.getElementById(e);let t=document.querySelector(e);return t||(t=document.getElementById(e)),t||(t=document.querySelector("."+e)),t}return e}static isIntercepted(e,t){return!(e.y>=t.y+t.h||e.y+e.h<=t.y||e.x+e.w<=t.x||e.x>=t.x+t.w)}static isTouching(e,t){return i.isIntercepted(e,{x:t.x-.5,y:t.y-.5,w:t.w+1,h:t.h+1})}static areaIntercept(e,t){let i=e.x>t.x?e.x:t.x,s=e.x+e.w<t.x+t.w?e.x+e.w:t.x+t.w;if(s<=i)return 0;let o=e.y>t.y?e.y:t.y,n=e.y+e.h<t.y+t.h?e.y+e.h:t.y+t.h;return n<=o?0:(s-i)*(n-o)}static area(e){return e.w*e.h}static sort(e,t,i){return i=i||e.reduce(((e,t)=>Math.max(t.x+t.w,e)),0)||12,-1===t?e.sort(((e,t)=>t.x+t.y*i-(e.x+e.y*i))):e.sort(((e,t)=>e.x+e.y*i-(t.x+t.y*i)))}static createStylesheet(e,t,i){let s=document.createElement("style");const o=null==i?void 0:i.nonce;return o&&(s.nonce=o),s.setAttribute("type","text/css"),s.setAttribute("gs-style-id",e),s.styleSheet?s.styleSheet.cssText="":s.appendChild(document.createTextNode("")),t?t.insertBefore(s,t.firstChild):(t=document.getElementsByTagName("head")[0]).appendChild(s),s.sheet}static removeStylesheet(e){let t=document.querySelector("STYLE[gs-style-id="+e+"]");t&&t.parentNode&&t.remove()}static addCSSRule(e,t,i){"function"==typeof e.addRule?e.addRule(t,i):"function"==typeof e.insertRule&&e.insertRule(`${t}{${i}}`)}static toBool(e){return"boolean"==typeof e?e:"string"==typeof e?!(""===(e=e.toLowerCase())||"no"===e||"false"===e||"0"===e):Boolean(e)}static toNumber(e){return null===e||0===e.length?void 0:Number(e)}static parseHeight(e){let t,i="px";if("string"==typeof e){let s=e.match(/^(-[0-9]+\.[0-9]+|[0-9]*\.[0-9]+|-[0-9]+|[0-9]+)(px|em|rem|vh|vw|%)?$/);if(!s)throw new Error("Invalid height");i=s[2]||"px",t=parseFloat(s[1])}else t=e;return{h:t,unit:i}}static defaults(e,...t){return t.forEach((t=>{for(const i in t){if(!t.hasOwnProperty(i))return;null===e[i]||void 0===e[i]?e[i]=t[i]:"object"==typeof t[i]&&"object"==typeof e[i]&&this.defaults(e[i],t[i])}})),e}static same(e,t){if("object"!=typeof e)return e==t;if(typeof e!=typeof t)return!1;if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const i in e)if(e[i]!==t[i])return!1;return!0}static copyPos(e,t,i=!1){return e.x=t.x,e.y=t.y,e.w=t.w,e.h=t.h,i&&(t.minW&&(e.minW=t.minW),t.minH&&(e.minH=t.minH),t.maxW&&(e.maxW=t.maxW),t.maxH&&(e.maxH=t.maxH)),e}static samePos(e,t){return e&&t&&e.x===t.x&&e.y===t.y&&e.w===t.w&&e.h===t.h}static removeInternalAndSame(e,t){if("object"==typeof e&&"object"==typeof t)for(let i in e){let s=e[i];if("_"===i[0]||s===t[i])delete e[i];else if(s&&"object"==typeof s&&void 0!==t[i]){for(let e in s)s[e]!==t[i][e]&&"_"!==e[0]||delete s[e];Object.keys(s).length||delete e[i]}}}static removeInternalForSave(e,t=!0){for(let t in e)"_"!==t[0]&&null!==e[t]&&void 0!==e[t]||delete e[t];delete e.grid,t&&delete e.el,e.autoPosition||delete e.autoPosition,e.noResize||delete e.noResize,e.noMove||delete e.noMove,e.locked||delete e.locked,1!==e.w&&e.w!==e.minW||delete e.w,1!==e.h&&e.h!==e.minH||delete e.h}static closestUpByClass(e,t){for(;e;){if(e.classList.contains(t))return e;e=e.parentElement}return null}static throttle(e,t){let i=!1;return(...s)=>{i||(i=!0,setTimeout((()=>{e(...s),i=!1}),t))}}static removePositioningStyles(e){let t=e.style;t.position&&t.removeProperty("position"),t.left&&t.removeProperty("left"),t.top&&t.removeProperty("top"),t.width&&t.removeProperty("width"),t.height&&t.removeProperty("height")}static getScrollElement(e){if(!e)return document.scrollingElement||document.documentElement;const t=getComputedStyle(e);return/(auto|scroll)/.test(t.overflow+t.overflowY)?e:this.getScrollElement(e.parentElement)}static updateScrollPosition(e,t,i){let s=e.getBoundingClientRect(),o=window.innerHeight||document.documentElement.clientHeight;if(s.top<0||s.bottom>o){let n=s.bottom-o,r=s.top,l=this.getScrollElement(e);if(null!==l){let a=l.scrollTop;s.top<0&&i<0?e.offsetHeight>o?l.scrollTop+=i:l.scrollTop+=Math.abs(r)>Math.abs(i)?i:r:i>0&&(e.offsetHeight>o?l.scrollTop+=i:l.scrollTop+=n>i?i:n),t.top+=l.scrollTop-a}}}static updateScrollResize(e,t,i){const s=this.getScrollElement(t),o=s.clientHeight,n=s===this.getScrollElement()?0:s.getBoundingClientRect().top,r=e.clientY-n,l=r>o-i;r<i?s.scrollBy({behavior:"smooth",top:r-i}):l&&s.scrollBy({behavior:"smooth",top:i-(o-r)})}static clone(e){return null==e||"object"!=typeof e?e:e instanceof Array?[...e]:Object.assign({},e)}static cloneDeep(e){const t=["parentGrid","el","grid","subGrid","engine"],s=i.clone(e);for(const o in s)s.hasOwnProperty(o)&&"object"==typeof s[o]&&"__"!==o.substring(0,2)&&!t.find((e=>e===o))&&(s[o]=i.cloneDeep(e[o]));return s}static cloneNode(e){const t=e.cloneNode(!0);return t.removeAttribute("id"),t}static appendTo(e,t){let i;i="string"==typeof t?document.querySelector(t):t,i&&i.appendChild(e)}static addElStyles(e,t){if(t instanceof Object)for(const i in t)t.hasOwnProperty(i)&&(Array.isArray(t[i])?t[i].forEach((t=>{e.style[i]=t})):e.style[i]=t[i])}static initEvent(e,t){const i={type:t.type},s={button:0,which:0,buttons:1,bubbles:!0,cancelable:!0,target:t.target?t.target:e.target};return e.dataTransfer&&(i.dataTransfer=e.dataTransfer),["altKey","ctrlKey","metaKey","shiftKey"].forEach((t=>i[t]=e[t])),["pageX","pageY","clientX","clientY","screenX","screenY"].forEach((t=>i[t]=e[t])),Object.assign(Object.assign({},i),s)}static simulateMouseEvent(e,t,i){const s=document.createEvent("MouseEvents");s.initMouseEvent(t,!0,!0,window,1,e.screenX,e.screenY,e.clientX,e.clientY,e.ctrlKey,e.altKey,e.shiftKey,e.metaKey,0,e.target),(i||e.target).dispatchEvent(s)}}t.Utils=i},3531:function(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.GridStackEngine=void 0;const s=i(1661);class o{constructor(e={}){this.addedNodes=[],this.removedNodes=[],this.column=e.column||12,this.maxRow=e.maxRow,this._float=e.float,this.nodes=e.nodes||[],this.onChange=e.onChange}batchUpdate(e=!0){return!!this.batchMode===e||(this.batchMode=e,e?(this._prevFloat=this._float,this._float=!0,this.saveInitial()):(this._float=this._prevFloat,delete this._prevFloat,this._packNodes()._notify())),this}_useEntireRowArea(e,t){return(!this.float||this.batchMode&&!this._prevFloat)&&!this._hasLocked&&(!e._moving||e._skipDown||t.y<=e.y)}_fixCollisions(e,t=e,i,o={}){if(this.sortNodes(-1),!(i=i||this.collide(e,t)))return!1;if(e._moving&&!o.nested&&!this.float&&this.swap(e,i))return!0;let n=t;this._useEntireRowArea(e,t)&&(n={x:0,w:this.column,y:t.y,h:t.h},i=this.collide(e,n,o.skip));let r=!1,l={nested:!0,pack:!1};for(;i=i||this.collide(e,n,o.skip);){let n;if(i.locked||e._moving&&!e._skipDown&&t.y>e.y&&!this.float&&(!this.collide(i,Object.assign(Object.assign({},i),{y:e.y}),e)||!this.collide(i,Object.assign(Object.assign({},i),{y:t.y-i.h}),e))?(e._skipDown=e._skipDown||t.y>e.y,n=this.moveNode(e,Object.assign(Object.assign(Object.assign({},t),{y:i.y+i.h}),l)),i.locked&&n?s.Utils.copyPos(t,e):!i.locked&&n&&o.pack&&(this._packNodes(),t.y=i.y+i.h,s.Utils.copyPos(e,t)),r=r||n):n=this.moveNode(i,Object.assign(Object.assign(Object.assign({},i),{y:t.y+t.h,skip:e}),l)),!n)return r;i=void 0}return r}collide(e,t=e,i){return this.nodes.find((o=>o!==e&&o!==i&&s.Utils.isIntercepted(o,t)))}collideAll(e,t=e,i){return this.nodes.filter((o=>o!==e&&o!==i&&s.Utils.isIntercepted(o,t)))}directionCollideCoverage(e,t,i){if(!t.rect||!e._rect)return;let s,o=e._rect,n=Object.assign({},t.rect);return n.y>o.y?(n.h+=n.y-o.y,n.y=o.y):n.h+=o.y-n.y,n.x>o.x?(n.w+=n.x-o.x,n.x=o.x):n.w+=o.x-n.x,i.forEach((e=>{if(e.locked||!e._rect)return;let t=e._rect,i=Number.MAX_VALUE,r=Number.MAX_VALUE,l=.5;o.y<t.y?i=(n.y+n.h-t.y)/t.h:o.y+o.h>t.y+t.h&&(i=(t.y+t.h-n.y)/t.h),o.x<t.x?r=(n.x+n.w-t.x)/t.w:o.x+o.w>t.x+t.w&&(r=(t.x+t.w-n.x)/t.w);let a=Math.min(r,i);a>l&&(l=a,s=e)})),t.collide=s,s}cacheRects(e,t,i,s,o,n){return this.nodes.forEach((r=>r._rect={y:r.y*t+i,x:r.x*e+n,w:r.w*e-n-s,h:r.h*t-i-o})),this}swap(e,t){if(!t||t.locked||!e||e.locked)return!1;function i(){let i=t.x,s=t.y;return t.x=e.x,t.y=e.y,e.h!=t.h?(e.x=i,e.y=t.y+t.h):e.w!=t.w?(e.x=t.x+t.w,e.y=s):(e.x=i,e.y=s),e._dirty=t._dirty=!0,!0}let o;if(e.w===t.w&&e.h===t.h&&(e.x===t.x||e.y===t.y)&&(o=s.Utils.isTouching(e,t)))return i();if(!1!==o){if(e.w===t.w&&e.x===t.x&&(o||(o=s.Utils.isTouching(e,t)))){if(t.y<e.y){let i=e;e=t,t=i}return i()}if(!1!==o){if(e.h===t.h&&e.y===t.y&&(o||(o=s.Utils.isTouching(e,t)))){if(t.x<e.x){let i=e;e=t,t=i}return i()}return!1}}}isAreaEmpty(e,t,i,s){let o={x:e||0,y:t||0,w:i||1,h:s||1};return!this.collide(o)}compact(){if(0===this.nodes.length)return this;this.batchUpdate().sortNodes();let e=this.nodes;return this.nodes=[],e.forEach((e=>{e.locked||(e.autoPosition=!0),this.addNode(e,!1),e._dirty=!0})),this.batchUpdate(!1)}set float(e){this._float!==e&&(this._float=e||!1,e||this._packNodes()._notify())}get float(){return this._float||!1}sortNodes(e){return this.nodes=s.Utils.sort(this.nodes,e,this.column),this}_packNodes(){return this.batchMode||(this.sortNodes(),this.float?this.nodes.forEach((e=>{if(e._updating||void 0===e._orig||e.y===e._orig.y)return;let t=e.y;for(;t>e._orig.y;){--t,this.collide(e,{x:e.x,y:t,w:e.w,h:e.h})||(e._dirty=!0,e.y=t)}})):this.nodes.forEach(((e,t)=>{if(!e.locked)for(;e.y>0;){let i=0===t?0:e.y-1;if(!(0===t||!this.collide(e,{x:e.x,y:i,w:e.w,h:e.h})))break;e._dirty=e.y!==i,e.y=i}}))),this}prepareNode(e,t){(e=e||{})._id=e._id||o._idSeq++,void 0!==e.x&&void 0!==e.y&&null!==e.x&&null!==e.y||(e.autoPosition=!0);let i={x:0,y:0,w:1,h:1};return s.Utils.defaults(e,i),e.autoPosition||delete e.autoPosition,e.noResize||delete e.noResize,e.noMove||delete e.noMove,"string"==typeof e.x&&(e.x=Number(e.x)),"string"==typeof e.y&&(e.y=Number(e.y)),"string"==typeof e.w&&(e.w=Number(e.w)),"string"==typeof e.h&&(e.h=Number(e.h)),isNaN(e.x)&&(e.x=i.x,e.autoPosition=!0),isNaN(e.y)&&(e.y=i.y,e.autoPosition=!0),isNaN(e.w)&&(e.w=i.w),isNaN(e.h)&&(e.h=i.h),this.nodeBoundFix(e,t)}nodeBoundFix(e,t){let i=e._orig||s.Utils.copyPos({},e);e.maxW&&(e.w=Math.min(e.w,e.maxW)),e.maxH&&(e.h=Math.min(e.h,e.maxH)),e.minW&&e.minW<=this.column&&(e.w=Math.max(e.w,e.minW)),e.minH&&(e.h=Math.max(e.h,e.minH));if((1===this.column||e.x+e.w>this.column)&&this.column<12&&!this._inColumnResize&&e._id&&-1===this.findCacheLayout(e,12)){let t=Object.assign({},e);t.autoPosition?(delete t.x,delete t.y):t.x=Math.min(11,t.x),t.w=Math.min(12,t.w),this.cacheOneLayout(t,12)}return e.w>this.column?e.w=this.column:e.w<1&&(e.w=1),this.maxRow&&e.h>this.maxRow?e.h=this.maxRow:e.h<1&&(e.h=1),e.x<0&&(e.x=0),e.y<0&&(e.y=0),e.x+e.w>this.column&&(t?e.w=this.column-e.x:e.x=this.column-e.w),this.maxRow&&e.y+e.h>this.maxRow&&(t?e.h=this.maxRow-e.y:e.y=this.maxRow-e.h),s.Utils.samePos(e,i)||(e._dirty=!0),e}getDirtyNodes(e){return e?this.nodes.filter((e=>e._dirty&&!s.Utils.samePos(e,e._orig))):this.nodes.filter((e=>e._dirty))}_notify(e){if(this.batchMode||!this.onChange)return this;let t=(e||[]).concat(this.getDirtyNodes());return this.onChange(t),this}cleanNodes(){return this.batchMode||this.nodes.forEach((e=>{delete e._dirty,delete e._lastTried})),this}saveInitial(){return this.nodes.forEach((e=>{e._orig=s.Utils.copyPos({},e),delete e._dirty})),this._hasLocked=this.nodes.some((e=>e.locked)),this}restoreInitial(){return this.nodes.forEach((e=>{s.Utils.samePos(e,e._orig)||(s.Utils.copyPos(e,e._orig),e._dirty=!0)})),this._notify(),this}findEmptyPosition(e,t=this.nodes,i=this.column){t=s.Utils.sort(t,-1,i);let o=!1;for(let n=0;!o;++n){let r=n%i,l=Math.floor(n/i);if(r+e.w>i)continue;let a={x:r,y:l,w:e.w,h:e.h};t.find((e=>s.Utils.isIntercepted(a,e)))||(e.x=r,e.y=l,delete e.autoPosition,o=!0)}return o}addNode(e,t=!1){let i=this.nodes.find((t=>t._id===e._id));return i||(delete(e=this._inColumnResize?this.nodeBoundFix(e):this.prepareNode(e))._temporaryRemoved,delete e._removeDOM,e.autoPosition&&this.findEmptyPosition(e)&&delete e.autoPosition,this.nodes.push(e),t&&this.addedNodes.push(e),this._fixCollisions(e),this.batchMode||this._packNodes()._notify(),e)}removeNode(e,t=!0,i=!1){return this.nodes.find((t=>t===e))?(i&&this.removedNodes.push(e),t&&(e._removeDOM=!0),this.nodes=this.nodes.filter((t=>t!==e)),this._packNodes()._notify([e])):this}removeAll(e=!0){return delete this._layouts,0===this.nodes.length?this:(e&&this.nodes.forEach((e=>e._removeDOM=!0)),this.removedNodes=this.nodes,this.nodes=[],this._notify(this.removedNodes))}moveNodeCheck(e,t){if(!this.changedPosConstrain(e,t))return!1;if(t.pack=!0,!this.maxRow)return this.moveNode(e,t);let i,n=new o({column:this.column,float:this.float,nodes:this.nodes.map((t=>t===e?(i=Object.assign({},t),i):Object.assign({},t)))});if(!i)return!1;let r=n.moveNode(i,t)&&n.getRow()<=this.maxRow;if(!r&&!t.resizing&&t.collide){let i=t.collide.el.gridstackNode;if(this.swap(e,i))return this._notify(),!0}return!!r&&(n.nodes.filter((e=>e._dirty)).forEach((e=>{let t=this.nodes.find((t=>t._id===e._id));t&&(s.Utils.copyPos(t,e),t._dirty=!0)})),this._notify(),!0)}willItFit(e){if(delete e._willFitPos,!this.maxRow)return!0;let t=new o({column:this.column,float:this.float,nodes:this.nodes.map((e=>Object.assign({},e)))}),i=Object.assign({},e);return this.cleanupNode(i),delete i.el,delete i._id,delete i.content,delete i.grid,t.addNode(i),t.getRow()<=this.maxRow&&(e._willFitPos=s.Utils.copyPos({},i),!0)}changedPosConstrain(e,t){return t.w=t.w||e.w,t.h=t.h||e.h,e.x!==t.x||e.y!==t.y||(e.maxW&&(t.w=Math.min(t.w,e.maxW)),e.maxH&&(t.h=Math.min(t.h,e.maxH)),e.minW&&(t.w=Math.max(t.w,e.minW)),e.minH&&(t.h=Math.max(t.h,e.minH)),e.w!==t.w||e.h!==t.h)}moveNode(e,t){var i,o;if(!e||!t)return!1;let n;void 0===t.pack&&(n=t.pack=!0),"number"!=typeof t.x&&(t.x=e.x),"number"!=typeof t.y&&(t.y=e.y),"number"!=typeof t.w&&(t.w=e.w),"number"!=typeof t.h&&(t.h=e.h);let r=e.w!==t.w||e.h!==t.h,l=s.Utils.copyPos({},e,!0);if(s.Utils.copyPos(l,t),l=this.nodeBoundFix(l,r),s.Utils.copyPos(t,l),s.Utils.samePos(e,t))return!1;let a=s.Utils.copyPos({},e),h=this.collideAll(e,l,t.skip),d=!0;if(h.length){let r=e._moving&&!t.nested,a=r?this.directionCollideCoverage(e,t,h):h[0];if(r&&a&&(null===(o=null===(i=e.grid)||void 0===i?void 0:i.opts)||void 0===o?void 0:o.subGridDynamic)&&!e.grid._isTemp){let i=s.Utils.areaIntercept(t.rect,a._rect),o=s.Utils.area(t.rect),n=s.Utils.area(a._rect);i/(o<n?o:n)>.8&&(a.grid.makeSubGrid(a.el,void 0,e),a=void 0)}a?d=!this._fixCollisions(e,l,a,t):(d=!1,n&&delete t.pack)}return d&&(e._dirty=!0,s.Utils.copyPos(e,l)),t.pack&&this._packNodes()._notify(),!s.Utils.samePos(e,a)}getRow(){return this.nodes.reduce(((e,t)=>Math.max(e,t.y+t.h)),0)}beginUpdate(e){return e._updating||(e._updating=!0,delete e._skipDown,this.batchMode||this.saveInitial()),this}endUpdate(){let e=this.nodes.find((e=>e._updating));return e&&(delete e._updating,delete e._skipDown),this}save(e=!0){var t;let i=null===(t=this._layouts)||void 0===t?void 0:t.length,o=i&&this.column!==i-1?this._layouts[i-1]:null,n=[];return this.sortNodes(),this.nodes.forEach((t=>{let i=null==o?void 0:o.find((e=>e._id===t._id)),r=Object.assign({},t);i&&(r.x=i.x,r.y=i.y,r.w=i.w),s.Utils.removeInternalForSave(r,!e),n.push(r)})),n}layoutsNodesChange(e){return!this._layouts||this._inColumnResize||this._layouts.forEach(((t,i)=>{if(!t||i===this.column)return this;if(i<this.column)this._layouts[i]=void 0;else{let s=i/this.column;e.forEach((e=>{if(!e._orig)return;let i=t.find((t=>t._id===e._id));i&&(e.y!==e._orig.y&&(i.y+=e.y-e._orig.y),e.x!==e._orig.x&&(i.x=Math.round(e.x*s)),e.w!==e._orig.w&&(i.w=Math.round(e.w*s)))}))}})),this}updateNodeWidths(e,t,i,o="moveScale"){var n;if(!this.nodes.length||!t||e===t)return this;this.cacheLayout(this.nodes,e),this.batchUpdate();let r=[],l=!1;if(1===t&&(null==i?void 0:i.length)){l=!0;let e=0;i.forEach((t=>{t.x=0,t.w=1,t.y=Math.max(t.y,e),e=t.y+t.h})),r=i,i=[]}else i=s.Utils.sort(this.nodes,-1,e);let a=[];if(t>e){a=this._layouts[t]||[];let s=this._layouts.length-1;!a.length&&e!==s&&(null===(n=this._layouts[s])||void 0===n?void 0:n.length)&&(e=s,this._layouts[s].forEach((e=>{let t=i.find((t=>t._id===e._id));t&&(t.x=e.x,t.y=e.y,t.w=e.w)})))}if(a.forEach((e=>{let t=i.findIndex((t=>t._id===e._id));-1!==t&&((e.autoPosition||isNaN(e.x)||isNaN(e.y))&&this.findEmptyPosition(e,r),e.autoPosition||(i[t].x=e.x,i[t].y=e.y,i[t].w=e.w,r.push(i[t])),i.splice(t,1))})),i.length)if("function"==typeof o)o(t,e,r,i);else if(!l){let s=t/e,n="move"===o||"moveScale"===o,l="scale"===o||"moveScale"===o;i.forEach((i=>{i.x=1===t?0:n?Math.round(i.x*s):Math.min(i.x,t-1),i.w=1===t||1===e?1:l?Math.round(i.w*s)||1:Math.min(i.w,t),r.push(i)})),i=[]}return l||(r=s.Utils.sort(r,-1,t)),this._inColumnResize=!0,this.nodes=[],r.forEach((e=>{this.addNode(e,!1),delete e._orig})),this.batchUpdate(!1),delete this._inColumnResize,this}cacheLayout(e,t,i=!1){let s=[];return e.forEach(((e,t)=>{e._id=e._id||o._idSeq++,s[t]={x:e.x,y:e.y,w:e.w,_id:e._id}})),this._layouts=i?[]:this._layouts||[],this._layouts[t]=s,this}cacheOneLayout(e,t){e._id=e._id||o._idSeq++;let i={x:e.x,y:e.y,w:e.w,_id:e._id};e.autoPosition&&(delete i.x,delete i.y,i.autoPosition=!0),this._layouts=this._layouts||[],this._layouts[t]=this._layouts[t]||[];let s=this.findCacheLayout(e,t);return-1===s?this._layouts[t].push(i):this._layouts[t][s]=i,this}findCacheLayout(e,t){var i,s,o;return null!==(o=null===(s=null===(i=this._layouts)||void 0===i?void 0:i[t])||void 0===s?void 0:s.findIndex((t=>t._id===e._id)))&&void 0!==o?o:-1}cleanupNode(e){for(let t in e)"_"===t[0]&&"_id"!==t&&delete e[t];return this}}t.GridStackEngine=o,o._idSeq=1},3668:function(e,t,i){var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i),Object.defineProperty(e,s,{enumerable:!0,get:function(){return t[i]}})}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__exportStar||function(e,t){for(var i in e)"default"===i||t.hasOwnProperty(i)||s(t,e,i)};Object.defineProperty(t,"__esModule",{value:!0}),t.GridStack=void 0;const n=i(3531),r=i(1661),l=i(3777),a=i(7939),h=i(1120),d=i(1432),g=new a.DDGridStack;o(i(3777),t),o(i(1661),t),o(i(3531),t),o(i(7939),t);class u{constructor(e,t={}){var i,s;this._gsEventHandler={},this._extraDragRow=0,this.el=e,t=t||{},e.classList.contains("grid-stack")||this.el.classList.add("grid-stack"),t.row&&(t.minRow=t.maxRow=t.row,delete t.row);let o=r.Utils.toNumber(e.getAttribute("gs-row"));"auto"===t.column&&delete t.column;let a=t;void 0!==a.minWidth&&(t.oneColumnSize=t.oneColumnSize||a.minWidth,delete a.minWidth),void 0!==t.alwaysShowResizeHandle&&(t._alwaysShowResizeHandle=t.alwaysShowResizeHandle);let g=Object.assign(Object.assign({},r.Utils.cloneDeep(l.gridDefaults)),{column:r.Utils.toNumber(e.getAttribute("gs-column"))||l.gridDefaults.column,minRow:o||(r.Utils.toNumber(e.getAttribute("gs-min-row"))||l.gridDefaults.minRow),maxRow:o||(r.Utils.toNumber(e.getAttribute("gs-max-row"))||l.gridDefaults.maxRow),staticGrid:r.Utils.toBool(e.getAttribute("gs-static"))||l.gridDefaults.staticGrid,draggable:{handle:(t.handleClass?"."+t.handleClass:t.handle?t.handle:"")||l.gridDefaults.draggable.handle},removableOptions:{accept:t.itemClass?"."+t.itemClass:l.gridDefaults.removableOptions.accept}});e.getAttribute("gs-animate")&&(g.animate=r.Utils.toBool(e.getAttribute("gs-animate"))),this.opts=r.Utils.defaults(t,g),t=null,this._initMargin(),1!==this.opts.column&&!this.opts.disableOneColumnMode&&this._widthOrContainer()<=this.opts.oneColumnSize&&(this._prevColumn=this.getColumn(),this.opts.column=1),"auto"===this.opts.rtl&&(this.opts.rtl="rtl"===e.style.direction),this.opts.rtl&&this.el.classList.add("grid-stack-rtl");let p=null===(i=r.Utils.closestUpByClass(this.el,l.gridDefaults.itemClass))||void 0===i?void 0:i.gridstackNode;p&&(p.subGrid=this,this.parentGridItem=p,this.el.classList.add("grid-stack-nested"),p.el.classList.add("grid-stack-sub-grid")),this._isAutoCellHeight="auto"===this.opts.cellHeight,this._isAutoCellHeight||"initial"===this.opts.cellHeight?this.cellHeight(void 0,!1):("number"==typeof this.opts.cellHeight&&this.opts.cellHeightUnit&&this.opts.cellHeightUnit!==l.gridDefaults.cellHeightUnit&&(this.opts.cellHeight=this.opts.cellHeight+this.opts.cellHeightUnit,delete this.opts.cellHeightUnit),this.cellHeight(this.opts.cellHeight,!1)),"mobile"===this.opts.alwaysShowResizeHandle&&(this.opts.alwaysShowResizeHandle=h.isTouch),this._styleSheetClass="grid-stack-instance-"+n.GridStackEngine._idSeq++,this.el.classList.add(this._styleSheetClass),this._setStaticClass();let c=this.opts.engineClass||u.engineClass||n.GridStackEngine;if(this.engine=new c({column:this.getColumn(),float:this.opts.float,maxRow:this.opts.maxRow,onChange:e=>{let t=0;this.engine.nodes.forEach((e=>{t=Math.max(t,e.y+e.h)})),e.forEach((e=>{let t=e.el;t&&(e._removeDOM?(t&&t.remove(),delete e._removeDOM):this._writePosAttr(t,e))})),this._updateStyles(!1,t)}}),this.opts.auto&&(this.batchUpdate(),this.getGridItems().forEach((e=>this._prepareElement(e))),this.batchUpdate(!1)),this.opts.children){let e=this.opts.children;delete this.opts.children,e.length&&this.load(e)}this.setAnimation(this.opts.animate),this._updateStyles(),12!=this.opts.column&&this.el.classList.add("grid-stack-"+this.opts.column),this.opts.dragIn&&u.setupDragIn(this.opts.dragIn,this.opts.dragInOptions),delete this.opts.dragIn,delete this.opts.dragInOptions,this.opts.subGridDynamic&&!d.DDManager.pauseDrag&&(d.DDManager.pauseDrag=!0),void 0!==(null===(s=this.opts.draggable)||void 0===s?void 0:s.pause)&&(d.DDManager.pauseDrag=this.opts.draggable.pause),this._setupRemoveDrop(),this._setupAcceptWidget(),this._updateWindowResizeEvent()}static init(e={},t=".grid-stack"){let i=u.getGridElement(t);return i?(i.gridstack||(i.gridstack=new u(i,r.Utils.cloneDeep(e))),i.gridstack):("string"==typeof t?console.error('GridStack.initAll() no grid was found with selector "'+t+'" - element missing or wrong selector ?\nNote: ".grid-stack" is required for proper CSS styling and drag/drop, and is the default selector.'):console.error("GridStack.init() no grid element was passed."),null)}static initAll(e={},t=".grid-stack"){let i=[];return u.getGridElements(t).forEach((t=>{t.gridstack||(t.gridstack=new u(t,r.Utils.cloneDeep(e)),delete e.dragIn,delete e.dragInOptions),i.push(t.gridstack)})),0===i.length&&console.error('GridStack.initAll() no grid was found with selector "'+t+'" - element missing or wrong selector ?\nNote: ".grid-stack" is required for proper CSS styling and drag/drop, and is the default selector.'),i}static addGrid(e,t={}){if(!e)return null;let i=e;if(!e.classList.contains("grid-stack")||t.addRemoveCB)if(t.addRemoveCB)i=t.addRemoveCB(e,t,!0,!0);else{let s=document.implementation.createHTMLDocument("");s.body.innerHTML=`<div class="grid-stack ${t.class||""}"></div>`,i=s.body.children[0],e.appendChild(i)}return u.init(t,i)}static registerEngine(e){u.engineClass=e}get placeholder(){if(!this._placeholder){let e=document.createElement("div");e.className="placeholder-content",this.opts.placeholderText&&(e.innerHTML=this.opts.placeholderText),this._placeholder=document.createElement("div"),this._placeholder.classList.add(this.opts.placeholderClass,l.gridDefaults.itemClass,this.opts.itemClass),this.placeholder.appendChild(e)}return this._placeholder}addWidget(e,t){let i,s;if("string"==typeof e){let t=document.implementation.createHTMLDocument("");t.body.innerHTML=e,i=t.body.children[0]}else if(0===arguments.length||1===arguments.length&&(void 0!==(o=e).el||void 0!==o.x||void 0!==o.y||void 0!==o.w||void 0!==o.h||void 0!==o.content))if(s=t=e,null==s?void 0:s.el)i=s.el;else if(this.opts.addRemoveCB)i=this.opts.addRemoveCB(this.el,t,!0,!1);else{let e=(null==t?void 0:t.content)||"",s=document.implementation.createHTMLDocument("");s.body.innerHTML=`<div class="grid-stack-item ${this.opts.itemClass||""}"><div class="grid-stack-item-content">${e}</div></div>`,i=s.body.children[0]}else i=e;var o;if(!i)return;let n=this._readAttr(i);return t=r.Utils.cloneDeep(t)||{},r.Utils.defaults(t,n),s=this.engine.prepareNode(t),this._writeAttr(i,t),this._insertNotAppend?this.el.prepend(i):this.el.appendChild(i),this._prepareElement(i,!0,t),this._updateContainerHeight(),s.subGrid&&this.makeSubGrid(s.el,void 0,void 0,!1),this._prevColumn&&1===this.opts.column&&(this._ignoreLayoutsNodeChange=!0),this._triggerAddEvent(),this._triggerChangeEvent(),delete this._ignoreLayoutsNodeChange,i}makeSubGrid(e,t,i,s=!0){var o,n,l;let a,h=e.gridstackNode;if(h||(h=this.makeWidget(e).gridstackNode),null===(o=h.subGrid)||void 0===o?void 0:o.el)return h.subGrid;let d,g=this;for(;g&&!a;)a=null===(n=g.opts)||void 0===n?void 0:n.subGrid,g=null===(l=g.parentGridItem)||void 0===l?void 0:l.grid;t=r.Utils.cloneDeep(Object.assign(Object.assign(Object.assign({},a||{}),{children:void 0}),t||h.subGrid)),h.subGrid=t,"auto"===t.column&&(d=!0,t.column=Math.max(h.w||1,(null==i?void 0:i.w)||1),t.disableOneColumnMode=!0);let p,c,m=h.el.querySelector(".grid-stack-item-content");if(s){if(this._removeDD(h.el),c=Object.assign(Object.assign({},h),{x:0,y:0}),r.Utils.removeInternalForSave(c),delete c.subGrid,h.content&&(c.content=h.content,delete h.content),this.opts.addRemoveCB)p=this.opts.addRemoveCB(this.el,c,!0,!1);else{let e=document.implementation.createHTMLDocument("");e.body.innerHTML='<div class="grid-stack-item"></div>',p=e.body.children[0],p.appendChild(m),e.body.innerHTML='<div class="grid-stack-item-content"></div>',m=e.body.children[0],h.el.appendChild(m)}this._prepareDragDropByNode(h)}if(i){let e=d?t.column:h.w,s=h.h+i.h,o=h.el.style;o.transition="none",this.update(h.el,{w:e,h:s}),setTimeout((()=>o.transition=null))}this.opts.addRemoveCB&&(t.addRemoveCB=t.addRemoveCB||this.opts.addRemoveCB);let v=h.subGrid=u.addGrid(m,t);return(null==i?void 0:i._moving)&&(v._isTemp=!0),d&&(v._autoColumn=!0),s&&v.addWidget(p,c),i&&(i._moving?window.setTimeout((()=>r.Utils.simulateMouseEvent(i._event,"mouseenter",v.el)),0):v.addWidget(h.el,h)),v}removeAsSubGrid(e){var t;let i=null===(t=this.parentGridItem)||void 0===t?void 0:t.grid;i&&(i.batchUpdate(),i.removeWidget(this.parentGridItem.el,!0,!0),this.engine.nodes.forEach((e=>{e.x+=this.parentGridItem.x,e.y+=this.parentGridItem.y,i.addWidget(e.el,e)})),i.batchUpdate(!1),this.parentGridItem&&delete this.parentGridItem.subGrid,delete this.parentGridItem,e&&window.setTimeout((()=>r.Utils.simulateMouseEvent(e._event,"mouseenter",i.el)),0))}save(e=!0,t=!1){let i=this.engine.save(e);if(i.forEach((i=>{var s;if(e&&i.el&&!i.subGrid){let e=i.el.querySelector(".grid-stack-item-content");i.content=e?e.innerHTML:void 0,i.content||delete i.content}else if(e||delete i.content,null===(s=i.subGrid)||void 0===s?void 0:s.el){const s=i.subGrid.save(e,t);i.subGrid=t?s:{children:s}}delete i.el})),t){let e=r.Utils.cloneDeep(this.opts);e.marginBottom===e.marginTop&&e.marginRight===e.marginLeft&&e.marginTop===e.marginRight&&(e.margin=e.marginTop,delete e.marginTop,delete e.marginRight,delete e.marginBottom,delete e.marginLeft),e.rtl===("rtl"===this.el.style.direction)&&(e.rtl="auto"),this._isAutoCellHeight&&(e.cellHeight="auto"),this._autoColumn&&(e.column="auto",delete e.disableOneColumnMode);const t=e._alwaysShowResizeHandle;return delete e._alwaysShowResizeHandle,void 0!==t?e.alwaysShowResizeHandle=t:delete e.alwaysShowResizeHandle,r.Utils.removeInternalAndSame(e,l.gridDefaults),e.children=i,e}return i}load(e,t=this.opts.addRemoveCB||!0){let i=u.Utils.sort([...e],-1,this._prevColumn||this.getColumn());this._insertNotAppend=!0,this._prevColumn&&this._prevColumn!==this.opts.column&&i.some((e=>e.x+e.w>this.opts.column))&&(this._ignoreLayoutsNodeChange=!0,this.engine.cacheLayout(i,this._prevColumn,!0));const s=this.opts.addRemoveCB;"function"==typeof t&&(this.opts.addRemoveCB=t);let o=[];if(this.batchUpdate(),t){[...this.engine.nodes].forEach((e=>{i.find((t=>e.id===t.id))||(this.opts.addRemoveCB&&this.opts.addRemoveCB(this.el,e,!1,!1),o.push(e),this.removeWidget(e.el,!0,!1))}))}return i.forEach((e=>{let i=e.id||0===e.id?this.engine.nodes.find((t=>t.id===e.id)):void 0;if(i){if(this.update(i.el,e),e.subGrid&&e.subGrid.children){let t=i.el.querySelector(".grid-stack");t&&t.gridstack&&(t.gridstack.load(e.subGrid.children),this._insertNotAppend=!0)}}else t&&this.addWidget(e)})),this.engine.removedNodes=o,this.batchUpdate(!1),delete this._ignoreLayoutsNodeChange,delete this._insertNotAppend,s?this.opts.addRemoveCB=s:delete this.opts.addRemoveCB,this}batchUpdate(e=!0){return this.engine.batchUpdate(e),e||(this._triggerRemoveEvent(),this._triggerAddEvent(),this._triggerChangeEvent()),this}getCellHeight(e=!1){if(this.opts.cellHeight&&"auto"!==this.opts.cellHeight&&(!e||!this.opts.cellHeightUnit||"px"===this.opts.cellHeightUnit))return this.opts.cellHeight;let t=this.el.querySelector("."+this.opts.itemClass);if(t){let e=r.Utils.toNumber(t.getAttribute("gs-h"));return Math.round(t.offsetHeight/e)}let i=parseInt(this.el.getAttribute("gs-current-row"));return i?Math.round(this.el.getBoundingClientRect().height/i):this.opts.cellHeight}cellHeight(e,t=!0){if(t&&void 0!==e&&this._isAutoCellHeight!==("auto"===e)&&(this._isAutoCellHeight="auto"===e,this._updateWindowResizeEvent()),"initial"!==e&&"auto"!==e||(e=void 0),void 0===e){let t=-this.opts.marginRight-this.opts.marginLeft+this.opts.marginTop+this.opts.marginBottom;e=this.cellWidth()+t}let i=r.Utils.parseHeight(e);return this.opts.cellHeightUnit===i.unit&&this.opts.cellHeight===i.h||(this.opts.cellHeightUnit=i.unit,this.opts.cellHeight=i.h,t&&this._updateStyles(!0)),this}cellWidth(){return this._widthOrContainer()/this.getColumn()}_widthOrContainer(){return this.el.clientWidth||this.el.parentElement.clientWidth||window.innerWidth}compact(){return this.engine.compact(),this._triggerChangeEvent(),this}column(e,t="moveScale"){if(e<1||this.opts.column===e)return this;let i,s=this.getColumn();return 1===e?this._prevColumn=s:delete this._prevColumn,this.el.classList.remove("grid-stack-"+s),this.el.classList.add("grid-stack-"+e),this.opts.column=this.engine.column=e,1===e&&this.opts.oneColumnModeDomSort&&(i=[],this.getGridItems().forEach((e=>{e.gridstackNode&&i.push(e.gridstackNode)})),i.length||(i=void 0)),this.engine.updateNodeWidths(s,e,i,t),this._isAutoCellHeight&&this.cellHeight(),this._ignoreLayoutsNodeChange=!0,this._triggerChangeEvent(),delete this._ignoreLayoutsNodeChange,this}getColumn(){return this.opts.column}getGridItems(){return Array.from(this.el.children).filter((e=>e.matches("."+this.opts.itemClass)&&!e.matches("."+this.opts.placeholderClass)))}destroy(e=!0){if(this.el)return this._updateWindowResizeEvent(!0),this.setStatic(!0,!1),this.setAnimation(!1),e?this.el.parentNode.removeChild(this.el):(this.removeAll(e),this.el.classList.remove(this._styleSheetClass)),this._removeStylesheet(),this.el.removeAttribute("gs-current-row"),this.parentGridItem&&delete this.parentGridItem.subGrid,delete this.parentGridItem,delete this.opts,delete this._placeholder,delete this.engine,delete this.el.gridstack,delete this.el,this}float(e){return this.opts.float!==e&&(this.opts.float=this.engine.float=e,this._triggerChangeEvent()),this}getFloat(){return this.engine.float}getCellFromPixel(e,t=!1){let i,s=this.el.getBoundingClientRect();i=t?{top:s.top+document.documentElement.scrollTop,left:s.left}:{top:this.el.offsetTop,left:this.el.offsetLeft};let o=e.left-i.left,n=e.top-i.top,r=s.width/this.getColumn(),l=s.height/parseInt(this.el.getAttribute("gs-current-row"));return{x:Math.floor(o/r),y:Math.floor(n/l)}}getRow(){return Math.max(this.engine.getRow(),this.opts.minRow)}isAreaEmpty(e,t,i,s){return this.engine.isAreaEmpty(e,t,i,s)}makeWidget(e){let t=u.getElement(e);return this._prepareElement(t,!0),this._updateContainerHeight(),this._triggerAddEvent(),this._triggerChangeEvent(),t}on(e,t){if(-1!==e.indexOf(" ")){return e.split(" ").forEach((e=>this.on(e,t))),this}if("change"===e||"added"===e||"removed"===e||"enable"===e||"disable"===e){let i="enable"===e||"disable"===e;this._gsEventHandler[e]=i?e=>t(e):e=>t(e,e.detail),this.el.addEventListener(e,this._gsEventHandler[e])}else"drag"===e||"dragstart"===e||"dragstop"===e||"resizestart"===e||"resize"===e||"resizestop"===e||"dropped"===e?this._gsEventHandler[e]=t:console.log("GridStack.on("+e+') event not supported, but you can still use $(".grid-stack").on(...) while jquery-ui is still used internally.');return this}off(e){if(-1!==e.indexOf(" ")){return e.split(" ").forEach((e=>this.off(e))),this}return"change"!==e&&"added"!==e&&"removed"!==e&&"enable"!==e&&"disable"!==e||this._gsEventHandler[e]&&this.el.removeEventListener(e,this._gsEventHandler[e]),delete this._gsEventHandler[e],this}removeWidget(e,t=!0,i=!0){return u.getElements(e).forEach((e=>{if(e.parentElement&&e.parentElement!==this.el)return;let s=e.gridstackNode;s||(s=this.engine.nodes.find((t=>e===t.el))),s&&(delete e.gridstackNode,this._removeDD(e),this.engine.removeNode(s,t,i),t&&e.parentElement&&e.remove())})),i&&(this._triggerRemoveEvent(),this._triggerChangeEvent()),this}removeAll(e=!0){return this.engine.nodes.forEach((e=>{delete e.el.gridstackNode,this._removeDD(e.el)})),this.engine.removeAll(e),this._triggerRemoveEvent(),this}setAnimation(e){return e?this.el.classList.add("grid-stack-animate"):this.el.classList.remove("grid-stack-animate"),this}setStatic(e,t=!0,i=!0){return this.opts.staticGrid===e||(this.opts.staticGrid=e,this._setupRemoveDrop(),this._setupAcceptWidget(),this.engine.nodes.forEach((s=>{this._prepareDragDropByNode(s),s.subGrid&&i&&s.subGrid.setStatic(e,t,i)})),t&&this._setStaticClass()),this}update(e,t){if(arguments.length>2){console.warn("gridstack.ts: `update(el, x, y, w, h)` is deprecated. Use `update(el, {x, w, content, ...})`. It will be removed soon");let i=arguments,s=1;return t={x:i[s++],y:i[s++],w:i[s++],h:i[s++]},this.update(e,t)}return u.getElements(e).forEach((e=>{if(!e||!e.gridstackNode)return;let i=e.gridstackNode,s=r.Utils.cloneDeep(t);delete s.autoPosition;let o,n=["x","y","w","h"];if(n.some((e=>void 0!==s[e]&&s[e]!==i[e]))&&(o={},n.forEach((e=>{o[e]=void 0!==s[e]?s[e]:i[e],delete s[e]}))),!o&&(s.minW||s.minH||s.maxW||s.maxH)&&(o={}),s.content){let t=e.querySelector(".grid-stack-item-content");t&&t.innerHTML!==s.content&&(t.innerHTML=s.content),delete s.content}let l=!1,a=!1;for(const e in s)"_"!==e[0]&&i[e]!==s[e]&&(i[e]=s[e],l=!0,a=a||!this.opts.staticGrid&&("noResize"===e||"noMove"===e||"locked"===e));o&&(this.engine.cleanNodes().beginUpdate(i).moveNode(i,o),this._updateContainerHeight(),this._triggerChangeEvent(),this.engine.endUpdate()),l&&this._writeAttr(e,i),a&&this._prepareDragDropByNode(i)})),this}margin(e){if(!("string"==typeof e&&e.split(" ").length>1)){let t=r.Utils.parseHeight(e);if(this.opts.marginUnit===t.unit&&this.opts.margin===t.h)return}return this.opts.margin=e,this.opts.marginTop=this.opts.marginBottom=this.opts.marginLeft=this.opts.marginRight=void 0,this._initMargin(),this._updateStyles(!0),this}getMargin(){return this.opts.margin}willItFit(e){if(arguments.length>1){console.warn("gridstack.ts: `willItFit(x,y,w,h,autoPosition)` is deprecated. Use `willItFit({x, y,...})`. It will be removed soon");let e=arguments,t=0,i={x:e[t++],y:e[t++],w:e[t++],h:e[t++],autoPosition:e[t++]};return this.willItFit(i)}return this.engine.willItFit(e)}_triggerChangeEvent(){if(this.engine.batchMode)return this;let e=this.engine.getDirtyNodes(!0);return e&&e.length&&(this._ignoreLayoutsNodeChange||this.engine.layoutsNodesChange(e),this._triggerEvent("change",e)),this.engine.saveInitial(),this}_triggerAddEvent(){return this.engine.batchMode||this.engine.addedNodes&&this.engine.addedNodes.length>0&&(this._ignoreLayoutsNodeChange||this.engine.layoutsNodesChange(this.engine.addedNodes),this.engine.addedNodes.forEach((e=>{delete e._dirty})),this._triggerEvent("added",this.engine.addedNodes),this.engine.addedNodes=[]),this}_triggerRemoveEvent(){return this.engine.batchMode||this.engine.removedNodes&&this.engine.removedNodes.length>0&&(this._triggerEvent("removed",this.engine.removedNodes),this.engine.removedNodes=[]),this}_triggerEvent(e,t){let i=t?new CustomEvent(e,{bubbles:!1,detail:t}):new Event(e);return this.el.dispatchEvent(i),this}_removeStylesheet(){return this._styles&&(r.Utils.removeStylesheet(this._styleSheetClass),delete this._styles),this}_updateStyles(e=!1,t){if(e&&this._removeStylesheet(),t||(t=this.getRow()),this._updateContainerHeight(),0===this.opts.cellHeight)return this;let i=this.opts.cellHeight,s=this.opts.cellHeightUnit,o=`.${this._styleSheetClass} > .${this.opts.itemClass}`;if(!this._styles){let e=this.opts.styleInHead?void 0:this.el.parentNode;if(this._styles=r.Utils.createStylesheet(this._styleSheetClass,e,{nonce:this.opts.nonce}),!this._styles)return this;this._styles._max=0,r.Utils.addCSSRule(this._styles,o,`min-height: ${i}${s}`);let t=this.opts.marginTop+this.opts.marginUnit,n=this.opts.marginBottom+this.opts.marginUnit,l=this.opts.marginRight+this.opts.marginUnit,a=this.opts.marginLeft+this.opts.marginUnit,h=`${o} > .grid-stack-item-content`,d=`.${this._styleSheetClass} > .grid-stack-placeholder > .placeholder-content`;r.Utils.addCSSRule(this._styles,h,`top: ${t}; right: ${l}; bottom: ${n}; left: ${a};`),r.Utils.addCSSRule(this._styles,d,`top: ${t}; right: ${l}; bottom: ${n}; left: ${a};`),r.Utils.addCSSRule(this._styles,`${o} > .ui-resizable-ne`,`right: ${l}`),r.Utils.addCSSRule(this._styles,`${o} > .ui-resizable-e`,`right: ${l}`),r.Utils.addCSSRule(this._styles,`${o} > .ui-resizable-se`,`right: ${l}; bottom: ${n}`),r.Utils.addCSSRule(this._styles,`${o} > .ui-resizable-nw`,`left: ${a}`),r.Utils.addCSSRule(this._styles,`${o} > .ui-resizable-w`,`left: ${a}`),r.Utils.addCSSRule(this._styles,`${o} > .ui-resizable-sw`,`left: ${a}; bottom: ${n}`)}if((t=t||this._styles._max)>this._styles._max){let e=e=>i*e+s;for(let i=this._styles._max+1;i<=t;i++){let t=e(i);r.Utils.addCSSRule(this._styles,`${o}[gs-y="${i-1}"]`,`top: ${e(i-1)}`),r.Utils.addCSSRule(this._styles,`${o}[gs-h="${i}"]`,`height: ${t}`),r.Utils.addCSSRule(this._styles,`${o}[gs-min-h="${i}"]`,`min-height: ${t}`),r.Utils.addCSSRule(this._styles,`${o}[gs-max-h="${i}"]`,`max-height: ${t}`)}this._styles._max=t}return this}_updateContainerHeight(){if(!this.engine||this.engine.batchMode)return this;let e=this.getRow()+this._extraDragRow;if(this.el.setAttribute("gs-current-row",String(e)),0===e)return this.el.style.removeProperty("min-height"),this;let t=this.opts.cellHeight,i=this.opts.cellHeightUnit;return t?(this.el.style.minHeight=e*t+i,this):this}_prepareElement(e,t=!1,i){e.classList.add(this.opts.itemClass),i=i||this._readAttr(e),e.gridstackNode=i,i.el=e,i.grid=this;let s=Object.assign({},i);return i=this.engine.addNode(i,t),r.Utils.same(i,s)||this._writeAttr(e,i),this._prepareDragDropByNode(i),this}_writePosAttr(e,t){return void 0!==t.x&&null!==t.x&&e.setAttribute("gs-x",String(t.x)),void 0!==t.y&&null!==t.y&&e.setAttribute("gs-y",String(t.y)),t.w&&e.setAttribute("gs-w",String(t.w)),t.h&&e.setAttribute("gs-h",String(t.h)),this}_writeAttr(e,t){if(!t)return this;this._writePosAttr(e,t);let i={autoPosition:"gs-auto-position",minW:"gs-min-w",minH:"gs-min-h",maxW:"gs-max-w",maxH:"gs-max-h",noResize:"gs-no-resize",noMove:"gs-no-move",locked:"gs-locked",id:"gs-id"};for(const s in i)t[s]?e.setAttribute(i[s],String(t[s])):e.removeAttribute(i[s]);return this}_readAttr(e){let t={};t.x=r.Utils.toNumber(e.getAttribute("gs-x")),t.y=r.Utils.toNumber(e.getAttribute("gs-y")),t.w=r.Utils.toNumber(e.getAttribute("gs-w")),t.h=r.Utils.toNumber(e.getAttribute("gs-h")),t.maxW=r.Utils.toNumber(e.getAttribute("gs-max-w")),t.minW=r.Utils.toNumber(e.getAttribute("gs-min-w")),t.maxH=r.Utils.toNumber(e.getAttribute("gs-max-h")),t.minH=r.Utils.toNumber(e.getAttribute("gs-min-h")),t.autoPosition=r.Utils.toBool(e.getAttribute("gs-auto-position")),t.noResize=r.Utils.toBool(e.getAttribute("gs-no-resize")),t.noMove=r.Utils.toBool(e.getAttribute("gs-no-move")),t.locked=r.Utils.toBool(e.getAttribute("gs-locked")),t.id=e.getAttribute("gs-id");for(const e in t){if(!t.hasOwnProperty(e))return;t[e]||0===t[e]||delete t[e]}return t}_setStaticClass(){let e=["grid-stack-static"];return this.opts.staticGrid?(this.el.classList.add(...e),this.el.setAttribute("gs-static","true")):(this.el.classList.remove(...e),this.el.removeAttribute("gs-static")),this}onParentResize(){if(!this.el||!this.el.clientWidth)return;let e=!1;if(this._autoColumn&&this.parentGridItem)this.opts.column!==this.parentGridItem.w&&(e=!0,this.column(this.parentGridItem.w,"none"));else{let t=!this.opts.disableOneColumnMode&&this.el.clientWidth<=this.opts.oneColumnSize;1===this.opts.column!==t&&(e=!0,this.opts.animate&&this.setAnimation(!1),this.column(t?1:this._prevColumn),this.opts.animate&&this.setAnimation(!0))}return this._isAutoCellHeight&&(!e&&this.opts.cellHeightThrottle?(this._cellHeightThrottle||(this._cellHeightThrottle=r.Utils.throttle((()=>this.cellHeight()),this.opts.cellHeightThrottle)),this._cellHeightThrottle()):this.cellHeight()),this.engine.nodes.forEach((e=>{e.subGrid&&e.subGrid.onParentResize()})),this}_updateWindowResizeEvent(e=!1){const t=(this._isAutoCellHeight||!this.opts.disableOneColumnMode)&&!this.parentGridItem;return e||!t||this._windowResizeBind?!e&&t||!this._windowResizeBind||(window.removeEventListener("resize",this._windowResizeBind),delete this._windowResizeBind):(this._windowResizeBind=this.onParentResize.bind(this),window.addEventListener("resize",this._windowResizeBind)),this}static getElement(e=".grid-stack-item"){return r.Utils.getElement(e)}static getElements(e=".grid-stack-item"){return r.Utils.getElements(e)}static getGridElement(e){return u.getElement(e)}static getGridElements(e){return r.Utils.getElements(e)}_initMargin(){let e,t=0,i=[];return"string"==typeof this.opts.margin&&(i=this.opts.margin.split(" ")),2===i.length?(this.opts.marginTop=this.opts.marginBottom=i[0],this.opts.marginLeft=this.opts.marginRight=i[1]):4===i.length?(this.opts.marginTop=i[0],this.opts.marginRight=i[1],this.opts.marginBottom=i[2],this.opts.marginLeft=i[3]):(e=r.Utils.parseHeight(this.opts.margin),this.opts.marginUnit=e.unit,t=this.opts.margin=e.h),void 0===this.opts.marginTop?this.opts.marginTop=t:(e=r.Utils.parseHeight(this.opts.marginTop),this.opts.marginTop=e.h,delete this.opts.margin),void 0===this.opts.marginBottom?this.opts.marginBottom=t:(e=r.Utils.parseHeight(this.opts.marginBottom),this.opts.marginBottom=e.h,delete this.opts.margin),void 0===this.opts.marginRight?this.opts.marginRight=t:(e=r.Utils.parseHeight(this.opts.marginRight),this.opts.marginRight=e.h,delete this.opts.margin),void 0===this.opts.marginLeft?this.opts.marginLeft=t:(e=r.Utils.parseHeight(this.opts.marginLeft),this.opts.marginLeft=e.h,delete this.opts.margin),this.opts.marginUnit=e.unit,this.opts.marginTop===this.opts.marginBottom&&this.opts.marginLeft===this.opts.marginRight&&this.opts.marginTop===this.opts.marginRight&&(this.opts.margin=this.opts.marginTop),this}static getDD(){return g}static setupDragIn(e,t){void 0!==(null==t?void 0:t.pause)&&(d.DDManager.pauseDrag=t.pause),"string"==typeof e&&(t=Object.assign(Object.assign({},l.dragInDefaultOptions),t||{}),r.Utils.getElements(e).forEach((e=>{g.isDraggable(e)||g.dragIn(e,t)})))}movable(e,t){return this.opts.staticGrid||u.getElements(e).forEach((e=>{let i=e.gridstackNode;i&&(t?delete i.noMove:i.noMove=!0,this._prepareDragDropByNode(i))})),this}resizable(e,t){return this.opts.staticGrid||u.getElements(e).forEach((e=>{let i=e.gridstackNode;i&&(t?delete i.noResize:i.noResize=!0,this._prepareDragDropByNode(i))})),this}disable(e=!0){if(!this.opts.staticGrid)return this.enableMove(!1,e),this.enableResize(!1,e),this._triggerEvent("disable"),this}enable(e=!0){if(!this.opts.staticGrid)return this.enableMove(!0,e),this.enableResize(!0,e),this._triggerEvent("enable"),this}enableMove(e,t=!0){return this.opts.staticGrid||(this.opts.disableDrag=!e,this.engine.nodes.forEach((i=>{this.movable(i.el,e),i.subGrid&&t&&i.subGrid.enableMove(e,t)}))),this}enableResize(e,t=!0){return this.opts.staticGrid||(this.opts.disableResize=!e,this.engine.nodes.forEach((i=>{this.resizable(i.el,e),i.subGrid&&t&&i.subGrid.enableResize(e,t)}))),this}_removeDD(e){return g.draggable(e,"destroy").resizable(e,"destroy"),e.gridstackNode&&delete e.gridstackNode._initDD,delete e.ddElement,this}_setupAcceptWidget(){if(this.opts.staticGrid||!this.opts.acceptWidgets&&!this.opts.removable)return g.droppable(this.el,"destroy"),this;let e,t,i=(i,s,o)=>{let n=s.gridstackNode;if(!n)return;o=o||s;let l=this.el.getBoundingClientRect(),{top:a,left:h}=o.getBoundingClientRect();h-=l.left,a-=l.top;let d={position:{top:a,left:h}};if(n._temporaryRemoved){if(n.x=Math.max(0,Math.round(h/t)),n.y=Math.max(0,Math.round(a/e)),delete n.autoPosition,this.engine.nodeBoundFix(n),!this.engine.willItFit(n)){if(n.autoPosition=!0,!this.engine.willItFit(n))return void g.off(s,"drag");n._willFitPos&&(r.Utils.copyPos(n,n._willFitPos),delete n._willFitPos)}this._onStartMoving(o,i,d,n,t,e)}else this._dragOrResize(o,i,d,n,t,e)};return g.droppable(this.el,{accept:e=>{let t=e.gridstackNode;if((null==t?void 0:t.grid)===this)return!0;if(!this.opts.acceptWidgets)return!1;let i=!0;if("function"==typeof this.opts.acceptWidgets)i=this.opts.acceptWidgets(e);else{let t=!0===this.opts.acceptWidgets?".grid-stack-item":this.opts.acceptWidgets;i=e.matches(t)}if(i&&t&&this.opts.maxRow){let e={w:t.w,h:t.h,minW:t.minW,minH:t.minH};i=this.engine.willItFit(e)}return i}}).on(this.el,"dropover",((s,o,n)=>{let r=o.gridstackNode;if((null==r?void 0:r.grid)===this&&!r._temporaryRemoved)return!1;if((null==r?void 0:r.grid)&&r.grid!==this&&!r._temporaryRemoved){r.grid._leave(o,n)}t=this.cellWidth(),e=this.getCellHeight(!0),r||(r=this._readAttr(o)),r.grid||(r._isExternal=!0,o.gridstackNode=r),n=n||o;let l=r.w||Math.round(n.offsetWidth/t)||1,a=r.h||Math.round(n.offsetHeight/e)||1;return r.grid&&r.grid!==this?(o._gridstackNodeOrig||(o._gridstackNodeOrig=r),o.gridstackNode=r=Object.assign(Object.assign({},r),{w:l,h:a,grid:this}),this.engine.cleanupNode(r).nodeBoundFix(r),r._initDD=r._isExternal=r._temporaryRemoved=!0):(r.w=l,r.h=a,r._temporaryRemoved=!0),this._itemRemoving(r.el,!1),g.on(o,"drag",i),i(s,o,n),!1})).on(this.el,"dropout",((e,t,i)=>{let s=t.gridstackNode;return!!s&&(s.grid&&s.grid!==this||(this._leave(t,i),this._isTemp&&this.removeAsSubGrid(s)),!1)})).on(this.el,"drop",((e,t,i)=>{var s,o;let n=t.gridstackNode;if((null==n?void 0:n.grid)===this&&!n._isExternal)return!1;let a=!!this.placeholder.parentElement;this.placeholder.remove();let h=t._gridstackNodeOrig;if(delete t._gridstackNodeOrig,a&&(null==h?void 0:h.grid)&&h.grid!==this){let e=h.grid;e.engine.removedNodes.push(h),e._triggerRemoveEvent()._triggerChangeEvent(),e.parentGridItem&&!e.engine.nodes.length&&e.opts.subGridDynamic&&e.removeAsSubGrid()}if(!n)return!1;if(a&&(this.engine.cleanupNode(n),n.grid=this),g.off(t,"drag"),i!==t?(i.remove(),t.gridstackNode=h,a&&(t=t.cloneNode(!0))):(t.remove(),this._removeDD(t)),!a)return!1;t.gridstackNode=n,n.el=t;let d=null===(o=null===(s=n.subGrid)||void 0===s?void 0:s.el)||void 0===o?void 0:o.gridstack;return r.Utils.copyPos(n,this._readAttr(this.placeholder)),r.Utils.removePositioningStyles(t),this._writeAttr(t,n),t.classList.add(l.gridDefaults.itemClass,this.opts.itemClass),this.el.appendChild(t),d&&(d.parentGridItem=n,d.opts.styleInHead||d._updateStyles(!0)),this._updateContainerHeight(),this.engine.addedNodes.push(n),this._triggerAddEvent(),this._triggerChangeEvent(),this.engine.endUpdate(),this._gsEventHandler.dropped&&this._gsEventHandler.dropped(Object.assign(Object.assign({},e),{type:"dropped"}),h&&h.grid?h:void 0,n),window.setTimeout((()=>{n.el&&n.el.parentElement?this._prepareDragDropByNode(n):this.engine.removeNode(n),delete n.grid._isTemp})),!1})),this}_itemRemoving(e,t){let i=e?e.gridstackNode:void 0;i&&i.grid&&(t?i._isAboutToRemove=!0:delete i._isAboutToRemove,t?e.classList.add("grid-stack-item-removing"):e.classList.remove("grid-stack-item-removing"))}_setupRemoveDrop(){if(!this.opts.staticGrid&&"string"==typeof this.opts.removable){let e=document.querySelector(this.opts.removable);if(!e)return this;g.isDroppable(e)||g.droppable(e,this.opts.removableOptions).on(e,"dropover",((e,t)=>this._itemRemoving(t,!0))).on(e,"dropout",((e,t)=>this._itemRemoving(t,!1)))}return this}_prepareDragDropByNode(e){let t=e.el;const i=e.noMove||this.opts.disableDrag,s=e.noResize||this.opts.disableResize;if(this.opts.staticGrid||i&&s)return e._initDD&&(this._removeDD(t),delete e._initDD),t.classList.add("ui-draggable-disabled","ui-resizable-disabled"),this;if(!e._initDD){let i,s,o=(o,n)=>{this._gsEventHandler[o.type]&&this._gsEventHandler[o.type](o,o.target),i=this.cellWidth(),s=this.getCellHeight(!0),this._onStartMoving(t,o,n,e,i,s)},n=(o,n)=>{this._dragOrResize(t,o,n,e,i,s)},l=i=>{this.placeholder.remove(),delete e._moving,delete e._event,delete e._lastTried;let s=i.target;if(s.gridstackNode&&s.gridstackNode.grid===this){if(e.el=s,e._isAboutToRemove){let o=t.gridstackNode.grid;o._gsEventHandler[i.type]&&o._gsEventHandler[i.type](i,s),this._removeDD(t),o.engine.removedNodes.push(e),o._triggerRemoveEvent(),delete t.gridstackNode,delete e.el,t.remove()}else r.Utils.removePositioningStyles(s),e._temporaryRemoved?(r.Utils.copyPos(e,e._orig),this._writePosAttr(s,e),this.engine.addNode(e)):this._writePosAttr(s,e),this._gsEventHandler[i.type]&&this._gsEventHandler[i.type](i,s);this._extraDragRow=0,this._updateContainerHeight(),this._triggerChangeEvent(),this.engine.endUpdate()}};g.draggable(t,{start:o,stop:l,drag:n}).resizable(t,{start:o,stop:l,resize:n}),e._initDD=!0}return g.draggable(t,i?"disable":"enable").resizable(t,s?"disable":"enable"),this}_onStartMoving(e,t,i,s,o,n){this.engine.cleanNodes().beginUpdate(s),this._writePosAttr(this.placeholder,s),this.el.appendChild(this.placeholder),s.el=this.placeholder,s._lastUiPosition=i.position,s._prevYPix=i.position.top,s._moving="dragstart"===t.type,delete s._lastTried,"dropover"===t.type&&s._temporaryRemoved&&(this.engine.addNode(s),s._moving=!0),this.engine.cacheRects(o,n,this.opts.marginTop,this.opts.marginRight,this.opts.marginBottom,this.opts.marginLeft),"resizestart"===t.type&&(g.resizable(e,"option","minWidth",o*(s.minW||1)).resizable(e,"option","minHeight",n*(s.minH||1)),s.maxW&&g.resizable(e,"option","maxWidth",o*s.maxW),s.maxH&&g.resizable(e,"option","maxHeight",n*s.maxH))}_dragOrResize(e,t,i,s,o,n){let l,a=Object.assign({},s._orig),h=this.opts.marginLeft,d=this.opts.marginRight,g=this.opts.marginTop,u=this.opts.marginBottom,p=Math.round(.1*n),c=Math.round(.1*o);if(h=Math.min(h,c),d=Math.min(d,c),g=Math.min(g,p),u=Math.min(u,p),"drag"===t.type){if(s._temporaryRemoved)return;let t=i.position.top-s._prevYPix;s._prevYPix=i.position.top,!1!==this.opts.draggable.scroll&&r.Utils.updateScrollPosition(e,i.position,t);let l=i.position.left+(i.position.left>s._lastUiPosition.left?-d:h),p=i.position.top+(i.position.top>s._lastUiPosition.top?-u:g);a.x=Math.round(l/o),a.y=Math.round(p/n);let c=this._extraDragRow;if(this.engine.collide(s,a)){let e=this.getRow(),t=Math.max(0,a.y+s.h-e);this.opts.maxRow&&e+t>this.opts.maxRow&&(t=Math.max(0,this.opts.maxRow-e)),this._extraDragRow=t}else this._extraDragRow=0;if(this._extraDragRow!==c&&this._updateContainerHeight(),s.x===a.x&&s.y===a.y)return}else if("resize"===t.type){if(a.x<0)return;if(r.Utils.updateScrollResize(t,e,n),a.w=Math.round((i.size.width-h)/o),a.h=Math.round((i.size.height-g)/n),s.w===a.w&&s.h===a.h)return;if(s._lastTried&&s._lastTried.w===a.w&&s._lastTried.h===a.h)return;let d=i.position.left+h,u=i.position.top+g;a.x=Math.round(d/o),a.y=Math.round(u/n),l=!0}s._event=t,s._lastTried=a;let m={x:i.position.left+h,y:i.position.top+g,w:(i.size?i.size.width:s.w*o)-h-d,h:(i.size?i.size.height:s.h*n)-g-u};if(this.engine.moveNodeCheck(s,Object.assign(Object.assign({},a),{cellWidth:o,cellHeight:n,rect:m,resizing:l}))){s._lastUiPosition=i.position,this.engine.cacheRects(o,n,g,d,u,h),delete s._skipDown,l&&s.subGrid&&s.subGrid.onParentResize(),this._extraDragRow=0,this._updateContainerHeight();let e=t.target;this._writePosAttr(e,s),this._gsEventHandler[t.type]&&this._gsEventHandler[t.type](t,e)}}_leave(e,t){let i=e.gridstackNode;i&&(g.off(e,"drag"),i._temporaryRemoved||(i._temporaryRemoved=!0,this.engine.removeNode(i),i.el=i._isExternal&&t?t:e,!0===this.opts.removable&&this._itemRemoving(e,!0),e._gridstackNodeOrig?(e.gridstackNode=e._gridstackNodeOrig,delete e._gridstackNodeOrig):i._isExternal&&(delete i.el,delete e.gridstackNode,this.engine.restoreInitial())))}commit(){return r.obsolete(this,this.batchUpdate(!1),"commit","batchUpdate","5.2"),this}}t.GridStack=u,u.Utils=r.Utils,u.Engine=n.GridStackEngine,u.GDRev="7.3.0"},3777:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.dragInDefaultOptions=t.gridDefaults=void 0,t.gridDefaults={alwaysShowResizeHandle:"mobile",animate:!0,auto:!0,cellHeight:"auto",cellHeightThrottle:100,cellHeightUnit:"px",column:12,draggable:{handle:".grid-stack-item-content",appendTo:"body",scroll:!0},handle:".grid-stack-item-content",itemClass:"grid-stack-item",margin:10,marginUnit:"px",maxRow:0,minRow:0,oneColumnSize:768,placeholderClass:"grid-stack-placeholder",placeholderText:"",removableOptions:{accept:".grid-stack-item"},resizable:{handles:"se"},rtl:"auto"},t.dragInDefaultOptions={handle:".grid-stack-item-content",appendTo:"body"}},5037:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.DDBaseImplement=void 0;t.DDBaseImplement=class{constructor(){this._eventRegister={}}get disabled(){return this._disabled}on(e,t){this._eventRegister[e]=t}off(e){delete this._eventRegister[e]}enable(){this._disabled=!1}disable(){this._disabled=!0}destroy(){delete this._eventRegister}triggerEvent(e,t){if(!this.disabled&&this._eventRegister&&this._eventRegister[e])return this._eventRegister[e](t)}}},6060:function(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.DDDraggable=void 0;const s=i(1432),o=i(1661),n=i(5037),r=i(1120);class l extends n.DDBaseImplement{constructor(e,t={}){super(),this.el=e,this.option=t;let i=t.handle.substring(1);this.dragEl=e.classList.contains(i)?e:e.querySelector(t.handle)||e,this._mouseDown=this._mouseDown.bind(this),this._mouseMove=this._mouseMove.bind(this),this._mouseUp=this._mouseUp.bind(this),this.enable()}on(e,t){super.on(e,t)}off(e){super.off(e)}enable(){!1!==this.disabled&&(super.enable(),this.dragEl.addEventListener("mousedown",this._mouseDown),r.isTouch&&(this.dragEl.addEventListener("touchstart",r.touchstart),this.dragEl.addEventListener("pointerdown",r.pointerdown)),this.el.classList.remove("ui-draggable-disabled"),this.el.classList.add("ui-draggable"))}disable(e=!1){!0!==this.disabled&&(super.disable(),this.dragEl.removeEventListener("mousedown",this._mouseDown),r.isTouch&&(this.dragEl.removeEventListener("touchstart",r.touchstart),this.dragEl.removeEventListener("pointerdown",r.pointerdown)),this.el.classList.remove("ui-draggable"),e||this.el.classList.add("ui-draggable-disabled"))}destroy(){this.dragTimeout&&window.clearTimeout(this.dragTimeout),delete this.dragTimeout,this.dragging&&this._mouseUp(this.mouseDownEvent),this.disable(!0),delete this.el,delete this.helper,delete this.option,super.destroy()}updateOption(e){return Object.keys(e).forEach((t=>this.option[t]=e[t])),this}_mouseDown(e){if(s.DDManager.mouseHandled)return;if(0!==e.button)return!0;const t=e.target.nodeName.toLowerCase();return["input","textarea","button","select","option"].find((e=>e===t))||e.target.closest('[contenteditable="true"]')||(this.mouseDownEvent=e,delete this.dragging,delete s.DDManager.dragElement,delete s.DDManager.dropElement,document.addEventListener("mousemove",this._mouseMove,!0),document.addEventListener("mouseup",this._mouseUp,!0),r.isTouch&&(this.dragEl.addEventListener("touchmove",r.touchmove),this.dragEl.addEventListener("touchend",r.touchend)),e.preventDefault(),document.activeElement&&document.activeElement.blur(),s.DDManager.mouseHandled=!0),!0}_callDrag(e){if(!this.dragging)return;const t=o.Utils.initEvent(e,{target:this.el,type:"drag"});this.option.drag&&this.option.drag(t,this.ui()),this.triggerEvent("drag",t)}_mouseMove(e){var t;let i=this.mouseDownEvent;if(this.dragging)if(this._dragFollow(e),s.DDManager.pauseDrag){const t=Number.isInteger(s.DDManager.pauseDrag)?s.DDManager.pauseDrag:100;this.dragTimeout&&window.clearTimeout(this.dragTimeout),this.dragTimeout=window.setTimeout((()=>this._callDrag(e)),t)}else this._callDrag(e);else if(Math.abs(e.x-i.x)+Math.abs(e.y-i.y)>3){this.dragging=!0,s.DDManager.dragElement=this;let i=null===(t=this.el.gridstackNode)||void 0===t?void 0:t.grid;i?s.DDManager.dropElement=i.el.ddElement.ddDroppable:delete s.DDManager.dropElement,this.helper=this._createHelper(e),this._setupHelperContainmentStyle(),this.dragOffset=this._getDragOffset(e,this.el,this.helperContainment);const n=o.Utils.initEvent(e,{target:this.el,type:"dragstart"});this._setupHelperStyle(e),this.option.start&&this.option.start(n,this.ui()),this.triggerEvent("dragstart",n)}return e.preventDefault(),!0}_mouseUp(e){var t;if(document.removeEventListener("mousemove",this._mouseMove,!0),document.removeEventListener("mouseup",this._mouseUp,!0),r.isTouch&&(this.dragEl.removeEventListener("touchmove",r.touchmove,!0),this.dragEl.removeEventListener("touchend",r.touchend,!0)),this.dragging){delete this.dragging,(null===(t=s.DDManager.dropElement)||void 0===t?void 0:t.el)===this.el.parentElement&&delete s.DDManager.dropElement,this.helperContainment.style.position=this.parentOriginStylePosition||null,this.helper===this.el?this._removeHelperStyle():this.helper.remove();const i=o.Utils.initEvent(e,{target:this.el,type:"dragstop"});this.option.stop&&this.option.stop(i),this.triggerEvent("dragstop",i),s.DDManager.dropElement&&s.DDManager.dropElement.drop(e)}delete this.helper,delete this.mouseDownEvent,delete s.DDManager.dragElement,delete s.DDManager.dropElement,delete s.DDManager.mouseHandled,e.preventDefault()}_createHelper(e){let t=this.el;return"function"==typeof this.option.helper?t=this.option.helper(e):"clone"===this.option.helper&&(t=o.Utils.cloneNode(this.el)),document.body.contains(t)||o.Utils.appendTo(t,"parent"===this.option.appendTo?this.el.parentNode:this.option.appendTo),t===this.el&&(this.dragElementOriginStyle=l.originStyleProp.map((e=>this.el.style[e]))),t}_setupHelperStyle(e){this.helper.classList.add("ui-draggable-dragging");const t=this.helper.style;return t.pointerEvents="none",t["min-width"]=0,t.width=this.dragOffset.width+"px",t.height=this.dragOffset.height+"px",t.willChange="left, top",t.position="fixed",this._dragFollow(e),t.transition="none",setTimeout((()=>{this.helper&&(t.transition=null)}),0),this}_removeHelperStyle(){var e;this.helper.classList.remove("ui-draggable-dragging");let t=null===(e=this.helper)||void 0===e?void 0:e.gridstackNode;if(!(null==t?void 0:t._isAboutToRemove)&&this.dragElementOriginStyle){let e=this.helper,t=this.dragElementOriginStyle.transition||null;e.style.transition=this.dragElementOriginStyle.transition="none",l.originStyleProp.forEach((t=>e.style[t]=this.dragElementOriginStyle[t]||null)),setTimeout((()=>e.style.transition=t),50)}return delete this.dragElementOriginStyle,this}_dragFollow(e){let t=0,i=0;const s=this.helper.style,o=this.dragOffset;s.left=e.clientX+o.offsetLeft-t+"px",s.top=e.clientY+o.offsetTop-i+"px"}_setupHelperContainmentStyle(){return this.helperContainment=this.helper.parentElement,"fixed"!==this.helper.style.position&&(this.parentOriginStylePosition=this.helperContainment.style.position,window.getComputedStyle(this.helperContainment).position.match(/static/)&&(this.helperContainment.style.position="relative")),this}_getDragOffset(e,t,i){let s=0,n=0;if(i){const e=document.createElement("div");o.Utils.addElStyles(e,{opacity:"0",position:"fixed",top:"0px",left:"0px",width:"1px",height:"1px",zIndex:"-999999"}),i.appendChild(e);const t=e.getBoundingClientRect();i.removeChild(e),s=t.left,n=t.top}const r=t.getBoundingClientRect();return{left:r.left,top:r.top,offsetLeft:-e.clientX+r.left-s,offsetTop:-e.clientY+r.top-n,width:r.width,height:r.height}}ui(){const e=this.el.parentElement.getBoundingClientRect(),t=this.helper.getBoundingClientRect();return{position:{top:t.top-e.top,left:t.left-e.left}}}}t.DDDraggable=l,l.originStyleProp=["transition","pointerEvents","position","left","top","minWidth","willChange"]},6839:function(e,t,i){var s=i(3668);i(8692),i(241),i.g.GridStack=s.GridStack},7540:function(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.DDDroppable=void 0;const s=i(1432),o=i(5037),n=i(1661),r=i(1120);class l extends o.DDBaseImplement{constructor(e,t={}){super(),this.el=e,this.option=t,this._mouseEnter=this._mouseEnter.bind(this),this._mouseLeave=this._mouseLeave.bind(this),this.enable(),this._setupAccept()}on(e,t){super.on(e,t)}off(e){super.off(e)}enable(){!1!==this.disabled&&(super.enable(),this.el.classList.add("ui-droppable"),this.el.classList.remove("ui-droppable-disabled"),this.el.addEventListen
    ... [truncated]
    
  • public/build/dashboard.faf42d4e.js+2 0 added
    @@ -0,0 +1,2 @@
    +/*! For license information please see dashboard.faf42d4e.js.LICENSE.txt */
    +"use strict";(self.webpackChunkkimai=self.webpackChunkkimai||[]).push([[945],{823:function(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.DDDroppable=void 0;const s=i(4839),o=i(5630),n=i(1128),r=i(9931);class l extends o.DDBaseImplement{constructor(e,t={}){super(),this.el=e,this.option=t,this._mouseEnter=this._mouseEnter.bind(this),this._mouseLeave=this._mouseLeave.bind(this),this.enable(),this._setupAccept()}on(e,t){super.on(e,t)}off(e){super.off(e)}enable(){!1!==this.disabled&&(super.enable(),this.el.classList.add("ui-droppable"),this.el.classList.remove("ui-droppable-disabled"),this.el.addEventListener("mouseenter",this._mouseEnter),this.el.addEventListener("mouseleave",this._mouseLeave),r.isTouch&&(this.el.addEventListener("pointerenter",r.pointerenter),this.el.addEventListener("pointerleave",r.pointerleave)))}disable(e=!1){!0!==this.disabled&&(super.disable(),this.el.classList.remove("ui-droppable"),e||this.el.classList.add("ui-droppable-disabled"),this.el.removeEventListener("mouseenter",this._mouseEnter),this.el.removeEventListener("mouseleave",this._mouseLeave),r.isTouch&&(this.el.removeEventListener("pointerenter",r.pointerenter),this.el.removeEventListener("pointerleave",r.pointerleave)))}destroy(){this.disable(!0),this.el.classList.remove("ui-droppable"),this.el.classList.remove("ui-droppable-disabled"),super.destroy()}updateOption(e){return Object.keys(e).forEach((t=>this.option[t]=e[t])),this._setupAccept(),this}_mouseEnter(e){if(!s.DDManager.dragElement)return;if(!this._canDrop(s.DDManager.dragElement.el))return;e.preventDefault(),e.stopPropagation(),s.DDManager.dropElement&&s.DDManager.dropElement!==this&&s.DDManager.dropElement._mouseLeave(e),s.DDManager.dropElement=this;const t=n.Utils.initEvent(e,{target:this.el,type:"dropover"});this.option.over&&this.option.over(t,this._ui(s.DDManager.dragElement)),this.triggerEvent("dropover",t),this.el.classList.add("ui-droppable-over")}_mouseLeave(e){var t;if(!s.DDManager.dragElement||s.DDManager.dropElement!==this)return;e.preventDefault(),e.stopPropagation();const i=n.Utils.initEvent(e,{target:this.el,type:"dropout"});if(this.option.out&&this.option.out(i,this._ui(s.DDManager.dragElement)),this.triggerEvent("dropout",i),s.DDManager.dropElement===this){let i;delete s.DDManager.dropElement;let o=this.el.parentElement;for(;!i&&o;)i=null===(t=o.ddElement)||void 0===t?void 0:t.ddDroppable,o=o.parentElement;i&&i._mouseEnter(e)}}drop(e){e.preventDefault();const t=n.Utils.initEvent(e,{target:this.el,type:"drop"});this.option.drop&&this.option.drop(t,this._ui(s.DDManager.dragElement)),this.triggerEvent("drop",t)}_canDrop(e){return e&&(!this.accept||this.accept(e))}_setupAccept(){return this.option.accept?("string"==typeof this.option.accept?this.accept=e=>e.matches(this.option.accept):this.accept=this.option.accept,this):this}_ui(e){return Object.assign({draggable:e.el},e.ui())}}t.DDDroppable=l},1096:function(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.DDElement=void 0;const s=i(5593),o=i(7263),n=i(823);class r{constructor(e){this.el=e}static init(e){return e.ddElement||(e.ddElement=new r(e)),e.ddElement}on(e,t){return this.ddDraggable&&["drag","dragstart","dragstop"].indexOf(e)>-1?this.ddDraggable.on(e,t):this.ddDroppable&&["drop","dropover","dropout"].indexOf(e)>-1?this.ddDroppable.on(e,t):this.ddResizable&&["resizestart","resize","resizestop"].indexOf(e)>-1&&this.ddResizable.on(e,t),this}off(e){return this.ddDraggable&&["drag","dragstart","dragstop"].indexOf(e)>-1?this.ddDraggable.off(e):this.ddDroppable&&["drop","dropover","dropout"].indexOf(e)>-1?this.ddDroppable.off(e):this.ddResizable&&["resizestart","resize","resizestop"].indexOf(e)>-1&&this.ddResizable.off(e),this}setupDraggable(e){return this.ddDraggable?this.ddDraggable.updateOption(e):this.ddDraggable=new o.DDDraggable(this.el,e),this}cleanDraggable(){return this.ddDraggable&&(this.ddDraggable.destroy(),delete this.ddDraggable),this}setupResizable(e){return this.ddResizable?this.ddResizable.updateOption(e):this.ddResizable=new s.DDResizable(this.el,e),this}cleanResizable(){return this.ddResizable&&(this.ddResizable.destroy(),delete this.ddResizable),this}setupDroppable(e){return this.ddDroppable?this.ddDroppable.updateOption(e):this.ddDroppable=new n.DDDroppable(this.el,e),this}cleanDroppable(){return this.ddDroppable&&(this.ddDroppable.destroy(),delete this.ddDroppable),this}}t.DDElement=r},1128:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.Utils=t.obsoleteAttr=t.obsoleteOptsDel=t.obsoleteOpts=t.obsolete=void 0,t.obsolete=function(e,t,i,s,o){let n=(...n)=>(console.warn("gridstack.js: Function `"+i+"` is deprecated in "+o+" and has been replaced with `"+s+"`. It will be **removed** in a future release"),t.apply(e,n));return n.prototype=t.prototype,n},t.obsoleteOpts=function(e,t,i,s){void 0!==e[t]&&(e[i]=e[t],console.warn("gridstack.js: Option `"+t+"` is deprecated in "+s+" and has been replaced with `"+i+"`. It will be **removed** in a future release"))},t.obsoleteOptsDel=function(e,t,i,s){void 0!==e[t]&&console.warn("gridstack.js: Option `"+t+"` is deprecated in "+i+s)},t.obsoleteAttr=function(e,t,i,s){let o=e.getAttribute(t);null!==o&&(e.setAttribute(i,o),console.warn("gridstack.js: attribute `"+t+"`="+o+" is deprecated on this object in "+s+" and has been replaced with `"+i+"`. It will be **removed** in a future release"))};class i{static getElements(e){if("string"==typeof e){let t=document.querySelectorAll(e);return t.length||"."===e[0]||"#"===e[0]||(t=document.querySelectorAll("."+e),t.length||(t=document.querySelectorAll("#"+e))),Array.from(t)}return[e]}static getElement(e){if("string"==typeof e){if(!e.length)return null;if("#"===e[0])return document.getElementById(e.substring(1));if("."===e[0]||"["===e[0])return document.querySelector(e);if(!isNaN(+e[0]))return document.getElementById(e);let t=document.querySelector(e);return t||(t=document.getElementById(e)),t||(t=document.querySelector("."+e)),t}return e}static isIntercepted(e,t){return!(e.y>=t.y+t.h||e.y+e.h<=t.y||e.x+e.w<=t.x||e.x>=t.x+t.w)}static isTouching(e,t){return i.isIntercepted(e,{x:t.x-.5,y:t.y-.5,w:t.w+1,h:t.h+1})}static areaIntercept(e,t){let i=e.x>t.x?e.x:t.x,s=e.x+e.w<t.x+t.w?e.x+e.w:t.x+t.w;if(s<=i)return 0;let o=e.y>t.y?e.y:t.y,n=e.y+e.h<t.y+t.h?e.y+e.h:t.y+t.h;return n<=o?0:(s-i)*(n-o)}static area(e){return e.w*e.h}static sort(e,t,i){return i=i||e.reduce(((e,t)=>Math.max(t.x+t.w,e)),0)||12,-1===t?e.sort(((e,t)=>t.x+t.y*i-(e.x+e.y*i))):e.sort(((e,t)=>e.x+e.y*i-(t.x+t.y*i)))}static createStylesheet(e,t,i){let s=document.createElement("style");const o=null==i?void 0:i.nonce;return o&&(s.nonce=o),s.setAttribute("type","text/css"),s.setAttribute("gs-style-id",e),s.styleSheet?s.styleSheet.cssText="":s.appendChild(document.createTextNode("")),t?t.insertBefore(s,t.firstChild):(t=document.getElementsByTagName("head")[0]).appendChild(s),s.sheet}static removeStylesheet(e){let t=document.querySelector("STYLE[gs-style-id="+e+"]");t&&t.parentNode&&t.remove()}static addCSSRule(e,t,i){"function"==typeof e.addRule?e.addRule(t,i):"function"==typeof e.insertRule&&e.insertRule(`${t}{${i}}`)}static toBool(e){return"boolean"==typeof e?e:"string"==typeof e?!(""===(e=e.toLowerCase())||"no"===e||"false"===e||"0"===e):Boolean(e)}static toNumber(e){return null===e||0===e.length?void 0:Number(e)}static parseHeight(e){let t,i="px";if("string"==typeof e){let s=e.match(/^(-[0-9]+\.[0-9]+|[0-9]*\.[0-9]+|-[0-9]+|[0-9]+)(px|em|rem|vh|vw|%)?$/);if(!s)throw new Error("Invalid height");i=s[2]||"px",t=parseFloat(s[1])}else t=e;return{h:t,unit:i}}static defaults(e,...t){return t.forEach((t=>{for(const i in t){if(!t.hasOwnProperty(i))return;null===e[i]||void 0===e[i]?e[i]=t[i]:"object"==typeof t[i]&&"object"==typeof e[i]&&this.defaults(e[i],t[i])}})),e}static same(e,t){if("object"!=typeof e)return e==t;if(typeof e!=typeof t)return!1;if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const i in e)if(e[i]!==t[i])return!1;return!0}static copyPos(e,t,i=!1){return e.x=t.x,e.y=t.y,e.w=t.w,e.h=t.h,i&&(t.minW&&(e.minW=t.minW),t.minH&&(e.minH=t.minH),t.maxW&&(e.maxW=t.maxW),t.maxH&&(e.maxH=t.maxH)),e}static samePos(e,t){return e&&t&&e.x===t.x&&e.y===t.y&&e.w===t.w&&e.h===t.h}static removeInternalAndSame(e,t){if("object"==typeof e&&"object"==typeof t)for(let i in e){let s=e[i];if("_"===i[0]||s===t[i])delete e[i];else if(s&&"object"==typeof s&&void 0!==t[i]){for(let e in s)s[e]!==t[i][e]&&"_"!==e[0]||delete s[e];Object.keys(s).length||delete e[i]}}}static removeInternalForSave(e,t=!0){for(let t in e)"_"!==t[0]&&null!==e[t]&&void 0!==e[t]||delete e[t];delete e.grid,t&&delete e.el,e.autoPosition||delete e.autoPosition,e.noResize||delete e.noResize,e.noMove||delete e.noMove,e.locked||delete e.locked,1!==e.w&&e.w!==e.minW||delete e.w,1!==e.h&&e.h!==e.minH||delete e.h}static closestUpByClass(e,t){for(;e;){if(e.classList.contains(t))return e;e=e.parentElement}return null}static throttle(e,t){let i=!1;return(...s)=>{i||(i=!0,setTimeout((()=>{e(...s),i=!1}),t))}}static removePositioningStyles(e){let t=e.style;t.position&&t.removeProperty("position"),t.left&&t.removeProperty("left"),t.top&&t.removeProperty("top"),t.width&&t.removeProperty("width"),t.height&&t.removeProperty("height")}static getScrollElement(e){if(!e)return document.scrollingElement||document.documentElement;const t=getComputedStyle(e);return/(auto|scroll)/.test(t.overflow+t.overflowY)?e:this.getScrollElement(e.parentElement)}static updateScrollPosition(e,t,i){let s=e.getBoundingClientRect(),o=window.innerHeight||document.documentElement.clientHeight;if(s.top<0||s.bottom>o){let n=s.bottom-o,r=s.top,l=this.getScrollElement(e);if(null!==l){let a=l.scrollTop;s.top<0&&i<0?e.offsetHeight>o?l.scrollTop+=i:l.scrollTop+=Math.abs(r)>Math.abs(i)?i:r:i>0&&(e.offsetHeight>o?l.scrollTop+=i:l.scrollTop+=n>i?i:n),t.top+=l.scrollTop-a}}}static updateScrollResize(e,t,i){const s=this.getScrollElement(t),o=s.clientHeight,n=s===this.getScrollElement()?0:s.getBoundingClientRect().top,r=e.clientY-n,l=r>o-i;r<i?s.scrollBy({behavior:"smooth",top:r-i}):l&&s.scrollBy({behavior:"smooth",top:i-(o-r)})}static clone(e){return null==e||"object"!=typeof e?e:e instanceof Array?[...e]:Object.assign({},e)}static cloneDeep(e){const t=["parentGrid","el","grid","subGrid","engine"],s=i.clone(e);for(const o in s)s.hasOwnProperty(o)&&"object"==typeof s[o]&&"__"!==o.substring(0,2)&&!t.find((e=>e===o))&&(s[o]=i.cloneDeep(e[o]));return s}static cloneNode(e){const t=e.cloneNode(!0);return t.removeAttribute("id"),t}static appendTo(e,t){let i;i="string"==typeof t?document.querySelector(t):t,i&&i.appendChild(e)}static addElStyles(e,t){if(t instanceof Object)for(const i in t)t.hasOwnProperty(i)&&(Array.isArray(t[i])?t[i].forEach((t=>{e.style[i]=t})):e.style[i]=t[i])}static initEvent(e,t){const i={type:t.type},s={button:0,which:0,buttons:1,bubbles:!0,cancelable:!0,target:t.target?t.target:e.target};return e.dataTransfer&&(i.dataTransfer=e.dataTransfer),["altKey","ctrlKey","metaKey","shiftKey"].forEach((t=>i[t]=e[t])),["pageX","pageY","clientX","clientY","screenX","screenY"].forEach((t=>i[t]=e[t])),Object.assign(Object.assign({},i),s)}static simulateMouseEvent(e,t,i){const s=document.createEvent("MouseEvents");s.initMouseEvent(t,!0,!0,window,1,e.screenX,e.screenY,e.clientX,e.clientY,e.ctrlKey,e.altKey,e.shiftKey,e.metaKey,0,e.target),(i||e.target).dispatchEvent(s)}}t.Utils=i},1692:function(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.GridStackEngine=void 0;const s=i(1128);class o{constructor(e={}){this.addedNodes=[],this.removedNodes=[],this.column=e.column||12,this.maxRow=e.maxRow,this._float=e.float,this.nodes=e.nodes||[],this.onChange=e.onChange}batchUpdate(e=!0){return!!this.batchMode===e||(this.batchMode=e,e?(this._prevFloat=this._float,this._float=!0,this.saveInitial()):(this._float=this._prevFloat,delete this._prevFloat,this._packNodes()._notify())),this}_useEntireRowArea(e,t){return(!this.float||this.batchMode&&!this._prevFloat)&&!this._hasLocked&&(!e._moving||e._skipDown||t.y<=e.y)}_fixCollisions(e,t=e,i,o={}){if(this.sortNodes(-1),!(i=i||this.collide(e,t)))return!1;if(e._moving&&!o.nested&&!this.float&&this.swap(e,i))return!0;let n=t;this._useEntireRowArea(e,t)&&(n={x:0,w:this.column,y:t.y,h:t.h},i=this.collide(e,n,o.skip));let r=!1,l={nested:!0,pack:!1};for(;i=i||this.collide(e,n,o.skip);){let n;if(i.locked||e._moving&&!e._skipDown&&t.y>e.y&&!this.float&&(!this.collide(i,Object.assign(Object.assign({},i),{y:e.y}),e)||!this.collide(i,Object.assign(Object.assign({},i),{y:t.y-i.h}),e))?(e._skipDown=e._skipDown||t.y>e.y,n=this.moveNode(e,Object.assign(Object.assign(Object.assign({},t),{y:i.y+i.h}),l)),i.locked&&n?s.Utils.copyPos(t,e):!i.locked&&n&&o.pack&&(this._packNodes(),t.y=i.y+i.h,s.Utils.copyPos(e,t)),r=r||n):n=this.moveNode(i,Object.assign(Object.assign(Object.assign({},i),{y:t.y+t.h,skip:e}),l)),!n)return r;i=void 0}return r}collide(e,t=e,i){return this.nodes.find((o=>o!==e&&o!==i&&s.Utils.isIntercepted(o,t)))}collideAll(e,t=e,i){return this.nodes.filter((o=>o!==e&&o!==i&&s.Utils.isIntercepted(o,t)))}directionCollideCoverage(e,t,i){if(!t.rect||!e._rect)return;let s,o=e._rect,n=Object.assign({},t.rect);return n.y>o.y?(n.h+=n.y-o.y,n.y=o.y):n.h+=o.y-n.y,n.x>o.x?(n.w+=n.x-o.x,n.x=o.x):n.w+=o.x-n.x,i.forEach((e=>{if(e.locked||!e._rect)return;let t=e._rect,i=Number.MAX_VALUE,r=Number.MAX_VALUE,l=.5;o.y<t.y?i=(n.y+n.h-t.y)/t.h:o.y+o.h>t.y+t.h&&(i=(t.y+t.h-n.y)/t.h),o.x<t.x?r=(n.x+n.w-t.x)/t.w:o.x+o.w>t.x+t.w&&(r=(t.x+t.w-n.x)/t.w);let a=Math.min(r,i);a>l&&(l=a,s=e)})),t.collide=s,s}cacheRects(e,t,i,s,o,n){return this.nodes.forEach((r=>r._rect={y:r.y*t+i,x:r.x*e+n,w:r.w*e-n-s,h:r.h*t-i-o})),this}swap(e,t){if(!t||t.locked||!e||e.locked)return!1;function i(){let i=t.x,s=t.y;return t.x=e.x,t.y=e.y,e.h!=t.h?(e.x=i,e.y=t.y+t.h):e.w!=t.w?(e.x=t.x+t.w,e.y=s):(e.x=i,e.y=s),e._dirty=t._dirty=!0,!0}let o;if(e.w===t.w&&e.h===t.h&&(e.x===t.x||e.y===t.y)&&(o=s.Utils.isTouching(e,t)))return i();if(!1!==o){if(e.w===t.w&&e.x===t.x&&(o||(o=s.Utils.isTouching(e,t)))){if(t.y<e.y){let i=e;e=t,t=i}return i()}if(!1!==o){if(e.h===t.h&&e.y===t.y&&(o||(o=s.Utils.isTouching(e,t)))){if(t.x<e.x){let i=e;e=t,t=i}return i()}return!1}}}isAreaEmpty(e,t,i,s){let o={x:e||0,y:t||0,w:i||1,h:s||1};return!this.collide(o)}compact(){if(0===this.nodes.length)return this;this.batchUpdate().sortNodes();let e=this.nodes;return this.nodes=[],e.forEach((e=>{e.locked||(e.autoPosition=!0),this.addNode(e,!1),e._dirty=!0})),this.batchUpdate(!1)}set float(e){this._float!==e&&(this._float=e||!1,e||this._packNodes()._notify())}get float(){return this._float||!1}sortNodes(e){return this.nodes=s.Utils.sort(this.nodes,e,this.column),this}_packNodes(){return this.batchMode||(this.sortNodes(),this.float?this.nodes.forEach((e=>{if(e._updating||void 0===e._orig||e.y===e._orig.y)return;let t=e.y;for(;t>e._orig.y;){--t,this.collide(e,{x:e.x,y:t,w:e.w,h:e.h})||(e._dirty=!0,e.y=t)}})):this.nodes.forEach(((e,t)=>{if(!e.locked)for(;e.y>0;){let i=0===t?0:e.y-1;if(!(0===t||!this.collide(e,{x:e.x,y:i,w:e.w,h:e.h})))break;e._dirty=e.y!==i,e.y=i}}))),this}prepareNode(e,t){(e=e||{})._id=e._id||o._idSeq++,void 0!==e.x&&void 0!==e.y&&null!==e.x&&null!==e.y||(e.autoPosition=!0);let i={x:0,y:0,w:1,h:1};return s.Utils.defaults(e,i),e.autoPosition||delete e.autoPosition,e.noResize||delete e.noResize,e.noMove||delete e.noMove,"string"==typeof e.x&&(e.x=Number(e.x)),"string"==typeof e.y&&(e.y=Number(e.y)),"string"==typeof e.w&&(e.w=Number(e.w)),"string"==typeof e.h&&(e.h=Number(e.h)),isNaN(e.x)&&(e.x=i.x,e.autoPosition=!0),isNaN(e.y)&&(e.y=i.y,e.autoPosition=!0),isNaN(e.w)&&(e.w=i.w),isNaN(e.h)&&(e.h=i.h),this.nodeBoundFix(e,t)}nodeBoundFix(e,t){let i=e._orig||s.Utils.copyPos({},e);e.maxW&&(e.w=Math.min(e.w,e.maxW)),e.maxH&&(e.h=Math.min(e.h,e.maxH)),e.minW&&e.minW<=this.column&&(e.w=Math.max(e.w,e.minW)),e.minH&&(e.h=Math.max(e.h,e.minH));if((1===this.column||e.x+e.w>this.column)&&this.column<12&&!this._inColumnResize&&e._id&&-1===this.findCacheLayout(e,12)){let t=Object.assign({},e);t.autoPosition?(delete t.x,delete t.y):t.x=Math.min(11,t.x),t.w=Math.min(12,t.w),this.cacheOneLayout(t,12)}return e.w>this.column?e.w=this.column:e.w<1&&(e.w=1),this.maxRow&&e.h>this.maxRow?e.h=this.maxRow:e.h<1&&(e.h=1),e.x<0&&(e.x=0),e.y<0&&(e.y=0),e.x+e.w>this.column&&(t?e.w=this.column-e.x:e.x=this.column-e.w),this.maxRow&&e.y+e.h>this.maxRow&&(t?e.h=this.maxRow-e.y:e.y=this.maxRow-e.h),s.Utils.samePos(e,i)||(e._dirty=!0),e}getDirtyNodes(e){return e?this.nodes.filter((e=>e._dirty&&!s.Utils.samePos(e,e._orig))):this.nodes.filter((e=>e._dirty))}_notify(e){if(this.batchMode||!this.onChange)return this;let t=(e||[]).concat(this.getDirtyNodes());return this.onChange(t),this}cleanNodes(){return this.batchMode||this.nodes.forEach((e=>{delete e._dirty,delete e._lastTried})),this}saveInitial(){return this.nodes.forEach((e=>{e._orig=s.Utils.copyPos({},e),delete e._dirty})),this._hasLocked=this.nodes.some((e=>e.locked)),this}restoreInitial(){return this.nodes.forEach((e=>{s.Utils.samePos(e,e._orig)||(s.Utils.copyPos(e,e._orig),e._dirty=!0)})),this._notify(),this}findEmptyPosition(e,t=this.nodes,i=this.column){t=s.Utils.sort(t,-1,i);let o=!1;for(let n=0;!o;++n){let r=n%i,l=Math.floor(n/i);if(r+e.w>i)continue;let a={x:r,y:l,w:e.w,h:e.h};t.find((e=>s.Utils.isIntercepted(a,e)))||(e.x=r,e.y=l,delete e.autoPosition,o=!0)}return o}addNode(e,t=!1){let i=this.nodes.find((t=>t._id===e._id));return i||(delete(e=this._inColumnResize?this.nodeBoundFix(e):this.prepareNode(e))._temporaryRemoved,delete e._removeDOM,e.autoPosition&&this.findEmptyPosition(e)&&delete e.autoPosition,this.nodes.push(e),t&&this.addedNodes.push(e),this._fixCollisions(e),this.batchMode||this._packNodes()._notify(),e)}removeNode(e,t=!0,i=!1){return this.nodes.find((t=>t===e))?(i&&this.removedNodes.push(e),t&&(e._removeDOM=!0),this.nodes=this.nodes.filter((t=>t!==e)),this._packNodes()._notify([e])):this}removeAll(e=!0){return delete this._layouts,0===this.nodes.length?this:(e&&this.nodes.forEach((e=>e._removeDOM=!0)),this.removedNodes=this.nodes,this.nodes=[],this._notify(this.removedNodes))}moveNodeCheck(e,t){if(!this.changedPosConstrain(e,t))return!1;if(t.pack=!0,!this.maxRow)return this.moveNode(e,t);let i,n=new o({column:this.column,float:this.float,nodes:this.nodes.map((t=>t===e?(i=Object.assign({},t),i):Object.assign({},t)))});if(!i)return!1;let r=n.moveNode(i,t)&&n.getRow()<=this.maxRow;if(!r&&!t.resizing&&t.collide){let i=t.collide.el.gridstackNode;if(this.swap(e,i))return this._notify(),!0}return!!r&&(n.nodes.filter((e=>e._dirty)).forEach((e=>{let t=this.nodes.find((t=>t._id===e._id));t&&(s.Utils.copyPos(t,e),t._dirty=!0)})),this._notify(),!0)}willItFit(e){if(delete e._willFitPos,!this.maxRow)return!0;let t=new o({column:this.column,float:this.float,nodes:this.nodes.map((e=>Object.assign({},e)))}),i=Object.assign({},e);return this.cleanupNode(i),delete i.el,delete i._id,delete i.content,delete i.grid,t.addNode(i),t.getRow()<=this.maxRow&&(e._willFitPos=s.Utils.copyPos({},i),!0)}changedPosConstrain(e,t){return t.w=t.w||e.w,t.h=t.h||e.h,e.x!==t.x||e.y!==t.y||(e.maxW&&(t.w=Math.min(t.w,e.maxW)),e.maxH&&(t.h=Math.min(t.h,e.maxH)),e.minW&&(t.w=Math.max(t.w,e.minW)),e.minH&&(t.h=Math.max(t.h,e.minH)),e.w!==t.w||e.h!==t.h)}moveNode(e,t){var i,o;if(!e||!t)return!1;let n;void 0===t.pack&&(n=t.pack=!0),"number"!=typeof t.x&&(t.x=e.x),"number"!=typeof t.y&&(t.y=e.y),"number"!=typeof t.w&&(t.w=e.w),"number"!=typeof t.h&&(t.h=e.h);let r=e.w!==t.w||e.h!==t.h,l=s.Utils.copyPos({},e,!0);if(s.Utils.copyPos(l,t),l=this.nodeBoundFix(l,r),s.Utils.copyPos(t,l),s.Utils.samePos(e,t))return!1;let a=s.Utils.copyPos({},e),h=this.collideAll(e,l,t.skip),d=!0;if(h.length){let r=e._moving&&!t.nested,a=r?this.directionCollideCoverage(e,t,h):h[0];if(r&&a&&(null===(o=null===(i=e.grid)||void 0===i?void 0:i.opts)||void 0===o?void 0:o.subGridDynamic)&&!e.grid._isTemp){let i=s.Utils.areaIntercept(t.rect,a._rect),o=s.Utils.area(t.rect),n=s.Utils.area(a._rect);i/(o<n?o:n)>.8&&(a.grid.makeSubGrid(a.el,void 0,e),a=void 0)}a?d=!this._fixCollisions(e,l,a,t):(d=!1,n&&delete t.pack)}return d&&(e._dirty=!0,s.Utils.copyPos(e,l)),t.pack&&this._packNodes()._notify(),!s.Utils.samePos(e,a)}getRow(){return this.nodes.reduce(((e,t)=>Math.max(e,t.y+t.h)),0)}beginUpdate(e){return e._updating||(e._updating=!0,delete e._skipDown,this.batchMode||this.saveInitial()),this}endUpdate(){let e=this.nodes.find((e=>e._updating));return e&&(delete e._updating,delete e._skipDown),this}save(e=!0){var t;let i=null===(t=this._layouts)||void 0===t?void 0:t.length,o=i&&this.column!==i-1?this._layouts[i-1]:null,n=[];return this.sortNodes(),this.nodes.forEach((t=>{let i=null==o?void 0:o.find((e=>e._id===t._id)),r=Object.assign({},t);i&&(r.x=i.x,r.y=i.y,r.w=i.w),s.Utils.removeInternalForSave(r,!e),n.push(r)})),n}layoutsNodesChange(e){return!this._layouts||this._inColumnResize||this._layouts.forEach(((t,i)=>{if(!t||i===this.column)return this;if(i<this.column)this._layouts[i]=void 0;else{let s=i/this.column;e.forEach((e=>{if(!e._orig)return;let i=t.find((t=>t._id===e._id));i&&(e.y!==e._orig.y&&(i.y+=e.y-e._orig.y),e.x!==e._orig.x&&(i.x=Math.round(e.x*s)),e.w!==e._orig.w&&(i.w=Math.round(e.w*s)))}))}})),this}updateNodeWidths(e,t,i,o="moveScale"){var n;if(!this.nodes.length||!t||e===t)return this;this.cacheLayout(this.nodes,e),this.batchUpdate();let r=[],l=!1;if(1===t&&(null==i?void 0:i.length)){l=!0;let e=0;i.forEach((t=>{t.x=0,t.w=1,t.y=Math.max(t.y,e),e=t.y+t.h})),r=i,i=[]}else i=s.Utils.sort(this.nodes,-1,e);let a=[];if(t>e){a=this._layouts[t]||[];let s=this._layouts.length-1;!a.length&&e!==s&&(null===(n=this._layouts[s])||void 0===n?void 0:n.length)&&(e=s,this._layouts[s].forEach((e=>{let t=i.find((t=>t._id===e._id));t&&(t.x=e.x,t.y=e.y,t.w=e.w)})))}if(a.forEach((e=>{let t=i.findIndex((t=>t._id===e._id));-1!==t&&((e.autoPosition||isNaN(e.x)||isNaN(e.y))&&this.findEmptyPosition(e,r),e.autoPosition||(i[t].x=e.x,i[t].y=e.y,i[t].w=e.w,r.push(i[t])),i.splice(t,1))})),i.length)if("function"==typeof o)o(t,e,r,i);else if(!l){let s=t/e,n="move"===o||"moveScale"===o,l="scale"===o||"moveScale"===o;i.forEach((i=>{i.x=1===t?0:n?Math.round(i.x*s):Math.min(i.x,t-1),i.w=1===t||1===e?1:l?Math.round(i.w*s)||1:Math.min(i.w,t),r.push(i)})),i=[]}return l||(r=s.Utils.sort(r,-1,t)),this._inColumnResize=!0,this.nodes=[],r.forEach((e=>{this.addNode(e,!1),delete e._orig})),this.batchUpdate(!1),delete this._inColumnResize,this}cacheLayout(e,t,i=!1){let s=[];return e.forEach(((e,t)=>{e._id=e._id||o._idSeq++,s[t]={x:e.x,y:e.y,w:e.w,_id:e._id}})),this._layouts=i?[]:this._layouts||[],this._layouts[t]=s,this}cacheOneLayout(e,t){e._id=e._id||o._idSeq++;let i={x:e.x,y:e.y,w:e.w,_id:e._id};e.autoPosition&&(delete i.x,delete i.y,i.autoPosition=!0),this._layouts=this._layouts||[],this._layouts[t]=this._layouts[t]||[];let s=this.findCacheLayout(e,t);return-1===s?this._layouts[t].push(i):this._layouts[t][s]=i,this}findCacheLayout(e,t){var i,s,o;return null!==(o=null===(s=null===(i=this._layouts)||void 0===i?void 0:i[t])||void 0===s?void 0:s.findIndex((t=>t._id===e._id)))&&void 0!==o?o:-1}cleanupNode(e){for(let t in e)"_"===t[0]&&"_id"!==t&&delete e[t];return this}}t.GridStackEngine=o,o._idSeq=1},2764:function(e,t,i){i.r(t)},2966:function(e,t,i){i.r(t)},3572:function(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.DDResizableHandle=void 0;const s=i(9931);class o{constructor(e,t,i){this.moving=!1,this.host=e,this.dir=t,this.option=i,this._mouseDown=this._mouseDown.bind(this),this._mouseMove=this._mouseMove.bind(this),this._mouseUp=this._mouseUp.bind(this),this._init()}_init(){const e=document.createElement("div");return e.classList.add("ui-resizable-handle"),e.classList.add(`${o.prefix}${this.dir}`),e.style.zIndex="100",e.style.userSelect="none",this.el=e,this.host.appendChild(this.el),this.el.addEventListener("mousedown",this._mouseDown),s.isTouch&&(this.el.addEventListener("touchstart",s.touchstart),this.el.addEventListener("pointerdown",s.pointerdown)),this}destroy(){return this.moving&&this._mouseUp(this.mouseDownEvent),this.el.removeEventListener("mousedown",this._mouseDown),s.isTouch&&(this.el.removeEventListener("touchstart",s.touchstart),this.el.removeEventListener("pointerdown",s.pointerdown)),this.host.removeChild(this.el),delete this.el,delete this.host,this}_mouseDown(e){this.mouseDownEvent=e,document.addEventListener("mousemove",this._mouseMove,!0),document.addEventListener("mouseup",this._mouseUp,!0),s.isTouch&&(this.el.addEventListener("touchmove",s.touchmove),this.el.addEventListener("touchend",s.touchend)),e.stopPropagation(),e.preventDefault()}_mouseMove(e){let t=this.mouseDownEvent;this.moving?this._triggerEvent("move",e):Math.abs(e.x-t.x)+Math.abs(e.y-t.y)>2&&(this.moving=!0,this._triggerEvent("start",this.mouseDownEvent),this._triggerEvent("move",e)),e.stopPropagation(),e.preventDefault()}_mouseUp(e){this.moving&&this._triggerEvent("stop",e),document.removeEventListener("mousemove",this._mouseMove,!0),document.removeEventListener("mouseup",this._mouseUp,!0),s.isTouch&&(this.el.removeEventListener("touchmove",s.touchmove),this.el.removeEventListener("touchend",s.touchend)),delete this.moving,delete this.mouseDownEvent,e.stopPropagation(),e.preventDefault()}_triggerEvent(e,t){return this.option[e]&&this.option[e](t),this}}t.DDResizableHandle=o,o.prefix="ui-resizable-"},3700:function(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.DDGridStack=void 0;const s=i(1128),o=i(4839),n=i(1096);t.DDGridStack=class{resizable(e,t,i,s){return this._getDDElements(e).forEach((e=>{if("disable"===t||"enable"===t)e.ddResizable&&e.ddResizable[t]();else if("destroy"===t)e.ddResizable&&e.cleanResizable();else if("option"===t)e.setupResizable({[i]:s});else{const i=e.el.gridstackNode.grid;let s=e.el.getAttribute("gs-resize-handles")?e.el.getAttribute("gs-resize-handles"):i.opts.resizable.handles,o=!i.opts.alwaysShowResizeHandle;e.setupResizable(Object.assign(Object.assign(Object.assign({},i.opts.resizable),{handles:s,autoHide:o}),{start:t.start,stop:t.stop,resize:t.resize}))}})),this}draggable(e,t,i,s){return this._getDDElements(e).forEach((e=>{if("disable"===t||"enable"===t)e.ddDraggable&&e.ddDraggable[t]();else if("destroy"===t)e.ddDraggable&&e.cleanDraggable();else if("option"===t)e.setupDraggable({[i]:s});else{const i=e.el.gridstackNode.grid;e.setupDraggable(Object.assign(Object.assign({},i.opts.draggable),{start:t.start,stop:t.stop,drag:t.drag}))}})),this}dragIn(e,t){return this._getDDElements(e).forEach((e=>e.setupDraggable(t))),this}droppable(e,t,i,s){return"function"!=typeof t.accept||t._accept||(t._accept=t.accept,t.accept=e=>t._accept(e)),this._getDDElements(e).forEach((e=>{"disable"===t||"enable"===t?e.ddDroppable&&e.ddDroppable[t]():"destroy"===t?e.ddDroppable&&e.cleanDroppable():"option"===t?e.setupDroppable({[i]:s}):e.setupDroppable(t)})),this}isDroppable(e){return!(!(e&&e.ddElement&&e.ddElement.ddDroppable)||e.ddElement.ddDroppable.disabled)}isDraggable(e){return!(!(e&&e.ddElement&&e.ddElement.ddDraggable)||e.ddElement.ddDraggable.disabled)}isResizable(e){return!(!(e&&e.ddElement&&e.ddElement.ddResizable)||e.ddElement.ddResizable.disabled)}on(e,t,i){return this._getDDElements(e).forEach((e=>e.on(t,(e=>{i(e,o.DDManager.dragElement?o.DDManager.dragElement.el:e.target,o.DDManager.dragElement?o.DDManager.dragElement.helper:null)})))),this}off(e,t){return this._getDDElements(e).forEach((e=>e.off(t))),this}_getDDElements(e,t=!0){let i=s.Utils.getElements(e);if(!i.length)return[];let o=i.map((e=>e.ddElement||(t?n.DDElement.init(e):null)));return t||o.filter((e=>e)),o}}},4839:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.DDManager=void 0;t.DDManager=class{}},5593:function(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.DDResizable=void 0;const s=i(3572),o=i(5630),n=i(1128),r=i(4839);class l extends o.DDBaseImplement{constructor(e,t={}){super(),this._ui=()=>{const e=this.el.parentElement.getBoundingClientRect(),t={width:this.originalRect.width,height:this.originalRect.height+this.scrolled,left:this.originalRect.left,top:this.originalRect.top-this.scrolled},i=this.temporalRect||t;return{position:{left:i.left-e.left,top:i.top-e.top},size:{width:i.width,height:i.height}}},this.el=e,this.option=t,this._mouseOver=this._mouseOver.bind(this),this._mouseOut=this._mouseOut.bind(this),this.enable(),this._setupAutoHide(this.option.autoHide),this._setupHandlers()}on(e,t){super.on(e,t)}off(e){super.off(e)}enable(){super.enable(),this.el.classList.add("ui-resizable"),this.el.classList.remove("ui-resizable-disabled"),this._setupAutoHide(this.option.autoHide)}disable(){super.disable(),this.el.classList.add("ui-resizable-disabled"),this.el.classList.remove("ui-resizable"),this._setupAutoHide(!1)}destroy(){this._removeHandlers(),this._setupAutoHide(!1),this.el.classList.remove("ui-resizable"),delete this.el,super.destroy()}updateOption(e){let t=e.handles&&e.handles!==this.option.handles,i=e.autoHide&&e.autoHide!==this.option.autoHide;return Object.keys(e).forEach((t=>this.option[t]=e[t])),t&&(this._removeHandlers(),this._setupHandlers()),i&&this._setupAutoHide(this.option.autoHide),this}_setupAutoHide(e){return e?(this.el.classList.add("ui-resizable-autohide"),this.el.addEventListener("mouseover",this._mouseOver),this.el.addEventListener("mouseout",this._mouseOut)):(this.el.classList.remove("ui-resizable-autohide"),this.el.removeEventListener("mouseover",this._mouseOver),this.el.removeEventListener("mouseout",this._mouseOut),r.DDManager.overResizeElement===this&&delete r.DDManager.overResizeElement),this}_mouseOver(e){r.DDManager.overResizeElement||r.DDManager.dragElement||(r.DDManager.overResizeElement=this,this.el.classList.remove("ui-resizable-autohide"))}_mouseOut(e){r.DDManager.overResizeElement===this&&(delete r.DDManager.overResizeElement,this.el.classList.add("ui-resizable-autohide"))}_setupHandlers(){let e=this.option.handles||"e,s,se";return"all"===e&&(e="n,e,s,w,se,sw,ne,nw"),this.handlers=e.split(",").map((e=>e.trim())).map((e=>new s.DDResizableHandle(this.el,e,{start:e=>{this._resizeStart(e)},stop:e=>{this._resizeStop(e)},move:t=>{this._resizing(t,e)}}))),this}_resizeStart(e){this.originalRect=this.el.getBoundingClientRect(),this.scrollEl=n.Utils.getScrollElement(this.el),this.scrollY=this.scrollEl.scrollTop,this.scrolled=0,this.startEvent=e,this._setupHelper(),this._applyChange();const t=n.Utils.initEvent(e,{type:"resizestart",target:this.el});return this.option.start&&this.option.start(t,this._ui()),this.el.classList.add("ui-resizable-resizing"),this.triggerEvent("resizestart",t),this}_resizing(e,t){this.scrolled=this.scrollEl.scrollTop-this.scrollY,this.temporalRect=this._getChange(e,t),this._applyChange();const i=n.Utils.initEvent(e,{type:"resize",target:this.el});return this.option.resize&&this.option.resize(i,this._ui()),this.triggerEvent("resize",i),this}_resizeStop(e){const t=n.Utils.initEvent(e,{type:"resizestop",target:this.el});return this.option.stop&&this.option.stop(t),this.el.classList.remove("ui-resizable-resizing"),this.triggerEvent("resizestop",t),this._cleanHelper(),delete this.startEvent,delete this.originalRect,delete this.temporalRect,delete this.scrollY,delete this.scrolled,this}_setupHelper(){return this.elOriginStyleVal=l._originStyleProp.map((e=>this.el.style[e])),this.parentOriginStylePosition=this.el.parentElement.style.position,window.getComputedStyle(this.el.parentElement).position.match(/static/)&&(this.el.parentElement.style.position="relative"),this.el.style.position="absolute",this.el.style.opacity="0.8",this}_cleanHelper(){return l._originStyleProp.forEach(((e,t)=>{this.el.style[e]=this.elOriginStyleVal[t]||null})),this.el.parentElement.style.position=this.parentOriginStylePosition||null,this}_getChange(e,t){const i=this.startEvent,s={width:this.originalRect.width,height:this.originalRect.height+this.scrolled,left:this.originalRect.left,top:this.originalRect.top-this.scrolled},o=e.clientX-i.clientX,n=e.clientY-i.clientY;t.indexOf("e")>-1?s.width+=o:t.indexOf("w")>-1&&(s.width-=o,s.left+=o),t.indexOf("s")>-1?s.height+=n:t.indexOf("n")>-1&&(s.height-=n,s.top+=n);const r=this._constrainSize(s.width,s.height);return Math.round(s.width)!==Math.round(r.width)&&(t.indexOf("w")>-1&&(s.left+=s.width-r.width),s.width=r.width),Math.round(s.height)!==Math.round(r.height)&&(t.indexOf("n")>-1&&(s.top+=s.height-r.height),s.height=r.height),s}_constrainSize(e,t){const i=this.option.maxWidth||Number.MAX_SAFE_INTEGER,s=this.option.minWidth||e,o=this.option.maxHeight||Number.MAX_SAFE_INTEGER,n=this.option.minHeight||t;return{width:Math.min(i,Math.max(s,e)),height:Math.min(o,Math.max(n,t))}}_applyChange(){let e={left:0,top:0,width:0,height:0};if("absolute"===this.el.style.position){const t=this.el.parentElement,{left:i,top:s}=t.getBoundingClientRect();e={left:i,top:s,width:0,height:0}}return this.temporalRect?(Object.keys(this.temporalRect).forEach((t=>{const i=this.temporalRect[t];this.el.style[t]=i-e[t]+"px"})),this):this}_removeHandlers(){return this.handlers.forEach((e=>e.destroy())),delete this.handlers,this}}t.DDResizable=l,l._originStyleProp=["width","height","position","left","top","opacity","zIndex"]},5630:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.DDBaseImplement=void 0;t.DDBaseImplement=class{constructor(){this._eventRegister={}}get disabled(){return this._disabled}on(e,t){this._eventRegister[e]=t}off(e){delete this._eventRegister[e]}enable(){this._disabled=!1}disable(){this._disabled=!0}destroy(){delete this._eventRegister}triggerEvent(e,t){if(!this.disabled&&this._eventRegister&&this._eventRegister[e])return this._eventRegister[e](t)}}},5844:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.dragInDefaultOptions=t.gridDefaults=void 0,t.gridDefaults={alwaysShowResizeHandle:"mobile",animate:!0,auto:!0,cellHeight:"auto",cellHeightThrottle:100,cellHeightUnit:"px",column:12,draggable:{handle:".grid-stack-item-content",appendTo:"body",scroll:!0},handle:".grid-stack-item-content",itemClass:"grid-stack-item",margin:10,marginUnit:"px",maxRow:0,minRow:0,oneColumnSize:768,placeholderClass:"grid-stack-placeholder",placeholderText:"",removableOptions:{accept:".grid-stack-item"},resizable:{handles:"se"},rtl:"auto"},t.dragInDefaultOptions={handle:".grid-stack-item-content",appendTo:"body"}},6857:function(e,t,i){var s=i(7129);i(2764),i(2966),i.g.GridStack=s.GridStack},7129:function(e,t,i){var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i),Object.defineProperty(e,s,{enumerable:!0,get:function(){return t[i]}})}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__exportStar||function(e,t){for(var i in e)"default"===i||t.hasOwnProperty(i)||s(t,e,i)};Object.defineProperty(t,"__esModule",{value:!0}),t.GridStack=void 0;const n=i(1692),r=i(1128),l=i(5844),a=i(3700),h=i(9931),d=i(4839),g=new a.DDGridStack;o(i(5844),t),o(i(1128),t),o(i(1692),t),o(i(3700),t);class u{constructor(e,t={}){var i,s;this._gsEventHandler={},this._extraDragRow=0,this.el=e,t=t||{},e.classList.contains("grid-stack")||this.el.classList.add("grid-stack"),t.row&&(t.minRow=t.maxRow=t.row,delete t.row);let o=r.Utils.toNumber(e.getAttribute("gs-row"));"auto"===t.column&&delete t.column;let a=t;void 0!==a.minWidth&&(t.oneColumnSize=t.oneColumnSize||a.minWidth,delete a.minWidth),void 0!==t.alwaysShowResizeHandle&&(t._alwaysShowResizeHandle=t.alwaysShowResizeHandle);let g=Object.assign(Object.assign({},r.Utils.cloneDeep(l.gridDefaults)),{column:r.Utils.toNumber(e.getAttribute("gs-column"))||l.gridDefaults.column,minRow:o||(r.Utils.toNumber(e.getAttribute("gs-min-row"))||l.gridDefaults.minRow),maxRow:o||(r.Utils.toNumber(e.getAttribute("gs-max-row"))||l.gridDefaults.maxRow),staticGrid:r.Utils.toBool(e.getAttribute("gs-static"))||l.gridDefaults.staticGrid,draggable:{handle:(t.handleClass?"."+t.handleClass:t.handle?t.handle:"")||l.gridDefaults.draggable.handle},removableOptions:{accept:t.itemClass?"."+t.itemClass:l.gridDefaults.removableOptions.accept}});e.getAttribute("gs-animate")&&(g.animate=r.Utils.toBool(e.getAttribute("gs-animate"))),this.opts=r.Utils.defaults(t,g),t=null,this._initMargin(),1!==this.opts.column&&!this.opts.disableOneColumnMode&&this._widthOrContainer()<=this.opts.oneColumnSize&&(this._prevColumn=this.getColumn(),this.opts.column=1),"auto"===this.opts.rtl&&(this.opts.rtl="rtl"===e.style.direction),this.opts.rtl&&this.el.classList.add("grid-stack-rtl");let p=null===(i=r.Utils.closestUpByClass(this.el,l.gridDefaults.itemClass))||void 0===i?void 0:i.gridstackNode;p&&(p.subGrid=this,this.parentGridItem=p,this.el.classList.add("grid-stack-nested"),p.el.classList.add("grid-stack-sub-grid")),this._isAutoCellHeight="auto"===this.opts.cellHeight,this._isAutoCellHeight||"initial"===this.opts.cellHeight?this.cellHeight(void 0,!1):("number"==typeof this.opts.cellHeight&&this.opts.cellHeightUnit&&this.opts.cellHeightUnit!==l.gridDefaults.cellHeightUnit&&(this.opts.cellHeight=this.opts.cellHeight+this.opts.cellHeightUnit,delete this.opts.cellHeightUnit),this.cellHeight(this.opts.cellHeight,!1)),"mobile"===this.opts.alwaysShowResizeHandle&&(this.opts.alwaysShowResizeHandle=h.isTouch),this._styleSheetClass="grid-stack-instance-"+n.GridStackEngine._idSeq++,this.el.classList.add(this._styleSheetClass),this._setStaticClass();let c=this.opts.engineClass||u.engineClass||n.GridStackEngine;if(this.engine=new c({column:this.getColumn(),float:this.opts.float,maxRow:this.opts.maxRow,onChange:e=>{let t=0;this.engine.nodes.forEach((e=>{t=Math.max(t,e.y+e.h)})),e.forEach((e=>{let t=e.el;t&&(e._removeDOM?(t&&t.remove(),delete e._removeDOM):this._writePosAttr(t,e))})),this._updateStyles(!1,t)}}),this.opts.auto&&(this.batchUpdate(),this.getGridItems().forEach((e=>this._prepareElement(e))),this.batchUpdate(!1)),this.opts.children){let e=this.opts.children;delete this.opts.children,e.length&&this.load(e)}this.setAnimation(this.opts.animate),this._updateStyles(),12!=this.opts.column&&this.el.classList.add("grid-stack-"+this.opts.column),this.opts.dragIn&&u.setupDragIn(this.opts.dragIn,this.opts.dragInOptions),delete this.opts.dragIn,delete this.opts.dragInOptions,this.opts.subGridDynamic&&!d.DDManager.pauseDrag&&(d.DDManager.pauseDrag=!0),void 0!==(null===(s=this.opts.draggable)||void 0===s?void 0:s.pause)&&(d.DDManager.pauseDrag=this.opts.draggable.pause),this._setupRemoveDrop(),this._setupAcceptWidget(),this._updateWindowResizeEvent()}static init(e={},t=".grid-stack"){let i=u.getGridElement(t);return i?(i.gridstack||(i.gridstack=new u(i,r.Utils.cloneDeep(e))),i.gridstack):("string"==typeof t?console.error('GridStack.initAll() no grid was found with selector "'+t+'" - element missing or wrong selector ?\nNote: ".grid-stack" is required for proper CSS styling and drag/drop, and is the default selector.'):console.error("GridStack.init() no grid element was passed."),null)}static initAll(e={},t=".grid-stack"){let i=[];return u.getGridElements(t).forEach((t=>{t.gridstack||(t.gridstack=new u(t,r.Utils.cloneDeep(e)),delete e.dragIn,delete e.dragInOptions),i.push(t.gridstack)})),0===i.length&&console.error('GridStack.initAll() no grid was found with selector "'+t+'" - element missing or wrong selector ?\nNote: ".grid-stack" is required for proper CSS styling and drag/drop, and is the default selector.'),i}static addGrid(e,t={}){if(!e)return null;let i=e;if(!e.classList.contains("grid-stack")||t.addRemoveCB)if(t.addRemoveCB)i=t.addRemoveCB(e,t,!0,!0);else{let s=document.implementation.createHTMLDocument("");s.body.innerHTML=`<div class="grid-stack ${t.class||""}"></div>`,i=s.body.children[0],e.appendChild(i)}return u.init(t,i)}static registerEngine(e){u.engineClass=e}get placeholder(){if(!this._placeholder){let e=document.createElement("div");e.className="placeholder-content",this.opts.placeholderText&&(e.innerHTML=this.opts.placeholderText),this._placeholder=document.createElement("div"),this._placeholder.classList.add(this.opts.placeholderClass,l.gridDefaults.itemClass,this.opts.itemClass),this.placeholder.appendChild(e)}return this._placeholder}addWidget(e,t){let i,s;if("string"==typeof e){let t=document.implementation.createHTMLDocument("");t.body.innerHTML=e,i=t.body.children[0]}else if(0===arguments.length||1===arguments.length&&(void 0!==(o=e).el||void 0!==o.x||void 0!==o.y||void 0!==o.w||void 0!==o.h||void 0!==o.content))if(s=t=e,null==s?void 0:s.el)i=s.el;else if(this.opts.addRemoveCB)i=this.opts.addRemoveCB(this.el,t,!0,!1);else{let e=(null==t?void 0:t.content)||"",s=document.implementation.createHTMLDocument("");s.body.innerHTML=`<div class="grid-stack-item ${this.opts.itemClass||""}"><div class="grid-stack-item-content">${e}</div></div>`,i=s.body.children[0]}else i=e;var o;if(!i)return;let n=this._readAttr(i);return t=r.Utils.cloneDeep(t)||{},r.Utils.defaults(t,n),s=this.engine.prepareNode(t),this._writeAttr(i,t),this._insertNotAppend?this.el.prepend(i):this.el.appendChild(i),this._prepareElement(i,!0,t),this._updateContainerHeight(),s.subGrid&&this.makeSubGrid(s.el,void 0,void 0,!1),this._prevColumn&&1===this.opts.column&&(this._ignoreLayoutsNodeChange=!0),this._triggerAddEvent(),this._triggerChangeEvent(),delete this._ignoreLayoutsNodeChange,i}makeSubGrid(e,t,i,s=!0){var o,n,l;let a,h=e.gridstackNode;if(h||(h=this.makeWidget(e).gridstackNode),null===(o=h.subGrid)||void 0===o?void 0:o.el)return h.subGrid;let d,g=this;for(;g&&!a;)a=null===(n=g.opts)||void 0===n?void 0:n.subGrid,g=null===(l=g.parentGridItem)||void 0===l?void 0:l.grid;t=r.Utils.cloneDeep(Object.assign(Object.assign(Object.assign({},a||{}),{children:void 0}),t||h.subGrid)),h.subGrid=t,"auto"===t.column&&(d=!0,t.column=Math.max(h.w||1,(null==i?void 0:i.w)||1),t.disableOneColumnMode=!0);let p,c,m=h.el.querySelector(".grid-stack-item-content");if(s){if(this._removeDD(h.el),c=Object.assign(Object.assign({},h),{x:0,y:0}),r.Utils.removeInternalForSave(c),delete c.subGrid,h.content&&(c.content=h.content,delete h.content),this.opts.addRemoveCB)p=this.opts.addRemoveCB(this.el,c,!0,!1);else{let e=document.implementation.createHTMLDocument("");e.body.innerHTML='<div class="grid-stack-item"></div>',p=e.body.children[0],p.appendChild(m),e.body.innerHTML='<div class="grid-stack-item-content"></div>',m=e.body.children[0],h.el.appendChild(m)}this._prepareDragDropByNode(h)}if(i){let e=d?t.column:h.w,s=h.h+i.h,o=h.el.style;o.transition="none",this.update(h.el,{w:e,h:s}),setTimeout((()=>o.transition=null))}this.opts.addRemoveCB&&(t.addRemoveCB=t.addRemoveCB||this.opts.addRemoveCB);let v=h.subGrid=u.addGrid(m,t);return(null==i?void 0:i._moving)&&(v._isTemp=!0),d&&(v._autoColumn=!0),s&&v.addWidget(p,c),i&&(i._moving?window.setTimeout((()=>r.Utils.simulateMouseEvent(i._event,"mouseenter",v.el)),0):v.addWidget(h.el,h)),v}removeAsSubGrid(e){var t;let i=null===(t=this.parentGridItem)||void 0===t?void 0:t.grid;i&&(i.batchUpdate(),i.removeWidget(this.parentGridItem.el,!0,!0),this.engine.nodes.forEach((e=>{e.x+=this.parentGridItem.x,e.y+=this.parentGridItem.y,i.addWidget(e.el,e)})),i.batchUpdate(!1),this.parentGridItem&&delete this.parentGridItem.subGrid,delete this.parentGridItem,e&&window.setTimeout((()=>r.Utils.simulateMouseEvent(e._event,"mouseenter",i.el)),0))}save(e=!0,t=!1){let i=this.engine.save(e);if(i.forEach((i=>{var s;if(e&&i.el&&!i.subGrid){let e=i.el.querySelector(".grid-stack-item-content");i.content=e?e.innerHTML:void 0,i.content||delete i.content}else if(e||delete i.content,null===(s=i.subGrid)||void 0===s?void 0:s.el){const s=i.subGrid.save(e,t);i.subGrid=t?s:{children:s}}delete i.el})),t){let e=r.Utils.cloneDeep(this.opts);e.marginBottom===e.marginTop&&e.marginRight===e.marginLeft&&e.marginTop===e.marginRight&&(e.margin=e.marginTop,delete e.marginTop,delete e.marginRight,delete e.marginBottom,delete e.marginLeft),e.rtl===("rtl"===this.el.style.direction)&&(e.rtl="auto"),this._isAutoCellHeight&&(e.cellHeight="auto"),this._autoColumn&&(e.column="auto",delete e.disableOneColumnMode);const t=e._alwaysShowResizeHandle;return delete e._alwaysShowResizeHandle,void 0!==t?e.alwaysShowResizeHandle=t:delete e.alwaysShowResizeHandle,r.Utils.removeInternalAndSame(e,l.gridDefaults),e.children=i,e}return i}load(e,t=this.opts.addRemoveCB||!0){let i=u.Utils.sort([...e],-1,this._prevColumn||this.getColumn());this._insertNotAppend=!0,this._prevColumn&&this._prevColumn!==this.opts.column&&i.some((e=>e.x+e.w>this.opts.column))&&(this._ignoreLayoutsNodeChange=!0,this.engine.cacheLayout(i,this._prevColumn,!0));const s=this.opts.addRemoveCB;"function"==typeof t&&(this.opts.addRemoveCB=t);let o=[];if(this.batchUpdate(),t){[...this.engine.nodes].forEach((e=>{i.find((t=>e.id===t.id))||(this.opts.addRemoveCB&&this.opts.addRemoveCB(this.el,e,!1,!1),o.push(e),this.removeWidget(e.el,!0,!1))}))}return i.forEach((e=>{let i=e.id||0===e.id?this.engine.nodes.find((t=>t.id===e.id)):void 0;if(i){if(this.update(i.el,e),e.subGrid&&e.subGrid.children){let t=i.el.querySelector(".grid-stack");t&&t.gridstack&&(t.gridstack.load(e.subGrid.children),this._insertNotAppend=!0)}}else t&&this.addWidget(e)})),this.engine.removedNodes=o,this.batchUpdate(!1),delete this._ignoreLayoutsNodeChange,delete this._insertNotAppend,s?this.opts.addRemoveCB=s:delete this.opts.addRemoveCB,this}batchUpdate(e=!0){return this.engine.batchUpdate(e),e||(this._triggerRemoveEvent(),this._triggerAddEvent(),this._triggerChangeEvent()),this}getCellHeight(e=!1){if(this.opts.cellHeight&&"auto"!==this.opts.cellHeight&&(!e||!this.opts.cellHeightUnit||"px"===this.opts.cellHeightUnit))return this.opts.cellHeight;let t=this.el.querySelector("."+this.opts.itemClass);if(t){let e=r.Utils.toNumber(t.getAttribute("gs-h"));return Math.round(t.offsetHeight/e)}let i=parseInt(this.el.getAttribute("gs-current-row"));return i?Math.round(this.el.getBoundingClientRect().height/i):this.opts.cellHeight}cellHeight(e,t=!0){if(t&&void 0!==e&&this._isAutoCellHeight!==("auto"===e)&&(this._isAutoCellHeight="auto"===e,this._updateWindowResizeEvent()),"initial"!==e&&"auto"!==e||(e=void 0),void 0===e){let t=-this.opts.marginRight-this.opts.marginLeft+this.opts.marginTop+this.opts.marginBottom;e=this.cellWidth()+t}let i=r.Utils.parseHeight(e);return this.opts.cellHeightUnit===i.unit&&this.opts.cellHeight===i.h||(this.opts.cellHeightUnit=i.unit,this.opts.cellHeight=i.h,t&&this._updateStyles(!0)),this}cellWidth(){return this._widthOrContainer()/this.getColumn()}_widthOrContainer(){return this.el.clientWidth||this.el.parentElement.clientWidth||window.innerWidth}compact(){return this.engine.compact(),this._triggerChangeEvent(),this}column(e,t="moveScale"){if(e<1||this.opts.column===e)return this;let i,s=this.getColumn();return 1===e?this._prevColumn=s:delete this._prevColumn,this.el.classList.remove("grid-stack-"+s),this.el.classList.add("grid-stack-"+e),this.opts.column=this.engine.column=e,1===e&&this.opts.oneColumnModeDomSort&&(i=[],this.getGridItems().forEach((e=>{e.gridstackNode&&i.push(e.gridstackNode)})),i.length||(i=void 0)),this.engine.updateNodeWidths(s,e,i,t),this._isAutoCellHeight&&this.cellHeight(),this._ignoreLayoutsNodeChange=!0,this._triggerChangeEvent(),delete this._ignoreLayoutsNodeChange,this}getColumn(){return this.opts.column}getGridItems(){return Array.from(this.el.children).filter((e=>e.matches("."+this.opts.itemClass)&&!e.matches("."+this.opts.placeholderClass)))}destroy(e=!0){if(this.el)return this._updateWindowResizeEvent(!0),this.setStatic(!0,!1),this.setAnimation(!1),e?this.el.parentNode.removeChild(this.el):(this.removeAll(e),this.el.classList.remove(this._styleSheetClass)),this._removeStylesheet(),this.el.removeAttribute("gs-current-row"),this.parentGridItem&&delete this.parentGridItem.subGrid,delete this.parentGridItem,delete this.opts,delete this._placeholder,delete this.engine,delete this.el.gridstack,delete this.el,this}float(e){return this.opts.float!==e&&(this.opts.float=this.engine.float=e,this._triggerChangeEvent()),this}getFloat(){return this.engine.float}getCellFromPixel(e,t=!1){let i,s=this.el.getBoundingClientRect();i=t?{top:s.top+document.documentElement.scrollTop,left:s.left}:{top:this.el.offsetTop,left:this.el.offsetLeft};let o=e.left-i.left,n=e.top-i.top,r=s.width/this.getColumn(),l=s.height/parseInt(this.el.getAttribute("gs-current-row"));return{x:Math.floor(o/r),y:Math.floor(n/l)}}getRow(){return Math.max(this.engine.getRow(),this.opts.minRow)}isAreaEmpty(e,t,i,s){return this.engine.isAreaEmpty(e,t,i,s)}makeWidget(e){let t=u.getElement(e);return this._prepareElement(t,!0),this._updateContainerHeight(),this._triggerAddEvent(),this._triggerChangeEvent(),t}on(e,t){if(-1!==e.indexOf(" ")){return e.split(" ").forEach((e=>this.on(e,t))),this}if("change"===e||"added"===e||"removed"===e||"enable"===e||"disable"===e){let i="enable"===e||"disable"===e;this._gsEventHandler[e]=i?e=>t(e):e=>t(e,e.detail),this.el.addEventListener(e,this._gsEventHandler[e])}else"drag"===e||"dragstart"===e||"dragstop"===e||"resizestart"===e||"resize"===e||"resizestop"===e||"dropped"===e?this._gsEventHandler[e]=t:console.log("GridStack.on("+e+') event not supported, but you can still use $(".grid-stack").on(...) while jquery-ui is still used internally.');return this}off(e){if(-1!==e.indexOf(" ")){return e.split(" ").forEach((e=>this.off(e))),this}return"change"!==e&&"added"!==e&&"removed"!==e&&"enable"!==e&&"disable"!==e||this._gsEventHandler[e]&&this.el.removeEventListener(e,this._gsEventHandler[e]),delete this._gsEventHandler[e],this}removeWidget(e,t=!0,i=!0){return u.getElements(e).forEach((e=>{if(e.parentElement&&e.parentElement!==this.el)return;let s=e.gridstackNode;s||(s=this.engine.nodes.find((t=>e===t.el))),s&&(delete e.gridstackNode,this._removeDD(e),this.engine.removeNode(s,t,i),t&&e.parentElement&&e.remove())})),i&&(this._triggerRemoveEvent(),this._triggerChangeEvent()),this}removeAll(e=!0){return this.engine.nodes.forEach((e=>{delete e.el.gridstackNode,this._removeDD(e.el)})),this.engine.removeAll(e),this._triggerRemoveEvent(),this}setAnimation(e){return e?this.el.classList.add("grid-stack-animate"):this.el.classList.remove("grid-stack-animate"),this}setStatic(e,t=!0,i=!0){return this.opts.staticGrid===e||(this.opts.staticGrid=e,this._setupRemoveDrop(),this._setupAcceptWidget(),this.engine.nodes.forEach((s=>{this._prepareDragDropByNode(s),s.subGrid&&i&&s.subGrid.setStatic(e,t,i)})),t&&this._setStaticClass()),this}update(e,t){if(arguments.length>2){console.warn("gridstack.ts: `update(el, x, y, w, h)` is deprecated. Use `update(el, {x, w, content, ...})`. It will be removed soon");let i=arguments,s=1;return t={x:i[s++],y:i[s++],w:i[s++],h:i[s++]},this.update(e,t)}return u.getElements(e).forEach((e=>{if(!e||!e.gridstackNode)return;let i=e.gridstackNode,s=r.Utils.cloneDeep(t);delete s.autoPosition;let o,n=["x","y","w","h"];if(n.some((e=>void 0!==s[e]&&s[e]!==i[e]))&&(o={},n.forEach((e=>{o[e]=void 0!==s[e]?s[e]:i[e],delete s[e]}))),!o&&(s.minW||s.minH||s.maxW||s.maxH)&&(o={}),s.content){let t=e.querySelector(".grid-stack-item-content");t&&t.innerHTML!==s.content&&(t.innerHTML=s.content),delete s.content}let l=!1,a=!1;for(const e in s)"_"!==e[0]&&i[e]!==s[e]&&(i[e]=s[e],l=!0,a=a||!this.opts.staticGrid&&("noResize"===e||"noMove"===e||"locked"===e));o&&(this.engine.cleanNodes().beginUpdate(i).moveNode(i,o),this._updateContainerHeight(),this._triggerChangeEvent(),this.engine.endUpdate()),l&&this._writeAttr(e,i),a&&this._prepareDragDropByNode(i)})),this}margin(e){if(!("string"==typeof e&&e.split(" ").length>1)){let t=r.Utils.parseHeight(e);if(this.opts.marginUnit===t.unit&&this.opts.margin===t.h)return}return this.opts.margin=e,this.opts.marginTop=this.opts.marginBottom=this.opts.marginLeft=this.opts.marginRight=void 0,this._initMargin(),this._updateStyles(!0),this}getMargin(){return this.opts.margin}willItFit(e){if(arguments.length>1){console.warn("gridstack.ts: `willItFit(x,y,w,h,autoPosition)` is deprecated. Use `willItFit({x, y,...})`. It will be removed soon");let e=arguments,t=0,i={x:e[t++],y:e[t++],w:e[t++],h:e[t++],autoPosition:e[t++]};return this.willItFit(i)}return this.engine.willItFit(e)}_triggerChangeEvent(){if(this.engine.batchMode)return this;let e=this.engine.getDirtyNodes(!0);return e&&e.length&&(this._ignoreLayoutsNodeChange||this.engine.layoutsNodesChange(e),this._triggerEvent("change",e)),this.engine.saveInitial(),this}_triggerAddEvent(){return this.engine.batchMode||this.engine.addedNodes&&this.engine.addedNodes.length>0&&(this._ignoreLayoutsNodeChange||this.engine.layoutsNodesChange(this.engine.addedNodes),this.engine.addedNodes.forEach((e=>{delete e._dirty})),this._triggerEvent("added",this.engine.addedNodes),this.engine.addedNodes=[]),this}_triggerRemoveEvent(){return this.engine.batchMode||this.engine.removedNodes&&this.engine.removedNodes.length>0&&(this._triggerEvent("removed",this.engine.removedNodes),this.engine.removedNodes=[]),this}_triggerEvent(e,t){let i=t?new CustomEvent(e,{bubbles:!1,detail:t}):new Event(e);return this.el.dispatchEvent(i),this}_removeStylesheet(){return this._styles&&(r.Utils.removeStylesheet(this._styleSheetClass),delete this._styles),this}_updateStyles(e=!1,t){if(e&&this._removeStylesheet(),t||(t=this.getRow()),this._updateContainerHeight(),0===this.opts.cellHeight)return this;let i=this.opts.cellHeight,s=this.opts.cellHeightUnit,o=`.${this._styleSheetClass} > .${this.opts.itemClass}`;if(!this._styles){let e=this.opts.styleInHead?void 0:this.el.parentNode;if(this._styles=r.Utils.createStylesheet(this._styleSheetClass,e,{nonce:this.opts.nonce}),!this._styles)return this;this._styles._max=0,r.Utils.addCSSRule(this._styles,o,`min-height: ${i}${s}`);let t=this.opts.marginTop+this.opts.marginUnit,n=this.opts.marginBottom+this.opts.marginUnit,l=this.opts.marginRight+this.opts.marginUnit,a=this.opts.marginLeft+this.opts.marginUnit,h=`${o} > .grid-stack-item-content`,d=`.${this._styleSheetClass} > .grid-stack-placeholder > .placeholder-content`;r.Utils.addCSSRule(this._styles,h,`top: ${t}; right: ${l}; bottom: ${n}; left: ${a};`),r.Utils.addCSSRule(this._styles,d,`top: ${t}; right: ${l}; bottom: ${n}; left: ${a};`),r.Utils.addCSSRule(this._styles,`${o} > .ui-resizable-ne`,`right: ${l}`),r.Utils.addCSSRule(this._styles,`${o} > .ui-resizable-e`,`right: ${l}`),r.Utils.addCSSRule(this._styles,`${o} > .ui-resizable-se`,`right: ${l}; bottom: ${n}`),r.Utils.addCSSRule(this._styles,`${o} > .ui-resizable-nw`,`left: ${a}`),r.Utils.addCSSRule(this._styles,`${o} > .ui-resizable-w`,`left: ${a}`),r.Utils.addCSSRule(this._styles,`${o} > .ui-resizable-sw`,`left: ${a}; bottom: ${n}`)}if((t=t||this._styles._max)>this._styles._max){let e=e=>i*e+s;for(let i=this._styles._max+1;i<=t;i++){let t=e(i);r.Utils.addCSSRule(this._styles,`${o}[gs-y="${i-1}"]`,`top: ${e(i-1)}`),r.Utils.addCSSRule(this._styles,`${o}[gs-h="${i}"]`,`height: ${t}`),r.Utils.addCSSRule(this._styles,`${o}[gs-min-h="${i}"]`,`min-height: ${t}`),r.Utils.addCSSRule(this._styles,`${o}[gs-max-h="${i}"]`,`max-height: ${t}`)}this._styles._max=t}return this}_updateContainerHeight(){if(!this.engine||this.engine.batchMode)return this;let e=this.getRow()+this._extraDragRow;if(this.el.setAttribute("gs-current-row",String(e)),0===e)return this.el.style.removeProperty("min-height"),this;let t=this.opts.cellHeight,i=this.opts.cellHeightUnit;return t?(this.el.style.minHeight=e*t+i,this):this}_prepareElement(e,t=!1,i){e.classList.add(this.opts.itemClass),i=i||this._readAttr(e),e.gridstackNode=i,i.el=e,i.grid=this;let s=Object.assign({},i);return i=this.engine.addNode(i,t),r.Utils.same(i,s)||this._writeAttr(e,i),this._prepareDragDropByNode(i),this}_writePosAttr(e,t){return void 0!==t.x&&null!==t.x&&e.setAttribute("gs-x",String(t.x)),void 0!==t.y&&null!==t.y&&e.setAttribute("gs-y",String(t.y)),t.w&&e.setAttribute("gs-w",String(t.w)),t.h&&e.setAttribute("gs-h",String(t.h)),this}_writeAttr(e,t){if(!t)return this;this._writePosAttr(e,t);let i={autoPosition:"gs-auto-position",minW:"gs-min-w",minH:"gs-min-h",maxW:"gs-max-w",maxH:"gs-max-h",noResize:"gs-no-resize",noMove:"gs-no-move",locked:"gs-locked",id:"gs-id"};for(const s in i)t[s]?e.setAttribute(i[s],String(t[s])):e.removeAttribute(i[s]);return this}_readAttr(e){let t={};t.x=r.Utils.toNumber(e.getAttribute("gs-x")),t.y=r.Utils.toNumber(e.getAttribute("gs-y")),t.w=r.Utils.toNumber(e.getAttribute("gs-w")),t.h=r.Utils.toNumber(e.getAttribute("gs-h")),t.maxW=r.Utils.toNumber(e.getAttribute("gs-max-w")),t.minW=r.Utils.toNumber(e.getAttribute("gs-min-w")),t.maxH=r.Utils.toNumber(e.getAttribute("gs-max-h")),t.minH=r.Utils.toNumber(e.getAttribute("gs-min-h")),t.autoPosition=r.Utils.toBool(e.getAttribute("gs-auto-position")),t.noResize=r.Utils.toBool(e.getAttribute("gs-no-resize")),t.noMove=r.Utils.toBool(e.getAttribute("gs-no-move")),t.locked=r.Utils.toBool(e.getAttribute("gs-locked")),t.id=e.getAttribute("gs-id");for(const e in t){if(!t.hasOwnProperty(e))return;t[e]||0===t[e]||delete t[e]}return t}_setStaticClass(){let e=["grid-stack-static"];return this.opts.staticGrid?(this.el.classList.add(...e),this.el.setAttribute("gs-static","true")):(this.el.classList.remove(...e),this.el.removeAttribute("gs-static")),this}onParentResize(){if(!this.el||!this.el.clientWidth)return;let e=!1;if(this._autoColumn&&this.parentGridItem)this.opts.column!==this.parentGridItem.w&&(e=!0,this.column(this.parentGridItem.w,"none"));else{let t=!this.opts.disableOneColumnMode&&this.el.clientWidth<=this.opts.oneColumnSize;1===this.opts.column!==t&&(e=!0,this.opts.animate&&this.setAnimation(!1),this.column(t?1:this._prevColumn),this.opts.animate&&this.setAnimation(!0))}return this._isAutoCellHeight&&(!e&&this.opts.cellHeightThrottle?(this._cellHeightThrottle||(this._cellHeightThrottle=r.Utils.throttle((()=>this.cellHeight()),this.opts.cellHeightThrottle)),this._cellHeightThrottle()):this.cellHeight()),this.engine.nodes.forEach((e=>{e.subGrid&&e.subGrid.onParentResize()})),this}_updateWindowResizeEvent(e=!1){const t=(this._isAutoCellHeight||!this.opts.disableOneColumnMode)&&!this.parentGridItem;return e||!t||this._windowResizeBind?!e&&t||!this._windowResizeBind||(window.removeEventListener("resize",this._windowResizeBind),delete this._windowResizeBind):(this._windowResizeBind=this.onParentResize.bind(this),window.addEventListener("resize",this._windowResizeBind)),this}static getElement(e=".grid-stack-item"){return r.Utils.getElement(e)}static getElements(e=".grid-stack-item"){return r.Utils.getElements(e)}static getGridElement(e){return u.getElement(e)}static getGridElements(e){return r.Utils.getElements(e)}_initMargin(){let e,t=0,i=[];return"string"==typeof this.opts.margin&&(i=this.opts.margin.split(" ")),2===i.length?(this.opts.marginTop=this.opts.marginBottom=i[0],this.opts.marginLeft=this.opts.marginRight=i[1]):4===i.length?(this.opts.marginTop=i[0],this.opts.marginRight=i[1],this.opts.marginBottom=i[2],this.opts.marginLeft=i[3]):(e=r.Utils.parseHeight(this.opts.margin),this.opts.marginUnit=e.unit,t=this.opts.margin=e.h),void 0===this.opts.marginTop?this.opts.marginTop=t:(e=r.Utils.parseHeight(this.opts.marginTop),this.opts.marginTop=e.h,delete this.opts.margin),void 0===this.opts.marginBottom?this.opts.marginBottom=t:(e=r.Utils.parseHeight(this.opts.marginBottom),this.opts.marginBottom=e.h,delete this.opts.margin),void 0===this.opts.marginRight?this.opts.marginRight=t:(e=r.Utils.parseHeight(this.opts.marginRight),this.opts.marginRight=e.h,delete this.opts.margin),void 0===this.opts.marginLeft?this.opts.marginLeft=t:(e=r.Utils.parseHeight(this.opts.marginLeft),this.opts.marginLeft=e.h,delete this.opts.margin),this.opts.marginUnit=e.unit,this.opts.marginTop===this.opts.marginBottom&&this.opts.marginLeft===this.opts.marginRight&&this.opts.marginTop===this.opts.marginRight&&(this.opts.margin=this.opts.marginTop),this}static getDD(){return g}static setupDragIn(e,t){void 0!==(null==t?void 0:t.pause)&&(d.DDManager.pauseDrag=t.pause),"string"==typeof e&&(t=Object.assign(Object.assign({},l.dragInDefaultOptions),t||{}),r.Utils.getElements(e).forEach((e=>{g.isDraggable(e)||g.dragIn(e,t)})))}movable(e,t){return this.opts.staticGrid||u.getElements(e).forEach((e=>{let i=e.gridstackNode;i&&(t?delete i.noMove:i.noMove=!0,this._prepareDragDropByNode(i))})),this}resizable(e,t){return this.opts.staticGrid||u.getElements(e).forEach((e=>{let i=e.gridstackNode;i&&(t?delete i.noResize:i.noResize=!0,this._prepareDragDropByNode(i))})),this}disable(e=!0){if(!this.opts.staticGrid)return this.enableMove(!1,e),this.enableResize(!1,e),this._triggerEvent("disable"),this}enable(e=!0){if(!this.opts.staticGrid)return this.enableMove(!0,e),this.enableResize(!0,e),this._triggerEvent("enable"),this}enableMove(e,t=!0){return this.opts.staticGrid||(this.opts.disableDrag=!e,this.engine.nodes.forEach((i=>{this.movable(i.el,e),i.subGrid&&t&&i.subGrid.enableMove(e,t)}))),this}enableResize(e,t=!0){return this.opts.staticGrid||(this.opts.disableResize=!e,this.engine.nodes.forEach((i=>{this.resizable(i.el,e),i.subGrid&&t&&i.subGrid.enableResize(e,t)}))),this}_removeDD(e){return g.draggable(e,"destroy").resizable(e,"destroy"),e.gridstackNode&&delete e.gridstackNode._initDD,delete e.ddElement,this}_setupAcceptWidget(){if(this.opts.staticGrid||!this.opts.acceptWidgets&&!this.opts.removable)return g.droppable(this.el,"destroy"),this;let e,t,i=(i,s,o)=>{let n=s.gridstackNode;if(!n)return;o=o||s;let l=this.el.getBoundingClientRect(),{top:a,left:h}=o.getBoundingClientRect();h-=l.left,a-=l.top;let d={position:{top:a,left:h}};if(n._temporaryRemoved){if(n.x=Math.max(0,Math.round(h/t)),n.y=Math.max(0,Math.round(a/e)),delete n.autoPosition,this.engine.nodeBoundFix(n),!this.engine.willItFit(n)){if(n.autoPosition=!0,!this.engine.willItFit(n))return void g.off(s,"drag");n._willFitPos&&(r.Utils.copyPos(n,n._willFitPos),delete n._willFitPos)}this._onStartMoving(o,i,d,n,t,e)}else this._dragOrResize(o,i,d,n,t,e)};return g.droppable(this.el,{accept:e=>{let t=e.gridstackNode;if((null==t?void 0:t.grid)===this)return!0;if(!this.opts.acceptWidgets)return!1;let i=!0;if("function"==typeof this.opts.acceptWidgets)i=this.opts.acceptWidgets(e);else{let t=!0===this.opts.acceptWidgets?".grid-stack-item":this.opts.acceptWidgets;i=e.matches(t)}if(i&&t&&this.opts.maxRow){let e={w:t.w,h:t.h,minW:t.minW,minH:t.minH};i=this.engine.willItFit(e)}return i}}).on(this.el,"dropover",((s,o,n)=>{let r=o.gridstackNode;if((null==r?void 0:r.grid)===this&&!r._temporaryRemoved)return!1;if((null==r?void 0:r.grid)&&r.grid!==this&&!r._temporaryRemoved){r.grid._leave(o,n)}t=this.cellWidth(),e=this.getCellHeight(!0),r||(r=this._readAttr(o)),r.grid||(r._isExternal=!0,o.gridstackNode=r),n=n||o;let l=r.w||Math.round(n.offsetWidth/t)||1,a=r.h||Math.round(n.offsetHeight/e)||1;return r.grid&&r.grid!==this?(o._gridstackNodeOrig||(o._gridstackNodeOrig=r),o.gridstackNode=r=Object.assign(Object.assign({},r),{w:l,h:a,grid:this}),this.engine.cleanupNode(r).nodeBoundFix(r),r._initDD=r._isExternal=r._temporaryRemoved=!0):(r.w=l,r.h=a,r._temporaryRemoved=!0),this._itemRemoving(r.el,!1),g.on(o,"drag",i),i(s,o,n),!1})).on(this.el,"dropout",((e,t,i)=>{let s=t.gridstackNode;return!!s&&(s.grid&&s.grid!==this||(this._leave(t,i),this._isTemp&&this.removeAsSubGrid(s)),!1)})).on(this.el,"drop",((e,t,i)=>{var s,o;let n=t.gridstackNode;if((null==n?void 0:n.grid)===this&&!n._isExternal)return!1;let a=!!this.placeholder.parentElement;this.placeholder.remove();let h=t._gridstackNodeOrig;if(delete t._gridstackNodeOrig,a&&(null==h?void 0:h.grid)&&h.grid!==this){let e=h.grid;e.engine.removedNodes.push(h),e._triggerRemoveEvent()._triggerChangeEvent(),e.parentGridItem&&!e.engine.nodes.length&&e.opts.subGridDynamic&&e.removeAsSubGrid()}if(!n)return!1;if(a&&(this.engine.cleanupNode(n),n.grid=this),g.off(t,"drag"),i!==t?(i.remove(),t.gridstackNode=h,a&&(t=t.cloneNode(!0))):(t.remove(),this._removeDD(t)),!a)return!1;t.gridstackNode=n,n.el=t;let d=null===(o=null===(s=n.subGrid)||void 0===s?void 0:s.el)||void 0===o?void 0:o.gridstack;return r.Utils.copyPos(n,this._readAttr(this.placeholder)),r.Utils.removePositioningStyles(t),this._writeAttr(t,n),t.classList.add(l.gridDefaults.itemClass,this.opts.itemClass),this.el.appendChild(t),d&&(d.parentGridItem=n,d.opts.styleInHead||d._updateStyles(!0)),this._updateContainerHeight(),this.engine.addedNodes.push(n),this._triggerAddEvent(),this._triggerChangeEvent(),this.engine.endUpdate(),this._gsEventHandler.dropped&&this._gsEventHandler.dropped(Object.assign(Object.assign({},e),{type:"dropped"}),h&&h.grid?h:void 0,n),window.setTimeout((()=>{n.el&&n.el.parentElement?this._prepareDragDropByNode(n):this.engine.removeNode(n),delete n.grid._isTemp})),!1})),this}_itemRemoving(e,t){let i=e?e.gridstackNode:void 0;i&&i.grid&&(t?i._isAboutToRemove=!0:delete i._isAboutToRemove,t?e.classList.add("grid-stack-item-removing"):e.classList.remove("grid-stack-item-removing"))}_setupRemoveDrop(){if(!this.opts.staticGrid&&"string"==typeof this.opts.removable){let e=document.querySelector(this.opts.removable);if(!e)return this;g.isDroppable(e)||g.droppable(e,this.opts.removableOptions).on(e,"dropover",((e,t)=>this._itemRemoving(t,!0))).on(e,"dropout",((e,t)=>this._itemRemoving(t,!1)))}return this}_prepareDragDropByNode(e){let t=e.el;const i=e.noMove||this.opts.disableDrag,s=e.noResize||this.opts.disableResize;if(this.opts.staticGrid||i&&s)return e._initDD&&(this._removeDD(t),delete e._initDD),t.classList.add("ui-draggable-disabled","ui-resizable-disabled"),this;if(!e._initDD){let i,s,o=(o,n)=>{this._gsEventHandler[o.type]&&this._gsEventHandler[o.type](o,o.target),i=this.cellWidth(),s
    ... [truncated]
    
  • public/build/dashboard.faf42d4e.js.LICENSE.txt+0 0 renamed
  • public/build/entrypoints.json+18 18 modified
    @@ -3,7 +3,7 @@
         "app": {
           "js": [
             "/build/runtime.6c399d29.js",
    -        "/build/app.9662939e.js"
    +        "/build/app.307ea672.js"
           ],
           "css": [
             "/build/app.c18ba3c6.css"
    @@ -12,7 +12,7 @@
         "app-rtl": {
           "js": [
             "/build/runtime.6c399d29.js",
    -        "/build/app-rtl.15853b82.js"
    +        "/build/app-rtl.fe7c5bf2.js"
           ],
           "css": [
             "/build/app-rtl.2003dce5.css"
    @@ -21,7 +21,7 @@
         "export-pdf": {
           "js": [
             "/build/runtime.6c399d29.js",
    -        "/build/export-pdf.395749ab.js"
    +        "/build/export-pdf.5905454a.js"
           ],
           "css": [
             "/build/export-pdf.d8a6c23b.css"
    @@ -30,7 +30,7 @@
         "invoice": {
           "js": [
             "/build/runtime.6c399d29.js",
    -        "/build/invoice.42b319e4.js"
    +        "/build/invoice.0217cc18.js"
           ],
           "css": [
             "/build/invoice.36018785.css"
    @@ -39,7 +39,7 @@
         "invoice-pdf": {
           "js": [
             "/build/runtime.6c399d29.js",
    -        "/build/invoice-pdf.26d98626.js"
    +        "/build/invoice-pdf.1b6e735c.js"
           ],
           "css": [
             "/build/invoice-pdf.2b749265.css"
    @@ -48,13 +48,13 @@
         "chart": {
           "js": [
             "/build/runtime.6c399d29.js",
    -        "/build/chart.bafa38e7.js"
    +        "/build/chart.2c85f027.js"
           ]
         },
         "calendar": {
           "js": [
             "/build/runtime.6c399d29.js",
    -        "/build/calendar.b2d70caa.js"
    +        "/build/calendar.cef61816.js"
           ],
           "css": [
             "/build/calendar.d757753e.css"
    @@ -63,7 +63,7 @@
         "dashboard": {
           "js": [
             "/build/runtime.6c399d29.js",
    -        "/build/dashboard.6ce7ac9c.js"
    +        "/build/dashboard.faf42d4e.js"
           ],
           "css": [
             "/build/dashboard.b7129fa1.css"
    @@ -72,7 +72,7 @@
         "highlight": {
           "js": [
             "/build/runtime.6c399d29.js",
    -        "/build/highlight.13d5d50e.js"
    +        "/build/highlight.718fe73d.js"
           ],
           "css": [
             "/build/highlight.98bf3927.css"
    @@ -81,22 +81,22 @@
       },
       "integrity": {
         "/build/runtime.6c399d29.js": "sha384-/rm616f12czi8l/27GvWXtb3g608vJZf2XTUKxqCRI4tsa2vUHP+BW90edTok5zC",
    -    "/build/app.9662939e.js": "sha384-bRL78SVI1wjTweqKeb+ZSSbA78i6By89S8/xx+55zmbOVtF1TCu3lf3pvdki5ZVJ",
    +    "/build/app.307ea672.js": "sha384-XNjtkar2JbWyJQan/C6jHZAYFI9X5rPcExLeDV86FYZMPJl0vNWMh8Pa8NgXX/EB",
         "/build/app.c18ba3c6.css": "sha384-qkIgqLzngG2NchdFVImbGMU49lWlZg6Y9D0z2P0u1j2NQstDicA4KqIkkNYcpErm",
    -    "/build/app-rtl.15853b82.js": "sha384-UnKKgLMu9FnRT+CFE0no/+UiUks012bYriQdUWa6f02mo6Lswl947mPybjvKL503",
    +    "/build/app-rtl.fe7c5bf2.js": "sha384-UyLXlR/3/H6oFjRrIMH04PA8bAjVQ8rdVWkWoiO4LLYasg4H3UNWJ+Xh+FrYekKh",
         "/build/app-rtl.2003dce5.css": "sha384-pl8GyGo8sRRw1zLh9D42ZAvo450onfxeszZWocUUuzq70jAOwOlaoPYh/yyc20U7",
    -    "/build/export-pdf.395749ab.js": "sha384-3Hjvmu4FC/0dhHnR8kyRBU7k2xMNy1lxBpGgOkrw8PxXnwyQDM8/5bQmkJbjVT1+",
    +    "/build/export-pdf.5905454a.js": "sha384-AkNj018YEQJSxC1e/3jIvlh0uR5tHnlUe9ciBbHKIoRIwj7658tCLNVaT98SNFEX",
         "/build/export-pdf.d8a6c23b.css": "sha384-ztepocHE4rnGE9eKZ4kL6jTKaePUyiwiB9TjJjstjpf/ckcKg1HedrEOOk/8ElJg",
    -    "/build/invoice.42b319e4.js": "sha384-xxK7sCe/ZhTjMPFPeX1xvILURxNRZz2hJHZxAGVaw9zE6TC++2/6y2eKqg8cz852",
    +    "/build/invoice.0217cc18.js": "sha384-ORySQp4DBSYc7so90AAb8h5abgQtz3tdoO+Zv1uHJ2XqSPMAGtFBrb+Rdzc6sZg1",
         "/build/invoice.36018785.css": "sha384-jukM9uZ6pexDxXKgZThSxiqXimzsxzniBMHz08N9x8ryXZYkM5r/ZgaainCV0+J6",
    -    "/build/invoice-pdf.26d98626.js": "sha384-gwNzQiU1y6qU/M9DPGiNW0MVZkLctEHk37sCES2X9ov+zugEaDABdkMjKBYOC9lz",
    +    "/build/invoice-pdf.1b6e735c.js": "sha384-8I5YMkETBNl7wD12V/hKX2YGfwH+V6PXWsu4I+1xXXx9bDzqDhtq+S7x/jey2/Wr",
         "/build/invoice-pdf.2b749265.css": "sha384-DXXgkz2WWnrWnfBnXX5fmfPQSPb98upMnWxYKwTGYS04EhrPIWfDCutB2unIrWh7",
    -    "/build/chart.bafa38e7.js": "sha384-Ays2qGKvOqs4NSeN/zJOPcrnzIEC/uYSCIkC3kN0KQye+mA5Lq7UkfjZDjulvFUX",
    -    "/build/calendar.b2d70caa.js": "sha384-FS7Q9iCWHpo2Nzk0tmNvnuTbxJKLdbXFba9F6WcXfHbKFAS2Q+lW29H9Mv+ap5kA",
    +    "/build/chart.2c85f027.js": "sha384-5Dm9aFCCSzlh5QY/vtU7/UrBkKt6aWnTccjljmpbQVFfzSEfFh0wUUV8qlXqyFJj",
    +    "/build/calendar.cef61816.js": "sha384-qCpJrSB6fZLXT94oiWHofGbX8C0TFl86oCMTdeKWqCmNGJxABkIVo+8k19p7sGlY",
         "/build/calendar.d757753e.css": "sha384-cTmQMgHYjd2gfObFWmEUph7qQLCyXaIkneSf+bQ2mqVmZwqOB+pJOm/UYTyTjALJ",
    -    "/build/dashboard.6ce7ac9c.js": "sha384-5BOoyjZx/ZKr9IbogWfT/aBRHx7XA1fePADdUpUNlSF/rqVo3taXLLcrIoaDZ+8b",
    +    "/build/dashboard.faf42d4e.js": "sha384-xkM5D8wBK1sJekLI9DUQBxTjxjomfPQWWtWCo7NX03jNnFZtMv5anM0IISLWaFY0",
         "/build/dashboard.b7129fa1.css": "sha384-2nn5hLA+3YedgHYBpge62S8Losj8aoPwK9Zk9EvN1xEYatvOUQ7H3rIR2UUJAGOS",
    -    "/build/highlight.13d5d50e.js": "sha384-+RqBBPIzp5YQpbBeHgVNXzHiewK1PZtizPh4in1PlBU6RcT1bDleW3QSJMDeBnPO",
    +    "/build/highlight.718fe73d.js": "sha384-eHb21blXJ7SKqCFlMSxl4oblpusDif1B0Tsr2Iw1xQfUqxZxwtFwYYvMPllfw7BY",
         "/build/highlight.98bf3927.css": "sha384-YgweSwDwN0dI4DEmh478xYVw/TewJYvCO1QTbWHpaHFDPuLrZvYMR0Tc+QfFxVPE"
       }
     }
    \ No newline at end of file
    
  • public/build/export-pdf.395749ab.js+0 1 removed
    @@ -1 +0,0 @@
    -(self.webpackChunkkimai=self.webpackChunkkimai||[]).push([[872],{878:function(i,n,u){u(3385)},3385:function(i,n,u){"use strict";u.r(n)}},function(i){var n;n=878,i(i.s=n)}]);
    \ No newline at end of file
    
  • public/build/export-pdf.5905454a.js+1 0 added
    @@ -0,0 +1 @@
    +(self.webpackChunkkimai=self.webpackChunkkimai||[]).push([[872],{585:function(i,n,u){"use strict";u.r(n)},2352:function(i,n,u){u(585)}},function(i){var n;n=2352,i(i.s=n)}]);
    \ No newline at end of file
    
  • public/build/highlight.13d5d50e.js+0 1 removed
  • public/build/highlight.718fe73d.js+1 0 added
  • public/build/invoice.0217cc18.js+1 0 added
    @@ -0,0 +1 @@
    +(self.webpackChunkkimai=self.webpackChunkkimai||[]).push([[896],{239:function(i,n,u){"use strict";u.r(n)},8478:function(i,n,u){u(239)}},function(i){var n;n=8478,i(i.s=n)}]);
    \ No newline at end of file
    
  • public/build/invoice.42b319e4.js+0 1 removed
    @@ -1 +0,0 @@
    -(self.webpackChunkkimai=self.webpackChunkkimai||[]).push([[896],{3631:function(i,n,u){"use strict";u.r(n)},4820:function(i,n,u){u(3631)}},function(i){var n;n=4820,i(i.s=n)}]);
    \ No newline at end of file
    
  • public/build/invoice-pdf.1b6e735c.js+1 0 added
    @@ -0,0 +1 @@
    +(self.webpackChunkkimai=self.webpackChunkkimai||[]).push([[117],{2049:function(i,n,u){u(4860)},4860:function(i,n,u){"use strict";u.r(n)}},function(i){var n;n=2049,i(i.s=n)}]);
    \ No newline at end of file
    
  • public/build/invoice-pdf.26d98626.js+0 1 removed
    @@ -1 +0,0 @@
    -(self.webpackChunkkimai=self.webpackChunkkimai||[]).push([[117],{1555:function(i,n,u){u(6620)},6620:function(i,n,u){"use strict";u.r(n)}},function(i){var n;n=1555,i(i.s=n)}]);
    \ No newline at end of file
    
  • public/build/manifest.json+9 9 modified
    @@ -1,21 +1,21 @@
     {
       "build/app.css": "/build/app.c18ba3c6.css",
    -  "build/app.js": "/build/app.9662939e.js",
    +  "build/app.js": "/build/app.307ea672.js",
       "build/app-rtl.css": "/build/app-rtl.2003dce5.css",
    -  "build/app-rtl.js": "/build/app-rtl.15853b82.js",
    +  "build/app-rtl.js": "/build/app-rtl.fe7c5bf2.js",
       "build/export-pdf.css": "/build/export-pdf.d8a6c23b.css",
    -  "build/export-pdf.js": "/build/export-pdf.395749ab.js",
    +  "build/export-pdf.js": "/build/export-pdf.5905454a.js",
       "build/invoice.css": "/build/invoice.36018785.css",
    -  "build/invoice.js": "/build/invoice.42b319e4.js",
    +  "build/invoice.js": "/build/invoice.0217cc18.js",
       "build/invoice-pdf.css": "/build/invoice-pdf.2b749265.css",
    -  "build/invoice-pdf.js": "/build/invoice-pdf.26d98626.js",
    -  "build/chart.js": "/build/chart.bafa38e7.js",
    +  "build/invoice-pdf.js": "/build/invoice-pdf.1b6e735c.js",
    +  "build/chart.js": "/build/chart.2c85f027.js",
       "build/calendar.css": "/build/calendar.d757753e.css",
    -  "build/calendar.js": "/build/calendar.b2d70caa.js",
    +  "build/calendar.js": "/build/calendar.cef61816.js",
       "build/dashboard.css": "/build/dashboard.b7129fa1.css",
    -  "build/dashboard.js": "/build/dashboard.6ce7ac9c.js",
    +  "build/dashboard.js": "/build/dashboard.faf42d4e.js",
       "build/highlight.css": "/build/highlight.98bf3927.css",
    -  "build/highlight.js": "/build/highlight.13d5d50e.js",
    +  "build/highlight.js": "/build/highlight.718fe73d.js",
       "build/runtime.js": "/build/runtime.6c399d29.js",
       "build/fonts/fa-solid-900.ttf": "/build/fonts/fa-solid-900.2582b0e4.ttf",
       "build/fonts/fa-brands-400.ttf": "/build/fonts/fa-brands-400.1815e004.ttf",
    
  • public/manifest.json+1 1 modified
    @@ -25,7 +25,7 @@
       ],
       "scope": "./",
       "start_url": "./",
    -  "theme-color": "#1d273b",
    +  "theme-color": "#262626",
       "background_color": "#ffffff",
       "display": "standalone"
     }
    \ No newline at end of file
    
  • src/Constants.php+2 2 modified
    @@ -17,11 +17,11 @@ final class Constants
         /**
          * The current release version
          */
    -    public const VERSION = '2.45.0';
    +    public const VERSION = '2.46.0';
         /**
          * The current release: major * 10000 + minor * 100 + patch
          */
    -    public const VERSION_ID = 24500;
    +    public const VERSION_ID = 24600;
         /**
          * The software name
          */
    
  • src/Entity/ColorTrait.php+4 3 modified
    @@ -23,7 +23,8 @@ trait ColorTrait
          * The assigned color in HTML hex format, e.g. #dd1d00
          */
         #[ORM\Column(name: 'color', type: Types::STRING, length: 7, nullable: true)]
    -    #[Serializer\Exclude]
    +    #[Serializer\Expose]
    +    #[Serializer\Groups(['Default'])]
         #[Exporter\Expose(label: 'color')]
         #[Constraints\HexColor]
         private ?string $color = null;
    @@ -50,10 +51,10 @@ public function setColor(?string $color = null): void
         abstract public function getName(): ?string;
     
         /**
    -     * Internal value: this color will never be empty and is generated by the tag name if not set explicit.
    +     * Color will never be empty and is generated from the entity tag name if not set explicit.
          */
         #[Serializer\VirtualProperty]
    -    #[Serializer\SerializedName('color')]
    +    #[Serializer\SerializedName('color-safe')]
         #[Serializer\Groups(['Default'])]
         public function getColorSafe(): string
         {
    
  • src/Export/Base/HtmlRenderer.php+4 0 modified
    @@ -117,7 +117,11 @@ public function render(array $exportItems, TimesheetQuery $query): Response
                 'userPreferences' => $userPreferences,
             ], $this->getOptions($query)));
     
    +        // allows to run in development mode, otherwise toolbar would be blocked
    +        $sandbox->disableSandbox();
    +
             $response = new Response();
    +        $response->headers->set('Content-Type', 'text/html');
             $response->setContent($content);
     
             return $response;
    
  • src/Twig/SecurityPolicy/ChainPolicy.php+0 4 modified
    @@ -16,10 +16,6 @@ final class ChainPolicy implements SecurityPolicyInterface
         /** @var array<SecurityPolicyInterface> */
         private array $policies = [];
     
    -    public function __construct()
    -    {
    -    }
    -
         public function addPolicy(SecurityPolicyInterface $policy): void
         {
             $this->policies[] = $policy;
    
  • src/Twig/SecurityPolicy/DefaultPolicy.php+50 0 modified
    @@ -9,6 +9,13 @@
     
     namespace App\Twig\SecurityPolicy;
     
    +use App\Entity\User;
    +use App\Pdf\PdfContext;
    +use Symfony\Bridge\Twig\AppVariable;
    +use Symfony\Component\HttpFoundation\Request;
    +use Symfony\Component\HttpFoundation\ServerBag;
    +use Symfony\Component\HttpFoundation\Session\SessionInterface;
    +use Twig\Sandbox\SecurityNotAllowedMethodError;
     use Twig\Sandbox\SecurityPolicyInterface;
     
     /**
    @@ -22,6 +29,49 @@ public function checkSecurity($tags, $filters, $functions): void
     
         public function checkMethodAllowed($obj, $method): void
         {
    +        if ($obj instanceof ServerBag) {
    +            throw new SecurityNotAllowedMethodError('Tried to access server environment', ServerBag::class, $method);
    +        }
    +
    +        if ($obj instanceof SessionInterface) {
    +            throw new SecurityNotAllowedMethodError('Tried to access session', SessionInterface::class, $method);
    +        }
    +
    +        $lcm = strtolower($method);
    +
    +        if ($obj instanceof PdfContext) {
    +            if ($lcm !== 'setoption') {
    +                throw new SecurityNotAllowedMethodError('Tried to access forbidden method on PdfContext', PdfContext::class, $method);
    +            }
    +
    +            return;
    +        }
    +
    +        if (!str_starts_with($lcm, 'has') && !str_starts_with($lcm, 'is') && !str_starts_with($lcm, 'get') && $lcm !== '__tostring') {
    +            throw new SecurityNotAllowedMethodError('Tried to access non-read method', $obj::class, $method);
    +        }
    +
    +        if ($obj instanceof Request) {
    +            if (!str_starts_with($lcm, 'get')) {
    +                throw new SecurityNotAllowedMethodError('Tried to call setter() of app variable', AppVariable::class, $method);
    +            }
    +
    +            return;
    +        }
    +
    +        if ($obj instanceof AppVariable) {
    +            if (!\in_array($lcm, ['getrequest', 'getuser', 'getlocale'], true)) {
    +                throw new SecurityNotAllowedMethodError('Tried to access forbidden app variable method', User::class, $method);
    +            }
    +
    +            return;
    +        }
    +
    +        if ($obj instanceof User) {
    +            if (\in_array($lcm, ['getpassword', 'gettotpsecret', 'getplainpassword', 'getconfirmationtoken', 'gettotpauthenticationconfiguration'], true)) {
    +                throw new SecurityNotAllowedMethodError('Tried to access user secrets', User::class, $method);
    +            }
    +        }
         }
     
         public function checkPropertyAllowed($obj, $property): void
    
  • src/Twig/SecurityPolicy/ForbiddenPolicy.php+0 109 removed
    @@ -1,109 +0,0 @@
    -<?php
    -
    -/*
    - * This file is part of the Kimai time-tracking app.
    - *
    - * For the full copyright and license information, please view the LICENSE
    - * file that was distributed with this source code.
    - */
    -
    -namespace App\Twig\SecurityPolicy;
    -
    -use Twig\Markup;
    -use Twig\Sandbox\SecurityNotAllowedFilterError;
    -use Twig\Sandbox\SecurityNotAllowedFunctionError;
    -use Twig\Sandbox\SecurityNotAllowedMethodError;
    -use Twig\Sandbox\SecurityNotAllowedPropertyError;
    -use Twig\Sandbox\SecurityNotAllowedTagError;
    -use Twig\Sandbox\SecurityPolicyInterface;
    -use Twig\Template;
    -
    -/**
    - * A blocking approach for Twig templates.
    - */
    -final class ForbiddenPolicy implements SecurityPolicyInterface
    -{
    -    /** @var array<string, array<string>> */
    -    private array $forbiddenMethods = [];
    -
    -    /**
    -     * @param array<string> $forbiddenTags
    -     * @param array<string> $forbiddenFilters
    -     * @param array<string, array<string>> $forbiddenMethods
    -     * @param array<string, array<string>> $forbiddenProperties
    -     * @param array<string> $forbiddenFunctions
    -     */
    -    public function __construct(
    -        private readonly array $forbiddenTags = [],
    -        private readonly array $forbiddenFilters = [],
    -        array $forbiddenMethods = [],
    -        private readonly array $forbiddenProperties = [],
    -        private readonly array $forbiddenFunctions = []
    -    )
    -    {
    -        $this->forbiddenMethods = [];
    -        foreach ($forbiddenMethods as $class => $m) {
    -            $this->forbiddenMethods[$class] = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, \is_array($m) ? $m : [$m]);
    -        }
    -    }
    -
    -    public function checkSecurity($tags, $filters, $functions): void
    -    {
    -        foreach ($tags as $tag) {
    -            if (\in_array($tag, $this->forbiddenTags)) {
    -                throw new SecurityNotAllowedTagError(\sprintf('Tag "%s" is not allowed.', $tag), $tag);
    -            }
    -        }
    -
    -        foreach ($filters as $filter) {
    -            if (\in_array($filter, $this->forbiddenFilters)) {
    -                throw new SecurityNotAllowedFilterError(\sprintf('Filter "%s" is not allowed.', $filter), $filter);
    -            }
    -        }
    -
    -        foreach ($functions as $function) {
    -            if (\in_array($function, $this->forbiddenFunctions)) {
    -                throw new SecurityNotAllowedFunctionError(\sprintf('Function "%s" is not allowed.', $function), $function);
    -            }
    -        }
    -    }
    -
    -    public function checkMethodAllowed($obj, $method): void
    -    {
    -        if ($obj instanceof Template || $obj instanceof Markup) { // @phpstan-ignore instanceof.internalClass
    -            return;
    -        }
    -
    -        $forbidden = false;
    -        $method = strtr($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
    -        foreach ($this->forbiddenMethods as $class => $methods) {
    -            if ($obj instanceof $class) {
    -                $forbidden = \in_array($method, $methods);
    -
    -                break;
    -            }
    -        }
    -
    -        if ($forbidden) {
    -            $class = \get_class($obj);
    -            throw new SecurityNotAllowedMethodError(\sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, $class), $class, $method);
    -        }
    -    }
    -
    -    public function checkPropertyAllowed($obj, $property): void
    -    {
    -        $forbidden = false;
    -        foreach ($this->forbiddenProperties as $class => $properties) {
    -            if ($obj instanceof $class) {
    -                $forbidden = \in_array($property, \is_array($properties) ? $properties : [$properties]);
    -
    -                break;
    -            }
    -        }
    -
    -        if ($forbidden) {
    -            $class = \get_class($obj);
    -            throw new SecurityNotAllowedPropertyError(\sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, $class), $class, $property);
    -        }
    -    }
    -}
    
  • src/Twig/SecurityPolicy/InvoicePolicy.php+13 13 modified
    @@ -22,13 +22,13 @@
      */
     final class InvoicePolicy implements SecurityPolicyInterface
     {
    -    private ChainPolicy $policy;
    +    private SecurityPolicyInterface $default;
    +    private SecurityPolicyInterface $security;
     
         public function __construct()
         {
    -        $this->policy = new ChainPolicy();
    -        $this->policy->addPolicy(new DefaultPolicy());
    -        $this->policy->addPolicy(new SecurityPolicy(
    +        $this->default = new DefaultPolicy();
    +        $this->security = new SecurityPolicy(
                 ['block', 'if', 'for', 'set', 'extends', 'import'],
                 [
                     // =================================================================
    @@ -194,12 +194,13 @@ public function __construct()
                     'month_names',
                     'locale_format',
                 ]
    -        ));
    +        );
         }
     
         public function checkSecurity($tags, $filters, $functions): void
         {
    -        $this->policy->checkSecurity($tags, $filters, $functions);
    +        $this->default->checkSecurity($tags, $filters, $functions);
    +        $this->security->checkSecurity($tags, $filters, $functions);
         }
     
         public function checkMethodAllowed($obj, $method): void
    @@ -208,21 +209,20 @@ public function checkMethodAllowed($obj, $method): void
                 return;
             }
     
    -        $lm = strtolower($method);
    +        $this->default->checkMethodAllowed($obj, $method);
     
    -        if (str_starts_with($lm, 'get') || str_starts_with($lm, 'is') || str_starts_with($lm, 'has')) {
    -            return;
    -        }
    +        $lm = strtolower($method);
     
    -        if ($lm === '__tostring') {
    +        if (str_starts_with($lm, 'get') || str_starts_with($lm, 'is') || str_starts_with($lm, 'has') || $lm === '__tostring') {
                 return;
             }
     
    -        $this->policy->checkMethodAllowed($obj, $method);
    +        $this->security->checkMethodAllowed($obj, $method);
         }
     
         public function checkPropertyAllowed($obj, $property): void
         {
    -        $this->policy->checkPropertyAllowed($obj, $property);
    +        $this->default->checkPropertyAllowed($obj, $property);
    +        $this->security->checkPropertyAllowed($obj, $property);
         }
     }
    
  • templates/export/print.html.twig+118 122 modified
    @@ -1,7 +1,6 @@
     {% extends 'export/layout.html.twig' %}
     {% import "macros/widgets.html.twig" as widgets %}
     {% import "macros/datatables.html.twig" as tables %}
    -{% import "macros/webloader.html.twig" as webloader %}
     
     {% block document_title %}{{ 'export'|trans }}{% endblock %}
     
    @@ -52,147 +51,144 @@
     }) %}
     
     {% block javascripts %}
    -    {{ webloader.init_frontend_loader() }}
         <script type="text/javascript">
             let initialized = false;
     
    -        document.addEventListener('kimai.initialized', function(event) {
    -            document.getElementById('duration-decimal').addEventListener('click', function(event) {
    -                var spans = document.getElementsByClassName('duration-format');
    -                for (var span of spans) {
    -                    if (!event.target.checked) {
    -                        if (span.dataset['duration'] !== undefined) {
    -                            span.innerHTML = span.dataset['duration'];
    -                        }
    -                    } else {
    -                        if (span.dataset['durationDecimal'] !== undefined) {
    -                            span.innerHTML = span.dataset['durationDecimal'];
    -                        }
    +        document.getElementById('duration-decimal').addEventListener('click', function(event) {
    +            var spans = document.getElementsByClassName('duration-format');
    +            for (var span of spans) {
    +                if (!event.target.checked) {
    +                    if (span.dataset['duration'] !== undefined) {
    +                        span.innerHTML = span.dataset['duration'];
    +                    }
    +                } else {
    +                    if (span.dataset['durationDecimal'] !== undefined) {
    +                        span.innerHTML = span.dataset['durationDecimal'];
                         }
                     }
    -                saveVisibility();
    -            });
    +            }
    +            saveVisibility();
    +        });
     
    -            document.getElementById('summary-by-activities').addEventListener('click', function(event) {
    -                document.getElementById('summary-project').style.display = event.target.checked ? 'none' : 'table';
    -                document.getElementById('summary-activity').style.display = event.target.checked ? 'table' : 'none';
    -                saveVisibility();
    -            });
    -            document.getElementById('date-format').addEventListener('change', function(event) {
    -                changedDateFormat(event.target.value, 'dateformat');
    -            });
    -            document.getElementById('begin-format').addEventListener('change', function(event) {
    -                changedDateFormat(event.target.value, 'beginformat');
    -            });
    -            document.getElementById('end-format').addEventListener('change', function(event) {
    -                changedDateFormat(event.target.value, 'endformat');
    -            });
    -            document.getElementById('summary-show').addEventListener('click', function(event) {
    -                document.getElementById('export-summary').style.display = event.target.checked ? 'block' : 'none';
    -                saveVisibility();
    -            });
    -            document.getElementById('summary-timeBudget').addEventListener('click', function(event) {
    -                let cells = document.getElementsByClassName('export-timeBudget');
    -                for (let columnCell of cells) {
    -                    columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
    -                }
    -                saveVisibility();
    -            });
    -            document.getElementById('summary-budget').addEventListener('click', function(event) {
    -                let cells = document.getElementsByClassName('export-budget');
    -                for (let columnCell of cells) {
    -                    columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
    -                }
    -                saveVisibility();
    -            });
    -            document.getElementById('summary-duration').addEventListener('click', function(event) {
    -                let cells = document.getElementsByClassName('summary-duration');
    -                for (let columnCell of cells) {
    -                    columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
    -                }
    -                saveVisibility();
    -            });
    -            document.getElementById('summary-rate').addEventListener('click', function(event) {
    -                let cells = document.getElementsByClassName('summary-rate');
    -                for (let columnCell of cells) {
    -                    columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
    -                }
    -                saveVisibility();
    +        document.getElementById('summary-by-activities').addEventListener('click', function(event) {
    +            document.getElementById('summary-project').style.display = event.target.checked ? 'none' : 'table';
    +            document.getElementById('summary-activity').style.display = event.target.checked ? 'table' : 'none';
    +            saveVisibility();
    +        });
    +        document.getElementById('date-format').addEventListener('change', function(event) {
    +            changedDateFormat(event.target.value, 'dateformat');
    +        });
    +        document.getElementById('begin-format').addEventListener('change', function(event) {
    +            changedDateFormat(event.target.value, 'beginformat');
    +        });
    +        document.getElementById('end-format').addEventListener('change', function(event) {
    +            changedDateFormat(event.target.value, 'endformat');
    +        });
    +        document.getElementById('summary-show').addEventListener('click', function(event) {
    +            document.getElementById('export-summary').style.display = event.target.checked ? 'block' : 'none';
    +            saveVisibility();
    +        });
    +        document.getElementById('summary-timeBudget').addEventListener('click', function(event) {
    +            let cells = document.getElementsByClassName('export-timeBudget');
    +            for (let columnCell of cells) {
    +                columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
    +            }
    +            saveVisibility();
    +        });
    +        document.getElementById('summary-budget').addEventListener('click', function(event) {
    +            let cells = document.getElementsByClassName('export-budget');
    +            for (let columnCell of cells) {
    +                columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
    +            }
    +            saveVisibility();
    +        });
    +        document.getElementById('summary-duration').addEventListener('click', function(event) {
    +            let cells = document.getElementsByClassName('summary-duration');
    +            for (let columnCell of cells) {
    +                columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
    +            }
    +            saveVisibility();
    +        });
    +        document.getElementById('summary-rate').addEventListener('click', function(event) {
    +            let cells = document.getElementsByClassName('summary-rate');
    +            for (let columnCell of cells) {
    +                columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
    +            }
    +            saveVisibility();
    +        });
    +        document.getElementById('summary-internalRate').addEventListener('click', function(event) {
    +            let cells = document.getElementsByClassName('summary-internalRate');
    +            for (let columnCell of cells) {
    +                columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
    +            }
    +            saveVisibility();
    +        });
    +        document.getElementById('summary-show').addEventListener('click', function(event) {
    +            document.getElementById('export-summary').style.display = event.target.checked ? 'block' : 'none';
    +            saveVisibility();
    +        });
    +        document.getElementById('records-show').addEventListener('click', function(event) {
    +            document.getElementById('export-records').style.display = event.target.checked ? 'block' : 'none';
    +            saveVisibility();
    +        });
    +
    +        let columnCheckboxes = document.getElementsByClassName('column-visibility-changer');
    +
    +        for (let checkbox of columnCheckboxes) {
    +            checkbox.addEventListener('click', function(event) {
    +                changeVisibility(event.target.name, event.target.checked);
                 });
    -            document.getElementById('summary-internalRate').addEventListener('click', function(event) {
    -                let cells = document.getElementsByClassName('summary-internalRate');
    -                for (let columnCell of cells) {
    -                    columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
    +        }
    +
    +        let editableTitles = document.querySelectorAll('[contenteditable=true]');
    +
    +        for (let editable of editableTitles) {
    +            editable.addEventListener('input', function(event) {
    +                if (event.target.innerText === '') {
    +                    return;
                     }
                     saveVisibility();
                 });
    -            document.getElementById('summary-show').addEventListener('click', function(event) {
    -                document.getElementById('export-summary').style.display = event.target.checked ? 'block' : 'none';
    -                saveVisibility();
    -            });
    -            document.getElementById('records-show').addEventListener('click', function(event) {
    -                document.getElementById('export-records').style.display = event.target.checked ? 'block' : 'none';
    -                saveVisibility();
    -            });
    -
    -            let columnCheckboxes = document.getElementsByClassName('column-visibility-changer');
    -
    -            for (let checkbox of columnCheckboxes) {
    -                checkbox.addEventListener('click', function(event) {
    -                    changeVisibility(event.target.name, event.target.checked);
    -                });
    -            }
    -
    -            let editableTitles = document.querySelectorAll('[contenteditable=true]');
    +        }
     
    -            for (let editable of editableTitles) {
    -                editable.addEventListener('input', function(event) {
    -                    if (event.target.innerText === '') {
    -                        return;
    +        // needs to be executed as last action in the flow, after the listener were registered
    +        let config = localStorage.getItem('{{ storageItemName }}');
    +        if (config !== null) {
    +            try {
    +                config = JSON.parse(config);
    +                for (const elName in config) {
    +                    if (!config.hasOwnProperty(elName)) {
    +                        continue;
                         }
    -                    saveVisibility();
    -                });
    -            }
    -
    -            // needs to be executed as last action in the flow, after the listener were registered
    -            let config = localStorage.getItem('{{ storageItemName }}');
    -            if (config !== null) {
    -                try {
    -                    config = JSON.parse(config);
    -                    for (const elName in config) {
    -                        if (!config.hasOwnProperty(elName)) {
    +                    let elValue = config[elName];
    +                    let el = document.getElementById(elName);
    +                    if (el === undefined || el === null) {
    +                        el = document.getElementById('records-column-' + elName);
    +                        if (el === undefined || el === null) {
                                 continue;
                             }
    -                        let elValue = config[elName];
    -                        let el = document.getElementById(elName);
    -                        if (el === undefined || el === null) {
    -                            el = document.getElementById('records-column-' + elName);
    -                            if (el === undefined || el === null) {
    -                                continue;
    -                            }
    +                    }
    +                    if (el.type === 'checkbox') {
    +                        if (elValue !== el.checked) {
    +                            el.click();
                             }
    -                        if (el.type === 'checkbox') {
    -                            if (elValue !== el.checked) {
    -                                el.click();
    -                            }
    -                        } else if (el.type === 'select-one') {
    -                            if (el.value !== elValue) {
    -                                el.value = elValue;
    -                                el.dispatchEvent(new Event('change'));
    -                            }
    -                        } else if (el.isContentEditable) {
    -                            el.innerText = elValue;
    +                    } else if (el.type === 'select-one') {
    +                        if (el.value !== elValue) {
    +                            el.value = elValue;
    +                            el.dispatchEvent(new Event('change'));
                             }
    +                    } else if (el.isContentEditable) {
    +                        el.innerText = elValue;
                         }
    -                } catch (e) {
    -                    // ignore error in restoring
    -                    console.log('Failed to restore settings, removing invalid settings', e);
    -                    localStorage.removeItem('{{ storageItemName }}');
                     }
    +            } catch (e) {
    +                // ignore error in restoring
    +                console.log('Failed to restore settings, removing invalid settings', e);
    +                localStorage.removeItem('{{ storageItemName }}');
                 }
    +        }
     
    -            initialized = true;
    -        });
    +        initialized = true;
     
             function changeVisibility(column, visible)
             {
    
  • templates/partials/head.html.twig+1 1 modified
    @@ -14,7 +14,7 @@
     <meta name="mobile-web-app-capable" content="yes">
     <meta name="application-name" content="{{ constant('App\\Constants::SOFTWARE') }}">
     <meta name="msapplication-config" content="{{ asset('browserconfig.xml') }}">
    -<meta name="theme-color" content="#1d273b">
    +<meta name="theme-color" content="#262626">
     {% if tabler_bundle.isThemeAuto() %}
         <script>if (window.matchMedia) { document.documentElement.setAttribute('data-bs-theme', (window.matchMedia("(prefers-color-scheme: dark)").matches) ? 'dark': 'light'); }</script>
     {% endif %}
    
  • tests/API/ActivityControllerTest.php+3 3 modified
    @@ -244,7 +244,6 @@ public function testGetEntity(): void
             self::assertIsArray($result);
             self::assertApiResponseTypeStructure('ActivityEntity', $result);
     
    -        self::assertCount(14, array_keys($result));
             self::assertNull($result['parentTitle']);
             self::assertNotEmpty($result['id']);
             self::assertIsArray($result['teams']);
    @@ -258,7 +257,8 @@ public function testGetEntity(): void
             self::assertNull($result['budgetType']);
             self::assertNull($result['number']);
             self::assertEquals('Test comment', $result['comment']);
    -        self::assertEquals('#5319e7', $result['color']);
    +        self::assertNull($result['color']);
    +        self::assertEquals('#5319e7', $result['color-safe']);
             self::assertTrue($result['visible']);
             self::assertTrue($result['billable']);
         }
    @@ -297,7 +297,7 @@ public function testPostAction(): void
             self::assertEquals('Test', $result['parentTitle']);
             self::assertNotEmpty($result['id']);
             self::assertIsArray($result['teams']);
    -        self::assertEquals([['id' => 1, 'name' => 'Test team', 'color' => '#03A9F4']], $result['teams']);
    +        self::assertEquals([['id' => 1, 'name' => 'Test team', 'color' => null, 'color-safe' => '#03A9F4']], $result['teams']);
             self::assertIsArray($result['metaFields']);
             self::assertEquals([], $result['metaFields']);
             self::assertEquals('foo', $result['name']);
    
  • tests/API/APIControllerBaseTestCase.php+32 16 modified
    @@ -327,7 +327,8 @@ protected static function getExpectedResponseStructure(string $type): array
                     return [
                         'id' => 'int',
                         'name' => 'string',
    -                    'color' => 'string',
    +                    'color' => '@string',
    +                    'color-safe' => 'string',
                         'visible' => 'bool',
                     ];
     
    @@ -358,7 +359,8 @@ protected static function getExpectedResponseStructure(string $type): array
                         'enabled' => 'bool',
                         'apiToken' => 'bool',
                         'systemAccount' => 'bool',
    -                    'color' => 'string',
    +                    'color' => '@string',
    +                    'color-safe' => 'string',
                         'avatar' => '@string',
                         'alias' => '@string',
                         'accountNumber' => '@string',
    @@ -378,7 +380,8 @@ protected static function getExpectedResponseStructure(string $type): array
                         'enabled' => 'bool',
                         'apiToken' => 'bool',
                         'systemAccount' => 'bool',
    -                    'color' => 'string',
    +                    'color' => '@string',
    +                    'color-safe' => 'string',
                         'avatar' => '@string',
                         'alias' => '@string',
                         'accountNumber' => '@string',
    @@ -402,15 +405,17 @@ protected static function getExpectedResponseStructure(string $type): array
                     return [
                         'id' => 'int',
                         'name' => 'string',
    -                    'color' => 'string',
    +                    'color' => '@string',
    +                    'color-safe' => 'string',
                     ];
     
                     // explicitly requested team
                 case 'TeamEntity':
                     return [
                         'id' => 'int',
                         'name' => 'string',
    -                    'color' => 'string',
    +                    'color' => '@string',
    +                    'color-safe' => 'string',
                         'members' => ['result' => 'array', 'type' => 'TeamMember'],
                         // TODO more info in entity than in collection
                         'customers' => ['result' => 'array', 'type' => '@Customer'],
    @@ -439,7 +444,8 @@ protected static function getExpectedResponseStructure(string $type): array
                         'name' => 'string',
                         'visible' => 'bool',
                         'billable' => 'bool',
    -                    'color' => 'string',
    +                    'color' => '@string',
    +                    'color-safe' => 'string',
                         'number' => '@string',
                         'comment' => '@string',
                         'currency' => 'string',
    @@ -460,7 +466,8 @@ protected static function getExpectedResponseStructure(string $type): array
                         'name' => 'string',
                         'visible' => 'boolean',
                         'billable' => 'bool',
    -                    'color' => 'string',
    +                    'color' => '@string',
    +                    'color-safe' => 'string',
                         'number' => '@string',
                         'comment' => '@string',
                         'currency' => 'string',
    @@ -482,7 +489,8 @@ protected static function getExpectedResponseStructure(string $type): array
                         'name' => 'string',
                         'visible' => 'bool',
                         'billable' => 'bool',
    -                    'color' => 'string',
    +                    'color' => '@string',
    +                    'color-safe' => 'string',
                         'number' => '@string',
                         'comment' => '@string',
                         'currency' => 'string',
    @@ -519,7 +527,8 @@ protected static function getExpectedResponseStructure(string $type): array
                         'name' => 'string',
                         'visible' => 'bool',
                         'billable' => 'bool',
    -                    'color' => 'string',
    +                    'color' => '@string',
    +                    'color-safe' => 'string',
                         'customer' => 'int',
                         'number' => '@string',
                         'orderNumber' => '@string',
    @@ -538,7 +547,8 @@ protected static function getExpectedResponseStructure(string $type): array
                         'name' => 'string',
                         'visible' => 'bool',
                         'billable' => 'bool',
    -                    'color' => 'string',
    +                    'color' => '@string',
    +                    'color-safe' => 'string',
                         'customer' => ['result' => 'object', 'type' => 'Customer'],
                         'number' => '@string',
                         'orderNumber' => '@string',
    @@ -557,7 +567,8 @@ protected static function getExpectedResponseStructure(string $type): array
                         'name' => 'string',
                         'visible' => 'bool',
                         'billable' => 'bool',
    -                    'color' => 'string',
    +                    'color' => '@string',
    +                    'color-safe' => 'string',
                         'customer' => 'int',
                         'number' => '@string',
                         'orderNumber' => '@string',
    @@ -578,7 +589,8 @@ protected static function getExpectedResponseStructure(string $type): array
                         'name' => 'string',
                         'visible' => 'bool',
                         'billable' => 'bool',
    -                    'color' => 'string',
    +                    'color' => '@string',
    +                    'color-safe' => 'string',
                         'customer' => 'int',
                         'number' => '@string',
                         'orderNumber' => '@string',
    @@ -605,7 +617,8 @@ protected static function getExpectedResponseStructure(string $type): array
                         'billable' => 'bool',
                         'project' => '@int',
                         'number' => '@string',
    -                    'color' => 'string',
    +                    'color' => '@string',
    +                    'color-safe' => 'string',
                         'metaFields' => ['result' => 'array', 'type' => 'ProjectMeta'], // since 2.45
                         'comment' => '@string',
                     ];
    @@ -618,7 +631,8 @@ protected static function getExpectedResponseStructure(string $type): array
                         'billable' => 'bool',
                         'project' => ['result' => 'object', 'type' => '@ProjectExpanded'],
                         'number' => '@string',
    -                    'color' => 'string',
    +                    'color' => '@string',
    +                    'color-safe' => 'string',
                         'metaFields' => ['result' => 'array', 'type' => 'ProjectMeta'], // since 2.45
                         'comment' => '@string',
                     ];
    @@ -632,7 +646,8 @@ protected static function getExpectedResponseStructure(string $type): array
                         'billable' => 'bool',
                         'project' => '@int',
                         'number' => '@string',
    -                    'color' => 'string',
    +                    'color' => '@string',
    +                    'color-safe' => 'string',
                         'metaFields' => ['result' => 'array', 'type' => 'ProjectMeta'],
                         'comment' => '@string',
                         'parentTitle' => '@string',
    @@ -648,7 +663,8 @@ protected static function getExpectedResponseStructure(string $type): array
                         'billable' => 'bool',
                         'project' => '@int',
                         'number' => '@string',
    -                    'color' => 'string',
    +                    'color' => '@string',
    +                    'color-safe' => 'string',
                         'metaFields' => ['result' => 'array', 'type' => 'ProjectMeta'],
                         'comment' => '@string',
                         'parentTitle' => '@string',
    
  • tests/API/CustomerControllerTest.php+3 3 modified
    @@ -229,7 +229,6 @@ public function testGetEntityWithFullResponse(): void
             self::assertIsArray($result);
             self::assertApiResponseTypeStructure('CustomerEntity', $result);
     
    -        self::assertCount(30, array_keys($result));
             self::assertNotEmpty($result['id']);
             self::assertIsArray($result['teams']);
             self::assertCount(1, $result['teams']);
    @@ -262,7 +261,8 @@ public function testGetEntityWithFullResponse(): void
             self::assertNull($result['homepage']);
             self::assertEquals('Europe/Berlin', $result['timezone']);
             self::assertNull($result['buyerReference']);
    -        self::assertEquals('#5319e7', $result['color']);
    +        self::assertNull($result['color']);
    +        self::assertEquals('#5319e7', $result['color-safe']);
             self::assertTrue($result['visible']);
             self::assertTrue($result['billable']);
         }
    @@ -316,7 +316,7 @@ public function testPostAction(): void
             self::assertApiResponseTypeStructure('CustomerEntity', $result);
             self::assertNotEmpty($result['id']);
             self::assertIsArray($result['teams']);
    -        self::assertEquals([['id' => 1, 'name' => 'Test team', 'color' => '#03A9F4']], $result['teams']);
    +        self::assertEquals([['id' => 1, 'name' => 'Test team', 'color' => null, 'color-safe' => '#03A9F4']], $result['teams']);
             self::assertIsArray($result['metaFields']);
             self::assertEquals([], $result['metaFields']);
             self::assertEquals('foo', $result['name']);
    
  • tests/API/ProjectControllerTest.php+3 3 modified
    @@ -311,7 +311,6 @@ public function testGetEntityComplex(): void
             self::assertIsArray($result);
             self::assertApiResponseTypeStructure('ProjectEntity', $result);
     
    -        self::assertCount(19, array_keys($result));
             self::assertEquals('first one', $result['parentTitle']);
             self::assertEquals($project->getId(), $result['id']);
             self::assertIsArray($result['teams']);
    @@ -329,7 +328,8 @@ public function testGetEntityComplex(): void
             self::assertNull($result['orderNumber']);
             self::assertNull($result['number']);
             self::assertNull($result['comment']);
    -        self::assertEquals('#2ECC40', $result['color']);
    +        self::assertNull($result['color']);
    +        self::assertEquals('#2ECC40', $result['color-safe']);
             self::assertTrue($result['globalActivities']);
             self::assertTrue($result['billable']);
             self::assertTrue($result['visible']);
    @@ -374,7 +374,7 @@ public function testPostAction(): void
             self::assertEquals('Test', $result['parentTitle']);
             self::assertNotEmpty($result['id']);
             self::assertIsArray($result['teams']);
    -        self::assertEquals([['id' => 1, 'name' => 'Test team', 'color' => '#03A9F4']], $result['teams']);
    +        self::assertEquals([['id' => 1, 'name' => 'Test team', 'color' => null, 'color-safe' => '#03A9F4']], $result['teams']);
             self::assertIsArray($result['metaFields']);
             self::assertEquals([], $result['metaFields']);
             self::assertEquals('foo', $result['name']);
    
  • tests/Export/Base/DefaultRendererTest.php+128 0 added
    @@ -0,0 +1,128 @@
    +<?php
    +
    +/*
    + * This file is part of the Kimai time-tracking app.
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace App\Tests\Export\Base;
    +
    +use App\Export\Base\CsvRenderer;
    +use App\Export\Base\HtmlRenderer;
    +use App\Export\Base\PDFRenderer;
    +use App\Export\Base\XlsxRenderer;
    +use App\Export\ServiceExport;
    +use App\Repository\ExportTemplateRepository;
    +use App\Tests\Export\Renderer\AbstractRendererTestCase;
    +use App\Tests\Mocks\Export\CsvRendererFactoryMock;
    +use App\Tests\Mocks\Export\HtmlRendererFactoryMock;
    +use App\Tests\Mocks\Export\PdfRendererFactoryMock;
    +use App\Tests\Mocks\Export\XlsxRendererFactoryMock;
    +use PHPUnit\Framework\Attributes\CoversClass;
    +use PHPUnit\Framework\Attributes\Group;
    +use Psr\Log\LoggerInterface;
    +use Symfony\Component\Finder\Finder;
    +use Symfony\Component\HttpFoundation\Response;
    +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
    +
    +#[CoversClass(ServiceExport::class)]
    +#[CoversClass(CsvRenderer::class)]
    +#[CoversClass(XlsxRenderer::class)]
    +#[CoversClass(PDFRenderer::class)]
    +#[CoversClass(HtmlRenderer::class)]
    +#[Group('integration')]
    +class DefaultRendererTest extends AbstractRendererTestCase
    +{
    +    private function createServiceExport(): ServiceExport
    +    {
    +        $repository = $this->createMock(ExportTemplateRepository::class);
    +        $repository->expects($this->once())->method('findAll')->willReturn([]);
    +        $logger = $this->createMock(LoggerInterface::class);
    +
    +        return new ServiceExport(
    +            $this->createMock(EventDispatcherInterface::class),
    +            (new HtmlRendererFactoryMock($this))->create(),
    +            (new PdfRendererFactoryMock($this))->create(),
    +            (new CsvRendererFactoryMock($this))->create(),
    +            (new XlsxRendererFactoryMock($this))->create(),
    +            $repository,
    +            $logger,
    +        );
    +    }
    +
    +    public function testRenderDefaultTemplates(): void
    +    {
    +        $sut = $this->createServiceExport();
    +
    +        $renderer = $sut->getRenderer();
    +        self::assertCount(4, $renderer);
    +        self::assertInstanceOf(CsvRenderer::class, $renderer[0]);
    +        self::assertInstanceOf(XlsxRenderer::class, $renderer[1]);
    +        self::assertInstanceOf(PDFRenderer::class, $renderer[2]);
    +        self::assertInstanceOf(HtmlRenderer::class, $renderer[3]);
    +
    +        // make sure that the default templates do NOT violate the Twig SecurityPolicy
    +        $response = $this->render($renderer[0]);
    +        self::assertEquals('text/csv', $response->headers->get('Content-Type'));
    +        self::assertStringContainsString('attachment; filename', $response->headers->get('Content-Disposition') ?? '');
    +
    +        $response = $this->render($renderer[1]);
    +        self::assertEquals('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', $response->headers->get('Content-Type') ?? '');
    +        self::assertStringContainsString('attachment; filename', $response->headers->get('Content-Disposition') ?? '');
    +
    +        $response = $this->render($renderer[2]);
    +        self::assertEquals('application/pdf', $response->headers->get('Content-Type') ?? '');
    +        self::assertStringContainsString('attachment; filename', $response->headers->get('Content-Disposition') ?? '');
    +
    +        $response = $this->render($renderer[3]);
    +        self::assertEquals('text/html', $response->headers->get('Content-Type') ?? '');
    +        // HTML is attached to the body and twig is a mock in this setupo, so we just receive an empty string
    +    }
    +
    +    public function testRenderCustomTemplates(): void
    +    {
    +        $searchDir = __DIR__ . '/../../../var/templates';
    +        if (!is_dir($searchDir)) {
    +            $this->expectNotToPerformAssertions();
    +
    +            return;
    +        }
    +
    +        $finder = new Finder();
    +        $finder
    +            ->in($searchDir)
    +            ->name('*.twig')
    +            ->path('export-tpl/')
    +            ->files()
    +        ;
    +
    +        $files = [];
    +        $dirs = [];
    +        foreach ($finder->getIterator() as $filename => $splFile) {
    +            $files[] = $splFile->getRealPath();
    +            $dir = \dirname($splFile->getRealPath());
    +            $dirs[$dir] = $dir;
    +        }
    +        $dirs = array_keys($dirs);
    +
    +        if (\count($dirs) === 0) {
    +            $this->expectNotToPerformAssertions();
    +
    +            return;
    +        }
    +
    +        $sut = $this->createServiceExport();
    +        foreach ($dirs as $dir) {
    +            $sut->addDirectory($dir);
    +        }
    +
    +        $renderers = $sut->getRenderer();
    +        self::assertCount(4 + \count($files), $renderers);
    +        foreach ($renderers as $renderer) {
    +            $response = $this->render($renderer);
    +            self::assertInstanceOf(Response::class, $response);
    +        }
    +    }
    +}
    
  • tests/Export/Renderer/AbstractRendererTestCase.php+2 2 modified
    @@ -129,8 +129,8 @@ protected function render(ExportRendererInterface $renderer, bool $exportDecimal
     
             $entries = [$timesheet, $timesheet2, $timesheet3, $timesheet4, $timesheet5, $timesheet6];
     
    -        $currentUser = $this->createMock(User::class);
    -        $currentUser->expects($this->any())->method('isExportDecimal')->willReturn($exportDecimal);
    +        $currentUser = new User();
    +        $currentUser->setPreferenceValue('export_decimal', $exportDecimal);
     
             $query = new TimesheetQuery();
             $query->setActivities([$activity]);
    
  • tests/Invoice/Renderer/DebugRendererTest.php+2 0 modified
    @@ -14,10 +14,12 @@
     use App\Invoice\InvoiceModel;
     use App\Invoice\InvoiceModelHydrator;
     use App\Model\InvoiceDocument;
    +use PHPUnit\Framework\Attributes\CoversClass;
     use PHPUnit\Framework\Attributes\DataProvider;
     use PHPUnit\Framework\TestCase;
     use Symfony\Component\HttpFoundation\Response;
     
    +#[CoversClass(DebugRenderer::class)]
     class DebugRendererTest extends TestCase
     {
         use RendererTestTrait;
    
  • tests/Invoice/Renderer/PdfRendererTest.php+18 4 modified
    @@ -18,6 +18,7 @@
     use PHPUnit\Framework\Attributes\CoversClass;
     use PHPUnit\Framework\Attributes\Group;
     use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
    +use Symfony\Component\Finder\Finder;
     use Symfony\Component\HttpFoundation\Request;
     use Symfony\Component\HttpFoundation\RequestStack;
     use Twig\Environment;
    @@ -118,12 +119,26 @@ public function testRenderAll(): void
     
             $dirs = [
                 __DIR__ . '/../../../templates/invoice/renderer/',
    -            //__DIR__ . '/../../../var/invoices/',
    -            //__DIR__ . '/../../../var/invoices_customer/',
    -            //__DIR__ . '/../../../var/invoices_old/',
             ];
     
             $files = [];
    +
    +        $additionalTemplatesDir = __DIR__ . '/../../../var/templates';
    +        if (is_dir($additionalTemplatesDir)) {
    +            $finder = new Finder();
    +            $finder
    +                ->in($additionalTemplatesDir)
    +                ->name('*.pdf.twig')
    +                ->path('invoice-tpl/')
    +                ->files();
    +
    +            foreach ($finder->getIterator() as $splFile) {
    +                $filename = $splFile->getRealPath();
    +                $files[] = $filename;
    +                $loader->addPath(\dirname($filename) . '/', 'invoice');
    +            }
    +        }
    +
             foreach ($dirs as $dir) {
                 if (!is_dir($dir)) {
                     continue;
    @@ -145,7 +160,6 @@ public function testRenderAll(): void
                 $response = $sut->render($document, $model);
                 self::assertEquals('application/pdf', $response->headers->get('Content-Type'));
                 self::assertStringContainsString('attachment; filename', $response->headers->get('Content-Disposition'));
    -            self::assertNotEmpty($response->getContent());
             }
         }
     }
    
  • tests/Invoice/Renderer/RendererTestTrait.php+44 44 modified
    @@ -32,7 +32,6 @@
     use App\Repository\InvoiceRepository;
     use App\Repository\Query\InvoiceQuery;
     use App\Tests\Mocks\InvoiceModelFactoryFactory;
    -use Doctrine\Common\Collections\ArrayCollection;
     
     trait RendererTestTrait
     {
    @@ -85,6 +84,10 @@ protected function getFormatter(): InvoiceFormatter
     
         protected function getInvoiceModel(): InvoiceModel
         {
    +        $activityId = new \ReflectionProperty(Activity::class, 'id');
    +        $projectId = new \ReflectionProperty(Project::class, 'id');
    +        $userId = new \ReflectionProperty(User::class, 'id');
    +
             $user = new User();
             $user->setUserIdentifier('one-user');
             $user->setTitle('user title');
    @@ -107,59 +110,54 @@ protected function getInvoiceModel(): InvoiceModel
     
             $pMeta = new ProjectMeta();
             $pMeta->setName('foo-project')->setValue('bar-project')->setIsVisible(true);
    -        $project = $this->createMock(Project::class);
    -        $project->method('getId')->willReturn(0);
    -        $project->method('getName')->willReturn('project name');
    -        $project->method('getCustomer')->willReturn($customer);
    -        $project->method('getMetaFields')->willReturn(new ArrayCollection([$pMeta]));
    -        $project->method('getVisibleMetaFields')->willReturn([$pMeta]);
    +        $project = new Project();
    +        $projectId->setValue($project, 0);
    +        $project->setName('project name');
    +        $project->setCustomer($customer);
    +        $project->setMetaField($pMeta);
     
             $aMeta = new ActivityMeta();
             $aMeta->setName('foo-activity');
             $aMeta->setValue('bar-activity');
             $aMeta->setIsVisible(true);
    -        $activity = $this->createMock(Activity::class);
    -        $activity->method('getId')->willReturn(0);
    -        $activity->method('getName')->willReturn('activity description');
    -        $activity->method('getProject')->willReturn($project);
    -        $activity->method('getMetaFields')->willReturn(new ArrayCollection([$aMeta]));
    -        $activity->method('getVisibleMetaFields')->willReturn([$aMeta]);
    +        $activity = new Activity();
    +        $activityId->setValue($activity, 0);
    +        $activity->setName('activity description');
    +        $activity->setProject($project);
    +        $activity->setMetaField($aMeta);
     
             $pMeta2 = new ProjectMeta();
             $pMeta2->setName('foo-project')->setValue('bar-project2')->setIsVisible(true);
    -        $project2 = $this->createMock(Project::class);
    -        $project2->method('getId')->willReturn(1);
    -        $project2->method('getName')->willReturn('project 2 name');
    -        $project2->method('getCustomer')->willReturn($customer);
    -        $project2->method('getMetaFields')->willReturn(new ArrayCollection([$pMeta2]));
    -        $project2->method('getVisibleMetaFields')->willReturn([$pMeta2]);
    +        $project2 = new Project();
    +        $projectId->setValue($project2, 1);
    +        $project2->setName('project 2 name');
    +        $project2->setCustomer($customer);
    +        $project2->setMetaField($pMeta2);
     
             $aMeta2 = new ActivityMeta();
             $aMeta2->setName('foo-activity');
             $aMeta2->setValue('bar-activity2');
             $aMeta2->setIsVisible(true);
    -        $activity2 = $this->createMock(Activity::class);
    -        $activity2->method('getId')->willReturn(1);
    -        $activity2->method('getName')->willReturn('activity 1 description');
    -        $activity2->method('getProject')->willReturn($project2);
    -        $activity2->method('getMetaFields')->willReturn(new ArrayCollection([$aMeta2]));
    -        $activity2->method('getVisibleMetaFields')->willReturn([$aMeta2]);
    +        $activity2 = new Activity();
    +        $activityId->setValue($activity2, 1);
    +        $activity2->setName('activity 1 description');
    +        $activity2->setProject($project2);
    +        $activity2->setMetaField($aMeta2);
     
             $pref1 = new UserPreference('foo', 'bar');
             $pref2 = new UserPreference('mad', 123.45);
    -        $userMethods = ['getId', 'getPreferenceValue', 'getVisiblePreferences', 'getUsername', 'getUserIdentifier'];
    -        $user1 = $this->getMockBuilder(User::class)->onlyMethods($userMethods)->disableOriginalConstructor()->getMock();
    -        $user1->method('getId')->willReturn(1);
    -        $user1->method('getPreferenceValue')->willReturn('50');
    -        $user1->method('getUsername')->willReturn('foo-bar');
    -        $user1->method('getUserIdentifier')->willReturn('foo-bar');
    -        $user1->method('getVisiblePreferences')->willReturn([$pref1, $pref2]);
    -
    -        $user2 = $this->createMock(User::class);
    -        $user2->method('getId')->willReturn(2);
    -        $user2->method('getUsername')->willReturn('hello-world');
    -        $user2->method('getUserIdentifier')->willReturn('hello-world');
    -        $user2->method('getVisiblePreferences')->willReturn([$pref1, $pref2]);
    +        $user1 = new User();
    +        $user1->setUserIdentifier('foo-bar');
    +        $userId->setValue($user1, 1);
    +        //$user1->method('getPreferenceValue')->willReturn('50');
    +        $user1->addPreference($pref1);
    +        $user1->addPreference($pref2);
    +
    +        $user2 = new User();
    +        $userId->setValue($user2, 2);
    +        $user2->setUserIdentifier('hello-world');
    +        $user2->addPreference($pref1);
    +        $user2->addPreference($pref2);
     
             $timesheet = new Timesheet();
             $timesheet->setDuration(3600);
    @@ -302,12 +300,14 @@ protected function getInvoiceModelOneEntry(): InvoiceModel
     
             $pref1 = new UserPreference('foo', 'bar');
             $pref2 = new UserPreference('mad', 123.45);
    -        $user1 = $this->createMock(User::class);
    -        $user1->method('getId')->willReturn(1);
    -        $user1->method('getPreferenceValue')->willReturn('50');
    -        $user1->method('getUsername')->willReturn('foo-bar');
    -        $user1->method('getUserIdentifier')->willReturn('foo-bar');
    -        $user1->method('getVisiblePreferences')->willReturn([$pref1, $pref2]);
    +
    +        $userId = new \ReflectionProperty(User::class, 'id');
    +        $user1 = new User();
    +        $user1->setUserIdentifier('foo-bar');
    +        $user1->addPreference($pref1);
    +        $user1->addPreference($pref2);
    +        $userId->setValue($user1, 1);
    +        //$user1->method('getPreferenceValue')->willReturn('50');
     
             $timesheet = new Timesheet();
             $timesheet->setDuration(3600);
    
  • tests/Twig/SecurityPolicy/AbstractPolicyTestCase.php+91 0 added
    @@ -0,0 +1,91 @@
    +<?php
    +
    +/*
    + * This file is part of the Kimai time-tracking app.
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace App\Tests\Twig\SecurityPolicy;
    +
    +use App\Entity\User;
    +use App\Pdf\PdfContext;
    +use PHPUnit\Framework\Attributes\DataProvider;
    +use PHPUnit\Framework\TestCase;
    +use Symfony\Bridge\Twig\AppVariable;
    +use Symfony\Component\HttpFoundation\Request;
    +use Symfony\Component\HttpFoundation\ServerBag;
    +use Symfony\Component\HttpFoundation\Session\SessionInterface;
    +use Symfony\Component\String\UnicodeString;
    +use Twig\Sandbox\SecurityNotAllowedMethodError;
    +use Twig\Sandbox\SecurityPolicyInterface;
    +
    +abstract class AbstractPolicyTestCase extends TestCase
    +{
    +    abstract protected function createPolicy(): SecurityPolicyInterface;
    +
    +    public function testCheckSecurity(): void
    +    {
    +        $sut = $this->createPolicy();
    +        $sut->checkSecurity([], [], []);
    +        $this->expectNotToPerformAssertions();
    +    }
    +
    +    public function testCheckPropertyAllowed(): void
    +    {
    +        $sut = $this->createPolicy();
    +        $sut->checkPropertyAllowed(new \stdClass(), 'foo');
    +        $this->expectNotToPerformAssertions();
    +    }
    +
    +    #[DataProvider('getCheckMethodAllowedData')]
    +    public function testCheckMethodAllowed(object $obj, string $method, ?string $expectedExceptionMessage = null): void
    +    {
    +        $sut = $this->createPolicy();
    +
    +        if ($expectedExceptionMessage !== null) {
    +            $this->expectException(SecurityNotAllowedMethodError::class);
    +            $this->expectExceptionMessage($expectedExceptionMessage);
    +        }
    +
    +        $sut->checkMethodAllowed($obj, $method);
    +
    +        if ($expectedExceptionMessage === null) {
    +            $this->expectNotToPerformAssertions();
    +        }
    +    }
    +
    +    public static function getCheckMethodAllowedData(): array
    +    {
    +        return [
    +            [new ServerBag(), 'get', 'Tried to access server environment'],
    +            [self::createStub(SessionInterface::class), 'getId', 'Tried to access session'],
    +            [new \stdClass(), 'foo', 'Tried to access non-read method'],
    +            [new \stdClass(), 'setFoo', 'Tried to access non-read method'],
    +            [new \stdClass(), 'getFoo'],
    +            [new \stdClass(), 'hasFoo'],
    +            [new \stdClass(), 'isFoo'],
    +            [new UnicodeString(), '__toString'],
    +            // Request
    +            [new Request(), 'get', null],
    +            [new Request(), 'isXmlHttpRequest', 'Tried to call setter() of app variable'],
    +            [new Request(), 'hasSession', 'Tried to call setter() of app variable'],
    +            // PdfContext
    +            [new PdfContext(), 'setOption'],
    +            [new PdfContext(), 'getOption', 'Tried to access forbidden method on PdfContext'],
    +            // AppVariable
    +            [new AppVariable(), 'getRequest'],
    +            [new AppVariable(), 'getUser'],
    +            [new AppVariable(), 'getLocale'],
    +            [new AppVariable(), 'getCharset', 'Tried to access forbidden app variable method'],
    +            // User
    +            [new User(), 'getUsername'],
    +            [new User(), 'getPassword', 'Tried to access user secrets'],
    +            [new User(), 'getTotpSecret', 'Tried to access user secrets'],
    +            [new User(), 'getPlainPassword', 'Tried to access user secrets'],
    +            [new User(), 'getConfirmationToken', 'Tried to access user secrets'],
    +            [new User(), 'getTotpAuthenticationConfiguration', 'Tried to access user secrets'],
    +        ];
    +    }
    +}
    
  • tests/Twig/SecurityPolicy/ChainPolicyTest.php+66 0 added
    @@ -0,0 +1,66 @@
    +<?php
    +
    +/*
    + * This file is part of the Kimai time-tracking app.
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace App\Tests\Twig\SecurityPolicy;
    +
    +use App\Twig\SecurityPolicy\ChainPolicy;
    +use PHPUnit\Framework\Attributes\CoversClass;
    +use PHPUnit\Framework\TestCase;
    +use Twig\Sandbox\SecurityPolicyInterface;
    +
    +#[CoversClass(ChainPolicy::class)]
    +class ChainPolicyTest extends TestCase
    +{
    +    public function testCheckSecurity(): void
    +    {
    +        $policy1 = $this->createMock(SecurityPolicyInterface::class);
    +        $policy1->expects(self::once())->method('checkSecurity')->with(['tag'], ['filter'], ['function']);
    +
    +        $policy2 = $this->createMock(SecurityPolicyInterface::class);
    +        $policy2->expects(self::once())->method('checkSecurity')->with(['tag'], ['filter'], ['function']);
    +
    +        $sut = new ChainPolicy();
    +        $sut->addPolicy($policy1);
    +        $sut->addPolicy($policy2);
    +
    +        $sut->checkSecurity(['tag'], ['filter'], ['function']);
    +    }
    +
    +    public function testCheckMethodAllowed(): void
    +    {
    +        $obj = new \stdClass();
    +        $policy1 = $this->createMock(SecurityPolicyInterface::class);
    +        $policy1->expects(self::once())->method('checkMethodAllowed')->with($obj, 'method');
    +
    +        $policy2 = $this->createMock(SecurityPolicyInterface::class);
    +        $policy2->expects(self::once())->method('checkMethodAllowed')->with($obj, 'method');
    +
    +        $sut = new ChainPolicy();
    +        $sut->addPolicy($policy1);
    +        $sut->addPolicy($policy2);
    +
    +        $sut->checkMethodAllowed($obj, 'method');
    +    }
    +
    +    public function testCheckPropertyAllowed(): void
    +    {
    +        $obj = new \stdClass();
    +        $policy1 = $this->createMock(SecurityPolicyInterface::class);
    +        $policy1->expects(self::once())->method('checkPropertyAllowed')->with($obj, 'property');
    +
    +        $policy2 = $this->createMock(SecurityPolicyInterface::class);
    +        $policy2->expects(self::once())->method('checkPropertyAllowed')->with($obj, 'property');
    +
    +        $sut = new ChainPolicy();
    +        $sut->addPolicy($policy1);
    +        $sut->addPolicy($policy2);
    +
    +        $sut->checkPropertyAllowed($obj, 'property');
    +    }
    +}
    
  • tests/Twig/SecurityPolicy/DefaultPolicyTest.php+23 0 added
    @@ -0,0 +1,23 @@
    +<?php
    +
    +/*
    + * This file is part of the Kimai time-tracking app.
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace App\Tests\Twig\SecurityPolicy;
    +
    +use App\Twig\SecurityPolicy\DefaultPolicy;
    +use PHPUnit\Framework\Attributes\CoversClass;
    +use Twig\Sandbox\SecurityPolicyInterface;
    +
    +#[CoversClass(DefaultPolicy::class)]
    +class DefaultPolicyTest extends AbstractPolicyTestCase
    +{
    +    protected function createPolicy(): SecurityPolicyInterface
    +    {
    +        return new DefaultPolicy();
    +    }
    +}
    
  • tests/Twig/SecurityPolicy/ExportPolicyTest.php+23 0 added
    @@ -0,0 +1,23 @@
    +<?php
    +
    +/*
    + * This file is part of the Kimai time-tracking app.
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace App\Tests\Twig\SecurityPolicy;
    +
    +use App\Twig\SecurityPolicy\ExportPolicy;
    +use PHPUnit\Framework\Attributes\CoversClass;
    +use Twig\Sandbox\SecurityPolicyInterface;
    +
    +#[CoversClass(ExportPolicy::class)]
    +class ExportPolicyTest extends AbstractPolicyTestCase
    +{
    +    protected function createPolicy(): SecurityPolicyInterface
    +    {
    +        return new ExportPolicy();
    +    }
    +}
    
  • tests/Twig/SecurityPolicy/InvoicePolicyTest.php+90 0 added
    @@ -0,0 +1,90 @@
    +<?php
    +
    +/*
    + * This file is part of the Kimai time-tracking app.
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace App\Tests\Twig\SecurityPolicy;
    +
    +use App\Entity\User;
    +use App\Pdf\PdfContext;
    +use App\Twig\SecurityPolicy\InvoicePolicy;
    +use PHPUnit\Framework\Attributes\CoversClass;
    +use PHPUnit\Framework\Attributes\DataProvider;
    +use PHPUnit\Framework\TestCase;
    +use Symfony\Bridge\Twig\AppVariable;
    +use Symfony\Component\HttpFoundation\Request;
    +use Symfony\Component\HttpFoundation\ServerBag;
    +use Symfony\Component\HttpFoundation\Session\SessionInterface;
    +use Symfony\Component\String\UnicodeString;
    +use Twig\Sandbox\SecurityNotAllowedMethodError;
    +use Twig\Sandbox\SecurityPolicyInterface;
    +
    +#[CoversClass(InvoicePolicy::class)]
    +class InvoicePolicyTest extends TestCase
    +{
    +    protected function createPolicy(): SecurityPolicyInterface
    +    {
    +        return new InvoicePolicy();
    +    }
    +
    +    public function testCheckSecurity(): void
    +    {
    +        $sut = $this->createPolicy();
    +        $sut->checkSecurity([], [], []);
    +        $this->expectNotToPerformAssertions();
    +    }
    +
    +    #[DataProvider('getCheckMethodAllowedData')]
    +    public function testCheckMethodAllowed(object $obj, string $method, ?string $expectedExceptionMessage = null): void
    +    {
    +        $sut = $this->createPolicy();
    +
    +        if ($expectedExceptionMessage !== null) {
    +            $this->expectException(SecurityNotAllowedMethodError::class);
    +            $this->expectExceptionMessage($expectedExceptionMessage);
    +        }
    +
    +        $sut->checkMethodAllowed($obj, $method);
    +
    +        if ($expectedExceptionMessage === null) {
    +            $this->expectNotToPerformAssertions();
    +        }
    +    }
    +
    +    public static function getCheckMethodAllowedData(): array
    +    {
    +        return [
    +            [new ServerBag(), 'get', 'Tried to access server environment'],
    +            [self::createStub(SessionInterface::class), 'getId', 'Tried to access session'],
    +            [new \stdClass(), 'foo', 'Tried to access non-read method'],
    +            [new \stdClass(), 'setFoo', 'Tried to access non-read method'],
    +            [new \stdClass(), 'getFoo'],
    +            [new \stdClass(), 'hasFoo'],
    +            [new \stdClass(), 'isFoo'],
    +            [new UnicodeString(), '__toString'],
    +            // Request
    +            [new Request(), 'get', null],
    +            [new Request(), 'isXmlHttpRequest', 'Tried to call setter() of app variable'],
    +            [new Request(), 'hasSession', 'Tried to call setter() of app variable'],
    +            // PdfContext
    +            [new PdfContext(), 'setOption'],
    +            [new PdfContext(), 'getOption', 'Tried to access forbidden method on PdfContext'],
    +            // AppVariable
    +            [new AppVariable(), 'getRequest'],
    +            [new AppVariable(), 'getUser'],
    +            [new AppVariable(), 'getLocale'],
    +            [new AppVariable(), 'getCharset', 'Tried to access forbidden app variable method'],
    +            // User
    +            [new User(), 'getUsername'],
    +            [new User(), 'getPassword', 'Tried to access user secrets'],
    +            [new User(), 'getTotpSecret', 'Tried to access user secrets'],
    +            [new User(), 'getPlainPassword', 'Tried to access user secrets'],
    +            [new User(), 'getConfirmationToken', 'Tried to access user secrets'],
    +            [new User(), 'getTotpAuthenticationConfiguration', 'Tried to access user secrets'],
    +        ];
    +    }
    +}
    
  • yarn.lock+614 614 modified

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.